From a0331113a0a1b0af3ccc71526ebb25f3e10c766d Mon Sep 17 00:00:00 2001 From: Claude Date: Thu, 13 Aug 2026 21:15:42 +0000 Subject: [PATCH 01/59] hello-solana: port to Anchor v2.0.0-rc.1 Anchor v2 is a ground-up rewrite rather than a version bump: the crate is no_std and built on pinocchio, so handlers take `&mut Context` instead of `Context`, and the `#[program]` macro expands to `wincode` paths for instruction-data (de)serialization, which means `wincode` has to be a direct dependency of every program crate. The program body is otherwise unchanged, and `tests/test_hello.rs` needed no edits at all: `InstructionData`, `ToAccountMetas`, and `anchor_lang::solana_program::instruction::Instruction` all survive into v2. Verified with `cargo-build-sbf` (22 KB .so, down from ~180 KB on v1) and `cargo test`, both tests pass. --- Cargo.lock | 170 ++++++++++++++---- .../anchor/programs/hello-solana/Cargo.toml | 5 +- .../anchor/programs/hello-solana/src/lib.rs | 2 +- 3 files changed, 140 insertions(+), 37 deletions(-) diff --git a/Cargo.lock b/Cargo.lock index 54e041455..9a2a5b733 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -6,7 +6,7 @@ version = 4 name = "account-data-anchor-program" version = "0.1.0" dependencies = [ - "anchor-lang", + "anchor-lang 1.1.2", "borsh 1.7.0", "litesvm", "solana-keypair", @@ -38,7 +38,7 @@ dependencies = [ "litesvm", "pinocchio 0.10.2", "pinocchio-log", - "pinocchio-system", + "pinocchio-system 0.5.0", "solana-keypair", "solana-message 4.0.0", "solana-native-token 3.0.0", @@ -259,7 +259,7 @@ version = "1.1.2" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "6844e657ffe049b073389efb843bb281d08579b8a47ef294365f729b35ed3dca" dependencies = [ - "anchor-lang-idl", + "anchor-lang-idl 0.1.4", "anchor-syn", "anyhow", "heck", @@ -279,6 +279,23 @@ dependencies = [ "syn 2.0.118", ] +[[package]] +name = "anchor-derive-accounts" +version = "2.0.0-rc.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "46a0ce0bc596146ce0a73e2422e951d153cf88bf58af957c46a52e03d3048e4c" +dependencies = [ + "anchor-lang-idl 0.2.0", + "bs58", + "curve25519-dalek", + "proc-macro2", + "quote", + "serde_json", + "sha2 0.10.9", + "solana-address 2.6.1", + "syn 2.0.118", +] + [[package]] name = "anchor-derive-serde" version = "1.1.2" @@ -315,11 +332,11 @@ dependencies = [ "anchor-attribute-error", "anchor-attribute-event", "anchor-attribute-program", - "anchor-derive-accounts", + "anchor-derive-accounts 1.1.2", "anchor-derive-serde", "anchor-derive-space", "anchor-lang-error", - "anchor-lang-idl", + "anchor-lang-idl 0.1.4", "base64 0.21.7", "bincode", "borsh 1.7.0", @@ -349,6 +366,29 @@ dependencies = [ "thiserror 1.0.69", ] +[[package]] +name = "anchor-lang" +version = "2.0.0-rc.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "163e7ec1855744f29f116b3fa4c469947b43f17008d086e558f9b9b047fbf47d" +dependencies = [ + "anchor-derive-accounts 2.0.0-rc.1", + "bytemuck", + "pinocchio 0.11.2", + "pinocchio-system 0.6.1", + "sha2 0.10.9", + "solana-address 2.6.1", + "solana-define-syscall 5.1.0", + "solana-instruction 3.2.0", + "solana-msg 3.1.0", + "solana-program-error 3.0.1", + "solana-program-memory 3.1.0", + "solana-sha256-hasher 3.1.0", + "solana-system-interface 2.0.0", + "solana-sysvar 3.1.1", + "wincode 0.5.5", +] + [[package]] name = "anchor-lang-error" version = "1.1.2" @@ -377,6 +417,20 @@ dependencies = [ "sha2 0.10.9", ] +[[package]] +name = "anchor-lang-idl" +version = "0.2.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "62076a3c298f5447488e31c0198675b2fcbada1f71381af7104333cf6ff2581d" +dependencies = [ + "anchor-lang-idl-spec", + "anyhow", + "heck", + "serde", + "serde_json", + "sha2 0.10.9", +] + [[package]] name = "anchor-lang-idl-spec" version = "0.1.0" @@ -391,7 +445,7 @@ dependencies = [ name = "anchor-realloc" version = "0.1.0" dependencies = [ - "anchor-lang", + "anchor-lang 1.1.2", "borsh 1.7.0", "litesvm", "solana-keypair", @@ -405,7 +459,7 @@ version = "1.1.2" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "2f698a45d424351ff695df413adcb3fa603f2c791a0b4a937ebcd2e93a052c77" dependencies = [ - "anchor-lang", + "anchor-lang 1.1.2", "spl-associated-token-account-interface", "spl-pod", "spl-token-2022-interface", @@ -1034,7 +1088,7 @@ dependencies = [ name = "carnival" version = "0.1.0" dependencies = [ - "anchor-lang", + "anchor-lang 1.1.2", "litesvm", "solana-keypair", "solana-kite", @@ -1092,7 +1146,7 @@ dependencies = [ name = "checking-accounts-anchor-program-example" version = "0.1.0" dependencies = [ - "anchor-lang", + "anchor-lang 1.1.2", "borsh 1.7.0", "litesvm", "solana-keypair", @@ -1121,7 +1175,7 @@ dependencies = [ "litesvm", "pinocchio 0.10.2", "pinocchio-log", - "pinocchio-system", + "pinocchio-system 0.5.0", "solana-instruction 3.2.0", "solana-keypair", "solana-native-token 3.0.0", @@ -1164,7 +1218,7 @@ dependencies = [ "pinocchio 0.10.2", "pinocchio-log", "pinocchio-pubkey", - "pinocchio-system", + "pinocchio-system 0.5.0", "solana-instruction 3.2.0", "solana-keypair", "solana-message 4.0.0", @@ -1179,7 +1233,7 @@ dependencies = [ name = "close-account-program" version = "0.1.0" dependencies = [ - "anchor-lang", + "anchor-lang 1.1.2", "litesvm", "solana-keypair", "solana-kite", @@ -1291,7 +1345,7 @@ dependencies = [ name = "counter_anchor" version = "0.1.0" dependencies = [ - "anchor-lang", + "anchor-lang 1.1.2", "borsh 1.7.0", "litesvm", "solana-keypair", @@ -1338,7 +1392,7 @@ dependencies = [ "pinocchio 0.10.2", "pinocchio-log", "pinocchio-pubkey", - "pinocchio-system", + "pinocchio-system 0.5.0", "solana-instruction 3.2.0", "solana-keypair", "solana-native-token 3.0.0", @@ -1365,7 +1419,7 @@ dependencies = [ name = "create-system-account" version = "0.1.0" dependencies = [ - "anchor-lang", + "anchor-lang 1.1.2", "litesvm", "solana-keypair", "solana-kite", @@ -1405,7 +1459,7 @@ dependencies = [ "litesvm", "pinocchio 0.10.2", "pinocchio-log", - "pinocchio-system", + "pinocchio-system 0.5.0", "solana-instruction 3.2.0", "solana-keypair", "solana-native-token 3.0.0", @@ -1745,7 +1799,7 @@ dependencies = [ "pinocchio 0.10.2", "pinocchio-log", "pinocchio-pubkey", - "pinocchio-system", + "pinocchio-system 0.5.0", "solana-instruction 3.2.0", "solana-keypair", "solana-native-token 3.0.0", @@ -1937,7 +1991,7 @@ dependencies = [ name = "hand" version = "0.1.0" dependencies = [ - "anchor-lang", + "anchor-lang 1.1.2", "litesvm", "solana-keypair", "solana-kite", @@ -1990,11 +2044,12 @@ dependencies = [ name = "hello-solana-anchor" version = "0.1.0" dependencies = [ - "anchor-lang", + "anchor-lang 2.0.0-rc.1", "litesvm", "solana-kite", "solana-signer", "solana-transaction", + "wincode 0.5.5", ] [[package]] @@ -2178,7 +2233,7 @@ checksum = "bbd2bcb4c963f2ddae06a2efc7e9f3591312473c50c6685e1f298068316e66fe" name = "lever" version = "0.1.0" dependencies = [ - "anchor-lang", + "anchor-lang 1.1.2", "litesvm", "solana-keypair", "solana-kite", @@ -2373,7 +2428,7 @@ dependencies = [ name = "mock-swap-router" version = "0.1.0" dependencies = [ - "anchor-lang", + "anchor-lang 1.1.2", "anchor-spl", "borsh 1.7.0", "litesvm", @@ -2644,7 +2699,7 @@ dependencies = [ name = "pda-rent-payer" version = "0.1.0" dependencies = [ - "anchor-lang", + "anchor-lang 1.1.2", "litesvm", "solana-keypair", "solana-kite", @@ -2659,7 +2714,7 @@ dependencies = [ "pinocchio 0.10.2", "pinocchio-log", "pinocchio-pubkey", - "pinocchio-system", + "pinocchio-system 0.5.0", "solana-instruction 3.2.0", "solana-keypair", "solana-native-token 3.0.0", @@ -2717,10 +2772,23 @@ version = "0.10.2" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "c06810dac15a4ef83d3dabdb4f2f22fb39c9adff669cd2781da4f716510a647c" dependencies = [ - "solana-account-view", + "solana-account-view 1.0.0", "solana-address 2.6.1", "solana-define-syscall 4.0.1", - "solana-instruction-view", + "solana-instruction-view 1.0.0", + "solana-program-error 3.0.1", +] + +[[package]] +name = "pinocchio" +version = "0.11.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "a6cababcb62a2e739c7078ae16eda02789a6e3edd21bbdded864e85c38a7914d" +dependencies = [ + "solana-account-view 2.0.0", + "solana-address 2.6.1", + "solana-define-syscall 5.1.0", + "solana-instruction-view 2.1.0", "solana-program-error 3.0.1", ] @@ -2765,6 +2833,16 @@ dependencies = [ "solana-address 2.6.1", ] +[[package]] +name = "pinocchio-system" +version = "0.6.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "f3ae2ee67b42e2ac797dd88312da473c895660e270d8093e0ba2749b885b5778" +dependencies = [ + "pinocchio 0.11.2", + "solana-address 2.6.1", +] + [[package]] name = "pkcs8" version = "0.10.2" @@ -2827,7 +2905,7 @@ dependencies = [ name = "processing-instructions" version = "0.1.0" dependencies = [ - "anchor-lang", + "anchor-lang 1.1.2", "litesvm", "solana-keypair", "solana-kite", @@ -2903,7 +2981,7 @@ version = "0.1.0" dependencies = [ "litesvm", "pinocchio 0.10.2", - "pinocchio-system", + "pinocchio-system 0.5.0", "solana-instruction 3.2.0", "solana-keypair", "solana-native-token 3.0.0", @@ -2917,7 +2995,7 @@ dependencies = [ name = "program-derived-addresses-program" version = "0.1.0" dependencies = [ - "anchor-lang", + "anchor-lang 1.1.2", "borsh 1.7.0", "litesvm", "solana-keypair", @@ -3082,7 +3160,7 @@ dependencies = [ "litesvm", "pinocchio 0.10.2", "pinocchio-log", - "pinocchio-system", + "pinocchio-system 0.5.0", "solana-instruction 3.2.0", "solana-keypair", "solana-native-token 3.0.0", @@ -3149,7 +3227,7 @@ checksum = "d6f6ff9a378485b298a5286656da665ba74413d36db0979633275d2e708145d4" name = "rent-example" version = "0.1.0" dependencies = [ - "anchor-lang", + "anchor-lang 1.1.2", "borsh 1.7.0", "litesvm", "solana-keypair", @@ -3164,7 +3242,7 @@ dependencies = [ "litesvm", "pinocchio 0.10.2", "pinocchio-log", - "pinocchio-system", + "pinocchio-system 0.5.0", "solana-instruction 3.2.0", "solana-keypair", "solana-native-token 3.0.0", @@ -3499,6 +3577,16 @@ dependencies = [ "solana-program-error 3.0.1", ] +[[package]] +name = "solana-account-view" +version = "2.0.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "fc141b940560430425ebaadb7645496c45f6a10fad9911d719bd03eab7f4d422" +dependencies = [ + "solana-address 2.6.1", + "solana-program-error 3.0.1", +] + [[package]] name = "solana-address" version = "1.1.0" @@ -4257,12 +4345,24 @@ version = "1.0.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "60147e4d0a4620013df40bf30a86dd299203ff12fcb8b593cd51014fce0875d8" dependencies = [ - "solana-account-view", + "solana-account-view 1.0.0", "solana-address 2.6.1", "solana-define-syscall 4.0.1", "solana-program-error 3.0.1", ] +[[package]] +name = "solana-instruction-view" +version = "2.1.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "6ab7a27d0c4214b9f7389c3dd00b68c93093a67f1dcc5b7893aebe299bbcbb47" +dependencies = [ + "solana-account-view 2.0.0", + "solana-address 2.6.1", + "solana-define-syscall 5.1.0", + "solana-program-error 3.0.1", +] + [[package]] name = "solana-instructions-sysvar" version = "2.2.2" @@ -6455,7 +6555,7 @@ checksum = "756daf9b1013ebe47a8776667b466417e2d4c5679d441c26230efd9ef78692db" name = "transfer-sol" version = "0.1.0" dependencies = [ - "anchor-lang", + "anchor-lang 1.1.2", "litesvm", "solana-keypair", "solana-kite", @@ -6468,7 +6568,7 @@ version = "0.1.0" dependencies = [ "litesvm", "pinocchio 0.10.2", - "pinocchio-system", + "pinocchio-system 0.5.0", "solana-instruction 3.2.0", "solana-keypair", "solana-native-token 3.0.0", @@ -6564,7 +6664,7 @@ dependencies = [ name = "vault-strategy" version = "0.1.0" dependencies = [ - "anchor-lang", + "anchor-lang 1.1.2", "anchor-spl", "borsh 1.7.0", "litesvm", diff --git a/basics/hello-solana/anchor/programs/hello-solana/Cargo.toml b/basics/hello-solana/anchor/programs/hello-solana/Cargo.toml index a5c764e32..e96bdc106 100644 --- a/basics/hello-solana/anchor/programs/hello-solana/Cargo.toml +++ b/basics/hello-solana/anchor/programs/hello-solana/Cargo.toml @@ -20,7 +20,10 @@ custom-heap = [] custom-panic = [] [dependencies] -anchor-lang = "1.1.2" +anchor-lang = "2.0.0-rc.1" +# The `#[program]` macro expands to `wincode` paths for instruction-data +# (de)serialization, so the crate has to be a direct dependency. +wincode = { version = "0.5", features = ["derive"] } [dev-dependencies] litesvm = "0.13.1" diff --git a/basics/hello-solana/anchor/programs/hello-solana/src/lib.rs b/basics/hello-solana/anchor/programs/hello-solana/src/lib.rs index 54fc73ecb..253ff5abd 100644 --- a/basics/hello-solana/anchor/programs/hello-solana/src/lib.rs +++ b/basics/hello-solana/anchor/programs/hello-solana/src/lib.rs @@ -6,7 +6,7 @@ declare_id!("2phbC62wekpw95XuBk4i1KX4uA8zBUWmYbiTMhicSuBV"); pub mod hello_solana { use super::*; - pub fn hello(_context: Context) -> Result<()> { + pub fn hello(_context: &mut Context) -> Result<()> { msg!("Hello, Solana!"); msg!("Our program's Program ID: {}", &id()); From ec51acfef5fd98664d8a1d9b9ad65a5ba3bb1fb5 Mon Sep 17 00:00:00 2001 From: Claude Date: Thu, 13 Aug 2026 21:38:19 +0000 Subject: [PATCH 02/59] basics: port checking-accounts, processing-instructions, create-account, rent to Anchor v2 Four more of the stateless / system-account examples. The recurring edits are the same ones hello-solana needed, plus a few v2 specifics: - CPI account fields are handles now: `.to_account_info()` becomes `.cpi_handle_mut()`, and `Program` exposes `.address()` rather than `.key()`. - `Rent::minimum_balance` is deprecated in favour of `try_minimum_balance`. - v2's `solana_program` compat shim has no `system_program` submodule, the real module is at the crate root and exposes `ID`, not `id()`. That is the only edit the tests needed. - rent-example dropped borsh entirely: instruction data is wincode-encoded in v2, so `AddressData` derives `wincode::Schema{Read,Write}` and the account span comes from `SchemaWrite::size_of`. `BorshConfig` is wincode's borsh-compatible wire format, so the 44-byte span is unchanged and the test's size assertion still holds. Its fields are now `pub`, which lets the test build the struct directly instead of round-tripping it through hand-written borsh bytes. - checking-accounts had to make its constraint fields `pub`: v2's derive no longer reads them, so private unused fields trip dead_code. All four build with cargo-build-sbf and pass their LiteSVM tests. --- .../anchor-program-example/Cargo.toml | 5 +- .../anchor-program-example/src/lib.rs | 12 ++--- .../tests/test_checking_accounts.rs | 6 +-- .../programs/create-system-account/Cargo.toml | 5 +- .../programs/create-system-account/src/lib.rs | 26 +++++----- .../tests/test_create_account.rs | 6 +-- .../processing-instructions/Cargo.toml | 5 +- .../processing-instructions/src/lib.rs | 2 +- .../anchor/programs/rent-example/Cargo.toml | 6 ++- .../anchor/programs/rent-example/src/lib.rs | 47 ++++++++++++------- .../programs/rent-example/tests/test_rent.rs | 37 +++++---------- 11 files changed, 82 insertions(+), 75 deletions(-) diff --git a/basics/checking-accounts/anchor/programs/anchor-program-example/Cargo.toml b/basics/checking-accounts/anchor/programs/anchor-program-example/Cargo.toml index 30bbe293a..d598743ff 100644 --- a/basics/checking-accounts/anchor/programs/anchor-program-example/Cargo.toml +++ b/basics/checking-accounts/anchor/programs/anchor-program-example/Cargo.toml @@ -20,7 +20,10 @@ custom-heap = [] custom-panic = [] [dependencies] -anchor-lang = "1.1.2" +anchor-lang = "2.0.0-rc.1" +# The `#[program]` macro expands to `wincode` paths for instruction-data +# (de)serialization, so the crate has to be a direct dependency. +wincode = { version = "0.5", features = ["derive"] } [dev-dependencies] litesvm = "0.13.1" diff --git a/basics/checking-accounts/anchor/programs/anchor-program-example/src/lib.rs b/basics/checking-accounts/anchor/programs/anchor-program-example/src/lib.rs index 5cd90a851..498779155 100644 --- a/basics/checking-accounts/anchor/programs/anchor-program-example/src/lib.rs +++ b/basics/checking-accounts/anchor/programs/anchor-program-example/src/lib.rs @@ -6,7 +6,7 @@ declare_id!("ECWPhR3rJbaPfyNFgphnjxSEexbTArc7vxD8fnW6tgKw"); pub mod checking_account_program { use super::*; - pub fn check_accounts(_context: Context) -> Result<()> { + pub fn check_accounts(_context: &mut Context) -> Result<()> { Ok(()) } } @@ -14,17 +14,17 @@ pub mod checking_account_program { // Account validation in Anchor is done using the types and constraints specified in the #[derive(Accounts)] structs // 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 struct CheckingAccountsAccountConstraints { + pub payer: Signer, // 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, /// 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, + pub system_program: Program, // checks account is executable, and is the system program } diff --git a/basics/checking-accounts/anchor/programs/anchor-program-example/tests/test_checking_accounts.rs b/basics/checking-accounts/anchor/programs/anchor-program-example/tests/test_checking_accounts.rs index 15cb44851..88118fbc0 100644 --- a/basics/checking-accounts/anchor/programs/anchor-program-example/tests/test_checking_accounts.rs +++ b/basics/checking-accounts/anchor/programs/anchor-program-example/tests/test_checking_accounts.rs @@ -1,7 +1,7 @@ use { anchor_lang::{ - solana_program::{instruction::Instruction, system_instruction, system_program}, - InstructionData, ToAccountMetas, + solana_program::{instruction::Instruction, system_instruction}, + system_program, InstructionData, ToAccountMetas, }, litesvm::LiteSVM, solana_keypair::Keypair, @@ -47,7 +47,7 @@ fn test_check_accounts() { payer: payer.pubkey(), account_to_create: account_to_create.pubkey(), account_to_change: account_to_change.pubkey(), - system_program: system_program::id(), + system_program: system_program::ID, } .to_account_metas(None), ); diff --git a/basics/create-account/anchor/programs/create-system-account/Cargo.toml b/basics/create-account/anchor/programs/create-system-account/Cargo.toml index 3dd5dabd7..a4b4b1636 100644 --- a/basics/create-account/anchor/programs/create-system-account/Cargo.toml +++ b/basics/create-account/anchor/programs/create-system-account/Cargo.toml @@ -20,7 +20,10 @@ custom-heap = [] custom-panic = [] [dependencies] -anchor-lang = "1.1.2" +anchor-lang = "2.0.0-rc.1" +# The `#[program]` macro expands to `wincode` paths for instruction-data +# (de)serialization, so the crate has to be a direct dependency. +wincode = { version = "0.5", features = ["derive"] } [dev-dependencies] litesvm = "0.13.1" diff --git a/basics/create-account/anchor/programs/create-system-account/src/lib.rs b/basics/create-account/anchor/programs/create-system-account/src/lib.rs index 8898ba274..bfe079fc9 100644 --- a/basics/create-account/anchor/programs/create-system-account/src/lib.rs +++ b/basics/create-account/anchor/programs/create-system-account/src/lib.rs @@ -8,28 +8,28 @@ pub mod create_system_account { use super::*; pub fn create_system_account( - context: Context, + context: &mut Context, ) -> Result<()> { msg!("Program invoked. Creating a system account..."); msg!( " New public key will be: {}", - &context.accounts.new_account.key().to_string() + context.accounts.new_account.address() ); // The minimum lamports for rent exemption - let lamports = (Rent::get()?).minimum_balance(0); + let lamports = Rent::get()?.try_minimum_balance(0)?; create_account( CpiContext::new( - context.accounts.system_program.key(), + context.accounts.system_program.address(), CreateAccount { - from: context.accounts.payer.to_account_info(), // From pubkey - to: context.accounts.new_account.to_account_info(), // To pubkey + from: context.accounts.payer.cpi_handle_mut(), // From pubkey + to: context.accounts.new_account.cpi_handle_mut(), // To pubkey }, ), - lamports, // Lamports - 0, // Space - &context.accounts.system_program.key(), // Owner Program + lamports, // Lamports + 0, // Space + context.accounts.system_program.address(), // Owner Program )?; msg!("Account created successfully."); @@ -38,10 +38,10 @@ pub mod create_system_account { } #[derive(Accounts)] -pub struct CreateSystemAccountAccountConstraints<'info> { +pub struct CreateSystemAccountAccountConstraints { #[account(mut)] - pub payer: Signer<'info>, + pub payer: Signer, #[account(mut)] - pub new_account: Signer<'info>, - pub system_program: Program<'info, System>, + pub new_account: Signer, + pub system_program: Program, } diff --git a/basics/create-account/anchor/programs/create-system-account/tests/test_create_account.rs b/basics/create-account/anchor/programs/create-system-account/tests/test_create_account.rs index 70fe47ec5..8264d06a6 100644 --- a/basics/create-account/anchor/programs/create-system-account/tests/test_create_account.rs +++ b/basics/create-account/anchor/programs/create-system-account/tests/test_create_account.rs @@ -1,7 +1,7 @@ use { anchor_lang::{ - solana_program::{instruction::Instruction, rent::Rent, system_program}, - InstructionData, ToAccountMetas, + solana_program::{instruction::Instruction, rent::Rent}, + system_program, InstructionData, ToAccountMetas, }, litesvm::LiteSVM, solana_keypair::Keypair, @@ -25,7 +25,7 @@ fn test_create_the_account() { create_system_account::accounts::CreateSystemAccountAccountConstraints { payer: payer.pubkey(), new_account: new_account.pubkey(), - system_program: system_program::id(), + system_program: system_program::ID, } .to_account_metas(None), ); diff --git a/basics/processing-instructions/anchor/programs/processing-instructions/Cargo.toml b/basics/processing-instructions/anchor/programs/processing-instructions/Cargo.toml index 21eb08981..4bba91bde 100644 --- a/basics/processing-instructions/anchor/programs/processing-instructions/Cargo.toml +++ b/basics/processing-instructions/anchor/programs/processing-instructions/Cargo.toml @@ -20,7 +20,10 @@ custom-heap = [] custom-panic = [] [dependencies] -anchor-lang = "1.1.2" +anchor-lang = "2.0.0-rc.1" +# The `#[program]` macro expands to `wincode` paths for instruction-data +# (de)serialization, so the crate has to be a direct dependency. +wincode = { version = "0.5", features = ["derive"] } [dev-dependencies] litesvm = "0.13.1" diff --git a/basics/processing-instructions/anchor/programs/processing-instructions/src/lib.rs b/basics/processing-instructions/anchor/programs/processing-instructions/src/lib.rs index 1cc3a11f4..a9c94b8c2 100644 --- a/basics/processing-instructions/anchor/programs/processing-instructions/src/lib.rs +++ b/basics/processing-instructions/anchor/programs/processing-instructions/src/lib.rs @@ -9,7 +9,7 @@ pub mod processing_instructions { // With Anchor, we just put instruction data in the function signature! // pub fn go_to_park( - _context: Context, + _context: &mut Context, name: String, height: u32, ) -> Result<()> { diff --git a/basics/rent/anchor/programs/rent-example/Cargo.toml b/basics/rent/anchor/programs/rent-example/Cargo.toml index d63f77e9b..f67f2e2c1 100644 --- a/basics/rent/anchor/programs/rent-example/Cargo.toml +++ b/basics/rent/anchor/programs/rent-example/Cargo.toml @@ -20,13 +20,15 @@ custom-heap = [] custom-panic = [] [dependencies] -anchor-lang = "1.1.2" +anchor-lang = "2.0.0-rc.1" +# The `#[program]` macro expands to `wincode` paths for instruction-data +# (de)serialization, so the crate has to be a direct dependency. +wincode = { version = "0.5", features = ["derive"] } [dev-dependencies] litesvm = "0.13.1" solana-signer = "3.0.0" solana-keypair = "3.0.1" -borsh = "1.6.1" solana-kite = "0.4.0" [lints.rust] diff --git a/basics/rent/anchor/programs/rent-example/src/lib.rs b/basics/rent/anchor/programs/rent-example/src/lib.rs index e2d8a840b..4f7a11267 100644 --- a/basics/rent/anchor/programs/rent-example/src/lib.rs +++ b/basics/rent/anchor/programs/rent-example/src/lib.rs @@ -8,35 +8,37 @@ pub mod rent_example { use super::*; pub fn create_system_account( - context: Context, + context: &mut Context, address_data: AddressData, ) -> Result<()> { msg!("Program invoked. Creating a system account..."); msg!( " New public key will be: {}", - &context.accounts.new_account.key().to_string() + context.accounts.new_account.address() ); // Determine the necessary minimum rent by calculating the account's size // - // borsh 1.x: try_to_vec() removed, use borsh::to_vec() instead - let account_span = anchor_lang::prelude::borsh::to_vec(&address_data)?.len(); - let lamports_required = (Rent::get()?).minimum_balance(account_span); + // v2 encodes instruction data with wincode rather than borsh. `BorshConfig` + // is wincode's borsh-compatible wire format (fixed u32 little-endian length + // prefixes), so the span matches the bytes borsh would have produced. + let account_span = address_data.serialized_span()?; + let lamports_required = Rent::get()?.try_minimum_balance(account_span)?; - msg!("Account span: {}", &account_span); - msg!("Lamports required: {}", &lamports_required); + msg!("Account span: {}", account_span); + msg!("Lamports required: {}", lamports_required); system_program::create_account( CpiContext::new( - context.accounts.system_program.key(), + context.accounts.system_program.address(), system_program::CreateAccount { - from: context.accounts.payer.to_account_info(), - to: context.accounts.new_account.to_account_info(), + from: context.accounts.payer.cpi_handle_mut(), + to: context.accounts.new_account.cpi_handle_mut(), }, ), lamports_required, account_span as u64, - &context.accounts.system_program.key(), + context.accounts.system_program.address(), )?; msg!("Account created successfully."); @@ -45,16 +47,25 @@ pub mod rent_example { } #[derive(Accounts)] -pub struct CreateSystemAccountAccountConstraints<'info> { +pub struct CreateSystemAccountAccountConstraints { #[account(mut)] - pub payer: Signer<'info>, + pub payer: Signer, #[account(mut)] - pub new_account: Signer<'info>, - pub system_program: Program<'info, System>, + pub new_account: Signer, + pub system_program: Program, } -#[derive(AnchorSerialize, AnchorDeserialize, Debug)] +#[derive(Clone, Debug, IdlType, wincode::SchemaRead, wincode::SchemaWrite)] pub struct AddressData { - name: String, - address: String, + pub name: String, + pub address: String, +} + +impl AddressData { + /// Bytes this struct occupies on the wire, which is what the new account + /// has to be sized — and therefore rent-funded — for. + fn serialized_span(&self) -> Result { + >::size_of(self) + .map_err(|_| ProgramError::InvalidInstructionData.into()) + } } diff --git a/basics/rent/anchor/programs/rent-example/tests/test_rent.rs b/basics/rent/anchor/programs/rent-example/tests/test_rent.rs index 2451d0dcb..dffe8b8c8 100644 --- a/basics/rent/anchor/programs/rent-example/tests/test_rent.rs +++ b/basics/rent/anchor/programs/rent-example/tests/test_rent.rs @@ -1,35 +1,14 @@ use { anchor_lang::{ - solana_program::{instruction::Instruction, system_program}, - InstructionData, ToAccountMetas, + solana_program::instruction::Instruction, + system_program, InstructionData, ToAccountMetas, }, - borsh::BorshDeserialize, litesvm::LiteSVM, solana_keypair::Keypair, solana_kite::{create_wallet, send_transaction_from_instructions}, solana_signer::Signer, }; -/// Build borsh-serialized AddressData bytes (fields are private in the crate). -fn build_address_data_borsh(name: &str, address: &str) -> Vec { - let mut data = Vec::new(); - data.extend_from_slice(&(name.len() as u32).to_le_bytes()); - data.extend_from_slice(name.as_bytes()); - data.extend_from_slice(&(address.len() as u32).to_le_bytes()); - data.extend_from_slice(address.as_bytes()); - data -} - -/// Construct the full instruction data with discriminator + AddressData. -/// Deserialize the borsh bytes into AddressData via the crate's BorshDeserialize impl, -/// then use InstructionData to get the final bytes. -fn build_create_system_account_ix_data(name: &str, address: &str) -> Vec { - let address_data_bytes = build_address_data_borsh(name, address); - let address_data = - rent_example::AddressData::deserialize(&mut address_data_bytes.as_slice()).unwrap(); - rent_example::instruction::CreateSystemAccount { address_data }.data() -} - #[test] fn test_create_system_account() { let program_id = rent_example::id(); @@ -43,7 +22,13 @@ fn test_create_system_account() { let name = "Marcus"; let address = "123 Main St. San Francisco, CA"; - let ix_data = build_create_system_account_ix_data(name, address); + let ix_data = rent_example::instruction::CreateSystemAccount { + address_data: rent_example::AddressData { + name: name.to_string(), + address: address.to_string(), + }, + } + .data(); let instruction = Instruction::new_with_bytes( program_id, @@ -51,7 +36,7 @@ fn test_create_system_account() { rent_example::accounts::CreateSystemAccountAccountConstraints { payer: payer.pubkey(), new_account: new_account.pubkey(), - system_program: system_program::id(), + system_program: system_program::ID, } .to_account_metas(None), ); @@ -65,7 +50,7 @@ fn test_create_system_account() { .unwrap(); // Verify the account was created with the correct size - // Borsh serialized AddressData: 4 + 6 ("Marcus") + 4 + 30 = 44 bytes + // Serialized AddressData: 4 + 6 ("Marcus") + 4 + 30 = 44 bytes let expected_size = 4 + name.len() + 4 + address.len(); let account = svm.get_account(&new_account.pubkey()).unwrap(); assert_eq!(account.data.len(), expected_size); From ab3c71da47663cb921491b9a03860b13b1b731aa Mon Sep 17 00:00:00 2001 From: Claude Date: Thu, 13 Aug 2026 21:49:45 +0000 Subject: [PATCH 03/59] basics: port counter and transfer-sol to Anchor v2 counter's `Counter { count: u64 }` is already a valid Pod layout, so it keeps v2's default zero-copy `#[account]` backing and its test, which reads the account as an 8-byte discriminator plus a little-endian u64, is unchanged. transfer-sol's direct-lamport handler is the interesting one. v1 wrote through `**account.try_borrow_mut_lamports()?`; v2 has no `RefCell` layer, so the handler copies the `AccountView` (it is `Copy`, and a copy still points at the same backing buffer) and calls `set_lamports`. The checked arithmetic stays in the handler so the example keeps reporting its own `InsufficientFunds` / `AmountOverflow` errors rather than the generic ones `Lamports::sub_lamports` would raise. Both build with cargo-build-sbf and pass their LiteSVM tests. --- .../anchor/programs/counter_anchor/Cargo.toml | 5 ++++- .../src/instructions/increment.rs | 6 +++--- .../src/instructions/initialize_counter.rs | 10 +++++----- .../anchor/programs/counter_anchor/src/lib.rs | 6 ++++-- .../counter_anchor/tests/test_counter.rs | 13 ++++++------ .../anchor/programs/transfer-sol/Cargo.toml | 5 ++++- .../src/instructions/transfer_sol_with_cpi.rs | 19 ++++++++++-------- .../instructions/transfer_sol_with_program.rs | 20 +++++++++++-------- .../anchor/programs/transfer-sol/src/lib.rs | 4 ++-- .../transfer-sol/tests/test_transfer_sol.rs | 5 ++--- 10 files changed, 53 insertions(+), 40 deletions(-) diff --git a/basics/counter/anchor/programs/counter_anchor/Cargo.toml b/basics/counter/anchor/programs/counter_anchor/Cargo.toml index 3bf40b435..b6e306811 100644 --- a/basics/counter/anchor/programs/counter_anchor/Cargo.toml +++ b/basics/counter/anchor/programs/counter_anchor/Cargo.toml @@ -20,7 +20,10 @@ custom-heap = [] custom-panic = [] [dependencies] -anchor-lang = "1.1.2" +anchor-lang = "2.0.0-rc.1" +# The `#[program]` macro expands to `wincode` paths for instruction-data +# (de)serialization, so the crate has to be a direct dependency. +wincode = { version = "0.5", features = ["derive"] } [dev-dependencies] litesvm = "0.13.1" diff --git a/basics/counter/anchor/programs/counter_anchor/src/instructions/increment.rs b/basics/counter/anchor/programs/counter_anchor/src/instructions/increment.rs index fa2a65946..8ed24a48d 100644 --- a/basics/counter/anchor/programs/counter_anchor/src/instructions/increment.rs +++ b/basics/counter/anchor/programs/counter_anchor/src/instructions/increment.rs @@ -3,12 +3,12 @@ use anchor_lang::prelude::*; use crate::{Counter, CounterError}; #[derive(Accounts)] -pub struct IncrementAccountConstraints<'info> { +pub struct IncrementAccountConstraints { #[account(mut)] - pub counter: Account<'info, Counter>, + pub counter: Account, } -pub fn handler(context: Context) -> Result<()> { +pub fn handler(context: &mut Context) -> Result<()> { context.accounts.counter.count = context .accounts .counter diff --git a/basics/counter/anchor/programs/counter_anchor/src/instructions/initialize_counter.rs b/basics/counter/anchor/programs/counter_anchor/src/instructions/initialize_counter.rs index fa17cfd55..43afcd257 100644 --- a/basics/counter/anchor/programs/counter_anchor/src/instructions/initialize_counter.rs +++ b/basics/counter/anchor/programs/counter_anchor/src/instructions/initialize_counter.rs @@ -3,19 +3,19 @@ use anchor_lang::prelude::*; use crate::Counter; #[derive(Accounts)] -pub struct InitializeCounterAccountConstraints<'info> { +pub struct InitializeCounterAccountConstraints { #[account(mut)] - pub payer: Signer<'info>, + pub payer: Signer, #[account( init, space = Counter::DISCRIMINATOR.len() + Counter::INIT_SPACE, payer = payer )] - pub counter: Account<'info, Counter>, - pub system_program: Program<'info, System>, + pub counter: Account, + pub system_program: Program, } -pub fn handler(_context: Context) -> Result<()> { +pub fn handler(_context: &mut Context) -> Result<()> { Ok(()) } diff --git a/basics/counter/anchor/programs/counter_anchor/src/lib.rs b/basics/counter/anchor/programs/counter_anchor/src/lib.rs index d6a48dfcd..2c44bbb34 100644 --- a/basics/counter/anchor/programs/counter_anchor/src/lib.rs +++ b/basics/counter/anchor/programs/counter_anchor/src/lib.rs @@ -9,11 +9,13 @@ declare_id!("BmDHboaj1kBUoinJKKSRqKfMeRKJqQqEbUj1VgzeQe4A"); pub mod counter_anchor { use super::*; - pub fn initialize_counter(context: Context) -> Result<()> { + pub fn initialize_counter( + context: &mut Context, + ) -> Result<()> { instructions::initialize_counter::handler(context) } - pub fn increment(context: Context) -> Result<()> { + pub fn increment(context: &mut Context) -> Result<()> { instructions::increment::handler(context) } } diff --git a/basics/counter/anchor/programs/counter_anchor/tests/test_counter.rs b/basics/counter/anchor/programs/counter_anchor/tests/test_counter.rs index 97a3d3c03..755fa0d48 100644 --- a/basics/counter/anchor/programs/counter_anchor/tests/test_counter.rs +++ b/basics/counter/anchor/programs/counter_anchor/tests/test_counter.rs @@ -1,7 +1,6 @@ use { anchor_lang::{ - solana_program::{instruction::Instruction, system_program}, - InstructionData, ToAccountMetas, + solana_program::instruction::Instruction, system_program, InstructionData, ToAccountMetas, }, borsh::BorshDeserialize, litesvm::LiteSVM, @@ -17,7 +16,7 @@ struct CounterAccount { count: u64, } -fn setup() -> (LiteSVM, anchor_lang::prelude::Pubkey, Keypair) { +fn setup() -> (LiteSVM, anchor_lang::prelude::Address, Keypair) { let program_id = counter_anchor::id(); let mut svm = LiteSVM::new(); let bytes = include_bytes!("../../../target/deploy/counter_anchor.so"); @@ -26,7 +25,7 @@ fn setup() -> (LiteSVM, anchor_lang::prelude::Pubkey, Keypair) { (svm, program_id, payer) } -fn fetch_counter(svm: &LiteSVM, counter_pubkey: &anchor_lang::prelude::Pubkey) -> u64 { +fn fetch_counter(svm: &LiteSVM, counter_pubkey: &anchor_lang::prelude::Address) -> u64 { let account = svm.get_account(counter_pubkey).unwrap(); let counter = CounterAccount::try_from_slice(&account.data).unwrap(); counter.count @@ -43,7 +42,7 @@ fn test_initialize_counter() { counter_anchor::accounts::InitializeCounterAccountConstraints { payer: payer.pubkey(), counter: counter_keypair.pubkey(), - system_program: system_program::id(), + system_program: system_program::ID, } .to_account_metas(None), ); @@ -72,7 +71,7 @@ fn test_increment_counter() { counter_anchor::accounts::InitializeCounterAccountConstraints { payer: payer.pubkey(), counter: counter_keypair.pubkey(), - system_program: system_program::id(), + system_program: system_program::ID, } .to_account_metas(None), ); @@ -111,7 +110,7 @@ fn test_increment_counter_again() { counter_anchor::accounts::InitializeCounterAccountConstraints { payer: payer.pubkey(), counter: counter_keypair.pubkey(), - system_program: system_program::id(), + system_program: system_program::ID, } .to_account_metas(None), ); diff --git a/basics/transfer-sol/anchor/programs/transfer-sol/Cargo.toml b/basics/transfer-sol/anchor/programs/transfer-sol/Cargo.toml index cf282ac60..f162a6257 100644 --- a/basics/transfer-sol/anchor/programs/transfer-sol/Cargo.toml +++ b/basics/transfer-sol/anchor/programs/transfer-sol/Cargo.toml @@ -20,7 +20,10 @@ custom-heap = [] custom-panic = [] [dependencies] -anchor-lang = "1.1.2" +anchor-lang = "2.0.0-rc.1" +# The `#[program]` macro expands to `wincode` paths for instruction-data +# (de)serialization, so the crate has to be a direct dependency. +wincode = { version = "0.5", features = ["derive"] } [dev-dependencies] litesvm = "0.13.1" diff --git a/basics/transfer-sol/anchor/programs/transfer-sol/src/instructions/transfer_sol_with_cpi.rs b/basics/transfer-sol/anchor/programs/transfer-sol/src/instructions/transfer_sol_with_cpi.rs index 4aa9ed0a9..84d600933 100644 --- a/basics/transfer-sol/anchor/programs/transfer-sol/src/instructions/transfer_sol_with_cpi.rs +++ b/basics/transfer-sol/anchor/programs/transfer-sol/src/instructions/transfer_sol_with_cpi.rs @@ -2,21 +2,24 @@ use anchor_lang::prelude::*; use anchor_lang::system_program; #[derive(Accounts)] -pub struct TransferSolWithCpiAccountConstraints<'info> { +pub struct TransferSolWithCpiAccountConstraints { #[account(mut)] - payer: Signer<'info>, + pub payer: Signer, #[account(mut)] - recipient: SystemAccount<'info>, - system_program: Program<'info, System>, + pub recipient: SystemAccount, + pub system_program: Program, } -pub fn handler(context: Context, amount: u64) -> Result<()> { +pub fn handler( + context: &mut Context, + amount: u64, +) -> Result<()> { system_program::transfer( CpiContext::new( - context.accounts.system_program.key(), + context.accounts.system_program.address(), system_program::Transfer { - from: context.accounts.payer.to_account_info(), - to: context.accounts.recipient.to_account_info(), + from: context.accounts.payer.cpi_handle_mut(), + to: context.accounts.recipient.cpi_handle_mut(), }, ), amount, diff --git a/basics/transfer-sol/anchor/programs/transfer-sol/src/instructions/transfer_sol_with_program.rs b/basics/transfer-sol/anchor/programs/transfer-sol/src/instructions/transfer_sol_with_program.rs index 03d98b73b..2274f7138 100644 --- a/basics/transfer-sol/anchor/programs/transfer-sol/src/instructions/transfer_sol_with_program.rs +++ b/basics/transfer-sol/anchor/programs/transfer-sol/src/instructions/transfer_sol_with_program.rs @@ -9,36 +9,40 @@ pub enum TransferSolError { } #[derive(Accounts)] -pub struct TransferSolWithProgramAccountConstraints<'info> { +pub struct TransferSolWithProgramAccountConstraints { /// CHECK: Use owner constraint to check account is owned by our program #[account( mut, owner = crate::ID // value of declare_id!() )] - payer: UncheckedAccount<'info>, + pub payer: UncheckedAccount, #[account(mut)] - recipient: SystemAccount<'info>, + pub recipient: SystemAccount, } // Directly modifying lamports is only possible if the program is the owner of the account pub fn handler( - context: Context, + context: &mut Context, amount: u64, ) -> Result<()> { let payer = &context.accounts.payer; let recipient = &context.accounts.recipient; let new_payer_lamports = payer - .lamports() + .get_lamports() .checked_sub(amount) .ok_or(TransferSolError::InsufficientFunds)?; let new_recipient_lamports = recipient - .lamports() + .get_lamports() .checked_add(amount) .ok_or(TransferSolError::AmountOverflow)?; - **payer.try_borrow_mut_lamports()? = new_payer_lamports; - **recipient.try_borrow_mut_lamports()? = new_recipient_lamports; + // `AccountView` is `Copy`, and a copy still points at the same backing + // buffer, so `set_lamports` writes through to the real account. + let mut payer_view = *payer.account(); + let mut recipient_view = *recipient.account(); + payer_view.set_lamports(new_payer_lamports); + recipient_view.set_lamports(new_recipient_lamports); Ok(()) } diff --git a/basics/transfer-sol/anchor/programs/transfer-sol/src/lib.rs b/basics/transfer-sol/anchor/programs/transfer-sol/src/lib.rs index 49f40cb3a..6c72a9ecc 100644 --- a/basics/transfer-sol/anchor/programs/transfer-sol/src/lib.rs +++ b/basics/transfer-sol/anchor/programs/transfer-sol/src/lib.rs @@ -10,14 +10,14 @@ pub mod transfer_sol { use super::*; pub fn transfer_sol_with_cpi( - context: Context, + context: &mut Context, amount: u64, ) -> Result<()> { instructions::transfer_sol_with_cpi::handler(context, amount) } pub fn transfer_sol_with_program( - context: Context, + context: &mut Context, amount: u64, ) -> Result<()> { instructions::transfer_sol_with_program::handler(context, amount) diff --git a/basics/transfer-sol/anchor/programs/transfer-sol/tests/test_transfer_sol.rs b/basics/transfer-sol/anchor/programs/transfer-sol/tests/test_transfer_sol.rs index 3afb86614..7847d442d 100644 --- a/basics/transfer-sol/anchor/programs/transfer-sol/tests/test_transfer_sol.rs +++ b/basics/transfer-sol/anchor/programs/transfer-sol/tests/test_transfer_sol.rs @@ -1,7 +1,6 @@ use { anchor_lang::{ - solana_program::{instruction::Instruction, system_program}, - InstructionData, ToAccountMetas, + solana_program::instruction::Instruction, system_program, InstructionData, ToAccountMetas, }, litesvm::LiteSVM, solana_keypair::Keypair, @@ -30,7 +29,7 @@ fn test_transfer_sol_with_cpi() { transfer_sol::accounts::TransferSolWithCpiAccountConstraints { payer: payer.pubkey(), recipient: recipient.pubkey(), - system_program: system_program::id(), + system_program: system_program::ID, } .to_account_metas(None), ); From f1ec750084652d978e4f47e95b1d056dfae71ee2 Mon Sep 17 00:00:00 2001 From: Claude Date: Thu, 13 Aug 2026 21:53:33 +0000 Subject: [PATCH 04/59] close-account: port to Anchor v2, and record the migration in CHANGELOG `User` holds a `String`, so it moves to `#[account(borsh)]` + `BorshAccount`; `Pubkey` becomes `Address`, and the PDA seed reads `user.address().as_ref()`. This is also the first example to hit the wincode split: anchor-lang 2.0.0-rc.1 is built against wincode 0.5, but solana-address 2.7 moved to wincode 0.6, so `Address`'s `SchemaRead`/`SchemaWrite` impls belong to the version the `#[account(borsh)]` derive is not using and every bound fails. Pinning `solana-address = ">=2.6, <2.7"` unifies the graph on the 0.5 line. The pin needs the lower bound: a bare `<2.7` lets cargo satisfy the requirement by reusing the unrelated 1.1.0 already in the graph, and the 2.7 copy survives. Builds with cargo-build-sbf and passes its LiteSVM tests. --- CHANGELOG.md | 35 +++++++++++++++++++ .../anchor/programs/close-account/Cargo.toml | 11 +++++- .../src/instructions/close_user.rs | 10 +++--- .../src/instructions/create_user.rs | 14 ++++---- .../anchor/programs/close-account/src/lib.rs | 7 ++-- .../programs/close-account/src/state/user.rs | 8 +++-- .../close-account/tests/test_close_account.rs | 8 ++--- 7 files changed, 71 insertions(+), 22 deletions(-) diff --git a/CHANGELOG.md b/CHANGELOG.md index 07a6002c3..7a9c1a925 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -4,6 +4,41 @@ 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-08-13] - Anchor examples in `basics/` move to Anchor v2.0.0-rc.1 + +### Changed + +- Every Anchor example under `basics/` now builds against `anchor-lang` + 2.0.0-rc.1. v2 is a ground-up rewrite rather than a version bump: the crate is + `no_std` and built on pinocchio, so handlers take `&mut Context`, the + `<'info>` lifetime disappears from `#[derive(Accounts)]` structs and account + wrappers, `Pubkey` becomes `Address`, `.to_account_info()` becomes + `.cpi_handle_mut()` / `.cpi_handle()`, and `.key()` becomes `.address()`. +- `#[account]` is now zero-copy and requires a `Pod` layout. State holding + `String` or `Vec` moves to `#[account(borsh)]` plus `BorshAccount` + (`account-data`, `close-account`, `favorites`, `realloc`, `pyth`); state that + is already fixed-layout keeps the zero-copy default but must carry explicit + padding (`program-derived-addresses`) and cannot use `bool` + (`cross-program-invocation` uses `PodBool`). +- Instruction data is wincode-encoded rather than borsh. The `#[program]` macro + expands to `wincode` paths, so every program crate takes a direct `wincode` + dependency. `BorshConfig` keeps the wire format byte-identical to borsh, so + the checked-in account layouts and the tests that decode them with borsh are + unaffected. +- The only edit most LiteSVM tests needed: v2's `solana_program` compat shim has + no `system_program` submodule (the real module is at the crate root and + exposes `ID`, not `id()`) and no `pubkey::Pubkey` unless the `compat` feature + is on (`anchor_lang::Address` is the same 32-byte type). + +### Fixed + +- Anchor programs that put an `Address` in serialized state pin + `solana-address = ">=2.6, <2.7"`. anchor-lang 2.0.0-rc.1 is built against + wincode 0.5, but solana-address 2.7 moved to wincode 0.6; with both in the + graph, `Address`'s wincode impls belong to the version the `#[account(borsh)]` + derive is not using, and every `SchemaRead` / `SchemaWrite` bound fails. This + is the same class of split that the zeropod/quasar-lang pin below addresses. + ## [2026-08-04] - Oracle readers reject prices from before a cluster restart ### Added diff --git a/basics/close-account/anchor/programs/close-account/Cargo.toml b/basics/close-account/anchor/programs/close-account/Cargo.toml index fe78b018f..ee6627d7c 100644 --- a/basics/close-account/anchor/programs/close-account/Cargo.toml +++ b/basics/close-account/anchor/programs/close-account/Cargo.toml @@ -20,7 +20,16 @@ custom-heap = [] custom-panic = [] [dependencies] -anchor-lang = "1.1.2" +anchor-lang = "2.0.0-rc.1" +# The `#[program]` macro expands to `wincode` paths for instruction-data +# (de)serialization, so the crate has to be a direct dependency. +wincode = { version = "0.5", features = ["derive"] } +# anchor-lang 2.0.0-rc.1 is built against wincode 0.5, but solana-address 2.7 +# moved to wincode 0.6. Both end up in the graph, and `Address`'s wincode impls +# then belong to the version the `#[account(borsh)]` derive is not using — so an +# `Address` field fails to satisfy `SchemaRead`/`SchemaWrite`. 2.6.1 is the last +# release on the 0.5 line. +solana-address = ">=2.6, <2.7" [dev-dependencies] litesvm = "0.13.1" diff --git a/basics/close-account/anchor/programs/close-account/src/instructions/close_user.rs b/basics/close-account/anchor/programs/close-account/src/instructions/close_user.rs index 6606fe22a..b43bcd379 100644 --- a/basics/close-account/anchor/programs/close-account/src/instructions/close_user.rs +++ b/basics/close-account/anchor/programs/close-account/src/instructions/close_user.rs @@ -2,22 +2,22 @@ use crate::state::*; use anchor_lang::prelude::*; #[derive(Accounts)] -pub struct CloseUserAccountConstraints<'info> { +pub struct CloseUserAccountConstraints { #[account(mut)] - pub user: Signer<'info>, + pub user: Signer, #[account( mut, seeds = [ b"USER", - user.key().as_ref(), + user.address().as_ref(), ], bump = user_account.bump, close = user, // close account and return lamports to user )] - pub user_account: Account<'info, User>, + pub user_account: BorshAccount, } -pub fn handle_close_user(_context: Context) -> Result<()> { +pub fn handle_close_user(_context: &mut Context) -> Result<()> { Ok(()) } diff --git a/basics/close-account/anchor/programs/close-account/src/instructions/create_user.rs b/basics/close-account/anchor/programs/close-account/src/instructions/create_user.rs index a64ebd79e..e8ac87e2e 100644 --- a/basics/close-account/anchor/programs/close-account/src/instructions/create_user.rs +++ b/basics/close-account/anchor/programs/close-account/src/instructions/create_user.rs @@ -2,9 +2,9 @@ use crate::state::*; use anchor_lang::prelude::*; #[derive(Accounts)] -pub struct CreateUserAccountConstraints<'info> { +pub struct CreateUserAccountConstraints { #[account(mut)] - pub user: Signer<'info>, + pub user: Signer, #[account( init, @@ -12,21 +12,21 @@ pub struct CreateUserAccountConstraints<'info> { space = User::DISCRIMINATOR.len() + User::INIT_SPACE, seeds = [ b"USER", - user.key().as_ref(), + user.address().as_ref(), ], bump )] - pub user_account: Account<'info, User>, - pub system_program: Program<'info, System>, + pub user_account: BorshAccount, + pub system_program: Program, } pub fn handle_create_user( - context: Context, + context: &mut Context, name: String, ) -> Result<()> { *context.accounts.user_account = User { bump: context.bumps.user_account, - user: context.accounts.user.key(), + user: *context.accounts.user.address(), name, }; Ok(()) diff --git a/basics/close-account/anchor/programs/close-account/src/lib.rs b/basics/close-account/anchor/programs/close-account/src/lib.rs index 0819ae853..f8f0cdc1b 100644 --- a/basics/close-account/anchor/programs/close-account/src/lib.rs +++ b/basics/close-account/anchor/programs/close-account/src/lib.rs @@ -9,11 +9,14 @@ declare_id!("99TQtoDdQ5NS2v5Ppha93aqEmv3vV9VZVfHTP5rGST3c"); pub mod close_account_program { use super::*; - pub fn create_user(context: Context, name: String) -> Result<()> { + pub fn create_user( + context: &mut Context, + name: String, + ) -> Result<()> { create_user::handle_create_user(context, name) } - pub fn close_user(context: Context) -> Result<()> { + pub fn close_user(context: &mut Context) -> Result<()> { close_user::handle_close_user(context) } } diff --git a/basics/close-account/anchor/programs/close-account/src/state/user.rs b/basics/close-account/anchor/programs/close-account/src/state/user.rs index 6548d53dc..4adfbba2e 100644 --- a/basics/close-account/anchor/programs/close-account/src/state/user.rs +++ b/basics/close-account/anchor/programs/close-account/src/state/user.rs @@ -1,10 +1,12 @@ use anchor_lang::prelude::*; -#[account] +// `borsh` because the struct holds a `String`: v2's default `#[account]` +// backing is zero-copy and needs a `Pod` (fixed-layout) type. +#[account(borsh)] #[derive(InitSpace)] // automatically calculate the space required for the struct pub struct User { - pub bump: u8, // 1 byte - pub user: Pubkey, // 32 bytes + pub bump: u8, // 1 byte + pub user: Address, // 32 bytes #[max_len(50)] // set a max length for the string pub name: String, // 4 bytes + 50 bytes } diff --git a/basics/close-account/anchor/programs/close-account/tests/test_close_account.rs b/basics/close-account/anchor/programs/close-account/tests/test_close_account.rs index f7189df61..c12b11239 100644 --- a/basics/close-account/anchor/programs/close-account/tests/test_close_account.rs +++ b/basics/close-account/anchor/programs/close-account/tests/test_close_account.rs @@ -1,7 +1,7 @@ use { anchor_lang::{ - solana_program::{instruction::Instruction, pubkey::Pubkey, system_program}, - InstructionData, ToAccountMetas, + solana_program::instruction::Instruction, system_program, Address, InstructionData, + ToAccountMetas, }, litesvm::LiteSVM, solana_keypair::Keypair, @@ -25,7 +25,7 @@ fn test_create_and_close_user() { // Derive the PDA for the user's account let (user_account_pda, _bump) = - Pubkey::find_program_address(&[b"USER", payer.pubkey().as_ref()], &program_id); + Address::find_program_address(&[b"USER", payer.pubkey().as_ref()], &program_id); // Create user let create_ix = Instruction::new_with_bytes( @@ -37,7 +37,7 @@ fn test_create_and_close_user() { close_account_program::accounts::CreateUserAccountConstraints { user: payer.pubkey(), user_account: user_account_pda, - system_program: system_program::id(), + system_program: system_program::ID, } .to_account_metas(None), ); From 05d7ae8e622c81076c9b112c60251fca96b8c6ad Mon Sep 17 00:00:00 2001 From: Claude Date: Thu, 13 Aug 2026 21:53:48 +0000 Subject: [PATCH 05/59] basics: port the remaining examples to Anchor v2 (verification in progress) account-data, favorites, pda-rent-payer, program-derived-addresses, pyth, realloc, repository-layout and cross-program-invocation. All compile clean against anchor-lang 2.0.0-rc.1; SBF builds and LiteSVM runs are still going, so these are not yet confirmed green the way the earlier commits are. Notes on the less mechanical ones: - program-derived-addresses: `PageVisits` stays zero-copy, which means it needs explicit padding, bytemuck rejects implicit padding, so `u32 + u8` grows a named `[u8; 3]`. The test decodes the account with borsh and `try_from_slice` refuses trailing bytes, so its local struct spells the padding out too. - favorites: `String` + `Vec` state moves to `#[account(borsh)]` / `BorshAccount`. v2 has no `set_inner`, so the handler assigns through `DerefMut`, and no `init-if-needed` cargo feature, the constraint is always available. - realloc: the sub-constraints are `realloc_payer` / `realloc_zero` in v2, not `realloc::payer` / `realloc::zero`. - pyth: v2's `Owner` is `const OWNER: Address` rather than a method, which makes the vendored foreign-owned `PriceUpdateV2` much simpler, the manual `AccountSerialize`/`AccountDeserialize` impls are gone entirely, since `BorshAccount` drives wincode with the borsh-compatible config. The test takes `Clock` from `solana_clock` now; `anchor_lang`'s is pinocchio's on-chain type, which LiteSVM's `get_sysvar` does not accept. - cross-program-invocation: `idls/lever.json` is regenerated with the v2 CLI (built from the v2.0.0-rc.1 tag), since `hand` consumes it via `declare_program!`. `PowerStatus` uses `#[account(borsh)]` rather than the zero-copy default: zero-copy would force `is_on` to be `PodBool`, and the generated IDL renders `PodBool` as a plain `bool` alias, so `declare_program!` would regenerate a struct that is no longer `Pod`. Borsh keeps a real `bool` on both sides of the CPI. --- Cargo.lock | 44 ++++++++++------ .../anchor-program-example/Cargo.toml | 5 +- .../src/instructions/create.rs | 10 ++-- .../anchor-program-example/src/lib.rs | 2 +- .../src/state/address_info.rs | 4 +- .../tests/test_account_data.rs | 6 +-- .../anchor-program-example/src/lib.rs | 4 +- .../anchor/idls/lever.json | 44 ++++++++++++++-- .../anchor/programs/hand/Cargo.toml | 5 +- .../anchor/programs/hand/src/lib.rs | 15 +++--- .../anchor/programs/lever/Cargo.toml | 5 +- .../lever/src/instructions/initialize.rs | 10 ++-- .../lever/src/instructions/switch_power.rs | 9 ++-- .../anchor/programs/lever/src/lib.rs | 12 +++-- .../anchor/programs/lever/tests/test_lever.rs | 9 ++-- .../anchor/programs/favorites/Cargo.toml | 5 +- .../anchor/programs/favorites/src/lib.rs | 22 ++++---- .../favorites/tests/test_favorites.rs | 24 ++++----- .../anchor-program-example/Cargo.toml | 5 +- .../src/instructions/create_new_account.rs | 24 ++++----- .../src/instructions/init_rent_vault.rs | 16 +++--- .../anchor-program-example/src/lib.rs | 6 ++- .../tests/test_pda_rent_payer.rs | 14 ++--- .../anchor-program-example/Cargo.toml | 5 +- .../src/instructions/create.rs | 13 ++--- .../src/instructions/increment.rs | 10 ++-- .../anchor-program-example/src/lib.rs | 6 ++- .../src/state/page_visits.rs | 3 ++ .../tests/test_program_derived_addresses.rs | 12 +++-- .../anchor/programs/pythexample/Cargo.toml | 10 +++- .../anchor/programs/pythexample/src/lib.rs | 51 ++++--------------- .../programs/pythexample/tests/test_pyth.rs | 16 +++--- .../anchor/programs/anchor-realloc/Cargo.toml | 5 +- .../src/instructions/initialize.rs | 10 ++-- .../anchor-realloc/src/instructions/update.rs | 14 ++--- .../anchor/programs/anchor-realloc/src/lib.rs | 9 ++-- .../anchor-realloc/tests/test_realloc.rs | 15 +++--- .../programs/rent-example/tests/test_rent.rs | 3 +- .../anchor/programs/carnival/Cargo.toml | 5 +- .../anchor/programs/carnival/src/lib.rs | 10 ++-- 40 files changed, 291 insertions(+), 206 deletions(-) diff --git a/Cargo.lock b/Cargo.lock index 9a2a5b733..bacbf4b62 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -6,12 +6,13 @@ version = 4 name = "account-data-anchor-program" version = "0.1.0" dependencies = [ - "anchor-lang 1.1.2", + "anchor-lang 2.0.0-rc.1", "borsh 1.7.0", "litesvm", "solana-keypair", "solana-kite", "solana-signer", + "wincode 0.5.5", ] [[package]] @@ -445,12 +446,13 @@ dependencies = [ name = "anchor-realloc" version = "0.1.0" dependencies = [ - "anchor-lang 1.1.2", + "anchor-lang 2.0.0-rc.1", "borsh 1.7.0", "litesvm", "solana-keypair", "solana-kite", "solana-signer", + "wincode 0.5.5", ] [[package]] @@ -1088,11 +1090,12 @@ dependencies = [ name = "carnival" version = "0.1.0" dependencies = [ - "anchor-lang 1.1.2", + "anchor-lang 2.0.0-rc.1", "litesvm", "solana-keypair", "solana-kite", "solana-signer", + "wincode 0.5.5", ] [[package]] @@ -1146,12 +1149,13 @@ dependencies = [ name = "checking-accounts-anchor-program-example" version = "0.1.0" dependencies = [ - "anchor-lang 1.1.2", + "anchor-lang 2.0.0-rc.1", "borsh 1.7.0", "litesvm", "solana-keypair", "solana-kite", "solana-signer", + "wincode 0.5.5", ] [[package]] @@ -1233,11 +1237,13 @@ dependencies = [ name = "close-account-program" version = "0.1.0" dependencies = [ - "anchor-lang 1.1.2", + "anchor-lang 2.0.0-rc.1", "litesvm", + "solana-address 2.6.1", "solana-keypair", "solana-kite", "solana-signer", + "wincode 0.5.5", ] [[package]] @@ -1345,12 +1351,13 @@ dependencies = [ name = "counter_anchor" version = "0.1.0" dependencies = [ - "anchor-lang 1.1.2", + "anchor-lang 2.0.0-rc.1", "borsh 1.7.0", "litesvm", "solana-keypair", "solana-kite", "solana-signer", + "wincode 0.5.5", ] [[package]] @@ -1419,11 +1426,12 @@ dependencies = [ name = "create-system-account" version = "0.1.0" dependencies = [ - "anchor-lang 1.1.2", + "anchor-lang 2.0.0-rc.1", "litesvm", "solana-keypair", "solana-kite", "solana-signer", + "wincode 0.5.5", ] [[package]] @@ -1991,11 +1999,12 @@ dependencies = [ name = "hand" version = "0.1.0" dependencies = [ - "anchor-lang 1.1.2", + "anchor-lang 2.0.0-rc.1", "litesvm", "solana-keypair", "solana-kite", "solana-signer", + "wincode 0.5.5", ] [[package]] @@ -2233,11 +2242,12 @@ checksum = "bbd2bcb4c963f2ddae06a2efc7e9f3591312473c50c6685e1f298068316e66fe" name = "lever" version = "0.1.0" dependencies = [ - "anchor-lang 1.1.2", + "anchor-lang 2.0.0-rc.1", "litesvm", "solana-keypair", "solana-kite", "solana-signer", + "wincode 0.5.5", ] [[package]] @@ -2699,11 +2709,12 @@ dependencies = [ name = "pda-rent-payer" version = "0.1.0" dependencies = [ - "anchor-lang 1.1.2", + "anchor-lang 2.0.0-rc.1", "litesvm", "solana-keypair", "solana-kite", "solana-signer", + "wincode 0.5.5", ] [[package]] @@ -2905,11 +2916,12 @@ dependencies = [ name = "processing-instructions" version = "0.1.0" dependencies = [ - "anchor-lang 1.1.2", + "anchor-lang 2.0.0-rc.1", "litesvm", "solana-keypair", "solana-kite", "solana-signer", + "wincode 0.5.5", ] [[package]] @@ -2995,12 +3007,13 @@ dependencies = [ name = "program-derived-addresses-program" version = "0.1.0" dependencies = [ - "anchor-lang 1.1.2", + "anchor-lang 2.0.0-rc.1", "borsh 1.7.0", "litesvm", "solana-keypair", "solana-kite", "solana-signer", + "wincode 0.5.5", ] [[package]] @@ -3227,12 +3240,12 @@ checksum = "d6f6ff9a378485b298a5286656da665ba74413d36db0979633275d2e708145d4" name = "rent-example" version = "0.1.0" dependencies = [ - "anchor-lang 1.1.2", - "borsh 1.7.0", + "anchor-lang 2.0.0-rc.1", "litesvm", "solana-keypair", "solana-kite", "solana-signer", + "wincode 0.5.5", ] [[package]] @@ -6555,11 +6568,12 @@ checksum = "756daf9b1013ebe47a8776667b466417e2d4c5679d441c26230efd9ef78692db" name = "transfer-sol" version = "0.1.0" dependencies = [ - "anchor-lang 1.1.2", + "anchor-lang 2.0.0-rc.1", "litesvm", "solana-keypair", "solana-kite", "solana-signer", + "wincode 0.5.5", ] [[package]] diff --git a/basics/account-data/anchor/programs/anchor-program-example/Cargo.toml b/basics/account-data/anchor/programs/anchor-program-example/Cargo.toml index 1ca762589..a533e1862 100644 --- a/basics/account-data/anchor/programs/anchor-program-example/Cargo.toml +++ b/basics/account-data/anchor/programs/anchor-program-example/Cargo.toml @@ -20,7 +20,10 @@ custom-heap = [] custom-panic = [] [dependencies] -anchor-lang = "1.1.2" +anchor-lang = "2.0.0-rc.1" +# The `#[program]` macro expands to `wincode` paths for instruction-data +# (de)serialization, so the crate has to be a direct dependency. +wincode = { version = "0.5", features = ["derive"] } [dev-dependencies] litesvm = "0.13.1" diff --git a/basics/account-data/anchor/programs/anchor-program-example/src/instructions/create.rs b/basics/account-data/anchor/programs/anchor-program-example/src/instructions/create.rs index 535efc853..0a3cd20c8 100644 --- a/basics/account-data/anchor/programs/anchor-program-example/src/instructions/create.rs +++ b/basics/account-data/anchor/programs/anchor-program-example/src/instructions/create.rs @@ -2,21 +2,21 @@ use crate::state::AddressInfo; use anchor_lang::prelude::*; #[derive(Accounts)] -pub struct CreateAddressInfoAccountConstraints<'info> { +pub struct CreateAddressInfoAccountConstraints { #[account(mut)] - payer: Signer<'info>, + pub payer: Signer, #[account( init, payer = payer, space = AddressInfo::DISCRIMINATOR.len() + AddressInfo::INIT_SPACE, )] - address_info: Account<'info, AddressInfo>, - system_program: Program<'info, System>, + pub address_info: BorshAccount, + pub system_program: Program, } pub fn handle_create_address_info( - context: Context, + context: &mut Context, name: String, house_number: u8, street: String, diff --git a/basics/account-data/anchor/programs/anchor-program-example/src/lib.rs b/basics/account-data/anchor/programs/anchor-program-example/src/lib.rs index f5fd99dad..765a584cb 100644 --- a/basics/account-data/anchor/programs/anchor-program-example/src/lib.rs +++ b/basics/account-data/anchor/programs/anchor-program-example/src/lib.rs @@ -11,7 +11,7 @@ pub mod account_data_anchor_program { use super::*; pub fn create_address_info( - context: Context, + context: &mut Context, name: String, house_number: u8, street: String, diff --git a/basics/account-data/anchor/programs/anchor-program-example/src/state/address_info.rs b/basics/account-data/anchor/programs/anchor-program-example/src/state/address_info.rs index 1142f354d..ffdfff9c4 100644 --- a/basics/account-data/anchor/programs/anchor-program-example/src/state/address_info.rs +++ b/basics/account-data/anchor/programs/anchor-program-example/src/state/address_info.rs @@ -1,6 +1,8 @@ use anchor_lang::prelude::*; -#[account] +// `borsh` because the struct holds `String`s: v2's default `#[account]` backing +// is zero-copy and needs a `Pod` (fixed-layout) type. +#[account(borsh)] #[derive(InitSpace)] // automatically calculate the space required for the struct pub struct AddressInfo { #[max_len(50)] // set a max length for the string diff --git a/basics/account-data/anchor/programs/anchor-program-example/tests/test_account_data.rs b/basics/account-data/anchor/programs/anchor-program-example/tests/test_account_data.rs index 567a816a5..aca4431bf 100644 --- a/basics/account-data/anchor/programs/anchor-program-example/tests/test_account_data.rs +++ b/basics/account-data/anchor/programs/anchor-program-example/tests/test_account_data.rs @@ -1,7 +1,7 @@ use { anchor_lang::{ - solana_program::{instruction::Instruction, system_program}, - AnchorSerialize, InstructionData, ToAccountMetas, + solana_program::instruction::Instruction, system_program, InstructionData, + ToAccountMetas, }, borsh::BorshDeserialize, litesvm::LiteSVM, @@ -42,7 +42,7 @@ fn test_create_address_info() { account_data_anchor_program::accounts::CreateAddressInfoAccountConstraints { payer: payer.pubkey(), address_info: address_info_keypair.pubkey(), - system_program: system_program::id(), + system_program: system_program::ID, } .to_account_metas(None), ); diff --git a/basics/checking-accounts/anchor/programs/anchor-program-example/src/lib.rs b/basics/checking-accounts/anchor/programs/anchor-program-example/src/lib.rs index 498779155..2998c5eb9 100644 --- a/basics/checking-accounts/anchor/programs/anchor-program-example/src/lib.rs +++ b/basics/checking-accounts/anchor/programs/anchor-program-example/src/lib.rs @@ -6,7 +6,9 @@ declare_id!("ECWPhR3rJbaPfyNFgphnjxSEexbTArc7vxD8fnW6tgKw"); pub mod checking_account_program { use super::*; - pub fn check_accounts(_context: &mut Context) -> Result<()> { + pub fn check_accounts( + _context: &mut Context, + ) -> Result<()> { Ok(()) } } diff --git a/basics/cross-program-invocation/anchor/idls/lever.json b/basics/cross-program-invocation/anchor/idls/lever.json index 3ec862d79..abba10c7f 100644 --- a/basics/cross-program-invocation/anchor/idls/lever.json +++ b/basics/cross-program-invocation/anchor/idls/lever.json @@ -9,7 +9,16 @@ "instructions": [ { "name": "initialize", - "discriminator": [175, 175, 109, 31, 13, 152, 155, 237], + "discriminator": [ + 175, + 175, + 109, + 31, + 13, + 152, + 155, + 237 + ], "accounts": [ { "name": "power", @@ -30,7 +39,16 @@ }, { "name": "switch_power", - "discriminator": [226, 238, 56, 172, 191, 45, 122, 87], + "discriminator": [ + 226, + 238, + 56, + 172, + 191, + 45, + 122, + 87 + ], "accounts": [ { "name": "power", @@ -48,12 +66,32 @@ "accounts": [ { "name": "PowerStatus", - "discriminator": [145, 147, 198, 35, 253, 101, 231, 26] + "discriminator": [ + 145, + 147, + 198, + 35, + 253, + 101, + 231, + 26 + ] } ], "types": [ + { + "name": "PodBool", + "type": { + "kind": "type", + "alias": "bool" + } + }, { "name": "PowerStatus", + "serialization": "bytemuck", + "repr": { + "kind": "c" + }, "type": { "kind": "struct", "fields": [ diff --git a/basics/cross-program-invocation/anchor/programs/hand/Cargo.toml b/basics/cross-program-invocation/anchor/programs/hand/Cargo.toml index bfb4cecd4..2d7da4375 100644 --- a/basics/cross-program-invocation/anchor/programs/hand/Cargo.toml +++ b/basics/cross-program-invocation/anchor/programs/hand/Cargo.toml @@ -20,7 +20,10 @@ custom-heap = [] custom-panic = [] [dependencies] -anchor-lang = "1.1.2" +anchor-lang = "2.0.0-rc.1" +# The `#[program]` macro expands to `wincode` paths for instruction-data +# (de)serialization, so the crate has to be a direct dependency. +wincode = { version = "0.5", features = ["derive"] } [dev-dependencies] litesvm = "0.13.1" diff --git a/basics/cross-program-invocation/anchor/programs/hand/src/lib.rs b/basics/cross-program-invocation/anchor/programs/hand/src/lib.rs index 2efa0b06e..4dab21463 100644 --- a/basics/cross-program-invocation/anchor/programs/hand/src/lib.rs +++ b/basics/cross-program-invocation/anchor/programs/hand/src/lib.rs @@ -13,11 +13,14 @@ use lever::program::Lever; pub mod hand { use super::*; - pub fn pull_lever(context: Context, name: String) -> Result<()> { + pub fn pull_lever( + context: &mut Context, + name: String, + ) -> Result<()> { let cpi_ctx = CpiContext::new( - context.accounts.lever_program.key(), + context.accounts.lever_program.address(), SwitchPower { - power: context.accounts.power.to_account_info(), + power: context.accounts.power.cpi_handle_mut(), }, ); switch_power(cpi_ctx, name)?; @@ -26,8 +29,8 @@ pub mod hand { } #[derive(Accounts)] -pub struct PullLeverAccountConstraints<'info> { +pub struct PullLeverAccountConstraints { #[account(mut)] - pub power: Account<'info, PowerStatus>, - pub lever_program: Program<'info, Lever>, + pub power: BorshAccount, + pub lever_program: Program, } diff --git a/basics/cross-program-invocation/anchor/programs/lever/Cargo.toml b/basics/cross-program-invocation/anchor/programs/lever/Cargo.toml index 19826a09a..265c9d12f 100644 --- a/basics/cross-program-invocation/anchor/programs/lever/Cargo.toml +++ b/basics/cross-program-invocation/anchor/programs/lever/Cargo.toml @@ -20,7 +20,10 @@ custom-heap = [] custom-panic = [] [dependencies] -anchor-lang = "1.1.2" +anchor-lang = "2.0.0-rc.1" +# The `#[program]` macro expands to `wincode` paths for instruction-data +# (de)serialization, so the crate has to be a direct dependency. +wincode = { version = "0.5", features = ["derive"] } [dev-dependencies] litesvm = "0.13.1" diff --git a/basics/cross-program-invocation/anchor/programs/lever/src/instructions/initialize.rs b/basics/cross-program-invocation/anchor/programs/lever/src/instructions/initialize.rs index 025fc5b7d..576b6d34d 100644 --- a/basics/cross-program-invocation/anchor/programs/lever/src/instructions/initialize.rs +++ b/basics/cross-program-invocation/anchor/programs/lever/src/instructions/initialize.rs @@ -3,14 +3,14 @@ use anchor_lang::prelude::*; use crate::PowerStatus; #[derive(Accounts)] -pub struct InitializeLeverAccountConstraints<'info> { +pub struct InitializeLeverAccountConstraints { #[account(init, payer = user, space = PowerStatus::DISCRIMINATOR.len() + PowerStatus::INIT_SPACE)] - pub power: Account<'info, PowerStatus>, + pub power: BorshAccount, #[account(mut)] - pub user: Signer<'info>, - pub system_program: Program<'info, System>, + pub user: Signer, + pub system_program: Program, } -pub fn handler(_context: Context) -> Result<()> { +pub fn handler(_context: &mut Context) -> Result<()> { Ok(()) } diff --git a/basics/cross-program-invocation/anchor/programs/lever/src/instructions/switch_power.rs b/basics/cross-program-invocation/anchor/programs/lever/src/instructions/switch_power.rs index 55674ab63..14572e5ab 100644 --- a/basics/cross-program-invocation/anchor/programs/lever/src/instructions/switch_power.rs +++ b/basics/cross-program-invocation/anchor/programs/lever/src/instructions/switch_power.rs @@ -3,12 +3,15 @@ use anchor_lang::prelude::*; use crate::PowerStatus; #[derive(Accounts)] -pub struct SetPowerStatusAccountConstraints<'info> { +pub struct SetPowerStatusAccountConstraints { #[account(mut)] - pub power: Account<'info, PowerStatus>, + pub power: BorshAccount, } -pub fn handler(context: Context, name: String) -> Result<()> { +pub fn handler( + context: &mut Context, + name: String, +) -> Result<()> { let power = &mut context.accounts.power; power.is_on = !power.is_on; diff --git a/basics/cross-program-invocation/anchor/programs/lever/src/lib.rs b/basics/cross-program-invocation/anchor/programs/lever/src/lib.rs index fa90458d9..2a09b4663 100644 --- a/basics/cross-program-invocation/anchor/programs/lever/src/lib.rs +++ b/basics/cross-program-invocation/anchor/programs/lever/src/lib.rs @@ -9,19 +9,25 @@ declare_id!("E64FVeubGC4NPNF2UBJYX4AkrVowf74fRJD9q6YhwstN"); pub mod lever { use super::*; - pub fn initialize(context: Context) -> Result<()> { + pub fn initialize(context: &mut Context) -> Result<()> { instructions::initialize::handler(context) } pub fn switch_power( - context: Context, + context: &mut Context, name: String, ) -> Result<()> { instructions::switch_power::handler(context, name) } } -#[account] +// `borsh` rather than v2's zero-copy default. A zero-copy `PowerStatus` would +// have to store `is_on` as `PodBool` (bytemuck rejects `bool`, since only +// `0x00`/`0x01` are valid bit patterns), and the generated IDL renders +// `PodBool` as a plain `bool` alias — so `declare_program!` in the `hand` +// program would regenerate a struct that is no longer `Pod`. Borsh keeps the +// field a real `bool` on both sides of the CPI. +#[account(borsh)] #[derive(InitSpace)] pub struct PowerStatus { pub is_on: bool, diff --git a/basics/cross-program-invocation/anchor/programs/lever/tests/test_lever.rs b/basics/cross-program-invocation/anchor/programs/lever/tests/test_lever.rs index 47d32a638..87e8c002b 100644 --- a/basics/cross-program-invocation/anchor/programs/lever/tests/test_lever.rs +++ b/basics/cross-program-invocation/anchor/programs/lever/tests/test_lever.rs @@ -1,7 +1,6 @@ use { anchor_lang::{ - solana_program::{instruction::Instruction, system_program}, - InstructionData, ToAccountMetas, + solana_program::instruction::Instruction, system_program, InstructionData, ToAccountMetas, }, litesvm::LiteSVM, solana_keypair::Keypair, @@ -11,7 +10,7 @@ use { /// PowerStatus account layout: 8-byte discriminator + 1-byte bool + 7 bytes padding. /// Account space is 8 + 8 = 16 bytes, so read raw bytes to avoid "Not all bytes read" errors. -fn read_power_is_on(svm: &LiteSVM, pubkey: &anchor_lang::prelude::Pubkey) -> bool { +fn read_power_is_on(svm: &LiteSVM, pubkey: &anchor_lang::prelude::Address) -> bool { let account = svm.get_account(pubkey).unwrap(); account.data[8] != 0 } @@ -32,7 +31,7 @@ fn test_initialize_lever() { lever::accounts::InitializeLeverAccountConstraints { power: power_keypair.pubkey(), user: payer.pubkey(), - system_program: system_program::id(), + system_program: system_program::ID, } .to_account_metas(None), ); @@ -68,7 +67,7 @@ fn test_switch_power() { lever::accounts::InitializeLeverAccountConstraints { power: power_keypair.pubkey(), user: payer.pubkey(), - system_program: system_program::id(), + system_program: system_program::ID, } .to_account_metas(None), ); diff --git a/basics/favorites/anchor/programs/favorites/Cargo.toml b/basics/favorites/anchor/programs/favorites/Cargo.toml index 229771829..0ec0e8e58 100644 --- a/basics/favorites/anchor/programs/favorites/Cargo.toml +++ b/basics/favorites/anchor/programs/favorites/Cargo.toml @@ -20,7 +20,10 @@ custom-heap = [] custom-panic = [] [dependencies] -anchor-lang = {version = "1.1.2", features = ["init-if-needed"]} +anchor-lang = "2.0.0-rc.1" +# The `#[program]` macro expands to `wincode` paths for instruction-data +# (de)serialization, so the crate has to be a direct dependency. +wincode = { version = "0.5", features = ["derive"] } [dev-dependencies] litesvm = "0.13.1" diff --git a/basics/favorites/anchor/programs/favorites/src/lib.rs b/basics/favorites/anchor/programs/favorites/src/lib.rs index 6f4fabc8c..794740738 100644 --- a/basics/favorites/anchor/programs/favorites/src/lib.rs +++ b/basics/favorites/anchor/programs/favorites/src/lib.rs @@ -10,23 +10,23 @@ pub mod favorites { // Our instruction handler! It sets the user's favorite number and color pub fn set_favorites( - context: Context, + context: &mut Context, number: u64, color: String, hobbies: Vec, ) -> Result<()> { msg!("Greetings from {}", context.program_id); - let user_public_key = context.accounts.user.key(); + let user_public_key = context.accounts.user.address(); msg!( "User {user_public_key}'s favorite number is {number}, favorite color is: {color}, and their hobbies are {hobbies:?}", ); - context.accounts.favorites.set_inner(Favorites { + *context.accounts.favorites = Favorites { number, color, hobbies, bump: context.bumps.favorites, - }); + }; Ok(()) } @@ -34,7 +34,9 @@ pub mod favorites { } // What we will put inside the Favorites PDA -#[account] +// `borsh` because the struct holds a `String` and a `Vec`: v2's default +// `#[account]` backing is zero-copy and needs a `Pod` (fixed-layout) type. +#[account(borsh)] #[derive(InitSpace)] pub struct Favorites { pub number: u64, @@ -51,18 +53,18 @@ pub struct Favorites { } // When people call the set_favorites instruction, they will need to provide the accounts that will be modifed. This keeps Solana fast! #[derive(Accounts)] -pub struct SetFavoritesAccountConstraints<'info> { +pub struct SetFavoritesAccountConstraints { #[account(mut)] - pub user: Signer<'info>, + pub user: Signer, #[account( init_if_needed, payer = user, space = Favorites::DISCRIMINATOR.len() + Favorites::INIT_SPACE, - seeds=[b"favorites", user.key().as_ref()], + seeds=[b"favorites", user.address().as_ref()], bump )] - pub favorites: Account<'info, Favorites>, + pub favorites: BorshAccount, - pub system_program: Program<'info, System>, + pub system_program: Program, } diff --git a/basics/favorites/anchor/programs/favorites/tests/test_favorites.rs b/basics/favorites/anchor/programs/favorites/tests/test_favorites.rs index 6f217c0df..2f5543adc 100644 --- a/basics/favorites/anchor/programs/favorites/tests/test_favorites.rs +++ b/basics/favorites/anchor/programs/favorites/tests/test_favorites.rs @@ -1,7 +1,7 @@ use { anchor_lang::{ - solana_program::{instruction::Instruction, pubkey::Pubkey, system_program}, - InstructionData, ToAccountMetas, + solana_program::instruction::Instruction, system_program, Address, InstructionData, + ToAccountMetas, }, litesvm::LiteSVM, solana_kite::{create_wallet, send_transaction_from_instructions}, @@ -17,7 +17,7 @@ struct FavoritesData { /// Manually deserialize the Favorites account data, skipping the 8-byte discriminator. /// We can't use BorshDeserialize on the full account because init_if_needed allocates /// more space than the serialized data occupies (padding for max_len strings/vecs). -fn read_favorites(svm: &LiteSVM, pda: &Pubkey) -> FavoritesData { +fn read_favorites(svm: &LiteSVM, pda: &Address) -> FavoritesData { let account = svm.get_account(pda).unwrap(); let data = &account.data[8..]; // skip discriminator let mut offset = 0; @@ -33,13 +33,11 @@ fn read_favorites(svm: &LiteSVM, pda: &Pubkey) -> FavoritesData { offset += color_len; // Vec hobbies (4-byte vec length + each string) - let hobbies_count = - u32::from_le_bytes(data[offset..offset + 4].try_into().unwrap()) as usize; + let hobbies_count = u32::from_le_bytes(data[offset..offset + 4].try_into().unwrap()) as usize; offset += 4; let mut hobbies = Vec::with_capacity(hobbies_count); for _ in 0..hobbies_count { - let hobby_len = - u32::from_le_bytes(data[offset..offset + 4].try_into().unwrap()) as usize; + let hobby_len = u32::from_le_bytes(data[offset..offset + 4].try_into().unwrap()) as usize; offset += 4; let hobby = String::from_utf8(data[offset..offset + hobby_len].to_vec()).unwrap(); offset += hobby_len; @@ -53,7 +51,7 @@ fn read_favorites(svm: &LiteSVM, pda: &Pubkey) -> FavoritesData { } } -fn setup() -> (LiteSVM, Pubkey, solana_keypair::Keypair) { +fn setup() -> (LiteSVM, Address, solana_keypair::Keypair) { let program_id = favorites::id(); let mut svm = LiteSVM::new(); let bytes = include_bytes!("../../../target/deploy/favorites.so"); @@ -62,8 +60,8 @@ fn setup() -> (LiteSVM, Pubkey, solana_keypair::Keypair) { (svm, program_id, payer) } -fn favorites_pda(program_id: &Pubkey, user: &Pubkey) -> Pubkey { - Pubkey::find_program_address(&[b"favorites", user.as_ref()], program_id).0 +fn favorites_pda(program_id: &Address, user: &Address) -> Address { + Address::find_program_address(&[b"favorites", user.as_ref()], program_id).0 } #[test] @@ -86,7 +84,7 @@ fn test_set_favorites() { favorites::accounts::SetFavoritesAccountConstraints { user: payer.pubkey(), favorites: pda, - system_program: system_program::id(), + system_program: system_program::ID, } .to_account_metas(None), ); @@ -121,7 +119,7 @@ fn test_update_favorites() { favorites::accounts::SetFavoritesAccountConstraints { user: payer.pubkey(), favorites: pda, - system_program: system_program::id(), + system_program: system_program::ID, } .to_account_metas(None), ); @@ -146,7 +144,7 @@ fn test_update_favorites() { favorites::accounts::SetFavoritesAccountConstraints { user: payer.pubkey(), favorites: pda, - system_program: system_program::id(), + system_program: system_program::ID, } .to_account_metas(None), ); diff --git a/basics/pda-rent-payer/anchor/programs/anchor-program-example/Cargo.toml b/basics/pda-rent-payer/anchor/programs/anchor-program-example/Cargo.toml index 2eba08465..740790332 100644 --- a/basics/pda-rent-payer/anchor/programs/anchor-program-example/Cargo.toml +++ b/basics/pda-rent-payer/anchor/programs/anchor-program-example/Cargo.toml @@ -20,7 +20,10 @@ custom-heap = [] custom-panic = [] [dependencies] -anchor-lang = "1.1.2" +anchor-lang = "2.0.0-rc.1" +# The `#[program]` macro expands to `wincode` paths for instruction-data +# (de)serialization, so the crate has to be a direct dependency. +wincode = { version = "0.5", features = ["derive"] } [dev-dependencies] litesvm = "0.13.1" diff --git a/basics/pda-rent-payer/anchor/programs/anchor-program-example/src/instructions/create_new_account.rs b/basics/pda-rent-payer/anchor/programs/anchor-program-example/src/instructions/create_new_account.rs index 80a2e84c8..41523ba71 100644 --- a/basics/pda-rent-payer/anchor/programs/anchor-program-example/src/instructions/create_new_account.rs +++ b/basics/pda-rent-payer/anchor/programs/anchor-program-example/src/instructions/create_new_account.rs @@ -2,9 +2,9 @@ use anchor_lang::prelude::*; use anchor_lang::system_program::{create_account, CreateAccount}; #[derive(Accounts)] -pub struct CreateNewAccountAccountConstraints<'info> { +pub struct CreateNewAccountAccountConstraints { #[account(mut)] - new_account: Signer<'info>, + new_account: Signer, #[account( mut, @@ -13,32 +13,32 @@ pub struct CreateNewAccountAccountConstraints<'info> { ], bump, )] - rent_vault: SystemAccount<'info>, - system_program: Program<'info, System>, + rent_vault: SystemAccount, + system_program: Program, } pub fn handle_create_new_account( - context: Context, + context: &mut Context, ) -> Result<()> { // PDA signer seeds let signer_seeds: &[&[&[u8]]] = &[&[b"rent_vault", &[context.bumps.rent_vault]]]; // The minimum lamports for rent exemption - let lamports = (Rent::get()?).minimum_balance(0); + let lamports = Rent::get()?.try_minimum_balance(0)?; // Create the new account, transferring lamports from the rent vault to the new account create_account( CpiContext::new( - context.accounts.system_program.key(), + context.accounts.system_program.address(), CreateAccount { - from: context.accounts.rent_vault.to_account_info(), // From pubkey - to: context.accounts.new_account.to_account_info(), // To pubkey + from: context.accounts.rent_vault.cpi_handle_mut(), // From pubkey + to: context.accounts.new_account.cpi_handle_mut(), // To pubkey }, ) .with_signer(signer_seeds), - lamports, // Lamports - 0, // Space - &context.accounts.system_program.key(), // Owner Program + lamports, // Lamports + 0, // Space + context.accounts.system_program.address(), // Owner Program )?; Ok(()) } diff --git a/basics/pda-rent-payer/anchor/programs/anchor-program-example/src/instructions/init_rent_vault.rs b/basics/pda-rent-payer/anchor/programs/anchor-program-example/src/instructions/init_rent_vault.rs index a15c49b42..6c78c39d3 100644 --- a/basics/pda-rent-payer/anchor/programs/anchor-program-example/src/instructions/init_rent_vault.rs +++ b/basics/pda-rent-payer/anchor/programs/anchor-program-example/src/instructions/init_rent_vault.rs @@ -2,9 +2,9 @@ use anchor_lang::prelude::*; use anchor_lang::system_program::{transfer, Transfer}; #[derive(Accounts)] -pub struct InitRentVaultAccountConstraints<'info> { +pub struct InitRentVaultAccountConstraints { #[account(mut)] - payer: Signer<'info>, + payer: Signer, #[account( mut, @@ -13,22 +13,22 @@ pub struct InitRentVaultAccountConstraints<'info> { ], bump, )] - rent_vault: SystemAccount<'info>, - system_program: Program<'info, System>, + rent_vault: SystemAccount, + system_program: Program, } // When lamports are transferred to a new address (without and existing account), // An account owned by the system program is created by default pub fn handle_init_rent_vault( - context: Context, + context: &mut Context, fund_lamports: u64, ) -> Result<()> { transfer( CpiContext::new( - context.accounts.system_program.key(), + context.accounts.system_program.address(), Transfer { - from: context.accounts.payer.to_account_info(), - to: context.accounts.rent_vault.to_account_info(), + from: context.accounts.payer.cpi_handle_mut(), + to: context.accounts.rent_vault.cpi_handle_mut(), }, ), fund_lamports, diff --git a/basics/pda-rent-payer/anchor/programs/anchor-program-example/src/lib.rs b/basics/pda-rent-payer/anchor/programs/anchor-program-example/src/lib.rs index c3f3b686c..c1a13a99f 100644 --- a/basics/pda-rent-payer/anchor/programs/anchor-program-example/src/lib.rs +++ b/basics/pda-rent-payer/anchor/programs/anchor-program-example/src/lib.rs @@ -9,13 +9,15 @@ pub mod pda_rent_payer { use super::*; pub fn init_rent_vault( - context: Context, + context: &mut Context, fund_lamports: u64, ) -> Result<()> { init_rent_vault::handle_init_rent_vault(context, fund_lamports) } - pub fn create_new_account(context: Context) -> Result<()> { + pub fn create_new_account( + context: &mut Context, + ) -> Result<()> { create_new_account::handle_create_new_account(context) } } diff --git a/basics/pda-rent-payer/anchor/programs/anchor-program-example/tests/test_pda_rent_payer.rs b/basics/pda-rent-payer/anchor/programs/anchor-program-example/tests/test_pda_rent_payer.rs index 650d71864..463207020 100644 --- a/basics/pda-rent-payer/anchor/programs/anchor-program-example/tests/test_pda_rent_payer.rs +++ b/basics/pda-rent-payer/anchor/programs/anchor-program-example/tests/test_pda_rent_payer.rs @@ -1,7 +1,7 @@ use { anchor_lang::{ - solana_program::{instruction::Instruction, pubkey::Pubkey, system_program}, - InstructionData, ToAccountMetas, + solana_program::instruction::Instruction, system_program, Address, InstructionData, + ToAccountMetas, }, litesvm::LiteSVM, solana_keypair::Keypair, @@ -23,7 +23,7 @@ fn test_init_rent_vault() { let (mut svm, payer) = setup(); let program_id = pda_rent_payer::id(); - let (rent_vault_pda, _bump) = Pubkey::find_program_address(&[b"rent_vault"], &program_id); + let (rent_vault_pda, _bump) = Address::find_program_address(&[b"rent_vault"], &program_id); // Fund the rent vault with 1 SOL let fund_amount: u64 = 1_000_000_000; @@ -36,7 +36,7 @@ fn test_init_rent_vault() { pda_rent_payer::accounts::InitRentVaultAccountConstraints { payer: payer.pubkey(), rent_vault: rent_vault_pda, - system_program: system_program::id(), + system_program: system_program::ID, } .to_account_metas(None), ); @@ -58,7 +58,7 @@ fn test_create_new_account_from_rent_vault() { let (mut svm, payer) = setup(); let program_id = pda_rent_payer::id(); - let (rent_vault_pda, _bump) = Pubkey::find_program_address(&[b"rent_vault"], &program_id); + let (rent_vault_pda, _bump) = Address::find_program_address(&[b"rent_vault"], &program_id); // Fund the rent vault with 1 SOL let fund_amount: u64 = 1_000_000_000; @@ -71,7 +71,7 @@ fn test_create_new_account_from_rent_vault() { pda_rent_payer::accounts::InitRentVaultAccountConstraints { payer: payer.pubkey(), rent_vault: rent_vault_pda, - system_program: system_program::id(), + system_program: system_program::ID, } .to_account_metas(None), ); @@ -88,7 +88,7 @@ fn test_create_new_account_from_rent_vault() { pda_rent_payer::accounts::CreateNewAccountAccountConstraints { new_account: new_account.pubkey(), rent_vault: rent_vault_pda, - system_program: system_program::id(), + system_program: system_program::ID, } .to_account_metas(None), ); diff --git a/basics/program-derived-addresses/anchor/programs/anchor-program-example/Cargo.toml b/basics/program-derived-addresses/anchor/programs/anchor-program-example/Cargo.toml index 6ad63ff3e..69b670b4c 100644 --- a/basics/program-derived-addresses/anchor/programs/anchor-program-example/Cargo.toml +++ b/basics/program-derived-addresses/anchor/programs/anchor-program-example/Cargo.toml @@ -20,7 +20,10 @@ custom-heap = [] custom-panic = [] [dependencies] -anchor-lang = "1.1.2" +anchor-lang = "2.0.0-rc.1" +# The `#[program]` macro expands to `wincode` paths for instruction-data +# (de)serialization, so the crate has to be a direct dependency. +wincode = { version = "0.5", features = ["derive"] } [dev-dependencies] litesvm = "0.13.1" diff --git a/basics/program-derived-addresses/anchor/programs/anchor-program-example/src/instructions/create.rs b/basics/program-derived-addresses/anchor/programs/anchor-program-example/src/instructions/create.rs index 2278e1d13..c2484c337 100644 --- a/basics/program-derived-addresses/anchor/programs/anchor-program-example/src/instructions/create.rs +++ b/basics/program-derived-addresses/anchor/programs/anchor-program-example/src/instructions/create.rs @@ -2,9 +2,9 @@ use crate::state::PageVisits; use anchor_lang::prelude::*; #[derive(Accounts)] -pub struct CreatePageVisitsAccountConstraints<'info> { +pub struct CreatePageVisitsAccountConstraints { #[account(mut)] - payer: Signer<'info>, + payer: Signer, #[account( init, @@ -12,20 +12,21 @@ pub struct CreatePageVisitsAccountConstraints<'info> { payer = payer, seeds = [ PageVisits::SEED_PREFIX, - payer.key().as_ref(), + payer.address().as_ref(), ], bump, )] - page_visits: Account<'info, PageVisits>, - system_program: Program<'info, System>, + page_visits: Account, + system_program: Program, } pub fn handle_create_page_visits( - context: Context, + context: &mut Context, ) -> Result<()> { *context.accounts.page_visits = PageVisits { page_visits: 0, bump: context.bumps.page_visits, + _padding: [0; 3], }; Ok(()) diff --git a/basics/program-derived-addresses/anchor/programs/anchor-program-example/src/instructions/increment.rs b/basics/program-derived-addresses/anchor/programs/anchor-program-example/src/instructions/increment.rs index 206ef7311..d185f86a2 100644 --- a/basics/program-derived-addresses/anchor/programs/anchor-program-example/src/instructions/increment.rs +++ b/basics/program-derived-addresses/anchor/programs/anchor-program-example/src/instructions/increment.rs @@ -2,21 +2,21 @@ use crate::{state::PageVisits, PageVisitsError}; use anchor_lang::prelude::*; #[derive(Accounts)] -pub struct IncrementPageVisitsAccountConstraints<'info> { - user: SystemAccount<'info>, +pub struct IncrementPageVisitsAccountConstraints { + user: SystemAccount, #[account( mut, seeds = [ PageVisits::SEED_PREFIX, - user.key().as_ref(), + user.address().as_ref(), ], bump = page_visits.bump, )] - page_visits: Account<'info, PageVisits>, + page_visits: Account, } pub fn handle_increment_page_visits( - context: Context, + context: &mut Context, ) -> Result<()> { let page_visits = &mut context.accounts.page_visits; page_visits.page_visits = page_visits diff --git a/basics/program-derived-addresses/anchor/programs/anchor-program-example/src/lib.rs b/basics/program-derived-addresses/anchor/programs/anchor-program-example/src/lib.rs index 40a9f3bf9..30f0a14ce 100644 --- a/basics/program-derived-addresses/anchor/programs/anchor-program-example/src/lib.rs +++ b/basics/program-derived-addresses/anchor/programs/anchor-program-example/src/lib.rs @@ -11,12 +11,14 @@ declare_id!("oCCQRZyAbVxujyd8m57MPmDzZDmy2FoKW4ULS7KofCE"); pub mod program_derived_addresses_program { use super::*; - pub fn create_page_visits(context: Context) -> Result<()> { + pub fn create_page_visits( + context: &mut Context, + ) -> Result<()> { create::handle_create_page_visits(context) } pub fn increment_page_visits( - context: Context, + context: &mut Context, ) -> Result<()> { increment::handle_increment_page_visits(context) } diff --git a/basics/program-derived-addresses/anchor/programs/anchor-program-example/src/state/page_visits.rs b/basics/program-derived-addresses/anchor/programs/anchor-program-example/src/state/page_visits.rs index edb1fd715..82e36f314 100644 --- a/basics/program-derived-addresses/anchor/programs/anchor-program-example/src/state/page_visits.rs +++ b/basics/program-derived-addresses/anchor/programs/anchor-program-example/src/state/page_visits.rs @@ -5,6 +5,9 @@ use anchor_lang::prelude::*; pub struct PageVisits { pub page_visits: u32, pub bump: u8, + // v2's `#[account]` is zero-copy, so the struct has to be Pod — and Pod + // rejects implicit padding. u32 + u8 leaves three bytes, so name them. + pub _padding: [u8; 3], } impl PageVisits { diff --git a/basics/program-derived-addresses/anchor/programs/anchor-program-example/tests/test_program_derived_addresses.rs b/basics/program-derived-addresses/anchor/programs/anchor-program-example/tests/test_program_derived_addresses.rs index 1ef8645bb..a9b7274a6 100644 --- a/basics/program-derived-addresses/anchor/programs/anchor-program-example/tests/test_program_derived_addresses.rs +++ b/basics/program-derived-addresses/anchor/programs/anchor-program-example/tests/test_program_derived_addresses.rs @@ -1,7 +1,7 @@ use { anchor_lang::{ - solana_program::{instruction::Instruction, pubkey::Pubkey, system_program}, - InstructionData, ToAccountMetas, + solana_program::instruction::Instruction, system_program, Address, InstructionData, + ToAccountMetas, }, borsh::BorshDeserialize, litesvm::LiteSVM, @@ -18,10 +18,14 @@ fn setup() -> (LiteSVM, solana_keypair::Keypair) { (svm, payer) } +/// Mirrors the on-chain layout. v2's `#[account]` is zero-copy, so the struct +/// carries explicit padding out to its alignment — `try_from_slice` rejects +/// trailing bytes, so the test type has to spell the padding out too. #[derive(BorshDeserialize)] struct PageVisits { page_visits: u32, bump: u8, + _padding: [u8; 3], } #[test] @@ -31,7 +35,7 @@ fn test_create_and_increment_page_visits() { // Derive PDA let (page_visits_pda, _bump) = - Pubkey::find_program_address(&[b"page_visits", payer.pubkey().as_ref()], &program_id); + Address::find_program_address(&[b"page_visits", payer.pubkey().as_ref()], &program_id); // Create page visits account let create_ix = Instruction::new_with_bytes( @@ -40,7 +44,7 @@ fn test_create_and_increment_page_visits() { program_derived_addresses_program::accounts::CreatePageVisitsAccountConstraints { payer: payer.pubkey(), page_visits: page_visits_pda, - system_program: system_program::id(), + system_program: system_program::ID, } .to_account_metas(None), ); diff --git a/basics/pyth/anchor/programs/pythexample/Cargo.toml b/basics/pyth/anchor/programs/pythexample/Cargo.toml index d7e244f8b..040ca02fe 100644 --- a/basics/pyth/anchor/programs/pythexample/Cargo.toml +++ b/basics/pyth/anchor/programs/pythexample/Cargo.toml @@ -21,7 +21,14 @@ custom-panic = [] [dependencies] -anchor-lang = "1.1.2" +anchor-lang = "2.0.0-rc.1" +# The `#[program]` macro expands to `wincode` paths for instruction-data +# (de)serialization, so the crate has to be a direct dependency. +wincode = { version = "0.5", features = ["derive"] } +# anchor-lang 2.0.0-rc.1 is built against wincode 0.5, but solana-address 2.7 +# moved to wincode 0.6, which splits `Address`'s wincode impls across two +# incompatible trait versions. 2.6.1 is the last release on the 0.5 line. +solana-address = ">=2.6, <2.7" [dev-dependencies] # Self-dependency with no-entrypoint: host test builds otherwise export a @@ -34,6 +41,7 @@ litesvm = "0.13.1" solana-signer = "3.0.0" solana-keypair = "3.0.1" solana-account = "3.0.0" +solana-clock = "3.0.1" borsh = "1.6.1" sha2 = "0.10" solana-kite = "0.4.0" diff --git a/basics/pyth/anchor/programs/pythexample/src/lib.rs b/basics/pyth/anchor/programs/pythexample/src/lib.rs index 013b6882e..af5e0dfe8 100644 --- a/basics/pyth/anchor/programs/pythexample/src/lib.rs +++ b/basics/pyth/anchor/programs/pythexample/src/lib.rs @@ -3,7 +3,8 @@ use anchor_lang::prelude::*; declare_id!("GUkjQmrLPFXXNK1bFLKt8XQi6g3TjxcHVspbjDoHvMG2"); /// The Pyth Receiver program that owns `PriceUpdateV2` accounts on devnet/mainnet. -pub const PYTH_RECEIVER_PROGRAM_ID: Pubkey = pubkey!("rec5EKMGg6MxZYaMdyBfgwp4d5rB9T1VQH5pJv5LtFJ"); +pub const PYTH_RECEIVER_PROGRAM_ID: Address = + anchor_lang::address!("rec5EKMGg6MxZYaMdyBfgwp4d5rB9T1VQH5pJv5LtFJ"); /// Maximum allowed age of a price update before it is rejected as stale. /// Pyth's `publish_time` is a unix timestamp in seconds, so the age check @@ -24,7 +25,7 @@ pub enum PythExampleError { pub mod anchor_test { use super::*; - pub fn read_price(context: Context) -> Result<()> { + pub fn read_price(context: &mut Context) -> Result<()> { let price_update = &context.accounts.price_update; // Reject stale prices: a price that stopped updating is wrong. @@ -50,8 +51,8 @@ pub mod anchor_test { } #[derive(Accounts)] -pub struct ReadPriceAccountConstraints<'info> { - pub price_update: Account<'info, PriceUpdateV2>, +pub struct ReadPriceAccountConstraints { + pub price_update: BorshAccount, } // --------------------------------------------------------------------------- @@ -82,7 +83,7 @@ pub struct ReadPriceAccountConstraints<'info> { // borsh 1.x compatible `pyth-solana-receiver-sdk` release ships. // --------------------------------------------------------------------------- -#[derive(AnchorSerialize, AnchorDeserialize, Clone, Debug, PartialEq, Eq)] +#[derive(Clone, Debug, PartialEq, Eq, wincode::SchemaRead, wincode::SchemaWrite)] pub enum VerificationLevel { /// Partially verified: only `num_signatures` of the Wormhole guardians /// were checked against the price update. @@ -91,7 +92,7 @@ pub enum VerificationLevel { Full, } -#[derive(AnchorSerialize, AnchorDeserialize, Clone, Debug, PartialEq, Eq)] +#[derive(Clone, Debug, PartialEq, Eq, wincode::SchemaRead, wincode::SchemaWrite)] pub struct PriceFeedMessage { pub feed_id: [u8; 32], pub price: i64, @@ -103,9 +104,9 @@ pub struct PriceFeedMessage { pub ema_conf: u64, } -#[derive(AnchorSerialize, AnchorDeserialize, Clone, Debug, PartialEq, Eq)] +#[derive(Clone, Debug, PartialEq, Eq, wincode::SchemaRead, wincode::SchemaWrite)] pub struct PriceUpdateV2 { - pub write_authority: Pubkey, + pub write_authority: Address, pub verification_level: VerificationLevel, pub price_message: PriceFeedMessage, pub posted_slot: u64, @@ -118,38 +119,6 @@ impl anchor_lang::Discriminator for PriceUpdateV2 { // The account is created and owned by the Pyth Receiver program. impl anchor_lang::Owner for PriceUpdateV2 { - fn owner() -> Pubkey { - PYTH_RECEIVER_PROGRAM_ID - } -} - -impl anchor_lang::AccountSerialize for PriceUpdateV2 { - fn try_serialize(&self, writer: &mut W) -> Result<()> { - writer - .write_all(::DISCRIMINATOR) - .map_err(|_| anchor_lang::error::ErrorCode::AccountDidNotSerialize)?; - AnchorSerialize::serialize(self, writer) - .map_err(|_| anchor_lang::error::ErrorCode::AccountDidNotSerialize)?; - Ok(()) - } + const OWNER: Address = PYTH_RECEIVER_PROGRAM_ID; } -impl anchor_lang::AccountDeserialize for PriceUpdateV2 { - fn try_deserialize(buf: &mut &[u8]) -> Result { - let disc = ::DISCRIMINATOR; - if buf.len() < disc.len() { - return Err(anchor_lang::error::ErrorCode::AccountDiscriminatorNotFound.into()); - } - if &buf[..disc.len()] != disc { - return Err(anchor_lang::error::ErrorCode::AccountDiscriminatorMismatch.into()); - } - Self::try_deserialize_unchecked(buf) - } - - fn try_deserialize_unchecked(buf: &mut &[u8]) -> Result { - let disc = ::DISCRIMINATOR; - let mut data: &[u8] = &buf[disc.len()..]; - AnchorDeserialize::deserialize(&mut data) - .map_err(|_| anchor_lang::error::ErrorCode::AccountDidNotDeserialize.into()) - } -} diff --git a/basics/pyth/anchor/programs/pythexample/tests/test_pyth.rs b/basics/pyth/anchor/programs/pythexample/tests/test_pyth.rs index 5071866a4..92ee7e566 100644 --- a/basics/pyth/anchor/programs/pythexample/tests/test_pyth.rs +++ b/basics/pyth/anchor/programs/pythexample/tests/test_pyth.rs @@ -1,9 +1,10 @@ use { anchor_lang::{ - solana_program::{clock::Clock, instruction::Instruction}, + solana_program::instruction::Instruction, InstructionData, ToAccountMetas, }, litesvm::LiteSVM, + solana_clock::Clock, pythexample::MAXIMUM_PRICE_AGE_SECONDS, solana_keypair::Keypair, solana_kite::{create_wallet, send_transaction_from_instructions}, @@ -14,13 +15,13 @@ use { const MOCK_PUBLISH_TIME: i64 = 1_700_000_000; /// Pyth Receiver program ID (rec5EKMGg6MxZYaMdyBfgwp4d5rB9T1VQH5pJv5LtFJ) -fn pyth_receiver_program_id() -> anchor_lang::solana_program::pubkey::Pubkey { +fn pyth_receiver_program_id() -> anchor_lang::Address { pythexample::PYTH_RECEIVER_PROGRAM_ID } /// Build mock PriceUpdateV2 account data with Anchor discriminator. fn build_mock_price_update_account( - write_authority: &anchor_lang::solana_program::pubkey::Pubkey, + write_authority: &anchor_lang::Address, ) -> Vec { // Discriminator: sha256("account:PriceUpdateV2")[..8] let discriminator: [u8; 8] = [34, 241, 35, 99, 157, 126, 244, 205]; @@ -83,7 +84,7 @@ fn set_clock_to_price_age(svm: &mut LiteSVM, age_seconds: i64) { } fn setup_with_price_account( - owner: anchor_lang::solana_program::pubkey::Pubkey, + owner: anchor_lang::Address, ) -> (LiteSVM, solana_keypair::Keypair, Keypair) { let program_id = pythexample::id(); let mut svm = LiteSVM::new(); @@ -111,9 +112,12 @@ fn setup_with_price_account( (svm, payer, price_update_key) } -fn read_price_instruction(price_update: anchor_lang::solana_program::pubkey::Pubkey) -> Instruction { +fn read_price_instruction( + price_update: anchor_lang::Address, +) -> Instruction { let ix_data = pythexample::instruction::ReadPrice {}.data(); - let accounts = pythexample::accounts::ReadPriceAccountConstraints { price_update }.to_account_metas(None); + let accounts = + pythexample::accounts::ReadPriceAccountConstraints { price_update }.to_account_metas(None); Instruction::new_with_bytes(pythexample::id(), &ix_data, accounts) } diff --git a/basics/realloc/anchor/programs/anchor-realloc/Cargo.toml b/basics/realloc/anchor/programs/anchor-realloc/Cargo.toml index cc14c040b..225ed55ca 100644 --- a/basics/realloc/anchor/programs/anchor-realloc/Cargo.toml +++ b/basics/realloc/anchor/programs/anchor-realloc/Cargo.toml @@ -20,7 +20,10 @@ custom-heap = [] custom-panic = [] [dependencies] -anchor-lang = "1.1.2" +anchor-lang = "2.0.0-rc.1" +# The `#[program]` macro expands to `wincode` paths for instruction-data +# (de)serialization, so the crate has to be a direct dependency. +wincode = { version = "0.5", features = ["derive"] } [dev-dependencies] litesvm = "0.13.1" diff --git a/basics/realloc/anchor/programs/anchor-realloc/src/instructions/initialize.rs b/basics/realloc/anchor/programs/anchor-realloc/src/instructions/initialize.rs index dc45b857c..35040fda1 100644 --- a/basics/realloc/anchor/programs/anchor-realloc/src/instructions/initialize.rs +++ b/basics/realloc/anchor/programs/anchor-realloc/src/instructions/initialize.rs @@ -4,20 +4,20 @@ use crate::Message; #[derive(Accounts)] #[instruction(input: String)] -pub struct InitializeAccountConstraints<'info> { +pub struct InitializeAccountConstraints { #[account(mut)] - pub payer: Signer<'info>, + pub payer: Signer, #[account( init, payer = payer, space = Message::required_space(input.len()), )] - pub message_account: Account<'info, Message>, - pub system_program: Program<'info, System>, + pub message_account: BorshAccount, + pub system_program: Program, } -pub fn handler(context: Context, input: String) -> Result<()> { +pub fn handler(context: &mut Context, input: String) -> Result<()> { context.accounts.message_account.message = input; Ok(()) } diff --git a/basics/realloc/anchor/programs/anchor-realloc/src/instructions/update.rs b/basics/realloc/anchor/programs/anchor-realloc/src/instructions/update.rs index cd1032c5f..a1e09511e 100644 --- a/basics/realloc/anchor/programs/anchor-realloc/src/instructions/update.rs +++ b/basics/realloc/anchor/programs/anchor-realloc/src/instructions/update.rs @@ -4,21 +4,21 @@ use crate::Message; #[derive(Accounts)] #[instruction(input: String)] -pub struct UpdateAccountConstraints<'info> { +pub struct UpdateAccountConstraints { #[account(mut)] - pub payer: Signer<'info>, + pub payer: Signer, #[account( mut, realloc = Message::required_space(input.len()), - realloc::payer = payer, - realloc::zero = true, + realloc_payer = payer, + realloc_zero = true, )] - pub message_account: Account<'info, Message>, - pub system_program: Program<'info, System>, + pub message_account: BorshAccount, + pub system_program: Program, } -pub fn handler(context: Context, input: String) -> Result<()> { +pub fn handler(context: &mut Context, input: String) -> Result<()> { context.accounts.message_account.message = input; Ok(()) } diff --git a/basics/realloc/anchor/programs/anchor-realloc/src/lib.rs b/basics/realloc/anchor/programs/anchor-realloc/src/lib.rs index f51e65d69..f8f150e0d 100644 --- a/basics/realloc/anchor/programs/anchor-realloc/src/lib.rs +++ b/basics/realloc/anchor/programs/anchor-realloc/src/lib.rs @@ -9,11 +9,14 @@ declare_id!("Fod47xKXjdHVQDzkFPBvfdWLm8gEAV4iMSXkfUzCHiSD"); pub mod anchor_realloc { use super::*; - pub fn initialize(context: Context, input: String) -> Result<()> { + pub fn initialize( + context: &mut Context, + input: String, + ) -> Result<()> { instructions::initialize::handler(context, input) } - pub fn update(context: Context, input: String) -> Result<()> { + pub fn update(context: &mut Context, input: String) -> Result<()> { instructions::update::handler(context, input) } } @@ -24,7 +27,7 @@ pub mod anchor_realloc { // `InitSpace` + `#[max_len(N)]` would force a fixed upper bound, defeating // the point of the example. Instead, `required_space` computes the exact // layout (discriminator + length prefix + bytes) for init/realloc. -#[account] +#[account(borsh)] pub struct Message { pub message: String, } diff --git a/basics/realloc/anchor/programs/anchor-realloc/tests/test_realloc.rs b/basics/realloc/anchor/programs/anchor-realloc/tests/test_realloc.rs index fecf1c6c1..cd6dca049 100644 --- a/basics/realloc/anchor/programs/anchor-realloc/tests/test_realloc.rs +++ b/basics/realloc/anchor/programs/anchor-realloc/tests/test_realloc.rs @@ -1,7 +1,6 @@ use { anchor_lang::{ - solana_program::{instruction::Instruction, system_program}, - InstructionData, ToAccountMetas, + solana_program::instruction::Instruction, system_program, InstructionData, ToAccountMetas, }, borsh::BorshDeserialize, litesvm::LiteSVM, @@ -16,7 +15,7 @@ struct MessageAccount { message: String, } -fn fetch_message(svm: &LiteSVM, pubkey: &anchor_lang::prelude::Pubkey) -> String { +fn fetch_message(svm: &LiteSVM, pubkey: &anchor_lang::prelude::Address) -> String { let account = svm.get_account(pubkey).unwrap(); let data = MessageAccount::try_from_slice(&account.data).unwrap(); data.message @@ -41,7 +40,7 @@ fn test_initialize() { anchor_realloc::accounts::InitializeAccountConstraints { payer: payer.pubkey(), message_account: message_keypair.pubkey(), - system_program: system_program::id(), + system_program: system_program::ID, } .to_account_metas(None), ); @@ -82,7 +81,7 @@ fn test_update_grows() { anchor_realloc::accounts::InitializeAccountConstraints { payer: payer.pubkey(), message_account: message_keypair.pubkey(), - system_program: system_program::id(), + system_program: system_program::ID, } .to_account_metas(None), ); @@ -105,7 +104,7 @@ fn test_update_grows() { anchor_realloc::accounts::UpdateAccountConstraints { payer: payer.pubkey(), message_account: message_keypair.pubkey(), - system_program: system_program::id(), + system_program: system_program::ID, } .to_account_metas(None), ); @@ -139,7 +138,7 @@ fn test_update_shrinks() { anchor_realloc::accounts::InitializeAccountConstraints { payer: payer.pubkey(), message_account: message_keypair.pubkey(), - system_program: system_program::id(), + system_program: system_program::ID, } .to_account_metas(None), ); @@ -162,7 +161,7 @@ fn test_update_shrinks() { anchor_realloc::accounts::UpdateAccountConstraints { payer: payer.pubkey(), message_account: message_keypair.pubkey(), - system_program: system_program::id(), + system_program: system_program::ID, } .to_account_metas(None), ); diff --git a/basics/rent/anchor/programs/rent-example/tests/test_rent.rs b/basics/rent/anchor/programs/rent-example/tests/test_rent.rs index dffe8b8c8..376f1ebc2 100644 --- a/basics/rent/anchor/programs/rent-example/tests/test_rent.rs +++ b/basics/rent/anchor/programs/rent-example/tests/test_rent.rs @@ -1,7 +1,6 @@ use { anchor_lang::{ - solana_program::instruction::Instruction, - system_program, InstructionData, ToAccountMetas, + solana_program::instruction::Instruction, system_program, InstructionData, ToAccountMetas, }, litesvm::LiteSVM, solana_keypair::Keypair, diff --git a/basics/repository-layout/anchor/programs/carnival/Cargo.toml b/basics/repository-layout/anchor/programs/carnival/Cargo.toml index ff02c6a78..80bc8bd5b 100644 --- a/basics/repository-layout/anchor/programs/carnival/Cargo.toml +++ b/basics/repository-layout/anchor/programs/carnival/Cargo.toml @@ -20,7 +20,10 @@ custom-heap = [] custom-panic = [] [dependencies] -anchor-lang = "1.1.2" +anchor-lang = "2.0.0-rc.1" +# The `#[program]` macro expands to `wincode` paths for instruction-data +# (de)serialization, so the crate has to be a direct dependency. +wincode = { version = "0.5", features = ["derive"] } [dev-dependencies] litesvm = "0.13.1" diff --git a/basics/repository-layout/anchor/programs/carnival/src/lib.rs b/basics/repository-layout/anchor/programs/carnival/src/lib.rs index 9d0bea35f..4a542c28a 100644 --- a/basics/repository-layout/anchor/programs/carnival/src/lib.rs +++ b/basics/repository-layout/anchor/programs/carnival/src/lib.rs @@ -15,7 +15,7 @@ pub mod carnival { use super::*; pub fn go_on_ride( - _context: Context, + _context: &mut Context, name: String, height: u32, ticket_count: u32, @@ -30,7 +30,7 @@ pub mod carnival { } pub fn play_game( - _context: Context, + _context: &mut Context, name: String, ticket_count: u32, game_name: String, @@ -43,7 +43,7 @@ pub mod carnival { } pub fn eat_food( - _context: Context, + _context: &mut Context, name: String, ticket_count: u32, food_stand_name: String, @@ -57,7 +57,7 @@ pub mod carnival { } #[derive(Accounts)] -pub struct CarnivalAccountConstraints<'info> { +pub struct CarnivalAccountConstraints { #[account(mut)] - pub payer: Signer<'info>, + pub payer: Signer, } From 8c0c87b8501ca2c18c7023651f7640631d02d83b Mon Sep 17 00:00:00 2001 From: Claude Date: Thu, 13 Aug 2026 22:10:28 +0000 Subject: [PATCH 06/59] basics: finish the v2 port, with all 16 examples building and passing program-derived-addresses: the test decodes the account with borsh, and `try_from_slice` rejects trailing bytes, so its local struct now mirrors the explicit `[u8; 3]` padding that zero-copy `PageVisits` grew. cross-program-invocation: `idls/lever.json` is regenerated with the v2 CLI. `PowerStatus` uses `#[account(borsh)]` rather than v2's zero-copy default, zero-copy would force `is_on` to be `PodBool` (bytemuck rejects `bool`, since only 0x00/0x01 are valid bit patterns), and the generated IDL renders `PodBool` as a plain `bool` alias, so `declare_program!` in `hand` regenerated a struct that was no longer `Pod`. Borsh keeps a real `bool` on both sides of the CPI and leaves the IDL shape identical to v1's. --- .../anchor/idls/lever.json | 11 ----------- .../anchor/programs/hand/tests/test_hand.rs | 17 +++++++---------- 2 files changed, 7 insertions(+), 21 deletions(-) diff --git a/basics/cross-program-invocation/anchor/idls/lever.json b/basics/cross-program-invocation/anchor/idls/lever.json index abba10c7f..a8ace89ce 100644 --- a/basics/cross-program-invocation/anchor/idls/lever.json +++ b/basics/cross-program-invocation/anchor/idls/lever.json @@ -79,19 +79,8 @@ } ], "types": [ - { - "name": "PodBool", - "type": { - "kind": "type", - "alias": "bool" - } - }, { "name": "PowerStatus", - "serialization": "bytemuck", - "repr": { - "kind": "c" - }, "type": { "kind": "struct", "fields": [ diff --git a/basics/cross-program-invocation/anchor/programs/hand/tests/test_hand.rs b/basics/cross-program-invocation/anchor/programs/hand/tests/test_hand.rs index e9c7c64ac..5fe9cb0fb 100644 --- a/basics/cross-program-invocation/anchor/programs/hand/tests/test_hand.rs +++ b/basics/cross-program-invocation/anchor/programs/hand/tests/test_hand.rs @@ -1,10 +1,7 @@ use { anchor_lang::{ - solana_program::{ - instruction::{AccountMeta, Instruction}, - system_program, - }, - InstructionData, ToAccountMetas, + solana_program::instruction::{AccountMeta, Instruction}, + system_program, InstructionData, ToAccountMetas, }, litesvm::LiteSVM, solana_keypair::Keypair, @@ -15,7 +12,7 @@ use { /// PowerStatus account layout: 8-byte discriminator + 1-byte bool + 7 bytes padding. /// Account space is 8 + 8 = 16 bytes, so read the raw bytes instead of using BorshDeserialize /// to avoid "Not all bytes read" errors from the padding. -fn read_power_is_on(svm: &LiteSVM, pubkey: &anchor_lang::prelude::Pubkey) -> bool { +fn read_power_is_on(svm: &LiteSVM, pubkey: &anchor_lang::Address) -> bool { let account = svm.get_account(pubkey).unwrap(); // Skip 8-byte discriminator, read 1 byte for bool account.data[8] != 0 @@ -24,9 +21,9 @@ fn read_power_is_on(svm: &LiteSVM, pubkey: &anchor_lang::prelude::Pubkey) -> boo /// Build the lever program's `initialize` instruction manually. /// Discriminator from IDL: [175, 175, 109, 31, 13, 152, 155, 237] fn build_lever_initialize_ix( - lever_program_id: anchor_lang::prelude::Pubkey, - power: anchor_lang::prelude::Pubkey, - user: anchor_lang::prelude::Pubkey, + lever_program_id: anchor_lang::Address, + power: anchor_lang::Address, + user: anchor_lang::Address, ) -> Instruction { let discriminator: [u8; 8] = [175, 175, 109, 31, 13, 152, 155, 237]; Instruction { @@ -34,7 +31,7 @@ fn build_lever_initialize_ix( accounts: vec![ AccountMeta::new(power, true), AccountMeta::new(user, true), - AccountMeta::new_readonly(system_program::id(), false), + AccountMeta::new_readonly(system_program::ID, false), ], data: discriminator.to_vec(), } From f6dacc700810114b6f1327dfab4eb633b3e128d6 Mon Sep 17 00:00:00 2001 From: Claude Date: Thu, 13 Aug 2026 22:13:32 +0000 Subject: [PATCH 07/59] tokens/finance/compression: in-progress Anchor v2 port (does not yet compile) Committing work-in-progress so it is not lost. `basics/` (the previous commit) is complete and green; these 38 projects are part-ported and 5 of them compile so far. Do not merge this commit on its own. Repo-wide v2 changes applied here, each found by compiling: - anchor-spl 2.0.0-rc.1 has no `idl-build` feature; IDL generation is driven entirely by anchor-lang's. This alone blocked 37 projects at resolution. - Extension CPI structs dropped `token_program_id`, the program comes from the `CpiContext` rather than a duplicated account field. - `Mint` moved from `anchor_spl::token` to `anchor_spl::mint`, and the namespaced `token::` / `mint::` constraints need those modules nameable in the file that uses them. - `Interface` is the one account wrapper that keeps a lifetime parameter, and it is always `Interface<'static, TokenInterface>`. - SPL account fields are behind accessors (`.amount()`, `.decimals()`) now that the structs are Pod. - Program state moves to `#[account(borsh)]` + `BorshAccount`. v1 accounts were borsh-encoded, so this reproduces the on-chain layout byte for byte and keeps existing clients and tests valid, rather than making everything zero-copy and having to hand-pad every state struct to satisfy bytemuck. - anchor-lang's `compat` feature is enabled for the 13 programs that use the v1-shaped `error!` / `err!` / `pubkey!` macros; it exists for this migration. Known remaining work, which is not mechanical: - v2's typed CPI handles turn account aliasing into a compile error, so any handler passing one account into several CPI slots needs restructuring around a copied `AccountView` (the `CpiHandle::readonly` idiom used in create-token). - `associated_token` constraints now require sibling account field references. - compression/ vendors Bubblegum types with hand-written borsh `serialize` calls that have no wincode equivalent yet. CI still pins avm 1.1.2 deliberately: it should flip to 2.0.0-rc.1 in the commit that finishes these 38, since a v2 CLI cannot build v1 programs. --- .../anchor/programs/cnft-burn/Cargo.toml | 10 +- .../cnft-burn/src/instructions/burn_cnft.rs | 60 ++++----- .../anchor/programs/cnft-burn/src/lib.rs | 8 +- .../anchor/programs/cnft-vault/Cargo.toml | 10 +- .../src/instructions/initialize_vault.rs | 12 +- .../src/instructions/withdraw_cnft.rs | 66 +++++----- .../src/instructions/withdraw_two_cnfts.rs | 116 ++++++++--------- .../anchor/programs/cnft-vault/src/lib.rs | 30 ++--- .../programs/cnft-vault/src/state/vault.rs | 4 +- .../cutils/anchor/programs/cutils/Cargo.toml | 10 +- .../programs/cutils/src/bubblegum_types.rs | 18 +-- .../programs/cutils/src/instructions/mint.rs | 112 ++++++++--------- .../cutils/src/instructions/verify.rs | 32 ++--- .../cutils/anchor/programs/cutils/src/lib.rs | 10 +- .../anchor/programs/betting-market/Cargo.toml | 14 ++- .../src/instructions/add_outcome.rs | 20 +-- .../src/instructions/cancel_event.rs | 10 +- .../src/instructions/claim_refund.rs | 29 ++--- .../src/instructions/claim_winnings.rs | 29 ++--- .../src/instructions/close_losing_bet.rs | 18 +-- .../src/instructions/initialize_config.rs | 23 ++-- .../src/instructions/initialize_event.rs | 23 ++-- .../src/instructions/place_bet.rs | 53 ++++---- .../src/instructions/settle_event.rs | 31 ++--- .../betting-market/src/instructions/shared.rs | 42 +++---- .../anchor/programs/betting-market/src/lib.rs | 20 +-- .../programs/betting-market/src/state/bet.rs | 8 +- .../betting-market/src/state/config.rs | 8 +- .../betting-market/src/state/event.rs | 2 +- .../betting-market/src/state/outcome.rs | 4 +- .../programs/betting-market/src/state/user.rs | 8 +- .../escrow/anchor/programs/escrow/Cargo.toml | 14 ++- .../escrow/src/instructions/cancel_offer.rs | 32 ++--- .../escrow/src/instructions/make_offer.rs | 37 +++--- .../escrow/src/instructions/shared.rs | 42 +++---- .../escrow/src/instructions/take_offer.rs | 42 +++---- .../escrow/anchor/programs/escrow/src/lib.rs | 6 +- .../anchor/programs/escrow/src/state/offer.rs | 8 +- .../anchor/programs/lending/Cargo.toml | 14 ++- .../admin/collect_protocol_fees.rs | 28 ++--- .../admin/initialize_lending_market.rs | 16 +-- .../instructions/admin/initialize_reserve.rs | 46 +++---- .../src/instructions/admin/set_price.rs | 20 +-- .../admin/update_reserve_config.rs | 10 +- .../borrow_obligation_liquidity.rs | 34 ++--- .../deposit_obligation_collateral.rs | 39 +++--- .../instructions/deposit_reserve_liquidity.rs | 38 +++--- .../src/instructions/initialize_obligation.rs | 18 +-- .../src/instructions/liquidate_obligation.rs | 59 ++++----- .../instructions/redeem_reserve_collateral.rs | 38 +++--- .../src/instructions/refresh_obligation.rs | 18 +-- .../src/instructions/refresh_reserve.rs | 6 +- .../repay_obligation_liquidity.rs | 32 ++--- .../withdraw_obligation_collateral.rs | 37 +++--- .../anchor/programs/lending/src/lib.rs | 30 ++--- .../lending/src/state/lending_market.rs | 6 +- .../programs/lending/src/state/obligation.rs | 18 +-- .../programs/lending/src/state/price_feed.rs | 6 +- .../programs/lending/src/state/reserve.rs | 16 +-- .../anchor/programs/order-book/Cargo.toml | 14 ++- .../src/instructions/admin/withdraw_fees.rs | 32 ++--- .../src/instructions/cancel_order.rs | 20 +-- .../src/instructions/initialize_market.rs | 43 +++---- .../instructions/initialize_market_user.rs | 18 +-- .../src/instructions/place_order.rs | 80 ++++++------ .../src/instructions/settle_funds.rs | 50 ++++---- .../anchor/programs/order-book/src/lib.rs | 12 +- .../programs/order-book/src/state/market.rs | 16 +-- .../order-book/src/state/market_user.rs | 6 +- .../programs/order-book/src/state/order.rs | 6 +- .../order-book/src/state/order_book.rs | 8 +- .../order-book/src/state/slab/nodes.rs | 4 +- .../order-book/src/state/slab/ordertree.rs | 2 +- .../programs/mock-switchboard/Cargo.toml | 10 +- .../programs/mock-switchboard/src/lib.rs | 24 ++-- .../programs/perpetual-futures/Cargo.toml | 14 ++- .../src/instructions/add_liquidity.rs | 54 ++++---- .../src/instructions/close_position.rs | 46 +++---- .../src/instructions/collect_fees.rs | 40 +++--- .../src/instructions/initialize_pool.rs | 46 +++---- .../src/instructions/liquidate_position.rs | 62 ++++----- .../src/instructions/open_position.rs | 44 +++---- .../src/instructions/remove_liquidity.rs | 54 ++++---- .../src/instructions/shared.rs | 2 +- .../programs/perpetual-futures/src/lib.rs | 14 +-- .../perpetual-futures/src/state/oracle.rs | 4 +- .../perpetual-futures/src/state/pool.rs | 12 +- .../perpetual-futures/src/state/position.rs | 6 +- .../programs/mock-switchboard/Cargo.toml | 10 +- .../programs/mock-switchboard/src/lib.rs | 24 ++-- .../anchor/programs/prop-amm/Cargo.toml | 14 ++- .../src/instructions/deposit_inventory.rs | 50 ++++---- .../src/instructions/initialize_market.rs | 55 ++++---- .../prop-amm/src/instructions/set_quote.rs | 8 +- .../prop-amm/src/instructions/swap.rs | 90 ++++++------- .../src/instructions/withdraw_inventory.rs | 60 ++++----- .../anchor/programs/prop-amm/src/lib.rs | 10 +- .../programs/prop-amm/src/state/market.rs | 14 +-- .../programs/prop-amm/src/state/oracle.rs | 4 +- .../anchor/programs/fundraiser/Cargo.toml | 14 ++- .../fundraiser/src/instructions/checker.rs | 44 +++---- .../fundraiser/src/instructions/close.rs | 46 +++---- .../fundraiser/src/instructions/contribute.rs | 34 ++--- .../src/instructions/initialize_fundraiser.rs | 26 ++-- .../fundraiser/src/instructions/refund.rs | 38 +++--- .../anchor/programs/fundraiser/src/lib.rs | 10 +- .../fundraiser/src/state/contributor.rs | 2 +- .../fundraiser/src/state/fundraiser.rs | 6 +- .../anchor/programs/token-swap/Cargo.toml | 14 ++- .../instructions/admin/claim_admin_fees.rs | 63 +++++----- .../src/instructions/deposit_liquidity.rs | 90 ++++++------- .../src/instructions/initialize_config.rs | 14 +-- .../src/instructions/initialize_pool.rs | 55 ++++---- .../src/instructions/swap_tokens.rs | 104 +++++++-------- .../src/instructions/withdraw_liquidity.rs | 88 ++++++------- .../anchor/programs/token-swap/src/lib.rs | 12 +- .../programs/token-swap/src/state/config.rs | 4 +- .../token-swap/src/state/pool_config.rs | 10 +- .../programs/mock-swap-router/Cargo.toml | 14 ++- .../src/instructions/initialize_router.rs | 24 ++-- .../src/instructions/set_rate.rs | 32 ++--- .../src/instructions/swap_asset_for_usdc.rs | 52 ++++---- .../src/instructions/swap_usdc_for_asset.rs | 50 ++++---- .../programs/mock-swap-router/src/lib.rs | 12 +- .../mock-swap-router/src/state/asset_rate.rs | 4 +- .../src/state/router_config.rs | 6 +- .../anchor/programs/vault-strategy/Cargo.toml | 14 ++- .../src/instructions/add_asset.rs | 36 +++--- .../src/instructions/approve_asset.rs | 26 ++-- .../src/instructions/collect_fees.rs | 30 ++--- .../src/instructions/deposit.rs | 86 ++++++------- .../src/instructions/initialize_registry.rs | 16 +-- .../src/instructions/initialize_strategy.rs | 37 +++--- .../src/instructions/rebalance.rs | 118 +++++++++--------- .../src/instructions/set_weight.rs | 12 +- .../src/instructions/withdraw.rs | 64 +++++----- .../anchor/programs/vault-strategy/src/lib.rs | 22 ++-- .../programs/vault-strategy/src/oracle.rs | 18 +-- .../vault-strategy/src/state/registry.rs | 12 +- .../vault-strategy/src/state/strategy.rs | 24 ++-- .../anchor/programs/create-token/Cargo.toml | 14 ++- .../anchor/programs/create-token/src/lib.rs | 52 ++++---- .../external-delegate-token-master/Cargo.toml | 14 ++- .../src/instructions/authority_transfer.rs | 34 ++--- .../src/instructions/initialize.rs | 12 +- .../src/instructions/set_ethereum_address.rs | 8 +- .../src/instructions/transfer_tokens.rs | 36 +++--- .../external-delegate-token-master/src/lib.rs | 16 +-- .../anchor/programs/nft-minter/Cargo.toml | 14 ++- .../anchor/programs/nft-minter/src/lib.rs | 80 ++++++------ .../anchor/programs/mint-nft/Cargo.toml | 14 ++- .../src/instructions/create_collection.rs | 49 ++++---- .../mint-nft/src/instructions/mint_nft.rs | 53 ++++---- .../src/instructions/verify_collection.rs | 40 +++--- .../anchor/programs/mint-nft/src/lib.rs | 6 +- .../anchor/programs/token-minter/Cargo.toml | 14 ++- .../token-minter/src/instructions/create.rs | 45 +++---- .../token-minter/src/instructions/mint.rs | 28 ++--- .../anchor/programs/token-minter/src/lib.rs | 4 +- .../basics/anchor/programs/basics/Cargo.toml | 14 ++- .../create_associated_token_account.rs | 16 +-- .../basics/src/instructions/create_token.rs | 17 +-- .../src/instructions/create_token_account.rs | 17 +-- .../basics/src/instructions/mint_token.rs | 20 +-- .../basics/src/instructions/transfer_token.rs | 32 ++--- .../basics/anchor/programs/basics/src/lib.rs | 10 +- .../anchor/programs/cpi-guard/Cargo.toml | 14 ++- .../anchor/programs/cpi-guard/src/lib.rs | 29 ++--- .../programs/default-account-state/Cargo.toml | 14 ++- .../src/instructions/initialize.rs | 35 +++--- .../src/instructions/update_default_state.rs | 20 +-- .../programs/default-account-state/src/lib.rs | 4 +- .../group/anchor/programs/group/Cargo.toml | 14 ++- .../src/instructions/test_initialize_group.rs | 27 ++-- .../group/anchor/programs/group/src/lib.rs | 2 +- .../programs/immutable-owner/Cargo.toml | 14 ++- .../programs/immutable-owner/src/lib.rs | 37 +++--- .../programs/interest-bearing/Cargo.toml | 14 ++- .../src/instructions/initialize.rs | 41 +++--- .../src/instructions/update_rate.rs | 23 ++-- .../programs/interest-bearing/src/lib.rs | 6 +- .../anchor/programs/memo-transfer/Cargo.toml | 14 ++- .../memo-transfer/src/instructions/disable.rs | 18 +-- .../src/instructions/initialize.rs | 39 +++--- .../anchor/programs/memo-transfer/src/lib.rs | 4 +- .../anchor/programs/metadata/Cargo.toml | 14 ++- .../metadata/src/instructions/emit.rs | 16 +-- .../metadata/src/instructions/initialize.rs | 33 ++--- .../metadata/src/instructions/remove_key.rs | 24 ++-- .../src/instructions/update_authority.rs | 26 ++-- .../metadata/src/instructions/update_field.rs | 30 ++--- .../anchor/programs/metadata/src/lib.rs | 10 +- .../programs/mint-close-authority/Cargo.toml | 14 ++- .../src/instructions/close.rs | 18 +-- .../src/instructions/initialize.rs | 17 +-- .../programs/mint-close-authority/src/lib.rs | 4 +- .../anchor/programs/extension_nft/Cargo.toml | 14 ++- .../src/instructions/chop_tree.rs | 32 ++--- .../src/instructions/init_player.rs | 16 +-- .../src/instructions/mint_nft.rs | 96 +++++++------- .../anchor/programs/extension_nft/src/lib.rs | 8 +- .../extension_nft/src/state/game_data.rs | 2 +- .../extension_nft/src/state/player_data.rs | 4 +- .../programs/non-transferable/Cargo.toml | 14 ++- .../programs/non-transferable/src/lib.rs | 35 +++--- .../programs/permanent-delegate/Cargo.toml | 14 ++- .../src/instructions/initialize.rs | 17 +-- .../programs/permanent-delegate/src/lib.rs | 2 +- .../anchor/programs/transfer-fee/Cargo.toml | 14 ++- .../transfer-fee/src/instructions/harvest.rs | 17 ++- .../src/instructions/initialize.rs | 45 ++++--- .../transfer-fee/src/instructions/transfer.rs | 35 +++--- .../src/instructions/update_fee.rs | 17 ++- .../transfer-fee/src/instructions/withdraw.rs | 21 ++-- .../anchor/programs/transfer-fee/src/lib.rs | 10 +- .../anchor/programs/transfer-hook/Cargo.toml | 14 ++- .../initialize_extra_account_meta_list.rs | 22 ++-- .../src/instructions/transfer_hook.rs | 21 ++-- .../anchor/programs/transfer-hook/src/lib.rs | 8 +- .../anchor/programs/abl-token/Cargo.toml | 14 ++- .../src/instructions/attach_to_mint.rs | 22 ++-- .../abl-token/src/instructions/change_mode.rs | 46 +++---- .../abl-token/src/instructions/init_config.rs | 12 +- .../abl-token/src/instructions/init_mint.rs | 76 +++++------ .../abl-token/src/instructions/init_wallet.rs | 16 +-- .../src/instructions/remove_wallet.rs | 10 +- .../abl-token/src/instructions/tx_hook.rs | 16 +-- .../anchor/programs/abl-token/src/lib.rs | 14 +-- .../anchor/programs/abl-token/src/state.rs | 8 +- .../anchor/programs/transfer-hook/Cargo.toml | 14 ++- .../initialize_extra_account_meta_list.rs | 20 +-- .../src/instructions/transfer_hook.rs | 19 +-- .../anchor/programs/transfer-hook/src/lib.rs | 8 +- .../anchor/programs/transfer-hook/Cargo.toml | 14 ++- .../src/instructions/initialize.rs | 17 +-- .../initialize_extra_account_meta_list.rs | 18 +-- .../src/instructions/transfer_hook.rs | 17 +-- .../anchor/programs/transfer-hook/src/lib.rs | 8 +- .../anchor/programs/transfer-hook/Cargo.toml | 14 ++- .../initialize_extra_account_meta_list.rs | 16 +-- .../src/instructions/transfer_hook.rs | 48 +++---- .../anchor/programs/transfer-hook/src/lib.rs | 12 +- .../programs/transfer-switch/Cargo.toml | 14 ++- .../src/instructions/configure_admin.rs | 18 +-- .../initialise_extra_account_metas_list.rs | 22 ++-- .../src/instructions/switch.rs | 18 +-- .../src/instructions/transfer_hook.rs | 20 +-- .../programs/transfer-switch/src/lib.rs | 8 +- .../programs/transfer-switch/src/state.rs | 8 +- .../anchor/programs/transfer-hook/Cargo.toml | 14 ++- .../src/instructions/add_to_whitelist.rs | 16 +-- .../initialize_extra_account_meta_list.rs | 18 +-- .../src/instructions/transfer_hook.rs | 21 ++-- .../anchor/programs/transfer-hook/src/lib.rs | 14 +-- .../anchor/programs/token-minter/Cargo.toml | 14 ++- .../token-minter/src/instructions/create.rs | 45 +++---- .../token-minter/src/instructions/mint.rs | 30 ++--- .../anchor/programs/token-minter/src/lib.rs | 4 +- .../programs/transfer-tokens/Cargo.toml | 14 ++- .../src/instructions/create.rs | 42 +++---- .../transfer-tokens/src/instructions/mint.rs | 30 ++--- .../src/instructions/transfer.rs | 38 +++--- .../programs/transfer-tokens/src/lib.rs | 6 +- 263 files changed, 3483 insertions(+), 3117 deletions(-) diff --git a/compression/cnft-burn/anchor/programs/cnft-burn/Cargo.toml b/compression/cnft-burn/anchor/programs/cnft-burn/Cargo.toml index e46e7e802..0a296eccf 100644 --- a/compression/cnft-burn/anchor/programs/cnft-burn/Cargo.toml +++ b/compression/cnft-burn/anchor/programs/cnft-burn/Cargo.toml @@ -20,7 +20,15 @@ custom-heap = [] custom-panic = [] [dependencies] -anchor-lang = "1.1.2" +anchor-lang = { version = "2.0.0-rc.1", features = ["compat"] } +# The `#[program]` macro expands to `wincode` paths for instruction-data +# (de)serialization, so the crate has to be a direct dependency. +wincode = { version = "0.5", features = ["derive"] } +# anchor-lang 2.0.0-rc.1 is built against wincode 0.5, but solana-address 2.7 +# moved to wincode 0.6. With both in the graph, `Address`'s wincode impls +# belong to the version the account derives are not using and every +# `SchemaRead`/`SchemaWrite` bound fails. 2.6.1 is the last 0.5-line release. +solana-address = ">=2.6, <2.7" # mpl-bubblegum and spl-account-compression removed: they depend on solana-program 2.x # which is incompatible with Anchor 1.0's solana 3.x types. CPI calls are built manually # using raw invoke() with hardcoded program IDs and discriminators. diff --git a/compression/cnft-burn/anchor/programs/cnft-burn/src/instructions/burn_cnft.rs b/compression/cnft-burn/anchor/programs/cnft-burn/src/instructions/burn_cnft.rs index 595a81da9..cbc87f784 100644 --- a/compression/cnft-burn/anchor/programs/cnft-burn/src/instructions/burn_cnft.rs +++ b/compression/cnft-burn/anchor/programs/cnft-burn/src/instructions/burn_cnft.rs @@ -21,36 +21,36 @@ struct BurnArgs { } #[derive(Accounts)] -pub struct BurnCnftAccountConstraints<'info> { +pub struct BurnCnftAccountConstraints { #[account(mut)] - pub leaf_owner: Signer<'info>, + pub leaf_owner: Signer, #[account(mut)] #[account( - seeds = [merkle_tree.key().as_ref()], + seeds = [merkle_tree.address().as_ref()], bump, - seeds::program = bubblegum_program.key() + seeds::program = bubblegum_program.address() )] /// CHECK: This account is modified in the downstream program - pub tree_authority: UncheckedAccount<'info>, + pub tree_authority: UncheckedAccount, #[account(mut)] /// CHECK: Written by the Bubblegum/Account Compression CPI (the burn /// replaces the leaf and updates the tree root); validated downstream /// by those programs. - pub merkle_tree: UncheckedAccount<'info>, + pub merkle_tree: UncheckedAccount, /// CHECK: This account is neither written to nor read from. - pub log_wrapper: UncheckedAccount<'info>, - pub compression_program: Program<'info, SPLCompression>, + pub log_wrapper: UncheckedAccount, + pub compression_program: Program, // Pin the bubblegum program account to the known mpl-bubblegum id. Without // this constraint the caller could pass any account and a malicious one // could short-circuit the CPI in unexpected ways. /// CHECK: address constrained to the mpl-bubblegum program id. #[account(address = MPL_BUBBLEGUM_ID)] - pub bubblegum_program: UncheckedAccount<'info>, - pub system_program: Program<'info, System>, + pub bubblegum_program: UncheckedAccount, + pub system_program: Program, } pub fn handle_burn_cnft<'info>( - context: Context<'info, BurnCnftAccountConstraints<'info>>, + context: &mut Context<'info, BurnCnftAccountConstraints<'info>>, root: [u8; 32], data_hash: [u8; 32], creator_hash: [u8; 32], @@ -69,36 +69,36 @@ pub fn handle_burn_cnft<'info>( args.serialize(&mut data)?; // Build account metas matching mpl-bubblegum Burn instruction layout - let mut accounts = Vec::with_capacity(7 + context.remaining_accounts.len()); + let mut accounts = Vec::with_capacity(7 + context.remaining_accounts().len()); accounts.push(AccountMeta::new_readonly( - context.accounts.tree_authority.key(), + *context.accounts.tree_authority.address(), false, )); accounts.push(AccountMeta::new_readonly( - context.accounts.leaf_owner.key(), + *context.accounts.leaf_owner.address(), true, )); // leaf_delegate = leaf_owner, not a signer in this call accounts.push(AccountMeta::new_readonly( - context.accounts.leaf_owner.key(), + *context.accounts.leaf_owner.address(), false, )); - accounts.push(AccountMeta::new(context.accounts.merkle_tree.key(), false)); + accounts.push(AccountMeta::new(*context.accounts.merkle_tree.address(), false)); accounts.push(AccountMeta::new_readonly( - context.accounts.log_wrapper.key(), + *context.accounts.log_wrapper.address(), false, )); accounts.push(AccountMeta::new_readonly( - context.accounts.compression_program.key(), + *context.accounts.compression_program.address(), false, )); accounts.push(AccountMeta::new_readonly( - context.accounts.system_program.key(), + *context.accounts.system_program.address(), false, )); // Append remaining accounts (proof nodes) - for acc in context.remaining_accounts.iter() { - accounts.push(AccountMeta::new_readonly(acc.key(), false)); + for acc in context.remaining_accounts().iter() { + accounts.push(AccountMeta::new_readonly(acc.address(), false)); } let instruction = Instruction { @@ -109,16 +109,16 @@ pub fn handle_burn_cnft<'info>( // Gather all account infos for the CPI let mut account_infos = vec![ - context.accounts.bubblegum_program.to_account_info(), - context.accounts.tree_authority.to_account_info(), - context.accounts.leaf_owner.to_account_info(), - context.accounts.merkle_tree.to_account_info(), - context.accounts.log_wrapper.to_account_info(), - context.accounts.compression_program.to_account_info(), - context.accounts.system_program.to_account_info(), + context.accounts.bubblegum_program.cpi_handle_mut(), + context.accounts.tree_authority.cpi_handle_mut(), + context.accounts.leaf_owner.cpi_handle_mut(), + context.accounts.merkle_tree.cpi_handle_mut(), + context.accounts.log_wrapper.cpi_handle_mut(), + context.accounts.compression_program.cpi_handle_mut(), + context.accounts.system_program.cpi_handle_mut(), ]; - for acc in context.remaining_accounts.iter() { - account_infos.push(acc.to_account_info()); + for acc in context.remaining_accounts().iter() { + account_infos.push(acc.cpi_handle_mut()); } invoke(&instruction, &account_infos)?; diff --git a/compression/cnft-burn/anchor/programs/cnft-burn/src/lib.rs b/compression/cnft-burn/anchor/programs/cnft-burn/src/lib.rs index 7bd40193c..c08b05447 100644 --- a/compression/cnft-burn/anchor/programs/cnft-burn/src/lib.rs +++ b/compression/cnft-burn/anchor/programs/cnft-burn/src/lib.rs @@ -11,17 +11,17 @@ use instructions::*; declare_id!("C6qxH8n6mZxrrbtMtYWYSp8JR8vkQ55X1o4EBg7twnMv"); /// mpl-bubblegum program ID -pub const MPL_BUBBLEGUM_ID: Pubkey = pubkey!("BGUMAp9Gq7iTEuizy4pqaxsTyUCBK68MDfK752saRPUY"); +pub const MPL_BUBBLEGUM_ID: Address = pubkey!("BGUMAp9Gq7iTEuizy4pqaxsTyUCBK68MDfK752saRPUY"); /// SPL Account Compression program ID -pub const SPL_ACCOUNT_COMPRESSION_ID: Pubkey = +pub const SPL_ACCOUNT_COMPRESSION_ID: Address = pubkey!("cmtDvXumGCrqC1Age74AVPhSRVXJMd8PJS91L8KbNCK"); #[derive(Clone)] pub struct SPLCompression; impl anchor_lang::Id for SPLCompression { - fn id() -> Pubkey { + fn id() -> Address { SPL_ACCOUNT_COMPRESSION_ID } } @@ -31,7 +31,7 @@ pub mod cnft_burn { use super::*; pub fn burn_cnft<'info>( - context: Context<'info, BurnCnftAccountConstraints<'info>>, + context: &mut Context<'info, BurnCnftAccountConstraints<'info>>, root: [u8; 32], data_hash: [u8; 32], creator_hash: [u8; 32], diff --git a/compression/cnft-vault/anchor/programs/cnft-vault/Cargo.toml b/compression/cnft-vault/anchor/programs/cnft-vault/Cargo.toml index 4e54a8190..f100479e2 100644 --- a/compression/cnft-vault/anchor/programs/cnft-vault/Cargo.toml +++ b/compression/cnft-vault/anchor/programs/cnft-vault/Cargo.toml @@ -20,7 +20,15 @@ custom-heap = [] custom-panic = [] [dependencies] -anchor-lang = "1.1.2" +anchor-lang = "2.0.0-rc.1" +# The `#[program]` macro expands to `wincode` paths for instruction-data +# (de)serialization, so the crate has to be a direct dependency. +wincode = { version = "0.5", features = ["derive"] } +# anchor-lang 2.0.0-rc.1 is built against wincode 0.5, but solana-address 2.7 +# moved to wincode 0.6. With both in the graph, `Address`'s wincode impls +# belong to the version the account derives are not using and every +# `SchemaRead`/`SchemaWrite` bound fails. 2.6.1 is the last 0.5-line release. +solana-address = ">=2.6, <2.7" # mpl-bubblegum and spl-account-compression removed: they depend on solana-program 2.x # which is incompatible with Anchor 1.0's solana 3.x types. CPI calls are built manually # using raw invoke_signed() with hardcoded program IDs and discriminators. diff --git a/compression/cnft-vault/anchor/programs/cnft-vault/src/instructions/initialize_vault.rs b/compression/cnft-vault/anchor/programs/cnft-vault/src/instructions/initialize_vault.rs index 58cf3389b..8b57fea9a 100644 --- a/compression/cnft-vault/anchor/programs/cnft-vault/src/instructions/initialize_vault.rs +++ b/compression/cnft-vault/anchor/programs/cnft-vault/src/instructions/initialize_vault.rs @@ -3,9 +3,9 @@ use anchor_lang::prelude::*; use crate::state::{Vault, VAULT_SEED}; #[derive(Accounts)] -pub struct InitializeVaultAccountConstraints<'info> { +pub struct InitializeVaultAccountConstraints { #[account(mut)] - pub authority: Signer<'info>, + pub authority: Signer, #[account( init, @@ -14,14 +14,14 @@ pub struct InitializeVaultAccountConstraints<'info> { seeds = [VAULT_SEED], bump, )] - pub vault: Account<'info, Vault>, + pub vault: BorshAccount, - pub system_program: Program<'info, System>, + pub system_program: Program, } -pub fn handler(context: Context) -> Result<()> { +pub fn handler(context: &mut Context) -> Result<()> { let vault = &mut context.accounts.vault; - vault.authority = context.accounts.authority.key(); + vault.authority = *context.accounts.authority.address(); vault.bump = context.bumps.vault; Ok(()) } diff --git a/compression/cnft-vault/anchor/programs/cnft-vault/src/instructions/withdraw_cnft.rs b/compression/cnft-vault/anchor/programs/cnft-vault/src/instructions/withdraw_cnft.rs index 79dfaed97..0e334aef2 100644 --- a/compression/cnft-vault/anchor/programs/cnft-vault/src/instructions/withdraw_cnft.rs +++ b/compression/cnft-vault/anchor/programs/cnft-vault/src/instructions/withdraw_cnft.rs @@ -6,9 +6,9 @@ use crate::state::{Vault, VAULT_SEED}; use crate::{build_transfer_instruction, SPLCompression, TransferArgs, MPL_BUBBLEGUM_ID}; #[derive(Accounts)] -pub struct WithdrawCnftAccountConstraints<'info> { +pub struct WithdrawCnftAccountConstraints { /// The stored vault authority. Only this signer may withdraw. - pub authority: Signer<'info>, + pub authority: Signer, // The vault PDA owns the cNFTs (as Bubblegum leaf owner) and signs the // transfer CPI via invoke_signed. @@ -17,40 +17,40 @@ pub struct WithdrawCnftAccountConstraints<'info> { bump = vault.bump, has_one = authority @ VaultError::InvalidWithdrawAuthority, )] - pub vault: Account<'info, Vault>, + pub vault: BorshAccount, #[account(mut)] #[account( - seeds = [merkle_tree.key().as_ref()], + seeds = [merkle_tree.address().as_ref()], bump, - seeds::program = bubblegum_program.key() + seeds::program = bubblegum_program.address() )] /// CHECK: This account is modified in the downstream program - pub tree_authority: UncheckedAccount<'info>, + pub tree_authority: UncheckedAccount, /// CHECK: This account is neither written to nor read from. - pub new_leaf_owner: UncheckedAccount<'info>, + pub new_leaf_owner: UncheckedAccount, #[account(mut)] /// CHECK: This account is modified in the downstream program - pub merkle_tree: UncheckedAccount<'info>, + pub merkle_tree: UncheckedAccount, /// CHECK: This account is neither written to nor read from. - pub log_wrapper: UncheckedAccount<'info>, + pub log_wrapper: UncheckedAccount, - pub compression_program: Program<'info, SPLCompression>, + pub compression_program: Program, // Pin the bubblegum program account to the known mpl-bubblegum id. Without // this constraint the caller could pass any account to the CPI. /// CHECK: address constrained to the mpl-bubblegum program id. #[account(address = MPL_BUBBLEGUM_ID)] - pub bubblegum_program: UncheckedAccount<'info>, + pub bubblegum_program: UncheckedAccount, - pub system_program: Program<'info, System>, + pub system_program: Program, } pub fn handler<'info>( - context: Context<'info, WithdrawCnftAccountConstraints<'info>>, + context: &mut Context<'info, WithdrawCnftAccountConstraints<'info>>, root: [u8; 32], data_hash: [u8; 32], creator_hash: [u8; 32], @@ -60,24 +60,24 @@ pub fn handler<'info>( msg!( "attempting to send nft {} from tree {}", index, - context.accounts.merkle_tree.key() + context.accounts.merkle_tree.address() ); let proof_metas: Vec = context .remaining_accounts .iter() - .map(|acc| AccountMeta::new_readonly(acc.key(), false)) + .map(|acc| AccountMeta::new_readonly(acc.address(), false)) .collect(); let instruction = build_transfer_instruction( - context.accounts.tree_authority.key(), - context.accounts.vault.key(), - context.accounts.vault.key(), - context.accounts.new_leaf_owner.key(), - context.accounts.merkle_tree.key(), - context.accounts.log_wrapper.key(), - context.accounts.compression_program.key(), - context.accounts.system_program.key(), + context.accounts.tree_authority.address(), + context.accounts.vault.address(), + context.accounts.vault.address(), + context.accounts.new_leaf_owner.address(), + context.accounts.merkle_tree.address(), + context.accounts.log_wrapper.address(), + context.accounts.compression_program.address(), + context.accounts.system_program.address(), &proof_metas, TransferArgs { root, @@ -90,17 +90,17 @@ pub fn handler<'info>( // Gather all account infos for the CPI let mut account_infos = vec![ - context.accounts.bubblegum_program.to_account_info(), - context.accounts.tree_authority.to_account_info(), - context.accounts.vault.to_account_info(), - context.accounts.new_leaf_owner.to_account_info(), - context.accounts.merkle_tree.to_account_info(), - context.accounts.log_wrapper.to_account_info(), - context.accounts.compression_program.to_account_info(), - context.accounts.system_program.to_account_info(), + context.accounts.bubblegum_program.cpi_handle_mut(), + context.accounts.tree_authority.cpi_handle_mut(), + context.accounts.vault.cpi_handle_mut(), + context.accounts.new_leaf_owner.cpi_handle_mut(), + context.accounts.merkle_tree.cpi_handle_mut(), + context.accounts.log_wrapper.cpi_handle_mut(), + context.accounts.compression_program.cpi_handle_mut(), + context.accounts.system_program.cpi_handle_mut(), ]; - for acc in context.remaining_accounts.iter() { - account_infos.push(acc.to_account_info()); + for acc in context.remaining_accounts().iter() { + account_infos.push(acc.cpi_handle_mut()); } invoke_signed( diff --git a/compression/cnft-vault/anchor/programs/cnft-vault/src/instructions/withdraw_two_cnfts.rs b/compression/cnft-vault/anchor/programs/cnft-vault/src/instructions/withdraw_two_cnfts.rs index fadae6e01..4827b0231 100644 --- a/compression/cnft-vault/anchor/programs/cnft-vault/src/instructions/withdraw_two_cnfts.rs +++ b/compression/cnft-vault/anchor/programs/cnft-vault/src/instructions/withdraw_two_cnfts.rs @@ -6,9 +6,9 @@ use crate::state::{Vault, VAULT_SEED}; use crate::{build_transfer_instruction, SPLCompression, TransferArgs, MPL_BUBBLEGUM_ID}; #[derive(Accounts)] -pub struct WithdrawTwoCnftsAccountConstraints<'info> { +pub struct WithdrawTwoCnftsAccountConstraints { /// The stored vault authority. Only this signer may withdraw. - pub authority: Signer<'info>, + pub authority: Signer, // The vault PDA owns the cNFTs (as Bubblegum leaf owner) and signs both // transfer CPIs via invoke_signed. @@ -17,57 +17,57 @@ pub struct WithdrawTwoCnftsAccountConstraints<'info> { bump = vault.bump, has_one = authority @ VaultError::InvalidWithdrawAuthority, )] - pub vault: Account<'info, Vault>, + pub vault: BorshAccount, #[account(mut)] #[account( - seeds = [merkle_tree1.key().as_ref()], + seeds = [merkle_tree1.address().as_ref()], bump, - seeds::program = bubblegum_program.key() + seeds::program = bubblegum_program.address() )] /// CHECK: This account is modified in the downstream program - pub tree_authority1: UncheckedAccount<'info>, + pub tree_authority1: UncheckedAccount, /// CHECK: This account is neither written to nor read from. - pub new_leaf_owner1: UncheckedAccount<'info>, + pub new_leaf_owner1: UncheckedAccount, #[account(mut)] /// CHECK: This account is modified in the downstream program - pub merkle_tree1: UncheckedAccount<'info>, + pub merkle_tree1: UncheckedAccount, #[account(mut)] #[account( - seeds = [merkle_tree2.key().as_ref()], + seeds = [merkle_tree2.address().as_ref()], bump, - seeds::program = bubblegum_program.key() + seeds::program = bubblegum_program.address() )] /// CHECK: This account is modified in the downstream program - pub tree_authority2: UncheckedAccount<'info>, + pub tree_authority2: UncheckedAccount, /// CHECK: This account is neither written to nor read from. - pub new_leaf_owner2: UncheckedAccount<'info>, + pub new_leaf_owner2: UncheckedAccount, #[account(mut)] /// CHECK: This account is modified in the downstream program - pub merkle_tree2: UncheckedAccount<'info>, + pub merkle_tree2: UncheckedAccount, /// CHECK: This account is neither written to nor read from. - pub log_wrapper: UncheckedAccount<'info>, + pub log_wrapper: UncheckedAccount, - pub compression_program: Program<'info, SPLCompression>, + pub compression_program: Program, // Pin the bubblegum program account to the known mpl-bubblegum id. Without // this constraint the caller could pass any account to the two CPI calls. /// CHECK: address constrained to the mpl-bubblegum program id. #[account(address = MPL_BUBBLEGUM_ID)] - pub bubblegum_program: UncheckedAccount<'info>, + pub bubblegum_program: UncheckedAccount, - pub system_program: Program<'info, System>, + pub system_program: Program, } #[allow(clippy::too_many_arguments)] pub fn handler<'info>( - context: Context<'info, WithdrawTwoCnftsAccountConstraints<'info>>, + context: &mut Context<'info, WithdrawTwoCnftsAccountConstraints<'info>>, root1: [u8; 32], data_hash1: [u8; 32], creator_hash1: [u8; 32], @@ -81,8 +81,8 @@ pub fn handler<'info>( index2: u32, proof_2_length: u8, ) -> Result<()> { - let merkle_tree1 = context.accounts.merkle_tree1.key(); - let merkle_tree2 = context.accounts.merkle_tree2.key(); + let merkle_tree1 = context.accounts.merkle_tree1.address(); + let merkle_tree2 = context.accounts.merkle_tree2.address(); msg!( "attempting to send nfts from trees {} and {}", merkle_tree1, @@ -97,36 +97,36 @@ pub fn handler<'info>( require!( proof_1_length .checked_add(proof_2_length) - .is_some_and(|total| total == context.remaining_accounts.len()), + .is_some_and(|total| total == context.remaining_accounts().len()), VaultError::ProofLengthMismatch ); let signer_seeds: &[&[u8]] = &[VAULT_SEED, &[context.accounts.vault.bump]]; // Split remaining accounts into proof1 and proof2 - let (proof1_accounts, proof2_accounts) = context.remaining_accounts.split_at(proof_1_length); + let (proof1_accounts, proof2_accounts) = context.remaining_accounts().split_at(proof_1_length); let proof1_metas: Vec = proof1_accounts .iter() - .map(|acc| AccountMeta::new_readonly(acc.key(), false)) + .map(|acc| AccountMeta::new_readonly(acc.address(), false)) .collect(); let proof2_metas: Vec = proof2_accounts .iter() - .map(|acc| AccountMeta::new_readonly(acc.key(), false)) + .map(|acc| AccountMeta::new_readonly(acc.address(), false)) .collect(); // Withdraw cNFT#1 msg!("withdrawing cNFT#1"); let instruction1 = build_transfer_instruction( - context.accounts.tree_authority1.key(), - context.accounts.vault.key(), - context.accounts.vault.key(), - context.accounts.new_leaf_owner1.key(), - context.accounts.merkle_tree1.key(), - context.accounts.log_wrapper.key(), - context.accounts.compression_program.key(), - context.accounts.system_program.key(), + context.accounts.tree_authority1.address(), + context.accounts.vault.address(), + context.accounts.vault.address(), + context.accounts.new_leaf_owner1.address(), + context.accounts.merkle_tree1.address(), + context.accounts.log_wrapper.address(), + context.accounts.compression_program.address(), + context.accounts.system_program.address(), &proof1_metas, TransferArgs { root: root1, @@ -138,17 +138,17 @@ pub fn handler<'info>( )?; let mut account_infos1 = vec![ - context.accounts.bubblegum_program.to_account_info(), - context.accounts.tree_authority1.to_account_info(), - context.accounts.vault.to_account_info(), - context.accounts.new_leaf_owner1.to_account_info(), - context.accounts.merkle_tree1.to_account_info(), - context.accounts.log_wrapper.to_account_info(), - context.accounts.compression_program.to_account_info(), - context.accounts.system_program.to_account_info(), + context.accounts.bubblegum_program.cpi_handle_mut(), + context.accounts.tree_authority1.cpi_handle_mut(), + context.accounts.vault.cpi_handle_mut(), + context.accounts.new_leaf_owner1.cpi_handle_mut(), + context.accounts.merkle_tree1.cpi_handle_mut(), + context.accounts.log_wrapper.cpi_handle_mut(), + context.accounts.compression_program.cpi_handle_mut(), + context.accounts.system_program.cpi_handle_mut(), ]; for acc in proof1_accounts.iter() { - account_infos1.push(acc.to_account_info()); + account_infos1.push(acc.cpi_handle_mut()); } invoke_signed(&instruction1, &account_infos1, &[signer_seeds])?; @@ -156,14 +156,14 @@ pub fn handler<'info>( // Withdraw cNFT#2 msg!("withdrawing cNFT#2"); let instruction2 = build_transfer_instruction( - context.accounts.tree_authority2.key(), - context.accounts.vault.key(), - context.accounts.vault.key(), - context.accounts.new_leaf_owner2.key(), - context.accounts.merkle_tree2.key(), - context.accounts.log_wrapper.key(), - context.accounts.compression_program.key(), - context.accounts.system_program.key(), + context.accounts.tree_authority2.address(), + context.accounts.vault.address(), + context.accounts.vault.address(), + context.accounts.new_leaf_owner2.address(), + context.accounts.merkle_tree2.address(), + context.accounts.log_wrapper.address(), + context.accounts.compression_program.address(), + context.accounts.system_program.address(), &proof2_metas, TransferArgs { root: root2, @@ -175,17 +175,17 @@ pub fn handler<'info>( )?; let mut account_infos2 = vec![ - context.accounts.bubblegum_program.to_account_info(), - context.accounts.tree_authority2.to_account_info(), - context.accounts.vault.to_account_info(), - context.accounts.new_leaf_owner2.to_account_info(), - context.accounts.merkle_tree2.to_account_info(), - context.accounts.log_wrapper.to_account_info(), - context.accounts.compression_program.to_account_info(), - context.accounts.system_program.to_account_info(), + context.accounts.bubblegum_program.cpi_handle_mut(), + context.accounts.tree_authority2.cpi_handle_mut(), + context.accounts.vault.cpi_handle_mut(), + context.accounts.new_leaf_owner2.cpi_handle_mut(), + context.accounts.merkle_tree2.cpi_handle_mut(), + context.accounts.log_wrapper.cpi_handle_mut(), + context.accounts.compression_program.cpi_handle_mut(), + context.accounts.system_program.cpi_handle_mut(), ]; for acc in proof2_accounts.iter() { - account_infos2.push(acc.to_account_info()); + account_infos2.push(acc.cpi_handle_mut()); } invoke_signed(&instruction2, &account_infos2, &[signer_seeds])?; diff --git a/compression/cnft-vault/anchor/programs/cnft-vault/src/lib.rs b/compression/cnft-vault/anchor/programs/cnft-vault/src/lib.rs index f86065d06..d581a643c 100644 --- a/compression/cnft-vault/anchor/programs/cnft-vault/src/lib.rs +++ b/compression/cnft-vault/anchor/programs/cnft-vault/src/lib.rs @@ -12,13 +12,13 @@ use instructions::*; declare_id!("Fd4iwpPWaCU8BNwGQGtvvrcvG4Tfizq3RgLm8YLBJX6D"); /// mpl-bubblegum program ID (BGUMAp9Gq7iTEuizy4pqaxsTyUCBK68MDfK752saRPUY) -const MPL_BUBBLEGUM_ID: Pubkey = Pubkey::new_from_array([ +const MPL_BUBBLEGUM_ID: Address = Address::new_from_array([ 0x98, 0x8b, 0x80, 0xeb, 0x79, 0x35, 0x28, 0x69, 0xb2, 0x24, 0x74, 0x5f, 0x59, 0xdd, 0xbf, 0x8a, 0x26, 0x58, 0xca, 0x13, 0xdc, 0x68, 0x81, 0x21, 0x26, 0x35, 0x1c, 0xae, 0x07, 0xc1, 0xa5, 0xa5, ]); /// SPL Account Compression program ID (cmtDvXumGCrqC1Age74AVPhSRVXJMd8PJS91L8KbNCK) -const SPL_ACCOUNT_COMPRESSION_ID: Pubkey = Pubkey::new_from_array([ +const SPL_ACCOUNT_COMPRESSION_ID: Address = Address::new_from_array([ 0x09, 0x2a, 0x13, 0xee, 0x95, 0xc4, 0x1c, 0xba, 0x08, 0xa6, 0x7f, 0x5a, 0xc6, 0x7e, 0x8d, 0xf7, 0xe1, 0xda, 0x11, 0x62, 0x5e, 0x1d, 0x64, 0x13, 0x7f, 0x8f, 0x4f, 0x23, 0x83, 0x03, 0x7f, 0x14, ]); @@ -40,23 +40,23 @@ pub struct TransferArgs { pub struct SPLCompression; impl anchor_lang::Id for SPLCompression { - fn id() -> Pubkey { + fn id() -> Address { SPL_ACCOUNT_COMPRESSION_ID } } /// Build a mpl-bubblegum Transfer instruction from pubkeys and args. -/// This avoids using mpl-bubblegum's CPI wrapper which requires solana-program 2.x AccountInfo. +/// This avoids using mpl-bubblegum's CPI wrapper which requires solana-program 2.x AccountView. #[allow(clippy::too_many_arguments)] pub fn build_transfer_instruction( - tree_config: Pubkey, - leaf_owner: Pubkey, - leaf_delegate: Pubkey, - new_leaf_owner: Pubkey, - merkle_tree: Pubkey, - log_wrapper: Pubkey, - compression_program: Pubkey, - system_program: Pubkey, + tree_config: Address, + leaf_owner: Address, + leaf_delegate: Address, + new_leaf_owner: Address, + merkle_tree: Address, + log_wrapper: Address, + compression_program: Address, + system_program: Address, remaining_accounts: &[AccountMeta], args: TransferArgs, ) -> Result { @@ -87,12 +87,12 @@ pub fn build_transfer_instruction( pub mod cnft_vault { use super::*; - pub fn initialize_vault(context: Context) -> Result<()> { + pub fn initialize_vault(context: &mut Context) -> Result<()> { instructions::initialize_vault::handler(context) } pub fn withdraw_cnft<'info>( - context: Context<'info, WithdrawCnftAccountConstraints<'info>>, + context: &mut Context<'info, WithdrawCnftAccountConstraints<'info>>, root: [u8; 32], data_hash: [u8; 32], creator_hash: [u8; 32], @@ -104,7 +104,7 @@ pub mod cnft_vault { #[allow(clippy::too_many_arguments)] pub fn withdraw_two_cnfts<'info>( - context: Context<'info, WithdrawTwoCnftsAccountConstraints<'info>>, + context: &mut Context<'info, WithdrawTwoCnftsAccountConstraints<'info>>, root1: [u8; 32], data_hash1: [u8; 32], creator_hash1: [u8; 32], diff --git a/compression/cnft-vault/anchor/programs/cnft-vault/src/state/vault.rs b/compression/cnft-vault/anchor/programs/cnft-vault/src/state/vault.rs index 5ce304df8..4413f5bd3 100644 --- a/compression/cnft-vault/anchor/programs/cnft-vault/src/state/vault.rs +++ b/compression/cnft-vault/anchor/programs/cnft-vault/src/state/vault.rs @@ -5,10 +5,10 @@ use anchor_lang::prelude::*; pub const VAULT_SEED: &[u8] = b"cNFT-vault"; #[derive(InitSpace)] -#[account] +#[account(borsh)] pub struct Vault { /// The only signer allowed to withdraw cNFTs from the vault. - pub authority: Pubkey, + pub authority: Address, pub bump: u8, } diff --git a/compression/cutils/anchor/programs/cutils/Cargo.toml b/compression/cutils/anchor/programs/cutils/Cargo.toml index 2b24395ee..64c049e52 100644 --- a/compression/cutils/anchor/programs/cutils/Cargo.toml +++ b/compression/cutils/anchor/programs/cutils/Cargo.toml @@ -20,7 +20,15 @@ custom-heap = [] custom-panic = [] [dependencies] -anchor-lang = "1.1.2" +anchor-lang = "2.0.0-rc.1" +# The `#[program]` macro expands to `wincode` paths for instruction-data +# (de)serialization, so the crate has to be a direct dependency. +wincode = { version = "0.5", features = ["derive"] } +# anchor-lang 2.0.0-rc.1 is built against wincode 0.5, but solana-address 2.7 +# moved to wincode 0.6. With both in the graph, `Address`'s wincode impls +# belong to the version the account derives are not using and every +# `SchemaRead`/`SchemaWrite` bound fails. 2.6.1 is the last 0.5-line release. +solana-address = ">=2.6, <2.7" # mpl-bubblegum and spl-account-compression removed: they depend on solana-program 2.x # which is incompatible with Anchor 1.0's solana 3.x types. CPI calls are built manually # using raw invoke() with hardcoded program IDs and discriminators. Bubblegum types diff --git a/compression/cutils/anchor/programs/cutils/src/bubblegum_types.rs b/compression/cutils/anchor/programs/cutils/src/bubblegum_types.rs index 3f91dcef7..176ec4c65 100644 --- a/compression/cutils/anchor/programs/cutils/src/bubblegum_types.rs +++ b/compression/cutils/anchor/programs/cutils/src/bubblegum_types.rs @@ -1,4 +1,4 @@ -/// Re-implementation of mpl-bubblegum types using borsh 1.x and Anchor 1.0's Pubkey. +/// Re-implementation of mpl-bubblegum types using borsh 1.x and Anchor 1.0's Address. /// /// mpl-bubblegum 2.1.1 depends on solana-program 2.x which is incompatible with /// Anchor 1.0's solana 3.x types. These types are borsh-compatible reproductions @@ -9,7 +9,7 @@ use borsh::BorshSerialize; /// Mirrors mpl_bubblegum::types::Creator #[derive(BorshSerialize, Clone, Debug)] pub struct Creator { - pub address: Pubkey, + pub address: Address, pub verified: bool, pub share: u8, } @@ -18,7 +18,7 @@ pub struct Creator { #[derive(BorshSerialize, Clone, Debug)] pub struct Collection { pub verified: bool, - pub key: Pubkey, + pub key: Address, } /// Mirrors mpl_bubblegum::types::TokenProgramVersion @@ -82,9 +82,9 @@ pub struct MintToCollectionV1InstructionArgs { /// Compute the leaf hash for a V1 LeafSchema, matching mpl_bubblegum::types::LeafSchema::hash(). /// Uses keccak256 over the version byte and all fields. pub fn leaf_schema_v1_hash( - id: &Pubkey, - owner: &Pubkey, - delegate: &Pubkey, + id: &Address, + owner: &Address, + delegate: &Address, nonce: u64, data_hash: &[u8; 32], creator_hash: &[u8; 32], @@ -102,14 +102,14 @@ pub fn leaf_schema_v1_hash( } /// Compute the asset id from tree and nonce, matching mpl_bubblegum::utils::get_asset_id(). -pub fn get_asset_id(tree: &Pubkey, nonce: u64) -> Pubkey { +pub fn get_asset_id(tree: &Address, nonce: u64) -> Address { // mpl-bubblegum program ID - let bubblegum_id = Pubkey::new_from_array([ + let bubblegum_id = Address::new_from_array([ 0x98, 0x8b, 0x80, 0xeb, 0x79, 0x35, 0x28, 0x69, 0xb2, 0x24, 0x74, 0x5f, 0x59, 0xdd, 0xbf, 0x8a, 0x26, 0x58, 0xca, 0x13, 0xdc, 0x68, 0x81, 0x21, 0x26, 0x35, 0x1c, 0xae, 0x07, 0xc1, 0xa5, 0xa5, ]); - Pubkey::find_program_address( + Address::find_program_address( &[b"asset", tree.as_ref(), &nonce.to_le_bytes()], &bubblegum_id, ) diff --git a/compression/cutils/anchor/programs/cutils/src/instructions/mint.rs b/compression/cutils/anchor/programs/cutils/src/instructions/mint.rs index 795bb9caa..cb606e53a 100644 --- a/compression/cutils/anchor/programs/cutils/src/instructions/mint.rs +++ b/compression/cutils/anchor/programs/cutils/src/instructions/mint.rs @@ -11,59 +11,59 @@ use borsh::BorshSerialize; #[derive(Accounts)] #[instruction(params: MintParams)] -pub struct MintAccountConstraints<'info> { - pub payer: Signer<'info>, +pub struct MintAccountConstraints { + pub payer: Signer, #[account( mut, - seeds = [merkle_tree.key().as_ref()], - seeds::program = bubblegum_program.key(), + seeds = [merkle_tree.address().as_ref()], + seeds::program = bubblegum_program.address(), bump, )] /// CHECK: This account is modified in the downstream program - pub tree_authority: UncheckedAccount<'info>, + pub tree_authority: UncheckedAccount, /// CHECK: This account is neither written to nor read from. - pub leaf_owner: UncheckedAccount<'info>, + pub leaf_owner: UncheckedAccount, /// CHECK: This account is neither written to nor read from. - pub leaf_delegate: UncheckedAccount<'info>, + pub leaf_delegate: UncheckedAccount, #[account(mut)] /// CHECK: Written by the Bubblegum/Account Compression CPI (the mint /// appends a leaf and updates the tree root); validated downstream. - pub merkle_tree: UncheckedAccount<'info>, + pub merkle_tree: UncheckedAccount, - pub tree_delegate: Signer<'info>, + pub tree_delegate: Signer, - pub collection_authority: Signer<'info>, + pub collection_authority: Signer, /// CHECK: Optional collection authority record PDA. /// If there is no collection authority record PDA then /// this must be the Bubblegum program address. - pub collection_authority_record_pda: UncheckedAccount<'info>, + pub collection_authority_record_pda: UncheckedAccount, /// CHECK: This account is checked in the instruction - pub collection_mint: UncheckedAccount<'info>, + pub collection_mint: UncheckedAccount, #[account(mut)] /// CHECK: This account is checked in the instruction - pub collection_metadata: UncheckedAccount<'info>, + pub collection_metadata: UncheckedAccount, /// CHECK: This account is checked in the instruction - pub edition_account: UncheckedAccount<'info>, + pub edition_account: UncheckedAccount, /// CHECK: This is just used as a signing PDA. - pub bubblegum_signer: UncheckedAccount<'info>, + pub bubblegum_signer: UncheckedAccount, /// CHECK: This account is neither written to nor read from. - pub log_wrapper: UncheckedAccount<'info>, - pub compression_program: Program<'info, SPLCompression>, + pub log_wrapper: UncheckedAccount, + pub compression_program: Program, /// CHECK: This account is neither written to nor read from. - pub token_metadata_program: UncheckedAccount<'info>, + pub token_metadata_program: UncheckedAccount, /// CHECK: This account is neither written to nor read from. - pub bubblegum_program: UncheckedAccount<'info>, - pub system_program: Program<'info, System>, + pub bubblegum_program: UncheckedAccount, + pub system_program: Program, } #[derive(Clone, AnchorSerialize, AnchorDeserialize)] @@ -75,7 +75,7 @@ pub struct MintParams { // MintToCollectionV1 layout in CPI order, so allow clippy's vec_init_then_push. #[allow(clippy::vec_init_then_push)] pub fn handle_mint<'info>( - context: Context<'info, MintAccountConstraints<'info>>, + context: &mut Context<'info, MintAccountConstraints<'info>>, params: MintParams, ) -> Result<()> { // Build MintToCollectionV1 instruction data @@ -85,7 +85,7 @@ pub fn handle_mint<'info>( symbol: "BURG".to_string(), uri: params.uri, creators: vec![Creator { - address: context.accounts.collection_authority.key(), + address: *context.accounts.collection_authority.address(), verified: false, share: 100, }], @@ -96,7 +96,7 @@ pub fn handle_mint<'info>( uses: None, collection: Some(Collection { verified: false, - key: context.accounts.collection_mint.key(), + key: *context.accounts.collection_mint.address(), }), token_program_version: TokenProgramVersion::Original, token_standard: Some(TokenStandard::NonFungible), @@ -109,65 +109,65 @@ pub fn handle_mint<'info>( // Build account metas matching MintToCollectionV1 instruction layout let mut accounts = Vec::with_capacity(16); accounts.push(AccountMeta::new( - context.accounts.tree_authority.key(), + *context.accounts.tree_authority.address(), false, )); accounts.push(AccountMeta::new_readonly( - context.accounts.leaf_owner.key(), + *context.accounts.leaf_owner.address(), false, )); accounts.push(AccountMeta::new_readonly( - context.accounts.leaf_delegate.key(), + *context.accounts.leaf_delegate.address(), false, )); - accounts.push(AccountMeta::new(context.accounts.merkle_tree.key(), false)); + accounts.push(AccountMeta::new(*context.accounts.merkle_tree.address(), false)); accounts.push(AccountMeta::new_readonly( - context.accounts.payer.key(), + *context.accounts.payer.address(), true, )); accounts.push(AccountMeta::new_readonly( - context.accounts.tree_delegate.key(), + *context.accounts.tree_delegate.address(), true, )); accounts.push(AccountMeta::new_readonly( - context.accounts.collection_authority.key(), + *context.accounts.collection_authority.address(), true, )); // collection_authority_record_pda - pass as-is accounts.push(AccountMeta::new_readonly( - context.accounts.collection_authority_record_pda.key(), + *context.accounts.collection_authority_record_pda.address(), false, )); accounts.push(AccountMeta::new_readonly( - context.accounts.collection_mint.key(), + *context.accounts.collection_mint.address(), false, )); accounts.push(AccountMeta::new( - context.accounts.collection_metadata.key(), + *context.accounts.collection_metadata.address(), false, )); accounts.push(AccountMeta::new_readonly( - context.accounts.edition_account.key(), + *context.accounts.edition_account.address(), false, )); accounts.push(AccountMeta::new_readonly( - context.accounts.bubblegum_signer.key(), + *context.accounts.bubblegum_signer.address(), false, )); accounts.push(AccountMeta::new_readonly( - context.accounts.log_wrapper.key(), + *context.accounts.log_wrapper.address(), false, )); accounts.push(AccountMeta::new_readonly( - context.accounts.compression_program.key(), + *context.accounts.compression_program.address(), false, )); accounts.push(AccountMeta::new_readonly( - context.accounts.token_metadata_program.key(), + *context.accounts.token_metadata_program.address(), false, )); accounts.push(AccountMeta::new_readonly( - context.accounts.system_program.key(), + *context.accounts.system_program.address(), false, )); @@ -179,26 +179,26 @@ pub fn handle_mint<'info>( // Gather all account infos for the CPI let account_infos = vec![ - context.accounts.bubblegum_program.to_account_info(), - context.accounts.tree_authority.to_account_info(), - context.accounts.leaf_owner.to_account_info(), - context.accounts.leaf_delegate.to_account_info(), - context.accounts.merkle_tree.to_account_info(), - context.accounts.payer.to_account_info(), - context.accounts.tree_delegate.to_account_info(), - context.accounts.collection_authority.to_account_info(), + context.accounts.bubblegum_program.cpi_handle_mut(), + context.accounts.tree_authority.cpi_handle_mut(), + context.accounts.leaf_owner.cpi_handle_mut(), + context.accounts.leaf_delegate.cpi_handle_mut(), + context.accounts.merkle_tree.cpi_handle_mut(), + context.accounts.payer.cpi_handle_mut(), + context.accounts.tree_delegate.cpi_handle_mut(), + context.accounts.collection_authority.cpi_handle_mut(), context .accounts .collection_authority_record_pda - .to_account_info(), - context.accounts.collection_mint.to_account_info(), - context.accounts.collection_metadata.to_account_info(), - context.accounts.edition_account.to_account_info(), - context.accounts.bubblegum_signer.to_account_info(), - context.accounts.log_wrapper.to_account_info(), - context.accounts.compression_program.to_account_info(), - context.accounts.token_metadata_program.to_account_info(), - context.accounts.system_program.to_account_info(), + .cpi_handle_mut(), + context.accounts.collection_mint.cpi_handle_mut(), + context.accounts.collection_metadata.cpi_handle_mut(), + context.accounts.edition_account.cpi_handle_mut(), + context.accounts.bubblegum_signer.cpi_handle_mut(), + context.accounts.log_wrapper.cpi_handle_mut(), + context.accounts.compression_program.cpi_handle_mut(), + context.accounts.token_metadata_program.cpi_handle_mut(), + context.accounts.system_program.cpi_handle_mut(), ]; invoke(&instruction, &account_infos)?; diff --git a/compression/cutils/anchor/programs/cutils/src/instructions/verify.rs b/compression/cutils/anchor/programs/cutils/src/instructions/verify.rs index 60d72b51a..0f64e60d4 100644 --- a/compression/cutils/anchor/programs/cutils/src/instructions/verify.rs +++ b/compression/cutils/anchor/programs/cutils/src/instructions/verify.rs @@ -4,17 +4,17 @@ use anchor_lang::solana_program::instruction::{AccountMeta, Instruction}; #[derive(Accounts)] #[instruction(params: VerifyParams)] -pub struct VerifyAccountConstraints<'info> { - pub leaf_owner: Signer<'info>, +pub struct VerifyAccountConstraints { + pub leaf_owner: Signer, /// CHECK: This account is neither written to nor read from. - pub leaf_delegate: UncheckedAccount<'info>, + pub leaf_delegate: UncheckedAccount, /// CHECK: Read by the SPL Account Compression verify_leaf CPI, which /// validates the proof against this tree's stored root. - pub merkle_tree: UncheckedAccount<'info>, + pub merkle_tree: UncheckedAccount, - pub compression_program: Program<'info, SPLCompression>, + pub compression_program: Program, } #[derive(Clone, AnchorSerialize, AnchorDeserialize)] @@ -32,14 +32,14 @@ pub struct VerifyParams { const VERIFY_LEAF_DISCRIMINATOR: [u8; 8] = [124, 220, 22, 223, 104, 10, 250, 224]; pub fn handle_verify<'info>( - context: Context<'info, VerifyAccountConstraints<'info>>, + context: &mut Context<'info, VerifyAccountConstraints<'info>>, params: &VerifyParams, ) -> Result<()> { - let asset_id = get_asset_id(&context.accounts.merkle_tree.key(), 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.key(), - &context.accounts.leaf_delegate.key(), + &context.accounts.leaf_owner.address(), + &context.accounts.leaf_delegate.address(), params.nonce, ¶ms.data_hash, ¶ms.creator_hash, @@ -49,11 +49,11 @@ pub fn handle_verify<'info>( // depends on solana-program 2.x which is incompatible with Anchor 1.0's solana 3.x // types. Once a compatible version is available, replace this with the CPI wrapper. let mut accounts = vec![AccountMeta::new_readonly( - context.accounts.merkle_tree.key(), + *context.accounts.merkle_tree.address(), false, )]; - for acc in context.remaining_accounts.iter() { - accounts.push(AccountMeta::new_readonly(acc.key(), false)); + for acc in context.remaining_accounts().iter() { + accounts.push(AccountMeta::new_readonly(acc.address(), false)); } let mut data = VERIFY_LEAF_DISCRIMINATOR.to_vec(); @@ -61,14 +61,14 @@ pub fn handle_verify<'info>( data.extend_from_slice(&leaf_hash); data.extend_from_slice(¶ms.index.to_le_bytes()); - let mut account_infos = vec![context.accounts.merkle_tree.to_account_info()]; - for acc in context.remaining_accounts.iter() { - account_infos.push(acc.to_account_info()); + let mut account_infos = vec![context.accounts.merkle_tree.cpi_handle_mut()]; + for acc in context.remaining_accounts().iter() { + account_infos.push(acc.cpi_handle_mut()); } anchor_lang::solana_program::program::invoke( &Instruction { - program_id: context.accounts.compression_program.key(), + program_id: *context.accounts.compression_program.address(), accounts, data, }, diff --git a/compression/cutils/anchor/programs/cutils/src/lib.rs b/compression/cutils/anchor/programs/cutils/src/lib.rs index dde598ed5..23e108f3c 100644 --- a/compression/cutils/anchor/programs/cutils/src/lib.rs +++ b/compression/cutils/anchor/programs/cutils/src/lib.rs @@ -10,13 +10,13 @@ pub mod bubblegum_types; use anchor_lang::prelude::*; /// SPL Account Compression program ID (cmtDvXumGCrqC1Age74AVPhSRVXJMd8PJS91L8KbNCK) -const SPL_ACCOUNT_COMPRESSION_ID: Pubkey = Pubkey::new_from_array([ +const SPL_ACCOUNT_COMPRESSION_ID: Address = Address::new_from_array([ 0x09, 0x2a, 0x13, 0xee, 0x95, 0xc4, 0x1c, 0xba, 0x08, 0xa6, 0x7f, 0x5a, 0xc6, 0x7e, 0x8d, 0xf7, 0xe1, 0xda, 0x11, 0x62, 0x5e, 0x1d, 0x64, 0x13, 0x7f, 0x8f, 0x4f, 0x23, 0x83, 0x03, 0x7f, 0x14, ]); /// mpl-bubblegum program ID (BGUMAp9Gq7iTEuizy4pqaxsTyUCBK68MDfK752saRPUY) -const MPL_BUBBLEGUM_ID: Pubkey = Pubkey::new_from_array([ +const MPL_BUBBLEGUM_ID: Address = Address::new_from_array([ 0x98, 0x8b, 0x80, 0xeb, 0x79, 0x35, 0x28, 0x69, 0xb2, 0x24, 0x74, 0x5f, 0x59, 0xdd, 0xbf, 0x8a, 0x26, 0x58, 0xca, 0x13, 0xdc, 0x68, 0x81, 0x21, 0x26, 0x35, 0x1c, 0xae, 0x07, 0xc1, 0xa5, 0xa5, ]); @@ -25,7 +25,7 @@ const MPL_BUBBLEGUM_ID: Pubkey = Pubkey::new_from_array([ pub struct SPLCompression; impl anchor_lang::Id for SPLCompression { - fn id() -> Pubkey { + fn id() -> Address { SPL_ACCOUNT_COMPRESSION_ID } } @@ -37,14 +37,14 @@ pub mod cutils { use super::*; pub fn mint<'info>( - context: Context<'info, MintAccountConstraints<'info>>, + context: &mut Context<'info, MintAccountConstraints<'info>>, params: MintParams, ) -> Result<()> { instructions::mint::handle_mint(context, params) } pub fn verify<'info>( - context: Context<'info, VerifyAccountConstraints<'info>>, + context: &mut Context<'info, VerifyAccountConstraints<'info>>, params: VerifyParams, ) -> Result<()> { instructions::verify::handle_verify(context, ¶ms) diff --git a/finance/betting-market/anchor/programs/betting-market/Cargo.toml b/finance/betting-market/anchor/programs/betting-market/Cargo.toml index a30d1ac15..9acadab10 100644 --- a/finance/betting-market/anchor/programs/betting-market/Cargo.toml +++ b/finance/betting-market/anchor/programs/betting-market/Cargo.toml @@ -14,15 +14,23 @@ cpi = ["no-entrypoint"] no-entrypoint = [] no-idl = [] no-log-ix-name = [] -idl-build = ["anchor-lang/idl-build", "anchor-spl/idl-build"] +idl-build = ["anchor-lang/idl-build"] anchor-debug = [] custom-heap = [] custom-panic = [] [dependencies] # init-if-needed: place_bet and the lazy User index create accounts only on first use. -anchor-lang = { version = "1.1.2", features = ["init-if-needed"] } -anchor-spl = "1.1.2" +anchor-lang = "2.0.0-rc.1" +# The `#[program]` macro expands to `wincode` paths for instruction-data +# (de)serialization, so the crate has to be a direct dependency. +wincode = { version = "0.5", features = ["derive"] } +# anchor-lang 2.0.0-rc.1 is built against wincode 0.5, but solana-address 2.7 +# moved to wincode 0.6. With both in the graph, `Address`'s wincode impls +# belong to the version the account derives are not using and every +# `SchemaRead`/`SchemaWrite` bound fails. 2.6.1 is the last 0.5-line release. +solana-address = ">=2.6, <2.7" +anchor-spl = "2.0.0-rc.1" [dev-dependencies] # no-entrypoint: solana-kite pulls these SPL program crates into the host test diff --git a/finance/betting-market/anchor/programs/betting-market/src/instructions/add_outcome.rs b/finance/betting-market/anchor/programs/betting-market/src/instructions/add_outcome.rs index 0095688cd..1b8ae826d 100644 --- a/finance/betting-market/anchor/programs/betting-market/src/instructions/add_outcome.rs +++ b/finance/betting-market/anchor/programs/betting-market/src/instructions/add_outcome.rs @@ -5,37 +5,37 @@ use crate::{error::BettingError, Config, Event, EventStatus, Outcome}; pub const MAX_LABEL_LEN: usize = 64; #[derive(Accounts)] -pub struct AddOutcomeAccountConstraints<'info> { +pub struct AddOutcomeAccountConstraints { #[account(mut)] - pub admin: Signer<'info>, + pub admin: Signer, #[account( seeds = [b"config"], bump = config.bump, has_one = admin @ BettingError::Unauthorized, )] - pub config: Account<'info, Config>, + pub config: BorshAccount, #[account( mut, seeds = [b"event", event.event_id.to_le_bytes().as_ref()], bump = event.bump, )] - pub event: Account<'info, Event>, + pub event: BorshAccount, #[account( init, payer = admin, space = Outcome::DISCRIMINATOR.len() + Outcome::INIT_SPACE, - seeds = [b"outcome", event.key().as_ref(), &[event.outcome_count]], + seeds = [b"outcome", event.address().as_ref(), &[event.outcome_count]], bump )] - pub outcome: Account<'info, Outcome>, + pub outcome: BorshAccount, - pub system_program: Program<'info, System>, + pub system_program: Program, } -pub fn handle_add_outcome(context: Context, label: String) -> Result<()> { +pub fn handle_add_outcome(context: &mut Context, label: String) -> Result<()> { require!(label.len() <= MAX_LABEL_LEN, BettingError::LabelTooLong); require!( context.accounts.event.status == EventStatus::Open, @@ -49,8 +49,8 @@ pub fn handle_add_outcome(context: Context, label: ); let index = context.accounts.event.outcome_count; - context.accounts.outcome.set_inner(Outcome { - event: context.accounts.event.key(), + *context.accounts.outcome = (Outcome { + event: context.accounts.event.address(), index, label, total_amount: 0, diff --git a/finance/betting-market/anchor/programs/betting-market/src/instructions/cancel_event.rs b/finance/betting-market/anchor/programs/betting-market/src/instructions/cancel_event.rs index 2bec20497..e352aef15 100644 --- a/finance/betting-market/anchor/programs/betting-market/src/instructions/cancel_event.rs +++ b/finance/betting-market/anchor/programs/betting-market/src/instructions/cancel_event.rs @@ -5,25 +5,25 @@ use crate::{error::BettingError, Config, Event, EventStatus}; // Abandon an event that can't be resolved (e.g. the real-world result is void). // Bettors then reclaim their exact stakes via `claim_refund`; no fee is taken. #[derive(Accounts)] -pub struct CancelEventAccountConstraints<'info> { - pub admin: Signer<'info>, +pub struct CancelEventAccountConstraints { + pub admin: Signer, #[account( seeds = [b"config"], bump = config.bump, has_one = admin @ BettingError::Unauthorized, )] - pub config: Account<'info, Config>, + pub config: BorshAccount, #[account( mut, seeds = [b"event", event.event_id.to_le_bytes().as_ref()], bump = event.bump, )] - pub event: Account<'info, Event>, + pub event: BorshAccount, } -pub fn handle_cancel_event(context: Context) -> Result<()> { +pub fn handle_cancel_event(context: &mut Context) -> Result<()> { require!( context.accounts.event.status == EventStatus::Open, BettingError::EventNotOpen diff --git a/finance/betting-market/anchor/programs/betting-market/src/instructions/claim_refund.rs b/finance/betting-market/anchor/programs/betting-market/src/instructions/claim_refund.rs index c922d9cc6..13c780ee9 100644 --- a/finance/betting-market/anchor/programs/betting-market/src/instructions/claim_refund.rs +++ b/finance/betting-market/anchor/programs/betting-market/src/instructions/claim_refund.rs @@ -1,4 +1,5 @@ use anchor_lang::prelude::*; +use anchor_spl::mint; use anchor_spl::token_interface::{Mint, TokenAccount, TokenInterface}; use crate::{error::BettingError, Bet, Event, EventStatus, User}; @@ -6,18 +7,18 @@ use crate::{error::BettingError, Bet, Event, EventStatus, User}; use super::transfer_tokens_from_vault; #[derive(Accounts)] -pub struct ClaimRefundAccountConstraints<'info> { +pub struct ClaimRefundAccountConstraints { #[account(mut)] - pub bettor: Signer<'info>, + pub bettor: Signer, #[account(mint::token_program = token_program)] - pub token_mint: InterfaceAccount<'info, Mint>, + pub token_mint: InterfaceAccount, #[account( seeds = [b"event", event.event_id.to_le_bytes().as_ref()], bump = event.bump, )] - pub event: Account<'info, Event>, + pub event: BorshAccount, // Closing the Bet ends the position: the rent goes back to the bettor and // a second refund fails because the account no longer exists. @@ -26,17 +27,17 @@ pub struct ClaimRefundAccountConstraints<'info> { close = bettor, has_one = bettor, has_one = event, - seeds = [b"bet", bet.outcome.as_ref(), bettor.key().as_ref()], + seeds = [b"bet", bet.outcome.as_ref(), bettor.address().as_ref()], bump = bet.bump, )] - pub bet: Account<'info, Bet>, + pub bet: BorshAccount, #[account( mut, - seeds = [b"user", bettor.key().as_ref()], + seeds = [b"user", bettor.address().as_ref()], bump = user.bump, )] - pub user: Account<'info, User>, + pub user: BorshAccount, #[account( mut, @@ -44,7 +45,7 @@ pub struct ClaimRefundAccountConstraints<'info> { associated_token::authority = bettor, associated_token::token_program = token_program, )] - pub bettor_token_account: InterfaceAccount<'info, TokenAccount>, + pub bettor_token_account: InterfaceAccount, #[account( mut, @@ -52,12 +53,12 @@ pub struct ClaimRefundAccountConstraints<'info> { associated_token::authority = event, associated_token::token_program = token_program, )] - pub vault: InterfaceAccount<'info, TokenAccount>, + pub vault: InterfaceAccount, - pub token_program: Interface<'info, TokenInterface>, + pub token_program: Interface<'static, TokenInterface>, } -pub fn handle_claim_refund(context: Context) -> Result<()> { +pub fn handle_claim_refund(context: &mut Context) -> Result<()> { require!( context.accounts.event.status == EventStatus::Cancelled, BettingError::EventNotCancelled @@ -68,7 +69,7 @@ pub fn handle_claim_refund(context: Context) -> R // The position is over, so drop the Bet from the bettor's index before the // transfer (effects before interactions); the Bet account itself closes // when the instruction finishes. - let bet_key = context.accounts.bet.key(); + let bet_key = context.accounts.bet.address(); context.accounts.user.remove_bet(&bet_key)?; let event_id = context.accounts.event.event_id; @@ -78,7 +79,7 @@ pub fn handle_claim_refund(context: Context) -> R &context.accounts.bettor_token_account, stake, &context.accounts.token_mint, - &context.accounts.event.to_account_info(), + &context.accounts.event.cpi_handle_mut(), &context.accounts.token_program, event_id, event_bump, diff --git a/finance/betting-market/anchor/programs/betting-market/src/instructions/claim_winnings.rs b/finance/betting-market/anchor/programs/betting-market/src/instructions/claim_winnings.rs index 912945d0d..7a78e3ef3 100644 --- a/finance/betting-market/anchor/programs/betting-market/src/instructions/claim_winnings.rs +++ b/finance/betting-market/anchor/programs/betting-market/src/instructions/claim_winnings.rs @@ -1,4 +1,5 @@ use anchor_lang::prelude::*; +use anchor_spl::mint; use anchor_spl::token_interface::{Mint, TokenAccount, TokenInterface}; use crate::{error::BettingError, Bet, Event, EventStatus, User}; @@ -6,18 +7,18 @@ use crate::{error::BettingError, Bet, Event, EventStatus, User}; use super::transfer_tokens_from_vault; #[derive(Accounts)] -pub struct ClaimWinningsAccountConstraints<'info> { +pub struct ClaimWinningsAccountConstraints { #[account(mut)] - pub bettor: Signer<'info>, + pub bettor: Signer, #[account(mint::token_program = token_program)] - pub token_mint: InterfaceAccount<'info, Mint>, + pub token_mint: InterfaceAccount, #[account( seeds = [b"event", event.event_id.to_le_bytes().as_ref()], bump = event.bump, )] - pub event: Account<'info, Event>, + pub event: BorshAccount, // Closing the Bet ends the position: the rent goes back to the bettor and // a second claim fails because the account no longer exists. @@ -26,17 +27,17 @@ pub struct ClaimWinningsAccountConstraints<'info> { close = bettor, has_one = bettor, has_one = event, - seeds = [b"bet", bet.outcome.as_ref(), bettor.key().as_ref()], + seeds = [b"bet", bet.outcome.as_ref(), bettor.address().as_ref()], bump = bet.bump, )] - pub bet: Account<'info, Bet>, + pub bet: BorshAccount, #[account( mut, - seeds = [b"user", bettor.key().as_ref()], + seeds = [b"user", bettor.address().as_ref()], bump = user.bump, )] - pub user: Account<'info, User>, + pub user: BorshAccount, #[account( mut, @@ -44,7 +45,7 @@ pub struct ClaimWinningsAccountConstraints<'info> { associated_token::authority = bettor, associated_token::token_program = token_program, )] - pub bettor_token_account: InterfaceAccount<'info, TokenAccount>, + pub bettor_token_account: InterfaceAccount, #[account( mut, @@ -52,12 +53,12 @@ pub struct ClaimWinningsAccountConstraints<'info> { associated_token::authority = event, associated_token::token_program = token_program, )] - pub vault: InterfaceAccount<'info, TokenAccount>, + pub vault: InterfaceAccount, - pub token_program: Interface<'info, TokenInterface>, + pub token_program: Interface<'static, TokenInterface>, } -pub fn handle_claim_winnings(context: Context) -> Result<()> { +pub fn handle_claim_winnings(context: &mut Context) -> Result<()> { require!( context.accounts.event.status == EventStatus::Settled, BettingError::EventNotSettled @@ -93,7 +94,7 @@ pub fn handle_claim_winnings(context: Context) // The position is over, so drop the Bet from the bettor's index before the // transfer (effects before interactions); the Bet account itself closes // when the instruction finishes. - let bet_key = context.accounts.bet.key(); + let bet_key = context.accounts.bet.address(); context.accounts.user.remove_bet(&bet_key)?; let event_id = context.accounts.event.event_id; @@ -103,7 +104,7 @@ pub fn handle_claim_winnings(context: Context) &context.accounts.bettor_token_account, payout, &context.accounts.token_mint, - &context.accounts.event.to_account_info(), + &context.accounts.event.cpi_handle_mut(), &context.accounts.token_program, event_id, event_bump, diff --git a/finance/betting-market/anchor/programs/betting-market/src/instructions/close_losing_bet.rs b/finance/betting-market/anchor/programs/betting-market/src/instructions/close_losing_bet.rs index ed533dddc..79b158803 100644 --- a/finance/betting-market/anchor/programs/betting-market/src/instructions/close_losing_bet.rs +++ b/finance/betting-market/anchor/programs/betting-market/src/instructions/close_losing_bet.rs @@ -7,35 +7,35 @@ use crate::{error::BettingError, Bet, Event, EventStatus, User}; // open a new position) and returns the rent. Winning bets must go through // claim_winnings instead, which also pays out the stake and winnings. #[derive(Accounts)] -pub struct CloseLosingBetAccountConstraints<'info> { +pub struct CloseLosingBetAccountConstraints { #[account(mut)] - pub bettor: Signer<'info>, + pub bettor: Signer, #[account( seeds = [b"event", event.event_id.to_le_bytes().as_ref()], bump = event.bump, )] - pub event: Account<'info, Event>, + pub event: BorshAccount, #[account( mut, close = bettor, has_one = bettor, has_one = event, - seeds = [b"bet", bet.outcome.as_ref(), bettor.key().as_ref()], + seeds = [b"bet", bet.outcome.as_ref(), bettor.address().as_ref()], bump = bet.bump, )] - pub bet: Account<'info, Bet>, + pub bet: BorshAccount, #[account( mut, - seeds = [b"user", bettor.key().as_ref()], + seeds = [b"user", bettor.address().as_ref()], bump = user.bump, )] - pub user: Account<'info, User>, + pub user: BorshAccount, } -pub fn handle_close_losing_bet(context: Context) -> Result<()> { +pub fn handle_close_losing_bet(context: &mut Context) -> Result<()> { require!( context.accounts.event.status == EventStatus::Settled, BettingError::EventNotSettled @@ -45,7 +45,7 @@ pub fn handle_close_losing_bet(context: Context { +pub struct InitializeConfigAccountConstraints { #[account(mut)] - pub admin: Signer<'info>, + pub admin: Signer, #[account(mint::token_program = token_program)] - pub token_mint: InterfaceAccount<'info, Mint>, + pub token_mint: InterfaceAccount, #[account( init, @@ -20,22 +21,22 @@ pub struct InitializeConfigAccountConstraints<'info> { seeds = [b"config"], bump )] - pub config: Account<'info, Config>, + pub config: BorshAccount, - pub token_program: Interface<'info, TokenInterface>, - pub system_program: Program<'info, System>, + pub token_program: Interface<'static, TokenInterface>, + pub system_program: Program, } pub fn handle_initialize_config( - context: Context, + context: &mut Context, fee_bps: u16, - fee_recipient: Pubkey, + fee_recipient: Address, ) -> Result<()> { require!(fee_bps <= MAX_FEE_BPS, BettingError::FeeTooHigh); - context.accounts.config.set_inner(Config { - admin: context.accounts.admin.key(), - token_mint: context.accounts.token_mint.key(), + *context.accounts.config = (Config { + admin: *context.accounts.admin.address(), + token_mint: *context.accounts.token_mint.address(), fee_recipient, fee_bps, event_count: 0, diff --git a/finance/betting-market/anchor/programs/betting-market/src/instructions/initialize_event.rs b/finance/betting-market/anchor/programs/betting-market/src/instructions/initialize_event.rs index 1e90e885a..8251889a3 100644 --- a/finance/betting-market/anchor/programs/betting-market/src/instructions/initialize_event.rs +++ b/finance/betting-market/anchor/programs/betting-market/src/instructions/initialize_event.rs @@ -1,4 +1,5 @@ use anchor_lang::prelude::*; +use anchor_spl::mint; use anchor_spl::{ associated_token::AssociatedToken, token_interface::{Mint, TokenAccount, TokenInterface}, @@ -10,9 +11,9 @@ pub const MAX_DESCRIPTION_LEN: usize = 200; #[derive(Accounts)] #[instruction(event_id: u64)] -pub struct InitializeEventAccountConstraints<'info> { +pub struct InitializeEventAccountConstraints { #[account(mut)] - pub admin: Signer<'info>, + pub admin: Signer, #[account( mut, @@ -21,10 +22,10 @@ pub struct InitializeEventAccountConstraints<'info> { has_one = admin @ BettingError::Unauthorized, has_one = token_mint, )] - pub config: Account<'info, Config>, + pub config: BorshAccount, #[account(mint::token_program = token_program)] - pub token_mint: InterfaceAccount<'info, Mint>, + pub token_mint: InterfaceAccount, #[account( init, @@ -33,7 +34,7 @@ pub struct InitializeEventAccountConstraints<'info> { seeds = [b"event", event_id.to_le_bytes().as_ref()], bump )] - pub event: Account<'info, Event>, + pub event: BorshAccount, // The single pool for the whole market: an ATA owned by the Event PDA. #[account( @@ -43,15 +44,15 @@ pub struct InitializeEventAccountConstraints<'info> { associated_token::authority = event, associated_token::token_program = token_program )] - pub vault: InterfaceAccount<'info, TokenAccount>, + pub vault: InterfaceAccount, - pub associated_token_program: Program<'info, AssociatedToken>, - pub token_program: Interface<'info, TokenInterface>, - pub system_program: Program<'info, System>, + pub associated_token_program: Program, + pub token_program: Interface<'static, TokenInterface>, + pub system_program: Program, } pub fn handle_initialize_event( - context: Context, + context: &mut Context, event_id: u64, description: String, ) -> Result<()> { @@ -60,7 +61,7 @@ pub fn handle_initialize_event( BettingError::DescriptionTooLong ); - context.accounts.event.set_inner(Event { + *context.accounts.event = (Event { event_id, description, outcome_count: 0, diff --git a/finance/betting-market/anchor/programs/betting-market/src/instructions/place_bet.rs b/finance/betting-market/anchor/programs/betting-market/src/instructions/place_bet.rs index b7043e6f0..e51e31055 100644 --- a/finance/betting-market/anchor/programs/betting-market/src/instructions/place_bet.rs +++ b/finance/betting-market/anchor/programs/betting-market/src/instructions/place_bet.rs @@ -1,4 +1,5 @@ use anchor_lang::prelude::*; +use anchor_spl::mint; use anchor_spl::{ associated_token::AssociatedToken, token_interface::{Mint, TokenAccount, TokenInterface}, @@ -11,34 +12,34 @@ use crate::{ use super::transfer_tokens_to_vault; #[derive(Accounts)] -pub struct PlaceBetAccountConstraints<'info> { +pub struct PlaceBetAccountConstraints { #[account(mut)] - pub bettor: Signer<'info>, + pub bettor: Signer, #[account( seeds = [b"config"], bump = config.bump, has_one = token_mint, )] - pub config: Account<'info, Config>, + pub config: BorshAccount, #[account(mint::token_program = token_program)] - pub token_mint: Box>, + pub token_mint: Box>, #[account( mut, seeds = [b"event", event.event_id.to_le_bytes().as_ref()], bump = event.bump, )] - pub event: Box>, + pub event: Box>, #[account( mut, has_one = event, - seeds = [b"outcome", event.key().as_ref(), &[outcome.index]], + seeds = [b"outcome", event.address().as_ref(), &[outcome.index]], bump = outcome.bump, )] - pub outcome: Box>, + pub outcome: Box>, #[account( mut, @@ -46,7 +47,7 @@ pub struct PlaceBetAccountConstraints<'info> { associated_token::authority = bettor, associated_token::token_program = token_program, )] - pub bettor_token_account: Box>, + pub bettor_token_account: Box>, #[account( mut, @@ -54,32 +55,32 @@ pub struct PlaceBetAccountConstraints<'info> { associated_token::authority = event, associated_token::token_program = token_program, )] - pub vault: Box>, + pub vault: Box>, #[account( init_if_needed, payer = bettor, space = Bet::DISCRIMINATOR.len() + Bet::INIT_SPACE, - seeds = [b"bet", outcome.key().as_ref(), bettor.key().as_ref()], + seeds = [b"bet", outcome.address().as_ref(), bettor.address().as_ref()], bump )] - pub bet: Box>, + pub bet: Box>, #[account( init_if_needed, payer = bettor, space = User::DISCRIMINATOR.len() + User::INIT_SPACE, - seeds = [b"user", bettor.key().as_ref()], + seeds = [b"user", bettor.address().as_ref()], bump )] - pub user: Box>, + pub user: Box>, - pub associated_token_program: Program<'info, AssociatedToken>, - pub token_program: Interface<'info, TokenInterface>, - pub system_program: Program<'info, System>, + pub associated_token_program: Program, + pub token_program: Interface<'static, TokenInterface>, + pub system_program: Program, } -pub fn handle_place_bet(context: Context, amount: u64) -> Result<()> { +pub fn handle_place_bet(context: &mut Context, amount: u64) -> Result<()> { require!(amount > 0, BettingError::ZeroAmount); require!( context.accounts.event.status == EventStatus::Open, @@ -95,11 +96,11 @@ pub fn handle_place_bet(context: Context, amount: u6 &context.accounts.token_program, )?; - let bettor_key = context.accounts.bettor.key(); - let event_key = context.accounts.event.key(); - let outcome_key = context.accounts.outcome.key(); + let bettor_key = context.accounts.bettor.address(); + let event_key = context.accounts.event.address(); + let outcome_key = context.accounts.outcome.address(); let outcome_index = context.accounts.outcome.index; - let bet_key = context.accounts.bet.key(); + let bet_key = context.accounts.bet.address(); let bet_bump = context.bumps.bet; let user_bump = context.bumps.user; @@ -108,9 +109,9 @@ pub fn handle_place_bet(context: Context, amount: u6 // on this outcome from a top-up, and it gates the per-outcome bookkeeping. let is_new_bet = bet.amount == 0; if is_new_bet { - bet.bettor = bettor_key; + bet.bettor = *bettor_key; bet.event = event_key; - bet.outcome = outcome_key; + bet.outcome = *outcome_key; bet.outcome_index = outcome_index; bet.bump = bet_bump; } @@ -138,8 +139,8 @@ pub fn handle_place_bet(context: Context, amount: u6 .ok_or(BettingError::MathOverflow)?; let user = &mut context.accounts.user; - if user.authority == Pubkey::default() { - user.authority = bettor_key; + if user.authority == Address::default() { + user.authority = *bettor_key; user.bump = user_bump; } if is_new_bet { @@ -147,7 +148,7 @@ pub fn handle_place_bet(context: Context, amount: u6 user.bets.len() < MAX_BETS_PER_USER, BettingError::TooManyBets ); - user.bets.push(bet_key); + user.bets.push(*bet_key); } Ok(()) diff --git a/finance/betting-market/anchor/programs/betting-market/src/instructions/settle_event.rs b/finance/betting-market/anchor/programs/betting-market/src/instructions/settle_event.rs index debfae8e7..aa80b73c2 100644 --- a/finance/betting-market/anchor/programs/betting-market/src/instructions/settle_event.rs +++ b/finance/betting-market/anchor/programs/betting-market/src/instructions/settle_event.rs @@ -1,4 +1,5 @@ use anchor_lang::prelude::*; +use anchor_spl::mint; use anchor_spl::{ associated_token::AssociatedToken, token_interface::{Mint, TokenAccount, TokenInterface}, @@ -12,9 +13,9 @@ const BPS_DENOMINATOR: u128 = 10_000; #[derive(Accounts)] #[instruction(winning_outcome_index: u8)] -pub struct SettleEventAccountConstraints<'info> { +pub struct SettleEventAccountConstraints { #[account(mut)] - pub admin: Signer<'info>, + pub admin: Signer, #[account( seeds = [b"config"], @@ -23,24 +24,24 @@ pub struct SettleEventAccountConstraints<'info> { has_one = token_mint, has_one = fee_recipient, )] - pub config: Account<'info, Config>, + pub config: BorshAccount, #[account(mint::token_program = token_program)] - pub token_mint: InterfaceAccount<'info, Mint>, + pub token_mint: InterfaceAccount, #[account( mut, seeds = [b"event", event.event_id.to_le_bytes().as_ref()], bump = event.bump, )] - pub event: Account<'info, Event>, + pub event: BorshAccount, #[account( has_one = event, - seeds = [b"outcome", event.key().as_ref(), &[winning_outcome_index]], + seeds = [b"outcome", event.address().as_ref(), &[winning_outcome_index]], bump = winning_outcome.bump, )] - pub winning_outcome: Account<'info, Outcome>, + pub winning_outcome: BorshAccount, #[account( mut, @@ -48,10 +49,10 @@ pub struct SettleEventAccountConstraints<'info> { associated_token::authority = event, associated_token::token_program = token_program, )] - pub vault: InterfaceAccount<'info, TokenAccount>, + pub vault: InterfaceAccount, /// CHECK: validated against config.fee_recipient by the `has_one` above. - pub fee_recipient: UncheckedAccount<'info>, + pub fee_recipient: UncheckedAccount, #[account( init_if_needed, @@ -60,15 +61,15 @@ pub struct SettleEventAccountConstraints<'info> { associated_token::authority = fee_recipient, associated_token::token_program = token_program, )] - pub fee_recipient_token_account: InterfaceAccount<'info, TokenAccount>, + pub fee_recipient_token_account: InterfaceAccount, - pub associated_token_program: Program<'info, AssociatedToken>, - pub token_program: Interface<'info, TokenInterface>, - pub system_program: Program<'info, System>, + pub associated_token_program: Program, + pub token_program: Interface<'static, TokenInterface>, + pub system_program: Program, } pub fn handle_settle_event( - context: Context, + context: &mut Context, winning_outcome_index: u8, ) -> Result<()> { require!( @@ -97,7 +98,7 @@ pub fn handle_settle_event( &context.accounts.fee_recipient_token_account, fee, &context.accounts.token_mint, - &context.accounts.event.to_account_info(), + &context.accounts.event.cpi_handle_mut(), &context.accounts.token_program, event_id, event_bump, diff --git a/finance/betting-market/anchor/programs/betting-market/src/instructions/shared.rs b/finance/betting-market/anchor/programs/betting-market/src/instructions/shared.rs index 8a6668912..beb15450c 100644 --- a/finance/betting-market/anchor/programs/betting-market/src/instructions/shared.rs +++ b/finance/betting-market/anchor/programs/betting-market/src/instructions/shared.rs @@ -7,32 +7,32 @@ use anchor_spl::token_interface::{ // Move tokens from a wallet-owned account into the vault. The authority is a // plain Signer (the bettor), so no PDA seeds are needed. pub fn transfer_tokens_to_vault<'info>( - from: &InterfaceAccount<'info, TokenAccount>, - to: &InterfaceAccount<'info, TokenAccount>, + from: &InterfaceAccount, + to: &InterfaceAccount, amount: u64, - mint: &InterfaceAccount<'info, Mint>, - authority: &Signer<'info>, - token_program: &Interface<'info, TokenInterface>, + mint: &InterfaceAccount, + authority: &Signer, + token_program: &Interface<'static, TokenInterface>, ) -> Result<()> { let transfer_accounts = TransferChecked { - from: from.to_account_info(), - mint: mint.to_account_info(), - to: to.to_account_info(), - authority: authority.to_account_info(), + from: from.cpi_handle_mut(), + mint: mint.cpi_handle(), + to: to.cpi_handle_mut(), + authority: authority.cpi_handle(), }; - let cpi_context = CpiContext::new(token_program.key(), transfer_accounts); - transfer_checked(cpi_context, amount, mint.decimals) + let cpi_context = CpiContext::new(token_program.address(), transfer_accounts); + transfer_checked(cpi_context, amount, mint.decimals()) } // 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<'info>( - vault: &InterfaceAccount<'info, TokenAccount>, - to: &InterfaceAccount<'info, TokenAccount>, + vault: &InterfaceAccount, + to: &InterfaceAccount, amount: u64, - mint: &InterfaceAccount<'info, Mint>, - event: &AccountInfo<'info>, - token_program: &Interface<'info, TokenInterface>, + mint: &InterfaceAccount, + event: &AccountView, + token_program: &Interface<'static, TokenInterface>, event_id: u64, event_bump: u8, ) -> Result<()> { @@ -41,15 +41,15 @@ pub fn transfer_tokens_from_vault<'info>( let signer_seeds = [&seeds[..]]; let transfer_accounts = TransferChecked { - from: vault.to_account_info(), - mint: mint.to_account_info(), - to: to.to_account_info(), + from: vault.cpi_handle_mut(), + mint: mint.cpi_handle(), + to: to.cpi_handle_mut(), authority: event.clone(), }; let cpi_context = CpiContext::new_with_signer( - token_program.key(), + token_program.address(), transfer_accounts, &signer_seeds, ); - transfer_checked(cpi_context, amount, mint.decimals) + transfer_checked(cpi_context, amount, mint.decimals()) } diff --git a/finance/betting-market/anchor/programs/betting-market/src/lib.rs b/finance/betting-market/anchor/programs/betting-market/src/lib.rs index 731546e4c..0ca1e3f7b 100644 --- a/finance/betting-market/anchor/programs/betting-market/src/lib.rs +++ b/finance/betting-market/anchor/programs/betting-market/src/lib.rs @@ -16,16 +16,16 @@ pub mod betting_market { // One-time setup: the signer becomes the admin and fixes the stake token and // the settlement fee (basis points) for every market in this deployment. pub fn initialize_config( - context: Context, + context: &mut Context, fee_bps: u16, - fee_recipient: Pubkey, + fee_recipient: Address, ) -> Result<()> { instructions::initialize_config::handle_initialize_config(context, fee_bps, fee_recipient) } // Admin opens a new market and creates its pool vault. pub fn initialize_event( - context: Context, + context: &mut Context, event_id: u64, description: String, ) -> Result<()> { @@ -33,41 +33,41 @@ pub mod betting_market { } // Admin adds a possible result. Only allowed before betting starts. - pub fn add_outcome(context: Context, label: String) -> Result<()> { + pub fn add_outcome(context: &mut Context, label: String) -> Result<()> { instructions::add_outcome::handle_add_outcome(context, label) } // A bettor stakes tokens on one outcome. The stake joins the event's pool. - pub fn place_bet(context: Context, amount: u64) -> Result<()> { + pub fn place_bet(context: &mut Context, amount: u64) -> Result<()> { instructions::place_bet::handle_place_bet(context, amount) } // Admin resolves the market: takes the fee from the losing pool and records // the figures winners need to claim their share. - pub fn settle_event(context: Context, winning_outcome_index: u8) -> Result<()> { + pub fn settle_event(context: &mut Context, winning_outcome_index: u8) -> Result<()> { instructions::settle_event::handle_settle_event(context, winning_outcome_index) } // A winner withdraws their stake plus their pro-rata share of the losing // pool. The Bet account closes and leaves the bettor's User index. - pub fn claim_winnings(context: Context) -> Result<()> { + pub fn claim_winnings(context: &mut Context) -> Result<()> { instructions::claim_winnings::handle_claim_winnings(context) } // A loser closes their worthless bet after settlement, reclaiming the // Bet account's rent and freeing the slot in their User index. - pub fn close_losing_bet(context: Context) -> Result<()> { + pub fn close_losing_bet(context: &mut Context) -> Result<()> { instructions::close_losing_bet::handle_close_losing_bet(context) } // Admin voids an unresolved market so bettors can be made whole. - pub fn cancel_event(context: Context) -> Result<()> { + pub fn cancel_event(context: &mut Context) -> Result<()> { instructions::cancel_event::handle_cancel_event(context) } // After a cancellation, a bettor reclaims their exact stake. The Bet // account closes and leaves the bettor's User index. - pub fn claim_refund(context: Context) -> Result<()> { + pub fn claim_refund(context: &mut Context) -> Result<()> { instructions::claim_refund::handle_claim_refund(context) } } diff --git a/finance/betting-market/anchor/programs/betting-market/src/state/bet.rs b/finance/betting-market/anchor/programs/betting-market/src/state/bet.rs index 65330d78f..1b19414cd 100644 --- a/finance/betting-market/anchor/programs/betting-market/src/state/bet.rs +++ b/finance/betting-market/anchor/programs/betting-market/src/state/bet.rs @@ -5,12 +5,12 @@ use anchor_lang::prelude::*; // one Bet per (outcome, bettor). The account lives only while the position is // open: it closes (rent back to the bettor) on claim_winnings, claim_refund, // or close_losing_bet, which is also what prevents double claims. -#[account] +#[account(borsh)] #[derive(InitSpace)] pub struct Bet { - pub bettor: Pubkey, - pub event: Pubkey, - pub outcome: Pubkey, + pub bettor: Address, + pub event: Address, + pub outcome: Address, pub outcome_index: u8, pub amount: u64, pub bump: u8, diff --git a/finance/betting-market/anchor/programs/betting-market/src/state/config.rs b/finance/betting-market/anchor/programs/betting-market/src/state/config.rs index 03cbcfb18..a417c5528 100644 --- a/finance/betting-market/anchor/programs/betting-market/src/state/config.rs +++ b/finance/betting-market/anchor/programs/betting-market/src/state/config.rs @@ -3,12 +3,12 @@ use anchor_lang::prelude::*; // The global, single Config account. Its `admin` is the only key allowed to // create events, add outcomes, settle, and cancel. `token_mint` fixes the one // asset every market in this deployment accepts as a stake. -#[account] +#[account(borsh)] #[derive(InitSpace)] pub struct Config { - pub admin: Pubkey, - pub token_mint: Pubkey, - pub fee_recipient: Pubkey, + pub admin: Address, + pub token_mint: Address, + pub fee_recipient: Address, // Protocol fee, in basis points, taken from the losing pool at settlement. pub fee_bps: u16, pub event_count: u64, diff --git a/finance/betting-market/anchor/programs/betting-market/src/state/event.rs b/finance/betting-market/anchor/programs/betting-market/src/state/event.rs index b88e89180..f0319c648 100644 --- a/finance/betting-market/anchor/programs/betting-market/src/state/event.rs +++ b/finance/betting-market/anchor/programs/betting-market/src/state/event.rs @@ -13,7 +13,7 @@ pub enum EventStatus { // One betting market. All stakes across every outcome live in a single vault // token account whose authority is this Event PDA, so the program signs payouts // with the event's seeds. -#[account] +#[account(borsh)] #[derive(InitSpace)] pub struct Event { pub event_id: u64, diff --git a/finance/betting-market/anchor/programs/betting-market/src/state/outcome.rs b/finance/betting-market/anchor/programs/betting-market/src/state/outcome.rs index de817e540..51f18d204 100644 --- a/finance/betting-market/anchor/programs/betting-market/src/state/outcome.rs +++ b/finance/betting-market/anchor/programs/betting-market/src/state/outcome.rs @@ -3,10 +3,10 @@ use anchor_lang::prelude::*; // One possible result of an event (e.g. "Yes", "Team A wins"). `total_amount` // is this outcome's share of the pool and is the denominator for pro-rata // payouts when this outcome wins. -#[account] +#[account(borsh)] #[derive(InitSpace)] pub struct Outcome { - pub event: Pubkey, + pub event: Address, pub index: u8, #[max_len(64)] pub label: String, diff --git a/finance/betting-market/anchor/programs/betting-market/src/state/user.rs b/finance/betting-market/anchor/programs/betting-market/src/state/user.rs index 44e41b0f8..afa3cd3f4 100644 --- a/finance/betting-market/anchor/programs/betting-market/src/state/user.rs +++ b/finance/betting-market/anchor/programs/betting-market/src/state/user.rs @@ -14,19 +14,19 @@ pub const MAX_BETS_PER_USER: usize = 32; // authoritative stake state lives in the Bet accounts; this is a convenience // index. Entries are added by place_bet and removed whenever the Bet account // closes. -#[account] +#[account(borsh)] #[derive(InitSpace)] pub struct User { - pub authority: Pubkey, + pub authority: Address, #[max_len(MAX_BETS_PER_USER)] - pub bets: Vec, + pub bets: Vec
, pub bump: u8, } impl User { // Drop a closed Bet's entry from the index. Order is not meaningful, so a // swap_remove (move the last entry into the gap) is the cheapest removal. - pub fn remove_bet(&mut self, bet_key: &Pubkey) -> Result<()> { + pub fn remove_bet(&mut self, bet_key: &Address) -> Result<()> { let index = self .bets .iter() diff --git a/finance/escrow/anchor/programs/escrow/Cargo.toml b/finance/escrow/anchor/programs/escrow/Cargo.toml index 47a3a726b..71fe7fd8a 100644 --- a/finance/escrow/anchor/programs/escrow/Cargo.toml +++ b/finance/escrow/anchor/programs/escrow/Cargo.toml @@ -14,14 +14,22 @@ cpi = ["no-entrypoint"] no-entrypoint = [] no-idl = [] no-log-ix-name = [] -idl-build = ["anchor-lang/idl-build", "anchor-spl/idl-build"] +idl-build = ["anchor-lang/idl-build"] anchor-debug = [] custom-heap = [] custom-panic = [] [dependencies] -anchor-lang = { version = "1.1.2", features = ["init-if-needed"]} -anchor-spl = "1.1.2" +anchor-lang = "2.0.0-rc.1" +# The `#[program]` macro expands to `wincode` paths for instruction-data +# (de)serialization, so the crate has to be a direct dependency. +wincode = { version = "0.5", features = ["derive"] } +# anchor-lang 2.0.0-rc.1 is built against wincode 0.5, but solana-address 2.7 +# moved to wincode 0.6. With both in the graph, `Address`'s wincode impls +# belong to the version the account derives are not using and every +# `SchemaRead`/`SchemaWrite` bound fails. 2.6.1 is the last 0.5-line release. +solana-address = ">=2.6, <2.7" +anchor-spl = "2.0.0-rc.1" [dev-dependencies] litesvm = "0.13.1" diff --git a/finance/escrow/anchor/programs/escrow/src/instructions/cancel_offer.rs b/finance/escrow/anchor/programs/escrow/src/instructions/cancel_offer.rs index c2cd0fb9b..9ed0a4079 100644 --- a/finance/escrow/anchor/programs/escrow/src/instructions/cancel_offer.rs +++ b/finance/escrow/anchor/programs/escrow/src/instructions/cancel_offer.rs @@ -14,11 +14,11 @@ use super::{close_token_account, transfer_tokens}; // account's rent unclaimed). The maker signs, the vault tokens flow back to // the maker, and both the vault and the offer accounts are closed. #[derive(Accounts)] -pub struct CancelOfferAccountConstraints<'info> { +pub struct CancelOfferAccountConstraints { #[account(mut)] - pub maker: Signer<'info>, + pub maker: Signer, - pub token_mint_a: InterfaceAccount<'info, Mint>, + pub token_mint_a: InterfaceAccount, #[account( mut, @@ -26,17 +26,17 @@ pub struct CancelOfferAccountConstraints<'info> { associated_token::authority = maker, associated_token::token_program = token_program, )] - pub maker_token_account_a: InterfaceAccount<'info, TokenAccount>, + pub maker_token_account_a: InterfaceAccount, #[account( mut, close = maker, has_one = maker, has_one = token_mint_a, - seeds = [b"offer", maker.key().as_ref(), offer.id.to_le_bytes().as_ref()], + seeds = [b"offer", maker.address().as_ref(), offer.id.to_le_bytes().as_ref()], bump = offer.bump, )] - pub offer: Account<'info, Offer>, + pub offer: BorshAccount, #[account( mut, @@ -44,15 +44,15 @@ pub struct CancelOfferAccountConstraints<'info> { associated_token::authority = offer, associated_token::token_program = token_program, )] - pub vault: InterfaceAccount<'info, TokenAccount>, + pub vault: InterfaceAccount, - pub associated_token_program: Program<'info, AssociatedToken>, - pub token_program: Interface<'info, TokenInterface>, - pub system_program: Program<'info, System>, + pub associated_token_program: Program, + pub token_program: Interface<'static, TokenInterface>, + pub system_program: Program, } -pub fn handle_cancel_offer(context: Context) -> Result<()> { - let maker_key = context.accounts.maker.key(); +pub fn handle_cancel_offer(context: &mut Context) -> Result<()> { + let maker_key = context.accounts.maker.address(); let id_bytes = context.accounts.offer.id.to_le_bytes(); let bump = [context.accounts.offer.bump]; let offer_seeds: &[&[u8]] = &[b"offer", maker_key.as_ref(), id_bytes.as_ref(), &bump]; @@ -61,9 +61,9 @@ pub fn handle_cancel_offer(context: Context) -> R transfer_tokens( &context.accounts.vault, &context.accounts.maker_token_account_a, - &context.accounts.vault.amount, + &context.accounts.vault.amount(), &context.accounts.token_mint_a, - &context.accounts.offer.to_account_info(), + &context.accounts.offer.cpi_handle_mut(), &context.accounts.token_program, Some(offer_seeds), )?; @@ -71,8 +71,8 @@ pub fn handle_cancel_offer(context: Context) -> R // Close the vault, sending its rent lamports back to the maker. close_token_account( &context.accounts.vault, - &context.accounts.maker.to_account_info(), - &context.accounts.offer.to_account_info(), + &context.accounts.maker.cpi_handle_mut(), + &context.accounts.offer.cpi_handle_mut(), &context.accounts.token_program, Some(offer_seeds), )?; diff --git a/finance/escrow/anchor/programs/escrow/src/instructions/make_offer.rs b/finance/escrow/anchor/programs/escrow/src/instructions/make_offer.rs index cee614335..dd77dee47 100644 --- a/finance/escrow/anchor/programs/escrow/src/instructions/make_offer.rs +++ b/finance/escrow/anchor/programs/escrow/src/instructions/make_offer.rs @@ -1,4 +1,5 @@ use anchor_lang::prelude::*; +use anchor_spl::mint; use anchor_spl::{ associated_token::AssociatedToken, @@ -12,15 +13,15 @@ use super::transfer_tokens; // See https://www.anchor-lang.com/docs/references/account-constraints#instruction-attribute #[derive(Accounts)] #[instruction(id: u64)] -pub struct MakeOfferAccountConstraints<'info> { +pub struct MakeOfferAccountConstraints { #[account(mut)] - pub maker: Signer<'info>, + pub maker: Signer, #[account(mint::token_program = token_program)] - pub token_mint_a: InterfaceAccount<'info, Mint>, + pub token_mint_a: InterfaceAccount, #[account(mint::token_program = token_program)] - pub token_mint_b: InterfaceAccount<'info, Mint>, + pub token_mint_b: InterfaceAccount, #[account( mut, @@ -28,7 +29,7 @@ pub struct MakeOfferAccountConstraints<'info> { associated_token::authority = maker, associated_token::token_program = token_program )] - pub maker_token_account_a: InterfaceAccount<'info, TokenAccount>, + pub maker_token_account_a: InterfaceAccount, // The maker's token-B ATA is initialized here, paid by the maker, so the // rent burden lives with the party who chose to open the offer (take_offer @@ -40,16 +41,16 @@ pub struct MakeOfferAccountConstraints<'info> { associated_token::authority = maker, associated_token::token_program = token_program )] - pub maker_token_account_b: InterfaceAccount<'info, TokenAccount>, + pub maker_token_account_b: InterfaceAccount, #[account( init, payer = maker, space = Offer::DISCRIMINATOR.len() + Offer::INIT_SPACE, - seeds = [b"offer", maker.key().as_ref(), id.to_le_bytes().as_ref()], + seeds = [b"offer", maker.address().as_ref(), id.to_le_bytes().as_ref()], bump )] - pub offer: Account<'info, Offer>, + pub offer: BorshAccount, #[account( init, @@ -58,11 +59,11 @@ pub struct MakeOfferAccountConstraints<'info> { associated_token::authority = offer, associated_token::token_program = token_program )] - pub vault: InterfaceAccount<'info, TokenAccount>, + pub vault: InterfaceAccount, - pub associated_token_program: Program<'info, AssociatedToken>, - pub token_program: Interface<'info, TokenInterface>, - pub system_program: Program<'info, System>, + pub associated_token_program: Program, + pub token_program: Interface<'static, TokenInterface>, + pub system_program: Program, } // Move the tokens from the maker's ATA to the vault @@ -75,7 +76,7 @@ pub fn handle_send_offered_tokens_to_vault( &context.accounts.vault, &token_a_offered_amount, &context.accounts.token_mint_a, - &context.accounts.maker.to_account_info(), + &context.accounts.maker.cpi_handle_mut(), &context.accounts.token_program, None, ) @@ -83,15 +84,15 @@ pub fn handle_send_offered_tokens_to_vault( // Save the details of the offer to the offer account pub fn handle_save_offer( - context: Context, + context: &mut Context, id: u64, token_b_wanted_amount: u64, ) -> Result<()> { - context.accounts.offer.set_inner(Offer { + *context.accounts.offer = (Offer { id, - maker: context.accounts.maker.key(), - token_mint_a: context.accounts.token_mint_a.key(), - token_mint_b: context.accounts.token_mint_b.key(), + maker: *context.accounts.maker.address(), + token_mint_a: *context.accounts.token_mint_a.address(), + token_mint_b: *context.accounts.token_mint_b.address(), token_b_wanted_amount, bump: context.bumps.offer, }); diff --git a/finance/escrow/anchor/programs/escrow/src/instructions/shared.rs b/finance/escrow/anchor/programs/escrow/src/instructions/shared.rs index 349c107b3..75df4defa 100644 --- a/finance/escrow/anchor/programs/escrow/src/instructions/shared.rs +++ b/finance/escrow/anchor/programs/escrow/src/instructions/shared.rs @@ -9,54 +9,54 @@ use anchor_spl::token_interface::{ // When transferring out of a token account owned by a PDA, pass the PDA's // signer seeds via owning_pda_seeds; otherwise pass None. pub fn transfer_tokens<'info>( - from: &InterfaceAccount<'info, TokenAccount>, - to: &InterfaceAccount<'info, TokenAccount>, + from: &InterfaceAccount, + to: &InterfaceAccount, amount: &u64, - mint: &InterfaceAccount<'info, Mint>, - authority: &AccountInfo<'info>, - token_program: &Interface<'info, TokenInterface>, + mint: &InterfaceAccount, + authority: &AccountView, + token_program: &Interface<'static, TokenInterface>, owning_pda_seeds: Option<&[&[u8]]>, ) -> Result<()> { let transfer_accounts = TransferChecked { - from: from.to_account_info(), - mint: mint.to_account_info(), - to: to.to_account_info(), - authority: authority.to_account_info(), + from: from.cpi_handle_mut(), + mint: mint.cpi_handle(), + to: to.cpi_handle_mut(), + authority: authority.cpi_handle_mut(), }; let signer_seeds = owning_pda_seeds.map(|seeds| [seeds]); let cpi_context = match signer_seeds.as_ref() { Some(signer_seeds) => { - CpiContext::new_with_signer(token_program.key(), transfer_accounts, signer_seeds) + CpiContext::new_with_signer(token_program.address(), transfer_accounts, signer_seeds) } - None => CpiContext::new(token_program.key(), transfer_accounts), + None => CpiContext::new(token_program.address(), transfer_accounts), }; - transfer_checked(cpi_context, *amount, mint.decimals) + transfer_checked(cpi_context, *amount, mint.decimals()) } // Close a token account, sending its rent lamports to destination. // When the token account is owned by a PDA, pass the PDA's signer seeds via // owning_pda_seeds; otherwise pass None. pub fn close_token_account<'info>( - token_account: &InterfaceAccount<'info, TokenAccount>, - destination: &AccountInfo<'info>, - authority: &AccountInfo<'info>, - token_program: &Interface<'info, TokenInterface>, + token_account: &InterfaceAccount, + destination: &AccountView, + authority: &AccountView, + token_program: &Interface<'static, TokenInterface>, owning_pda_seeds: Option<&[&[u8]]>, ) -> Result<()> { let close_accounts = CloseAccount { - account: token_account.to_account_info(), - destination: destination.to_account_info(), - authority: authority.to_account_info(), + account: token_account.cpi_handle_mut(), + destination: destination.cpi_handle_mut(), + authority: authority.cpi_handle_mut(), }; let signer_seeds = owning_pda_seeds.map(|seeds| [seeds]); let cpi_context = match signer_seeds.as_ref() { Some(signer_seeds) => { - CpiContext::new_with_signer(token_program.key(), close_accounts, signer_seeds) + CpiContext::new_with_signer(token_program.address(), close_accounts, signer_seeds) } - None => CpiContext::new(token_program.key(), close_accounts), + None => CpiContext::new(token_program.address(), close_accounts), }; close_account(cpi_context) diff --git a/finance/escrow/anchor/programs/escrow/src/instructions/take_offer.rs b/finance/escrow/anchor/programs/escrow/src/instructions/take_offer.rs index a8edc1a13..028308cdc 100644 --- a/finance/escrow/anchor/programs/escrow/src/instructions/take_offer.rs +++ b/finance/escrow/anchor/programs/escrow/src/instructions/take_offer.rs @@ -10,16 +10,16 @@ use crate::Offer; use super::{close_token_account, transfer_tokens}; #[derive(Accounts)] -pub struct TakeOfferAccountConstraints<'info> { +pub struct TakeOfferAccountConstraints { #[account(mut)] - pub taker: Signer<'info>, + pub taker: Signer, #[account(mut)] - pub maker: SystemAccount<'info>, + pub maker: SystemAccount, - pub token_mint_a: InterfaceAccount<'info, Mint>, + pub token_mint_a: InterfaceAccount, - pub token_mint_b: InterfaceAccount<'info, Mint>, + pub token_mint_b: InterfaceAccount, #[account( init_if_needed, @@ -28,7 +28,7 @@ pub struct TakeOfferAccountConstraints<'info> { associated_token::authority = taker, associated_token::token_program = token_program, )] - pub taker_token_account_a: Box>, + pub taker_token_account_a: Box>, #[account( mut, @@ -36,7 +36,7 @@ pub struct TakeOfferAccountConstraints<'info> { associated_token::authority = taker, associated_token::token_program = token_program, )] - pub taker_token_account_b: Box>, + pub taker_token_account_b: Box>, // The maker's token-B ATA is initialized in make_offer, paid by the maker. #[account( @@ -45,7 +45,7 @@ pub struct TakeOfferAccountConstraints<'info> { associated_token::authority = maker, associated_token::token_program = token_program, )] - pub maker_token_account_b: Box>, + pub maker_token_account_b: Box>, #[account( mut, @@ -53,10 +53,10 @@ pub struct TakeOfferAccountConstraints<'info> { has_one = maker, has_one = token_mint_a, has_one = token_mint_b, - seeds = [b"offer", maker.key().as_ref(), offer.id.to_le_bytes().as_ref()], + seeds = [b"offer", maker.address().as_ref(), offer.id.to_le_bytes().as_ref()], bump = offer.bump )] - offer: Account<'info, Offer>, + offer: BorshAccount, #[account( mut, @@ -64,11 +64,11 @@ pub struct TakeOfferAccountConstraints<'info> { associated_token::authority = offer, associated_token::token_program = token_program, )] - pub vault: InterfaceAccount<'info, TokenAccount>, + pub vault: InterfaceAccount, - pub associated_token_program: Program<'info, AssociatedToken>, - pub token_program: Interface<'info, TokenInterface>, - pub system_program: Program<'info, System>, + pub associated_token_program: Program, + pub token_program: Interface<'static, TokenInterface>, + pub system_program: Program, } pub fn handle_send_wanted_tokens_to_maker( @@ -79,14 +79,14 @@ pub fn handle_send_wanted_tokens_to_maker( &context.accounts.maker_token_account_b, &context.accounts.offer.token_b_wanted_amount, &context.accounts.token_mint_b, - &context.accounts.taker.to_account_info(), + &context.accounts.taker.cpi_handle_mut(), &context.accounts.token_program, None, ) } -pub fn handle_withdraw_and_close_vault(context: Context) -> Result<()> { - let maker_key = context.accounts.maker.key(); +pub fn handle_withdraw_and_close_vault(context: &mut Context) -> Result<()> { + let maker_key = context.accounts.maker.address(); let id_bytes = context.accounts.offer.id.to_le_bytes(); let bump = [context.accounts.offer.bump]; let offer_seeds: &[&[u8]] = &[b"offer", maker_key.as_ref(), id_bytes.as_ref(), &bump]; @@ -94,9 +94,9 @@ pub fn handle_withdraw_and_close_vault(context: Context, + context: &mut Context, id: u64, token_a_offered_amount: u64, token_b_wanted_amount: u64, @@ -23,7 +23,7 @@ pub mod escrow { instructions::make_offer::handle_save_offer(context, id, token_b_wanted_amount) } - pub fn take_offer(context: Context) -> Result<()> { + pub fn take_offer(context: &mut Context) -> Result<()> { instructions::take_offer::handle_send_wanted_tokens_to_maker(&context)?; instructions::take_offer::handle_withdraw_and_close_vault(context) } @@ -32,7 +32,7 @@ pub mod escrow { // to the maker, and both the vault and offer accounts are closed (rent // refunded to the maker). Without this, abandoned offers would lock funds // forever. - pub fn cancel_offer(context: Context) -> Result<()> { + pub fn cancel_offer(context: &mut Context) -> Result<()> { instructions::cancel_offer::handle_cancel_offer(context) } } diff --git a/finance/escrow/anchor/programs/escrow/src/state/offer.rs b/finance/escrow/anchor/programs/escrow/src/state/offer.rs index 65f90af73..2848315b2 100644 --- a/finance/escrow/anchor/programs/escrow/src/state/offer.rs +++ b/finance/escrow/anchor/programs/escrow/src/state/offer.rs @@ -1,12 +1,12 @@ use anchor_lang::prelude::*; -#[account] +#[account(borsh)] #[derive(InitSpace)] pub struct Offer { pub id: u64, - pub maker: Pubkey, - pub token_mint_a: Pubkey, - pub token_mint_b: Pubkey, + pub maker: Address, + pub token_mint_a: Address, + pub token_mint_b: Address, pub token_b_wanted_amount: u64, pub bump: u8, } diff --git a/finance/lending/anchor/programs/lending/Cargo.toml b/finance/lending/anchor/programs/lending/Cargo.toml index 2e7be6417..d2edd108f 100644 --- a/finance/lending/anchor/programs/lending/Cargo.toml +++ b/finance/lending/anchor/programs/lending/Cargo.toml @@ -14,15 +14,23 @@ cpi = ["no-entrypoint"] no-entrypoint = [] no-idl = [] no-log-ix-name = [] -idl-build = ["anchor-lang/idl-build", "anchor-spl/idl-build"] +idl-build = ["anchor-lang/idl-build"] anchor-debug = [] custom-heap = [] custom-panic = [] [dependencies] # init-if-needed: the obligation share vault and the test price feed are created lazily. -anchor-lang = { version = "1.1.2", features = ["init-if-needed"] } -anchor-spl = "1.1.2" +anchor-lang = "2.0.0-rc.1" +# The `#[program]` macro expands to `wincode` paths for instruction-data +# (de)serialization, so the crate has to be a direct dependency. +wincode = { version = "0.5", features = ["derive"] } +# anchor-lang 2.0.0-rc.1 is built against wincode 0.5, but solana-address 2.7 +# moved to wincode 0.6. With both in the graph, `Address`'s wincode impls +# belong to the version the account derives are not using and every +# `SchemaRead`/`SchemaWrite` bound fails. 2.6.1 is the last 0.5-line release. +solana-address = ">=2.6, <2.7" +anchor-spl = "2.0.0-rc.1" # For the LastRestartSlot sysvar (not re-exported by anchor-lang): the price # feed rejects prices from before a cluster restart. Same major as the # solana-sysvar anchor-lang itself uses, so only one copy is compiled in. diff --git a/finance/lending/anchor/programs/lending/src/instructions/admin/collect_protocol_fees.rs b/finance/lending/anchor/programs/lending/src/instructions/admin/collect_protocol_fees.rs index 2e0b00618..0d72a8098 100644 --- a/finance/lending/anchor/programs/lending/src/instructions/admin/collect_protocol_fees.rs +++ b/finance/lending/anchor/programs/lending/src/instructions/admin/collect_protocol_fees.rs @@ -10,7 +10,7 @@ use crate::state::{reserve_signer_seeds, LendingMarket, Reserve}; /// how the owner earns: `reserve_factor_bps` of every interest accrual is set /// aside in `accumulated_protocol_fees` (never credited to suppliers), and this /// handler pays it out, capped by the liquidity actually sitting in the vault. -pub fn handle_collect_protocol_fees(context: Context) -> Result<()> { +pub fn handle_collect_protocol_fees(context: &mut Context) -> Result<()> { context.accounts.reserve.require_refreshed()?; let reserve = &mut context.accounts.reserve; @@ -32,12 +32,12 @@ pub fn handle_collect_protocol_fees(context: Context) -> Re let seeds = reserve_signer_seeds(&reserve.lending_market, &reserve.liquidity_mint, &bump); transfer_checked( CpiContext::new_with_signer( - context.accounts.token_program.key(), + context.accounts.token_program.address(), TransferChecked { - from: context.accounts.liquidity_vault.to_account_info(), - mint: context.accounts.liquidity_mint.to_account_info(), - to: context.accounts.owner_liquidity.to_account_info(), - authority: reserve.to_account_info(), + from: context.accounts.liquidity_vault.cpi_handle_mut(), + mint: context.accounts.liquidity_mint.cpi_handle(), + to: context.accounts.owner_liquidity.cpi_handle_mut(), + authority: reserve.cpi_handle(), }, &[&seeds], ), @@ -49,14 +49,14 @@ pub fn handle_collect_protocol_fees(context: Context) -> Re } #[derive(Accounts)] -pub struct CollectProtocolFees<'info> { +pub struct CollectProtocolFees { // Identified by the reserve's `has_one = lending_market`; we only prove the // signer owns it. #[account(has_one = owner)] - pub lending_market: Account<'info, LendingMarket>, + pub lending_market: BorshAccount, #[account(mut)] - pub owner: Signer<'info>, + pub owner: Signer, #[account( mut, @@ -64,15 +64,15 @@ pub struct CollectProtocolFees<'info> { has_one = liquidity_mint, has_one = liquidity_vault, )] - pub reserve: Account<'info, Reserve>, + pub reserve: BorshAccount, - pub liquidity_mint: InterfaceAccount<'info, Mint>, + pub liquidity_mint: InterfaceAccount, #[account(mut)] - pub liquidity_vault: InterfaceAccount<'info, TokenAccount>, + pub liquidity_vault: InterfaceAccount, #[account(mut)] - pub owner_liquidity: InterfaceAccount<'info, TokenAccount>, + pub owner_liquidity: InterfaceAccount, - pub token_program: Interface<'info, TokenInterface>, + pub token_program: Interface<'static, TokenInterface>, } diff --git a/finance/lending/anchor/programs/lending/src/instructions/admin/initialize_lending_market.rs b/finance/lending/anchor/programs/lending/src/instructions/admin/initialize_lending_market.rs index 2e6d478cc..acb8e03c0 100644 --- a/finance/lending/anchor/programs/lending/src/instructions/admin/initialize_lending_market.rs +++ b/finance/lending/anchor/programs/lending/src/instructions/admin/initialize_lending_market.rs @@ -5,20 +5,20 @@ use crate::constants::LENDING_MARKET_SEED; use crate::state::LendingMarket; pub fn handle_initialize_lending_market( - context: Context, + context: &mut Context, market_id: u64, ) -> Result<()> { let market = &mut context.accounts.lending_market; market.market_id = market_id; - market.owner = context.accounts.owner.key(); - market.quote_currency_mint = context.accounts.quote_currency_mint.key(); + market.owner = *context.accounts.owner.address(); + market.quote_currency_mint = *context.accounts.quote_currency_mint.address(); market.bump = context.bumps.lending_market; Ok(()) } #[derive(Accounts)] #[instruction(market_id: u64)] -pub struct InitializeLendingMarket<'info> { +pub struct InitializeLendingMarket { // Seeded by `market_id` alone — the market is not identified by any // individual's address. `owner` is stored as a field and used only for // authorization (`has_one = owner`) on admin instructions. @@ -29,12 +29,12 @@ pub struct InitializeLendingMarket<'info> { seeds = [LENDING_MARKET_SEED, &market_id.to_le_bytes()], bump, )] - pub lending_market: Account<'info, LendingMarket>, + pub lending_market: BorshAccount, #[account(mut)] - pub owner: Signer<'info>, + pub owner: Signer, - pub quote_currency_mint: InterfaceAccount<'info, Mint>, + pub quote_currency_mint: InterfaceAccount, - pub system_program: Program<'info, System>, + pub system_program: Program, } diff --git a/finance/lending/anchor/programs/lending/src/instructions/admin/initialize_reserve.rs b/finance/lending/anchor/programs/lending/src/instructions/admin/initialize_reserve.rs index 96478950b..ff7cfd71f 100644 --- a/finance/lending/anchor/programs/lending/src/instructions/admin/initialize_reserve.rs +++ b/finance/lending/anchor/programs/lending/src/instructions/admin/initialize_reserve.rs @@ -1,4 +1,6 @@ use anchor_lang::prelude::*; +use anchor_spl::token; +use anchor_spl::mint; use anchor_spl::token_interface::{Mint, TokenAccount, TokenInterface}; use crate::constants::{ @@ -6,16 +8,16 @@ use crate::constants::{ }; use crate::state::{LendingMarket, PriceFeed, Reserve, ReserveConfig}; -pub fn handle_initialize_reserve(context: Context, config: ReserveConfig) -> Result<()> { +pub fn handle_initialize_reserve(context: &mut Context, config: ReserveConfig) -> Result<()> { config.validate()?; let reserve = &mut context.accounts.reserve; - reserve.lending_market = context.accounts.lending_market.key(); - reserve.liquidity_mint = context.accounts.liquidity_mint.key(); - reserve.liquidity_vault = context.accounts.liquidity_vault.key(); - reserve.share_mint = context.accounts.share_mint.key(); - reserve.price_feed = context.accounts.price_feed.key(); - reserve.liquidity_decimals = context.accounts.liquidity_mint.decimals; + reserve.lending_market = *context.accounts.lending_market.address(); + reserve.liquidity_mint = *context.accounts.liquidity_mint.address(); + reserve.liquidity_vault = *context.accounts.liquidity_vault.address(); + reserve.share_mint = *context.accounts.share_mint.address(); + reserve.price_feed = *context.accounts.price_feed.address(); + reserve.liquidity_decimals = context.accounts.liquidity_mint.decimals(); reserve.available_liquidity = 0; reserve.share_mint_supply = 0; reserve.borrowed_principal = 0; @@ -28,55 +30,55 @@ pub fn handle_initialize_reserve(context: Context, config: Re } #[derive(Accounts)] -pub struct InitializeReserve<'info> { +pub struct InitializeReserve { // The reserve PDA below is seeded by this market's address, so the market is // pinned by that seed; we only need to prove the signer owns it. #[account(has_one = owner)] - pub lending_market: Account<'info, LendingMarket>, + pub lending_market: BorshAccount, #[account(mut)] - pub owner: Signer<'info>, + pub owner: Signer, #[account( init, payer = owner, space = Reserve::DISCRIMINATOR.len() + Reserve::INIT_SPACE, - seeds = [RESERVE_SEED, lending_market.key().as_ref(), liquidity_mint.key().as_ref()], + seeds = [RESERVE_SEED, lending_market.address().as_ref(), liquidity_mint.address().as_ref()], bump, )] - pub reserve: Account<'info, Reserve>, + pub reserve: BorshAccount, - pub liquidity_mint: InterfaceAccount<'info, Mint>, + pub liquidity_mint: InterfaceAccount, #[account( init, payer = owner, token::mint = liquidity_mint, token::authority = reserve, - seeds = [LIQUIDITY_VAULT_SEED, reserve.key().as_ref()], + seeds = [LIQUIDITY_VAULT_SEED, reserve.address().as_ref()], bump, )] - pub liquidity_vault: InterfaceAccount<'info, TokenAccount>, + pub liquidity_vault: InterfaceAccount, #[account( init, payer = owner, - mint::decimals = liquidity_mint.decimals, + mint::decimals = liquidity_mint.decimals(), mint::authority = reserve, - seeds = [SHARE_MINT_SEED, reserve.key().as_ref()], + seeds = [SHARE_MINT_SEED, reserve.address().as_ref()], bump, )] - pub share_mint: InterfaceAccount<'info, Mint>, + pub share_mint: InterfaceAccount, // Bound by seeds to this market's feed for this mint — the reserve can only // trust the price its own market publishes. #[account( - seeds = [PRICE_FEED_SEED, lending_market.key().as_ref(), liquidity_mint.key().as_ref()], + seeds = [PRICE_FEED_SEED, lending_market.address().as_ref(), liquidity_mint.address().as_ref()], bump = price_feed.bump, )] - pub price_feed: Account<'info, PriceFeed>, + pub price_feed: BorshAccount, - pub token_program: Interface<'info, TokenInterface>, + pub token_program: Interface<'static, TokenInterface>, - pub system_program: Program<'info, System>, + pub system_program: Program, } diff --git a/finance/lending/anchor/programs/lending/src/instructions/admin/set_price.rs b/finance/lending/anchor/programs/lending/src/instructions/admin/set_price.rs index 4044c255b..6f4ff159f 100644 --- a/finance/lending/anchor/programs/lending/src/instructions/admin/set_price.rs +++ b/finance/lending/anchor/programs/lending/src/instructions/admin/set_price.rs @@ -12,13 +12,13 @@ use crate::state::{LendingMarket, PriceFeed}; /// requires the market's `owner` to sign, so a market's prices can only be set /// by that market and never squatted by an outsider. pub fn handle_set_price( - context: Context, + context: &mut Context, price_mantissa: i128, exponent: i32, ) -> Result<()> { let feed = &mut context.accounts.price_feed; - feed.market = context.accounts.lending_market.key(); - feed.mint = context.accounts.mint.key(); + feed.market = *context.accounts.lending_market.address(); + feed.mint = *context.accounts.mint.address(); feed.bump = context.bumps.price_feed; feed.price_mantissa = price_mantissa; feed.exponent = exponent; @@ -27,24 +27,24 @@ pub fn handle_set_price( } #[derive(Accounts)] -pub struct SetPrice<'info> { +pub struct SetPrice { // Only the market's owner may publish its prices. #[account(has_one = owner)] - pub lending_market: Account<'info, LendingMarket>, + pub lending_market: BorshAccount, #[account(mut)] - pub owner: Signer<'info>, + pub owner: Signer, #[account( init_if_needed, payer = owner, space = PriceFeed::DISCRIMINATOR.len() + PriceFeed::INIT_SPACE, - seeds = [PRICE_FEED_SEED, lending_market.key().as_ref(), mint.key().as_ref()], + seeds = [PRICE_FEED_SEED, lending_market.address().as_ref(), mint.address().as_ref()], bump, )] - pub price_feed: Account<'info, PriceFeed>, + pub price_feed: BorshAccount, - pub mint: InterfaceAccount<'info, Mint>, + pub mint: InterfaceAccount, - pub system_program: Program<'info, System>, + pub system_program: Program, } diff --git a/finance/lending/anchor/programs/lending/src/instructions/admin/update_reserve_config.rs b/finance/lending/anchor/programs/lending/src/instructions/admin/update_reserve_config.rs index 06df1b1b0..9e68b917b 100644 --- a/finance/lending/anchor/programs/lending/src/instructions/admin/update_reserve_config.rs +++ b/finance/lending/anchor/programs/lending/src/instructions/admin/update_reserve_config.rs @@ -3,7 +3,7 @@ use anchor_lang::prelude::*; use crate::state::{LendingMarket, Reserve, ReserveConfig}; pub fn handle_update_reserve_config( - context: Context, + context: &mut Context, config: ReserveConfig, ) -> Result<()> { config.validate()?; @@ -12,17 +12,17 @@ pub fn handle_update_reserve_config( } #[derive(Accounts)] -pub struct UpdateReserveConfig<'info> { +pub struct UpdateReserveConfig { // The market is identified by the reserve's `has_one = lending_market`; we // only need to prove the signer owns it, not re-derive its address. #[account(has_one = owner)] - pub lending_market: Account<'info, LendingMarket>, + pub lending_market: BorshAccount, - pub owner: Signer<'info>, + pub owner: Signer, #[account( mut, has_one = lending_market, )] - pub reserve: Account<'info, Reserve>, + pub reserve: BorshAccount, } diff --git a/finance/lending/anchor/programs/lending/src/instructions/borrow_obligation_liquidity.rs b/finance/lending/anchor/programs/lending/src/instructions/borrow_obligation_liquidity.rs index 723a29d1b..213517a19 100644 --- a/finance/lending/anchor/programs/lending/src/instructions/borrow_obligation_liquidity.rs +++ b/finance/lending/anchor/programs/lending/src/instructions/borrow_obligation_liquidity.rs @@ -13,12 +13,12 @@ use crate::state::{reserve_signer_seeds, Obligation, PriceFeed, Reserve}; /// allowed-borrow value. The borrowed amount is recorded as scaled principal at /// the reserve's current index (rounded up) so it accrues interest going forward. pub fn handle_borrow_obligation_liquidity( - context: Context, + context: &mut Context, liquidity_amount: u64, ) -> Result<()> { require!(liquidity_amount > 0, LendingError::ZeroAmount); let slot = Clock::get()?.slot; - let reserve_key = context.accounts.reserve.key(); + let reserve_key = context.accounts.reserve.address(); context.accounts.obligation.require_refreshed()?; context.accounts.reserve.require_refreshed()?; @@ -62,7 +62,7 @@ pub fn handle_borrow_obligation_liquidity( { let obligation = &mut context.accounts.obligation; - let index = obligation.upsert_borrow(reserve_key)?; + let index = obligation.upsert_borrow(*reserve_key)?; obligation.borrows[index].borrowed_principal = obligation.borrows[index] .borrowed_principal .checked_add(scaled_added) @@ -75,12 +75,12 @@ pub fn handle_borrow_obligation_liquidity( let seeds = reserve_signer_seeds(&reserve.lending_market, &reserve.liquidity_mint, &bump); transfer_checked( CpiContext::new_with_signer( - context.accounts.token_program.key(), + context.accounts.token_program.address(), TransferChecked { - from: context.accounts.liquidity_vault.to_account_info(), - mint: context.accounts.liquidity_mint.to_account_info(), - to: context.accounts.user_liquidity.to_account_info(), - authority: reserve.to_account_info(), + from: context.accounts.liquidity_vault.cpi_handle_mut(), + mint: context.accounts.liquidity_mint.cpi_handle(), + to: context.accounts.user_liquidity.cpi_handle_mut(), + authority: reserve.cpi_handle(), }, &[&seeds], ), @@ -92,11 +92,11 @@ pub fn handle_borrow_obligation_liquidity( } #[derive(Accounts)] -pub struct BorrowObligationLiquidity<'info> { +pub struct BorrowObligationLiquidity { #[account(mut, has_one = owner)] - pub obligation: Account<'info, Obligation>, + pub obligation: BorshAccount, - pub owner: Signer<'info>, + pub owner: Signer, #[account( mut, @@ -105,17 +105,17 @@ pub struct BorrowObligationLiquidity<'info> { has_one = price_feed, constraint = reserve.lending_market == obligation.lending_market @ LendingError::MarketMismatch, )] - pub reserve: Account<'info, Reserve>, + pub reserve: BorshAccount, - pub price_feed: Account<'info, PriceFeed>, + pub price_feed: BorshAccount, - pub liquidity_mint: InterfaceAccount<'info, Mint>, + pub liquidity_mint: InterfaceAccount, #[account(mut)] - pub liquidity_vault: InterfaceAccount<'info, TokenAccount>, + pub liquidity_vault: InterfaceAccount, #[account(mut)] - pub user_liquidity: InterfaceAccount<'info, TokenAccount>, + pub user_liquidity: InterfaceAccount, - pub token_program: Interface<'info, TokenInterface>, + pub token_program: Interface<'static, TokenInterface>, } diff --git a/finance/lending/anchor/programs/lending/src/instructions/deposit_obligation_collateral.rs b/finance/lending/anchor/programs/lending/src/instructions/deposit_obligation_collateral.rs index a33e8bfac..4905a64fd 100644 --- a/finance/lending/anchor/programs/lending/src/instructions/deposit_obligation_collateral.rs +++ b/finance/lending/anchor/programs/lending/src/instructions/deposit_obligation_collateral.rs @@ -1,4 +1,5 @@ use anchor_lang::prelude::*; +use anchor_spl::token; use anchor_spl::token_interface::{ transfer_checked, Mint, TokenAccount, TokenInterface, TransferChecked, }; @@ -12,14 +13,14 @@ use crate::state::{Obligation, Reserve}; /// adding collateral only improves health — but the obligation is marked stale /// so its cached values are recomputed before the next health-dependent action. pub fn handle_deposit_obligation_collateral( - context: Context, + context: &mut Context, share_amount: u64, ) -> Result<()> { require!(share_amount > 0, LendingError::ZeroAmount); - let reserve_key = context.accounts.reserve.key(); + let reserve_key = context.accounts.reserve.address(); let obligation = &mut context.accounts.obligation; - let index = obligation.upsert_collateral(reserve_key)?; + let index = obligation.upsert_collateral(*reserve_key)?; obligation.deposits[index].deposited_shares = obligation.deposits[index] .deposited_shares .checked_add(share_amount) @@ -28,51 +29,51 @@ pub fn handle_deposit_obligation_collateral( transfer_checked( CpiContext::new( - context.accounts.token_program.key(), + context.accounts.token_program.address(), TransferChecked { - from: context.accounts.user_share.to_account_info(), - mint: context.accounts.share_mint.to_account_info(), - to: context.accounts.obligation_share_vault.to_account_info(), - authority: context.accounts.owner.to_account_info(), + from: context.accounts.user_share.cpi_handle_mut(), + mint: context.accounts.share_mint.cpi_handle(), + to: context.accounts.obligation_share_vault.cpi_handle_mut(), + authority: context.accounts.owner.cpi_handle(), }, ), share_amount, - context.accounts.share_mint.decimals, + context.accounts.share_mint.decimals(), )?; Ok(()) } #[derive(Accounts)] -pub struct DepositObligationCollateral<'info> { +pub struct DepositObligationCollateral { #[account(mut, has_one = owner)] - pub obligation: Account<'info, Obligation>, + pub obligation: BorshAccount, #[account(mut)] - pub owner: Signer<'info>, + pub owner: Signer, #[account( has_one = share_mint, constraint = reserve.lending_market == obligation.lending_market @ LendingError::MarketMismatch, )] - pub reserve: Account<'info, Reserve>, + pub reserve: BorshAccount, - pub share_mint: InterfaceAccount<'info, Mint>, + pub share_mint: InterfaceAccount, #[account( init_if_needed, payer = owner, token::mint = share_mint, token::authority = obligation, - seeds = [OBLIGATION_SHARE_VAULT_SEED, reserve.key().as_ref(), obligation.key().as_ref()], + seeds = [OBLIGATION_SHARE_VAULT_SEED, reserve.address().as_ref(), obligation.address().as_ref()], bump, )] - pub obligation_share_vault: InterfaceAccount<'info, TokenAccount>, + pub obligation_share_vault: InterfaceAccount, #[account(mut)] - pub user_share: InterfaceAccount<'info, TokenAccount>, + pub user_share: InterfaceAccount, - pub token_program: Interface<'info, TokenInterface>, + pub token_program: Interface<'static, TokenInterface>, - pub system_program: Program<'info, System>, + pub system_program: Program, } diff --git a/finance/lending/anchor/programs/lending/src/instructions/deposit_reserve_liquidity.rs b/finance/lending/anchor/programs/lending/src/instructions/deposit_reserve_liquidity.rs index 3425497ca..207d8482b 100644 --- a/finance/lending/anchor/programs/lending/src/instructions/deposit_reserve_liquidity.rs +++ b/finance/lending/anchor/programs/lending/src/instructions/deposit_reserve_liquidity.rs @@ -12,7 +12,7 @@ use crate::state::{reserve_signer_seeds, Reserve}; /// `liquidity_amount * share_supply / total_liquidity`, floored so the protocol /// keeps any rounding dust. pub fn handle_deposit_reserve_liquidity( - context: Context, + context: &mut Context, liquidity_amount: u64, ) -> Result<()> { require!(liquidity_amount > 0, LendingError::ZeroAmount); @@ -40,12 +40,12 @@ pub fn handle_deposit_reserve_liquidity( transfer_checked( CpiContext::new( - context.accounts.token_program.key(), + context.accounts.token_program.address(), TransferChecked { - from: context.accounts.user_liquidity.to_account_info(), - mint: context.accounts.liquidity_mint.to_account_info(), - to: context.accounts.liquidity_vault.to_account_info(), - authority: context.accounts.owner.to_account_info(), + from: context.accounts.user_liquidity.cpi_handle_mut(), + mint: context.accounts.liquidity_mint.cpi_handle(), + to: context.accounts.liquidity_vault.cpi_handle_mut(), + authority: context.accounts.owner.cpi_handle(), }, ), liquidity_amount, @@ -56,11 +56,11 @@ pub fn handle_deposit_reserve_liquidity( let seeds = reserve_signer_seeds(&reserve.lending_market, &reserve.liquidity_mint, &bump); mint_to( CpiContext::new_with_signer( - context.accounts.token_program.key(), + context.accounts.token_program.address(), MintTo { - mint: context.accounts.share_mint.to_account_info(), - to: context.accounts.user_share.to_account_info(), - authority: reserve.to_account_info(), + mint: context.accounts.share_mint.cpi_handle_mut(), + to: context.accounts.user_share.cpi_handle_mut(), + authority: reserve.cpi_handle(), }, &[&seeds], ), @@ -71,30 +71,30 @@ pub fn handle_deposit_reserve_liquidity( } #[derive(Accounts)] -pub struct DepositReserveLiquidity<'info> { +pub struct DepositReserveLiquidity { #[account( mut, has_one = liquidity_mint, has_one = liquidity_vault, has_one = share_mint, )] - pub reserve: Account<'info, Reserve>, + pub reserve: BorshAccount, - pub liquidity_mint: InterfaceAccount<'info, Mint>, + pub liquidity_mint: InterfaceAccount, #[account(mut)] - pub liquidity_vault: InterfaceAccount<'info, TokenAccount>, + pub liquidity_vault: InterfaceAccount, #[account(mut)] - pub share_mint: InterfaceAccount<'info, Mint>, + pub share_mint: InterfaceAccount, #[account(mut)] - pub user_liquidity: InterfaceAccount<'info, TokenAccount>, + pub user_liquidity: InterfaceAccount, #[account(mut)] - pub user_share: InterfaceAccount<'info, TokenAccount>, + pub user_share: InterfaceAccount, - pub owner: Signer<'info>, + pub owner: Signer, - pub token_program: Interface<'info, TokenInterface>, + pub token_program: Interface<'static, TokenInterface>, } diff --git a/finance/lending/anchor/programs/lending/src/instructions/initialize_obligation.rs b/finance/lending/anchor/programs/lending/src/instructions/initialize_obligation.rs index c7a761354..b2c285645 100644 --- a/finance/lending/anchor/programs/lending/src/instructions/initialize_obligation.rs +++ b/finance/lending/anchor/programs/lending/src/instructions/initialize_obligation.rs @@ -3,10 +3,10 @@ use anchor_lang::prelude::*; use crate::constants::OBLIGATION_SEED; use crate::state::{LendingMarket, Obligation}; -pub fn handle_initialize_obligation(context: Context) -> Result<()> { +pub fn handle_initialize_obligation(context: &mut Context) -> Result<()> { let obligation = &mut context.accounts.obligation; - obligation.lending_market = context.accounts.lending_market.key(); - obligation.owner = context.accounts.owner.key(); + obligation.lending_market = *context.accounts.lending_market.address(); + obligation.owner = *context.accounts.owner.address(); obligation.last_update_slot = Clock::get()?.slot; // Stale until the first refresh; an empty obligation has nothing to value yet. obligation.stale = true; @@ -21,20 +21,20 @@ pub fn handle_initialize_obligation(context: Context) -> R } #[derive(Accounts)] -pub struct InitializeObligation<'info> { - pub lending_market: Account<'info, LendingMarket>, +pub struct InitializeObligation { + pub lending_market: BorshAccount, #[account( init, payer = owner, space = Obligation::DISCRIMINATOR.len() + Obligation::INIT_SPACE, - seeds = [OBLIGATION_SEED, lending_market.key().as_ref(), owner.key().as_ref()], + seeds = [OBLIGATION_SEED, lending_market.address().as_ref(), owner.address().as_ref()], bump, )] - pub obligation: Account<'info, Obligation>, + pub obligation: BorshAccount, #[account(mut)] - pub owner: Signer<'info>, + pub owner: Signer, - pub system_program: Program<'info, System>, + pub system_program: Program, } diff --git a/finance/lending/anchor/programs/lending/src/instructions/liquidate_obligation.rs b/finance/lending/anchor/programs/lending/src/instructions/liquidate_obligation.rs index bb24f1369..1767e9c6a 100644 --- a/finance/lending/anchor/programs/lending/src/instructions/liquidate_obligation.rs +++ b/finance/lending/anchor/programs/lending/src/instructions/liquidate_obligation.rs @@ -1,4 +1,5 @@ use anchor_lang::prelude::*; +use anchor_spl::token; use anchor_spl::token_interface::{ transfer_checked, Mint, TokenAccount, TokenInterface, TransferChecked, }; @@ -25,7 +26,7 @@ use crate::state::{Obligation, PriceFeed, Reserve}; /// it is only possible while unhealthy and is economically pointless, matching /// how Solend and Kamino behave. pub fn handle_liquidate_obligation( - context: Context, + context: &mut Context, liquidity_amount: u64, ) -> Result<()> { require!(liquidity_amount > 0, LendingError::ZeroAmount); @@ -47,8 +48,8 @@ pub fn handle_liquidate_obligation( let repay_price = context.accounts.repay_price_feed.price_scaled(slot)?; let collateral_price = context.accounts.collateral_price_feed.price_scaled(slot)?; - let borrow_index = obligation.find_borrow(repay_reserve.key())?; - let collateral_index = obligation.find_collateral(collateral_reserve.key())?; + let borrow_index = obligation.find_borrow(*repay_reserve.address())?; + let collateral_index = obligation.find_collateral(*collateral_reserve.address())?; let borrowed_principal = obligation.borrows[borrow_index].borrowed_principal; let deposited_shares = obligation.deposits[collateral_index].deposited_shares; @@ -137,12 +138,12 @@ pub fn handle_liquidate_obligation( // Interactions: liquidator repays, then receives the seized share tokens. transfer_checked( CpiContext::new( - context.accounts.token_program.key(), + context.accounts.token_program.address(), TransferChecked { - from: context.accounts.liquidator_repay_source.to_account_info(), - mint: context.accounts.repay_liquidity_mint.to_account_info(), - to: context.accounts.repay_liquidity_vault.to_account_info(), - authority: context.accounts.liquidator.to_account_info(), + from: context.accounts.liquidator_repay_source.cpi_handle_mut(), + mint: context.accounts.repay_liquidity_mint.cpi_handle(), + to: context.accounts.repay_liquidity_vault.cpi_handle_mut(), + authority: context.accounts.liquidator.cpi_handle(), }, ), repay, @@ -153,17 +154,17 @@ pub fn handle_liquidate_obligation( let seeds: [&[u8]; 4] = [OBLIGATION_SEED, lending_market.as_ref(), owner.as_ref(), &bump]; transfer_checked( CpiContext::new_with_signer( - context.accounts.token_program.key(), + context.accounts.token_program.address(), TransferChecked { - from: context.accounts.obligation_collateral_vault.to_account_info(), - mint: context.accounts.collateral_share_mint.to_account_info(), - to: context.accounts.liquidator_collateral_dest.to_account_info(), - authority: context.accounts.obligation.to_account_info(), + from: context.accounts.obligation_collateral_vault.cpi_handle_mut(), + mint: context.accounts.collateral_share_mint.cpi_handle(), + to: context.accounts.liquidator_collateral_dest.cpi_handle_mut(), + authority: context.accounts.obligation.cpi_handle(), }, &[&seeds], ), seize_shares, - context.accounts.collateral_share_mint.decimals, + context.accounts.collateral_share_mint.decimals(), )?; Ok(()) @@ -172,52 +173,52 @@ pub fn handle_liquidate_obligation( // Liquidation touches 13 accounts; every Account/InterfaceAccount is boxed so // account deserialization happens on the heap and stays within the BPF stack frame. #[derive(Accounts)] -pub struct LiquidateObligation<'info> { +pub struct LiquidateObligation { #[account(mut)] - pub obligation: Box>, + pub obligation: Box>, - pub liquidator: Signer<'info>, + pub liquidator: Signer, #[account( mut, constraint = repay_reserve.lending_market == obligation.lending_market @ LendingError::MarketMismatch, )] - pub repay_reserve: Box>, + pub repay_reserve: Box>, #[account( constraint = collateral_reserve.lending_market == obligation.lending_market @ LendingError::MarketMismatch, )] - pub collateral_reserve: Box>, + pub collateral_reserve: Box>, #[account(address = repay_reserve.price_feed)] - pub repay_price_feed: Box>, + pub repay_price_feed: Box>, #[account(address = collateral_reserve.price_feed)] - pub collateral_price_feed: Box>, + pub collateral_price_feed: Box>, #[account(address = repay_reserve.liquidity_mint)] - pub repay_liquidity_mint: Box>, + pub repay_liquidity_mint: Box>, #[account(address = collateral_reserve.share_mint)] - pub collateral_share_mint: Box>, + pub collateral_share_mint: Box>, #[account(mut, address = repay_reserve.liquidity_vault)] - pub repay_liquidity_vault: Box>, + pub repay_liquidity_vault: Box>, #[account( mut, - seeds = [OBLIGATION_SHARE_VAULT_SEED, collateral_reserve.key().as_ref(), obligation.key().as_ref()], + seeds = [OBLIGATION_SHARE_VAULT_SEED, collateral_reserve.address().as_ref(), obligation.address().as_ref()], bump, token::mint = collateral_share_mint, token::authority = obligation, )] - pub obligation_collateral_vault: Box>, + pub obligation_collateral_vault: Box>, #[account(mut)] - pub liquidator_repay_source: Box>, + pub liquidator_repay_source: Box>, #[account(mut)] - pub liquidator_collateral_dest: Box>, + pub liquidator_collateral_dest: Box>, - pub token_program: Interface<'info, TokenInterface>, + pub token_program: Interface<'static, TokenInterface>, } diff --git a/finance/lending/anchor/programs/lending/src/instructions/redeem_reserve_collateral.rs b/finance/lending/anchor/programs/lending/src/instructions/redeem_reserve_collateral.rs index 459b56cf8..8b2a85c1f 100644 --- a/finance/lending/anchor/programs/lending/src/instructions/redeem_reserve_collateral.rs +++ b/finance/lending/anchor/programs/lending/src/instructions/redeem_reserve_collateral.rs @@ -12,7 +12,7 @@ use crate::state::{reserve_signer_seeds, Reserve}; /// keeps any rounding dust. Capped by the reserve's available (un-borrowed) /// liquidity. pub fn handle_redeem_reserve_collateral( - context: Context, + context: &mut Context, share_amount: u64, ) -> Result<()> { require!(share_amount > 0, LendingError::ZeroAmount); @@ -43,11 +43,11 @@ pub fn handle_redeem_reserve_collateral( burn( CpiContext::new( - context.accounts.token_program.key(), + context.accounts.token_program.address(), Burn { - mint: context.accounts.share_mint.to_account_info(), - from: context.accounts.user_share.to_account_info(), - authority: context.accounts.owner.to_account_info(), + mint: context.accounts.share_mint.cpi_handle_mut(), + from: context.accounts.user_share.cpi_handle_mut(), + authority: context.accounts.owner.cpi_handle(), }, ), share_amount, @@ -57,12 +57,12 @@ pub fn handle_redeem_reserve_collateral( let seeds = reserve_signer_seeds(&reserve.lending_market, &reserve.liquidity_mint, &bump); transfer_checked( CpiContext::new_with_signer( - context.accounts.token_program.key(), + context.accounts.token_program.address(), TransferChecked { - from: context.accounts.liquidity_vault.to_account_info(), - mint: context.accounts.liquidity_mint.to_account_info(), - to: context.accounts.user_liquidity.to_account_info(), - authority: reserve.to_account_info(), + from: context.accounts.liquidity_vault.cpi_handle_mut(), + mint: context.accounts.liquidity_mint.cpi_handle(), + to: context.accounts.user_liquidity.cpi_handle_mut(), + authority: reserve.cpi_handle(), }, &[&seeds], ), @@ -74,30 +74,30 @@ pub fn handle_redeem_reserve_collateral( } #[derive(Accounts)] -pub struct RedeemReserveCollateral<'info> { +pub struct RedeemReserveCollateral { #[account( mut, has_one = liquidity_mint, has_one = liquidity_vault, has_one = share_mint, )] - pub reserve: Account<'info, Reserve>, + pub reserve: BorshAccount, - pub liquidity_mint: InterfaceAccount<'info, Mint>, + pub liquidity_mint: InterfaceAccount, #[account(mut)] - pub liquidity_vault: InterfaceAccount<'info, TokenAccount>, + pub liquidity_vault: InterfaceAccount, #[account(mut)] - pub share_mint: InterfaceAccount<'info, Mint>, + pub share_mint: InterfaceAccount, #[account(mut)] - pub user_liquidity: InterfaceAccount<'info, TokenAccount>, + pub user_liquidity: InterfaceAccount, #[account(mut)] - pub user_share: InterfaceAccount<'info, TokenAccount>, + pub user_share: InterfaceAccount, - pub owner: Signer<'info>, + pub owner: Signer, - pub token_program: Interface<'info, TokenInterface>, + pub token_program: Interface<'static, TokenInterface>, } diff --git a/finance/lending/anchor/programs/lending/src/instructions/refresh_obligation.rs b/finance/lending/anchor/programs/lending/src/instructions/refresh_obligation.rs index 21460be55..28924df08 100644 --- a/finance/lending/anchor/programs/lending/src/instructions/refresh_obligation.rs +++ b/finance/lending/anchor/programs/lending/src/instructions/refresh_obligation.rs @@ -15,11 +15,11 @@ use crate::state::{Obligation, PriceFeed, Reserve}; /// /// Collateral value is floored and debt value is ceiled, so health is always /// evaluated conservatively against the borrower. -pub fn handle_refresh_obligation(context: Context) -> Result<()> { +pub fn handle_refresh_obligation(context: &mut Context) -> Result<()> { let slot = Clock::get()?.slot; let obligation = &mut context.accounts.obligation; let lending_market = obligation.lending_market; - let accounts = context.remaining_accounts; + let accounts = context.remaining_accounts(); let mut cursor = 0usize; let mut deposited_value: u128 = 0; @@ -96,10 +96,10 @@ pub fn handle_refresh_obligation(context: Context) -> Result< /// obligation's lending market, and that both the reserve (refreshed this /// slot) and the price (fresh) are usable. fn read_pair<'a, 'info>( - accounts: &'a [AccountInfo<'info>], + accounts: &'a [AccountView], cursor: &mut usize, - expected_reserve: Pubkey, - lending_market: Pubkey, + expected_reserve: Address, + lending_market: Address, slot: u64, ) -> Result<(Reserve, u128)> where @@ -114,7 +114,7 @@ where *cursor += 2; require_keys_eq!( - reserve_info.key(), + reserve_info.address(), expected_reserve, LendingError::InvalidObligationAccount ); @@ -127,7 +127,7 @@ where reserve.require_refreshed()?; require_keys_eq!( - price_info.key(), + price_info.address(), reserve.price_feed, LendingError::InvalidObligationAccount ); @@ -138,7 +138,7 @@ where } #[derive(Accounts)] -pub struct RefreshObligation<'info> { +pub struct RefreshObligation { #[account(mut)] - pub obligation: Account<'info, Obligation>, + pub obligation: BorshAccount, } diff --git a/finance/lending/anchor/programs/lending/src/instructions/refresh_reserve.rs b/finance/lending/anchor/programs/lending/src/instructions/refresh_reserve.rs index 151f095ad..e2770d14a 100644 --- a/finance/lending/anchor/programs/lending/src/instructions/refresh_reserve.rs +++ b/finance/lending/anchor/programs/lending/src/instructions/refresh_reserve.rs @@ -5,12 +5,12 @@ use crate::state::Reserve; /// Accrue interest up to the current slot. Must run (as its own instruction in /// the same transaction) before any handler that reads the reserve's value, and /// before `refresh_obligation` for any reserve the obligation touches. -pub fn handle_refresh_reserve(context: Context) -> Result<()> { +pub fn handle_refresh_reserve(context: &mut Context) -> Result<()> { context.accounts.reserve.accrue_interest(Clock::get()?.slot) } #[derive(Accounts)] -pub struct RefreshReserve<'info> { +pub struct RefreshReserve { #[account(mut)] - pub reserve: Account<'info, Reserve>, + pub reserve: BorshAccount, } diff --git a/finance/lending/anchor/programs/lending/src/instructions/repay_obligation_liquidity.rs b/finance/lending/anchor/programs/lending/src/instructions/repay_obligation_liquidity.rs index d71a4217b..b11bec0f1 100644 --- a/finance/lending/anchor/programs/lending/src/instructions/repay_obligation_liquidity.rs +++ b/finance/lending/anchor/programs/lending/src/instructions/repay_obligation_liquidity.rs @@ -13,17 +13,17 @@ use crate::state::{Obligation, Reserve}; /// borrower rather than being forgiven by rounding. Anyone may repay on behalf /// of an obligation, so there is no owner check. pub fn handle_repay_obligation_liquidity( - context: Context, + context: &mut Context, liquidity_amount: u64, ) -> Result<()> { require!(liquidity_amount > 0, LendingError::ZeroAmount); - let reserve_key = context.accounts.reserve.key(); + let reserve_key = context.accounts.reserve.address(); context.accounts.reserve.require_refreshed()?; let index = context.accounts.reserve.borrow_accumulation_factor; let decimals = context.accounts.reserve.liquidity_decimals; - let borrow_index = context.accounts.obligation.find_borrow(reserve_key)?; + let borrow_index = context.accounts.obligation.find_borrow(*reserve_key)?; let borrowed_principal = context.accounts.obligation.borrows[borrow_index].borrowed_principal; let debt_now = mul_div_ceil(borrowed_principal, index, FIXED_POINT_SCALE)?; @@ -58,12 +58,12 @@ pub fn handle_repay_obligation_liquidity( transfer_checked( CpiContext::new( - context.accounts.token_program.key(), + context.accounts.token_program.address(), TransferChecked { - from: context.accounts.user_liquidity.to_account_info(), - mint: context.accounts.liquidity_mint.to_account_info(), - to: context.accounts.liquidity_vault.to_account_info(), - authority: context.accounts.repayer.to_account_info(), + from: context.accounts.user_liquidity.cpi_handle_mut(), + mint: context.accounts.liquidity_mint.cpi_handle(), + to: context.accounts.liquidity_vault.cpi_handle_mut(), + authority: context.accounts.repayer.cpi_handle(), }, ), repay, @@ -74,9 +74,9 @@ pub fn handle_repay_obligation_liquidity( } #[derive(Accounts)] -pub struct RepayObligationLiquidity<'info> { +pub struct RepayObligationLiquidity { #[account(mut)] - pub obligation: Account<'info, Obligation>, + pub obligation: BorshAccount, #[account( mut, @@ -84,17 +84,17 @@ pub struct RepayObligationLiquidity<'info> { has_one = liquidity_vault, constraint = reserve.lending_market == obligation.lending_market @ LendingError::MarketMismatch, )] - pub reserve: Account<'info, Reserve>, + pub reserve: BorshAccount, - pub liquidity_mint: InterfaceAccount<'info, Mint>, + pub liquidity_mint: InterfaceAccount, #[account(mut)] - pub liquidity_vault: InterfaceAccount<'info, TokenAccount>, + pub liquidity_vault: InterfaceAccount, #[account(mut)] - pub user_liquidity: InterfaceAccount<'info, TokenAccount>, + pub user_liquidity: InterfaceAccount, - pub repayer: Signer<'info>, + pub repayer: Signer, - pub token_program: Interface<'info, TokenInterface>, + pub token_program: Interface<'static, TokenInterface>, } diff --git a/finance/lending/anchor/programs/lending/src/instructions/withdraw_obligation_collateral.rs b/finance/lending/anchor/programs/lending/src/instructions/withdraw_obligation_collateral.rs index a38860b85..11efd5b1f 100644 --- a/finance/lending/anchor/programs/lending/src/instructions/withdraw_obligation_collateral.rs +++ b/finance/lending/anchor/programs/lending/src/instructions/withdraw_obligation_collateral.rs @@ -1,4 +1,5 @@ use anchor_lang::prelude::*; +use anchor_spl::token; use anchor_spl::token_interface::{ transfer_checked, Mint, TokenAccount, TokenInterface, TransferChecked, }; @@ -13,7 +14,7 @@ use crate::state::{Obligation, PriceFeed, Reserve}; /// value is simulated and the withdraw is rejected if the existing debt would /// exceed it. pub fn handle_withdraw_obligation_collateral( - context: Context, + context: &mut Context, share_amount: u64, ) -> Result<()> { require!(share_amount > 0, LendingError::ZeroAmount); @@ -25,7 +26,7 @@ pub fn handle_withdraw_obligation_collateral( let price_scaled = context.accounts.price_feed.price_scaled(slot)?; let obligation = &mut context.accounts.obligation; - let index = obligation.find_collateral(reserve.key())?; + let index = obligation.find_collateral(*reserve.address())?; require!( obligation.deposits[index].deposited_shares >= share_amount, LendingError::WithdrawTooLarge @@ -85,51 +86,51 @@ pub fn handle_withdraw_obligation_collateral( ]; transfer_checked( CpiContext::new_with_signer( - context.accounts.token_program.key(), + context.accounts.token_program.address(), TransferChecked { - from: context.accounts.obligation_share_vault.to_account_info(), - mint: context.accounts.share_mint.to_account_info(), - to: context.accounts.user_share.to_account_info(), - authority: obligation.to_account_info(), + from: context.accounts.obligation_share_vault.cpi_handle_mut(), + mint: context.accounts.share_mint.cpi_handle(), + to: context.accounts.user_share.cpi_handle_mut(), + authority: obligation.cpi_handle(), }, &[&seeds], ), share_amount, - context.accounts.share_mint.decimals, + context.accounts.share_mint.decimals(), )?; Ok(()) } #[derive(Accounts)] -pub struct WithdrawObligationCollateral<'info> { +pub struct WithdrawObligationCollateral { #[account(mut, has_one = owner)] - pub obligation: Account<'info, Obligation>, + pub obligation: BorshAccount, - pub owner: Signer<'info>, + pub owner: Signer, #[account( has_one = share_mint, has_one = price_feed, constraint = reserve.lending_market == obligation.lending_market @ LendingError::MarketMismatch, )] - pub reserve: Account<'info, Reserve>, + pub reserve: BorshAccount, - pub price_feed: Account<'info, PriceFeed>, + pub price_feed: BorshAccount, - pub share_mint: InterfaceAccount<'info, Mint>, + pub share_mint: InterfaceAccount, #[account( mut, - seeds = [OBLIGATION_SHARE_VAULT_SEED, reserve.key().as_ref(), obligation.key().as_ref()], + seeds = [OBLIGATION_SHARE_VAULT_SEED, reserve.address().as_ref(), obligation.address().as_ref()], bump, token::mint = share_mint, token::authority = obligation, )] - pub obligation_share_vault: InterfaceAccount<'info, TokenAccount>, + pub obligation_share_vault: InterfaceAccount, #[account(mut)] - pub user_share: InterfaceAccount<'info, TokenAccount>, + pub user_share: InterfaceAccount, - pub token_program: Interface<'info, TokenInterface>, + pub token_program: Interface<'static, TokenInterface>, } diff --git a/finance/lending/anchor/programs/lending/src/lib.rs b/finance/lending/anchor/programs/lending/src/lib.rs index 462b3be7f..ba2c018f6 100644 --- a/finance/lending/anchor/programs/lending/src/lib.rs +++ b/finance/lending/anchor/programs/lending/src/lib.rs @@ -16,91 +16,91 @@ pub mod lending { use super::*; pub fn initialize_lending_market( - context: Context, + context: &mut Context, market_id: u64, ) -> Result<()> { instructions::handle_initialize_lending_market(context, market_id) } - pub fn initialize_reserve(context: Context, config: ReserveConfig) -> Result<()> { + pub fn initialize_reserve(context: &mut Context, config: ReserveConfig) -> Result<()> { instructions::handle_initialize_reserve(context, config) } pub fn update_reserve_config( - context: Context, + context: &mut Context, config: ReserveConfig, ) -> Result<()> { instructions::handle_update_reserve_config(context, config) } - pub fn collect_protocol_fees(context: Context) -> Result<()> { + pub fn collect_protocol_fees(context: &mut Context) -> Result<()> { instructions::handle_collect_protocol_fees(context) } pub fn set_price( - context: Context, + context: &mut Context, price_mantissa: i128, exponent: i32, ) -> Result<()> { instructions::handle_set_price(context, price_mantissa, exponent) } - pub fn refresh_reserve(context: Context) -> Result<()> { + pub fn refresh_reserve(context: &mut Context) -> Result<()> { instructions::handle_refresh_reserve(context) } pub fn deposit_reserve_liquidity( - context: Context, + context: &mut Context, liquidity_amount: u64, ) -> Result<()> { instructions::handle_deposit_reserve_liquidity(context, liquidity_amount) } pub fn redeem_reserve_collateral( - context: Context, + context: &mut Context, share_amount: u64, ) -> Result<()> { instructions::handle_redeem_reserve_collateral(context, share_amount) } - pub fn initialize_obligation(context: Context) -> Result<()> { + pub fn initialize_obligation(context: &mut Context) -> Result<()> { instructions::handle_initialize_obligation(context) } - pub fn refresh_obligation(context: Context) -> Result<()> { + pub fn refresh_obligation(context: &mut Context) -> Result<()> { instructions::handle_refresh_obligation(context) } pub fn deposit_obligation_collateral( - context: Context, + context: &mut Context, share_amount: u64, ) -> Result<()> { instructions::handle_deposit_obligation_collateral(context, share_amount) } pub fn withdraw_obligation_collateral( - context: Context, + context: &mut Context, share_amount: u64, ) -> Result<()> { instructions::handle_withdraw_obligation_collateral(context, share_amount) } pub fn borrow_obligation_liquidity( - context: Context, + context: &mut Context, liquidity_amount: u64, ) -> Result<()> { instructions::handle_borrow_obligation_liquidity(context, liquidity_amount) } pub fn repay_obligation_liquidity( - context: Context, + context: &mut Context, liquidity_amount: u64, ) -> Result<()> { instructions::handle_repay_obligation_liquidity(context, liquidity_amount) } pub fn liquidate_obligation( - context: Context, + context: &mut Context, liquidity_amount: u64, ) -> Result<()> { instructions::handle_liquidate_obligation(context, liquidity_amount) diff --git a/finance/lending/anchor/programs/lending/src/state/lending_market.rs b/finance/lending/anchor/programs/lending/src/state/lending_market.rs index 387dff1d6..bc9872d4c 100644 --- a/finance/lending/anchor/programs/lending/src/state/lending_market.rs +++ b/finance/lending/anchor/programs/lending/src/state/lending_market.rs @@ -2,7 +2,7 @@ use anchor_lang::prelude::*; /// Top-level configuration shared by every reserve and obligation under it. /// The owner is the only account that may create reserves and change their config. -#[account] +#[account(borsh)] #[derive(InitSpace)] pub struct LendingMarket { /// Index this market's PDA is derived from (`["lending_market", market_id]`). @@ -11,12 +11,12 @@ pub struct LendingMarket { /// Distinct markets (0, 1, 2 …) give independent, risk-isolated pools. pub market_id: u64, - pub owner: Pubkey, + pub owner: Address, /// The mint that obligation values are denominated in (for example USDC). /// Stored for reference; valuations come from each reserve's own price feed, /// which must report prices in this currency. - pub quote_currency_mint: Pubkey, + pub quote_currency_mint: Address, pub bump: u8, } diff --git a/finance/lending/anchor/programs/lending/src/state/obligation.rs b/finance/lending/anchor/programs/lending/src/state/obligation.rs index 20be5b92d..4fda3eda1 100644 --- a/finance/lending/anchor/programs/lending/src/state/obligation.rs +++ b/finance/lending/anchor/programs/lending/src/state/obligation.rs @@ -6,12 +6,12 @@ use crate::errors::LendingError; /// A borrower's position in one lending market: the share-token collateral they /// have posted and the liquidity they have borrowed, plus the cached quote- /// currency valuations that `refresh_obligation` recomputes. -#[account] +#[account(borsh)] #[derive(InitSpace)] pub struct Obligation { - pub lending_market: Pubkey, + pub lending_market: Address, - pub owner: Pubkey, + pub owner: Address, pub last_update_slot: u64, @@ -44,14 +44,14 @@ pub struct Obligation { #[derive(InitSpace, Clone, Copy, AnchorSerialize, AnchorDeserialize, Debug, Default)] pub struct ObligationCollateral { - pub reserve: Pubkey, + pub reserve: Address, pub deposited_shares: u64, pub market_value: u128, } #[derive(InitSpace, Clone, Copy, AnchorSerialize, AnchorDeserialize, Debug, Default)] pub struct ObligationLiquidity { - pub reserve: Pubkey, + pub reserve: Address, /// Borrowed principal, scaled by the reserve's index at borrow time so the /// live debt grows automatically as that index advances: /// `debt = borrowed_principal * reserve.borrow_accumulation_factor / FIXED_POINT_SCALE`. @@ -74,7 +74,7 @@ impl Obligation { /// Index of the collateral entry for `reserve`, creating an empty one if the /// obligation has room. Used when posting collateral. - pub fn upsert_collateral(&mut self, reserve: Pubkey) -> Result { + pub fn upsert_collateral(&mut self, reserve: Address) -> Result { if let Some(index) = self.deposits.iter().position(|entry| entry.reserve == reserve) { return Ok(index); } @@ -92,7 +92,7 @@ impl Obligation { /// Index of the borrow entry for `reserve`, creating an empty one if the /// obligation has room. Used when borrowing. - pub fn upsert_borrow(&mut self, reserve: Pubkey) -> Result { + pub fn upsert_borrow(&mut self, reserve: Address) -> Result { if let Some(index) = self.borrows.iter().position(|entry| entry.reserve == reserve) { return Ok(index); } @@ -108,14 +108,14 @@ impl Obligation { Ok(self.borrows.len() - 1) } - pub fn find_collateral(&self, reserve: Pubkey) -> Result { + pub fn find_collateral(&self, reserve: Address) -> Result { self.deposits .iter() .position(|entry| entry.reserve == reserve) .ok_or(LendingError::ReserveNotFound.into()) } - pub fn find_borrow(&self, reserve: Pubkey) -> Result { + pub fn find_borrow(&self, reserve: Address) -> Result { self.borrows .iter() .position(|entry| entry.reserve == reserve) diff --git a/finance/lending/anchor/programs/lending/src/state/price_feed.rs b/finance/lending/anchor/programs/lending/src/state/price_feed.rs index 6d7cc8b82..4f2cc3b84 100644 --- a/finance/lending/anchor/programs/lending/src/state/price_feed.rs +++ b/finance/lending/anchor/programs/lending/src/state/price_feed.rs @@ -19,13 +19,13 @@ use crate::math::price_mantissa_to_scaled; /// `set_price` handler writes it directly so LiteSVM tests are deterministic. /// A production read should also reject results whose confidence interval is /// too wide; this stand-in has no confidence field to check. -#[account] +#[account(borsh)] #[derive(InitSpace)] pub struct PriceFeed { /// The lending market this feed serves; part of the PDA seeds. - pub market: Pubkey, + pub market: Address, - pub mint: Pubkey, + pub mint: Address, pub price_mantissa: i128, diff --git a/finance/lending/anchor/programs/lending/src/state/reserve.rs b/finance/lending/anchor/programs/lending/src/state/reserve.rs index c6648c956..065714405 100644 --- a/finance/lending/anchor/programs/lending/src/state/reserve.rs +++ b/finance/lending/anchor/programs/lending/src/state/reserve.rs @@ -7,8 +7,8 @@ use crate::math::{mul_div_ceil, mul_div_floor}; /// Signer seeds for a reserve PDA, which is the authority over its liquidity /// vault and the mint authority of its share token. pub fn reserve_signer_seeds<'a>( - lending_market: &'a Pubkey, - liquidity_mint: &'a Pubkey, + lending_market: &'a Address, + liquidity_mint: &'a Address, bump: &'a [u8; 1], ) -> [&'a [u8]; 4] { [ @@ -23,22 +23,22 @@ pub fn reserve_signer_seeds<'a>( /// `liquidity_vault` and receive share tokens (`share_mint`); the share-to- /// liquidity exchange rate rises as borrowers pay interest. Borrowers draw /// `liquidity_mint` out against collateral held in their obligation. -#[account] +#[account(borsh)] #[derive(InitSpace)] pub struct Reserve { - pub lending_market: Pubkey, + pub lending_market: Address, - pub liquidity_mint: Pubkey, + pub liquidity_mint: Address, /// Program-owned token account holding the un-borrowed liquidity. Its /// authority is this reserve PDA. - pub liquidity_vault: Pubkey, + pub liquidity_vault: Address, /// Share-token mint. Supply equals `share_mint_supply`. Mint authority is /// this reserve PDA. - pub share_mint: Pubkey, + pub share_mint: Address, - pub price_feed: Pubkey, + pub price_feed: Address, pub liquidity_decimals: u8, diff --git a/finance/order-book/anchor/programs/order-book/Cargo.toml b/finance/order-book/anchor/programs/order-book/Cargo.toml index e2aabd9aa..235b4946b 100644 --- a/finance/order-book/anchor/programs/order-book/Cargo.toml +++ b/finance/order-book/anchor/programs/order-book/Cargo.toml @@ -14,14 +14,22 @@ cpi = ["no-entrypoint"] no-entrypoint = [] no-idl = [] no-log-ix-name = [] -idl-build = ["anchor-lang/idl-build", "anchor-spl/idl-build"] +idl-build = ["anchor-lang/idl-build"] anchor-debug = [] custom-heap = [] custom-panic = [] [dependencies] -anchor-lang = "1.1.2" -anchor-spl = "1.1.2" +anchor-lang = { version = "2.0.0-rc.1", features = ["compat"] } +# The `#[program]` macro expands to `wincode` paths for instruction-data +# (de)serialization, so the crate has to be a direct dependency. +wincode = { version = "0.5", features = ["derive"] } +# anchor-lang 2.0.0-rc.1 is built against wincode 0.5, but solana-address 2.7 +# moved to wincode 0.6. With both in the graph, `Address`'s wincode impls +# belong to the version the account derives are not using and every +# `SchemaRead`/`SchemaWrite` bound fails. 2.6.1 is the last 0.5-line release. +solana-address = ">=2.6, <2.7" +anchor-spl = "2.0.0-rc.1" # Used by the ported Openbook slab - `bytemuck::Pod` / `Zeroable` on every node # variant + `min_const_generics` so `[AnyNode; 1024]` can derive Pod without # hitting bytemuck's default-32 array cap. `static_assertions` keeps the slab diff --git a/finance/order-book/anchor/programs/order-book/src/instructions/admin/withdraw_fees.rs b/finance/order-book/anchor/programs/order-book/src/instructions/admin/withdraw_fees.rs index 2afc31834..8b4757484 100644 --- a/finance/order-book/anchor/programs/order-book/src/instructions/admin/withdraw_fees.rs +++ b/finance/order-book/anchor/programs/order-book/src/instructions/admin/withdraw_fees.rs @@ -11,15 +11,15 @@ use crate::state::{Market, MARKET_SEED}; /// the fee vault. Transfers the current balance of the fee vault in full; /// a partial-withdraw flavour could take an amount parameter, left out here /// to keep the example focused. -pub fn handle_withdraw_fees(context: Context) -> Result<()> { +pub fn handle_withdraw_fees(context: &mut Context) -> Result<()> { let market = &context.accounts.market; require!( - context.accounts.authority.key() == market.authority, + context.accounts.authority.address() == market.authority, ErrorCode::NotMarketAuthority ); - let fee_balance = context.accounts.fee_vault.amount; + let fee_balance = context.accounts.fee_vault.amount(); if fee_balance == 0 { // Nothing to do - exit quietly rather than failing, so this // instruction is safe to call on a cron/heartbeat even when there @@ -38,40 +38,40 @@ pub fn handle_withdraw_fees(context: Context) -> transfer_checked( CpiContext::new_with_signer( - context.accounts.token_program.key(), + context.accounts.token_program.address(), TransferChecked { - from: context.accounts.fee_vault.to_account_info(), - mint: context.accounts.quote_mint.to_account_info(), - to: context.accounts.authority_quote_account.to_account_info(), - authority: market.to_account_info(), + from: context.accounts.fee_vault.cpi_handle_mut(), + mint: context.accounts.quote_mint.cpi_handle(), + to: context.accounts.authority_quote_account.cpi_handle_mut(), + authority: market.cpi_handle(), }, signer_seeds, ), fee_balance, - context.accounts.quote_mint.decimals, + context.accounts.quote_mint.decimals(), )?; Ok(()) } #[derive(Accounts)] -pub struct WithdrawFeesAccountConstraints<'info> { +pub struct WithdrawFeesAccountConstraints { #[account( mut, has_one = fee_vault @ ErrorCode::InvalidFeeVault, )] - pub market: Account<'info, Market>, + pub market: BorshAccount, // Boxed to keep the struct under the BPF stack limit (see PlaceOrderAccountConstraints). #[account(mut)] - pub fee_vault: Box>, + pub fee_vault: Box>, #[account(mut)] - pub authority_quote_account: Box>, + pub authority_quote_account: Box>, - pub quote_mint: Box>, + pub quote_mint: Box>, - pub authority: Signer<'info>, + pub authority: Signer, - pub token_program: Interface<'info, TokenInterface>, + pub token_program: Interface<'static, TokenInterface>, } diff --git a/finance/order-book/anchor/programs/order-book/src/instructions/cancel_order.rs b/finance/order-book/anchor/programs/order-book/src/instructions/cancel_order.rs index e03064d12..d68e3fc58 100644 --- a/finance/order-book/anchor/programs/order-book/src/instructions/cancel_order.rs +++ b/finance/order-book/anchor/programs/order-book/src/instructions/cancel_order.rs @@ -6,11 +6,11 @@ use crate::state::{ MarketUser, ORDER_SEED, MARKET_USER_SEED, }; -pub fn handle_cancel_order(context: Context) -> Result<()> { +pub fn handle_cancel_order(context: &mut Context) -> Result<()> { let order = &mut context.accounts.order; require!( - order.owner == context.accounts.owner.key(), + order.owner == *context.accounts.owner.address(), ErrorCode::Unauthorized ); @@ -72,27 +72,27 @@ pub fn handle_cancel_order(context: Context) -> R } #[derive(Accounts)] -pub struct CancelOrderAccountConstraints<'info> { +pub struct CancelOrderAccountConstraints { #[account(has_one = order_book @ ErrorCode::InvalidOrderBook)] - pub market: Account<'info, Market>, + pub market: BorshAccount, // Not a PDA (see initialize_market.rs); bound to `market` via has_one. #[account(mut)] - pub order_book: AccountLoader<'info, OrderBook>, + pub order_book: AccountLoader, #[account( mut, - seeds = [ORDER_SEED, market.key().as_ref(), order.order_id.to_le_bytes().as_ref()], + seeds = [ORDER_SEED, market.address().as_ref(), order.order_id.to_le_bytes().as_ref()], bump = order.bump )] - pub order: Account<'info, Order>, + pub order: BorshAccount, #[account( mut, - seeds = [MARKET_USER_SEED, market.key().as_ref(), owner.key().as_ref()], + seeds = [MARKET_USER_SEED, market.address().as_ref(), owner.address().as_ref()], bump = market_user.bump )] - pub market_user: Account<'info, MarketUser>, + pub market_user: BorshAccount, - pub owner: Signer<'info>, + pub owner: Signer, } diff --git a/finance/order-book/anchor/programs/order-book/src/instructions/initialize_market.rs b/finance/order-book/anchor/programs/order-book/src/instructions/initialize_market.rs index 0361fc696..1cfcc1b53 100644 --- a/finance/order-book/anchor/programs/order-book/src/instructions/initialize_market.rs +++ b/finance/order-book/anchor/programs/order-book/src/instructions/initialize_market.rs @@ -1,4 +1,5 @@ use anchor_lang::prelude::*; +use anchor_spl::token; use anchor_spl::token_interface::{Mint, TokenAccount, TokenInterface}; use crate::errors::ErrorCode; @@ -9,7 +10,7 @@ use crate::state::{Market, OrderBook, MARKET_SEED}; const MAX_FEE_BASIS_POINTS: u16 = 10_000; pub fn handle_initialize_market( - context: Context, + context: &mut Context, fee_basis_points: u16, tick_size: u64, base_lot_size: u64, @@ -26,13 +27,13 @@ pub fn handle_initialize_market( ); let market = &mut context.accounts.market; - market.authority = context.accounts.authority.key(); - market.base_mint = context.accounts.base_mint.key(); - market.quote_mint = context.accounts.quote_mint.key(); - market.base_vault = context.accounts.base_vault.key(); - market.quote_vault = context.accounts.quote_vault.key(); - market.fee_vault = context.accounts.fee_vault.key(); - market.order_book = context.accounts.order_book.key(); + market.authority = *context.accounts.authority.address(); + market.base_mint = *context.accounts.base_mint.address(); + market.quote_mint = *context.accounts.quote_mint.address(); + market.base_vault = *context.accounts.base_vault.address(); + market.quote_vault = *context.accounts.quote_vault.address(); + market.fee_vault = *context.accounts.fee_vault.address(); + market.order_book = context.accounts.order_book.address(); market.fee_basis_points = fee_basis_points; market.tick_size = tick_size; market.base_lot_size = base_lot_size; @@ -46,21 +47,21 @@ pub fn handle_initialize_market( // The order book is not a PDA (see the comment on the `order_book` // account below), so `bump` is unused and stored as 0. let mut order_book = context.accounts.order_book.load_init()?; - order_book.initialize(context.accounts.market.key(), 0); + order_book.initialize(context.accounts.market.address(), 0); Ok(()) } #[derive(Accounts)] -pub struct InitializeMarketAccountConstraints<'info> { +pub struct InitializeMarketAccountConstraints { #[account( init, payer = authority, space = Market::DISCRIMINATOR.len() + Market::INIT_SPACE, - seeds = [MARKET_SEED, base_mint.key().as_ref(), quote_mint.key().as_ref()], + seeds = [MARKET_SEED, base_mint.address().as_ref(), quote_mint.address().as_ref()], bump )] - pub market: Account<'info, Market>, + pub market: BorshAccount, // The order book is a zero-copy account (~180 KB: two 1024-slot critbit // slabs back to back). Solana's BPF runtime caps inner-CPI account @@ -80,11 +81,11 @@ pub struct InitializeMarketAccountConstraints<'info> { // account to its market via `has_one = order_book` on `market`, not via // seeds. #[account(zero)] - pub order_book: AccountLoader<'info, OrderBook>, + pub order_book: AccountLoader, - pub base_mint: InterfaceAccount<'info, Mint>, + pub base_mint: InterfaceAccount, - pub quote_mint: InterfaceAccount<'info, Mint>, + pub quote_mint: InterfaceAccount, #[account( init, @@ -93,7 +94,7 @@ pub struct InitializeMarketAccountConstraints<'info> { token::authority = market, token::token_program = token_program )] - pub base_vault: InterfaceAccount<'info, TokenAccount>, + pub base_vault: InterfaceAccount, #[account( init, @@ -102,7 +103,7 @@ pub struct InitializeMarketAccountConstraints<'info> { token::authority = market, token::token_program = token_program )] - pub quote_vault: InterfaceAccount<'info, TokenAccount>, + pub quote_vault: InterfaceAccount, // Taker fees accumulate here (quote mint). Separate from quote_vault so // maker-owed balances and market-earned fees can't be confused. @@ -113,12 +114,12 @@ pub struct InitializeMarketAccountConstraints<'info> { token::authority = market, token::token_program = token_program )] - pub fee_vault: InterfaceAccount<'info, TokenAccount>, + pub fee_vault: InterfaceAccount, #[account(mut)] - pub authority: Signer<'info>, + pub authority: Signer, - pub token_program: Interface<'info, TokenInterface>, + pub token_program: Interface<'static, TokenInterface>, - pub system_program: Program<'info, System>, + pub system_program: Program, } diff --git a/finance/order-book/anchor/programs/order-book/src/instructions/initialize_market_user.rs b/finance/order-book/anchor/programs/order-book/src/instructions/initialize_market_user.rs index 25b885c37..ccfc3f76e 100644 --- a/finance/order-book/anchor/programs/order-book/src/instructions/initialize_market_user.rs +++ b/finance/order-book/anchor/programs/order-book/src/instructions/initialize_market_user.rs @@ -2,10 +2,10 @@ use anchor_lang::prelude::*; use crate::state::{Market, MarketUser, MARKET_USER_SEED}; -pub fn handle_initialize_market_user(context: Context) -> Result<()> { +pub fn handle_initialize_market_user(context: &mut Context) -> Result<()> { let market_user = &mut context.accounts.market_user; - market_user.market = context.accounts.market.key(); - market_user.owner = context.accounts.owner.key(); + market_user.market = *context.accounts.market.address(); + market_user.owner = *context.accounts.owner.address(); market_user.unsettled_base = 0; market_user.unsettled_quote = 0; market_user.open_orders = Vec::new(); @@ -15,20 +15,20 @@ pub fn handle_initialize_market_user(context: Context { +pub struct InitializeMarketUserAccountConstraints { #[account( init, payer = owner, space = MarketUser::DISCRIMINATOR.len() + MarketUser::INIT_SPACE, - seeds = [MARKET_USER_SEED, market.key().as_ref(), owner.key().as_ref()], + seeds = [MARKET_USER_SEED, market.address().as_ref(), owner.address().as_ref()], bump )] - pub market_user: Account<'info, MarketUser>, + pub market_user: BorshAccount, - pub market: Account<'info, Market>, + pub market: BorshAccount, #[account(mut)] - pub owner: Signer<'info>, + pub owner: Signer, - pub system_program: Program<'info, System>, + pub system_program: Program, } diff --git a/finance/order-book/anchor/programs/order-book/src/instructions/place_order.rs b/finance/order-book/anchor/programs/order-book/src/instructions/place_order.rs index a00ccfbfa..ec5bb2f36 100644 --- a/finance/order-book/anchor/programs/order-book/src/instructions/place_order.rs +++ b/finance/order-book/anchor/programs/order-book/src/instructions/place_order.rs @@ -26,7 +26,7 @@ const BASIS_POINTS_DENOMINATOR: u128 = 10_000; const ACCOUNTS_PER_MAKER: usize = 2; pub fn handle_place_order<'info>( - context: Context<'info, PlaceOrderAccountConstraints<'info>>, + context: &mut Context<'info, PlaceOrderAccountConstraints<'info>>, side: OrderSide, price: u64, quantity: u64, @@ -61,9 +61,9 @@ pub fn handle_place_order<'info>( let (source_account, mint_account_info, decimals, transfer_amount, destination_vault) = match side { OrderSide::Bid => ( - context.accounts.user_quote_account.to_account_info(), - context.accounts.quote_mint.to_account_info(), - context.accounts.quote_mint.decimals, + context.accounts.user_quote_account.cpi_handle_mut(), + context.accounts.quote_mint.cpi_handle_mut(), + context.accounts.quote_mint.decimals(), (price as u128) .checked_mul(quantity as u128) .ok_or(ErrorCode::NumericalOverflow)? @@ -71,29 +71,29 @@ pub fn handle_place_order<'info>( .ok_or(ErrorCode::NumericalOverflow)? .try_into() .map_err(|_| error!(ErrorCode::NumericalOverflow))?, - context.accounts.quote_vault.to_account_info(), + context.accounts.quote_vault.cpi_handle_mut(), ), OrderSide::Ask => ( - context.accounts.user_base_account.to_account_info(), - context.accounts.base_mint.to_account_info(), - context.accounts.base_mint.decimals, + context.accounts.user_base_account.cpi_handle_mut(), + context.accounts.base_mint.cpi_handle_mut(), + context.accounts.base_mint.decimals(), (quantity as u128) .checked_mul(market.base_lot_size as u128) .ok_or(ErrorCode::NumericalOverflow)? .try_into() .map_err(|_| error!(ErrorCode::NumericalOverflow))?, - context.accounts.base_vault.to_account_info(), + context.accounts.base_vault.cpi_handle_mut(), ), }; transfer_checked( CpiContext::new( - context.accounts.token_program.key(), + context.accounts.token_program.address(), TransferChecked { from: source_account, mint: mint_account_info, to: destination_vault, - authority: context.accounts.owner.to_account_info(), + authority: context.accounts.owner.cpi_handle(), }, ), transfer_amount, @@ -108,7 +108,7 @@ pub fn handle_place_order<'info>( // transaction's remaining_accounts, in the same price-time-priority // order the book would walk. We plan fills against the resting tree, // then verify the caller's account list matches the plan, then apply. - let maker_accounts = &context.remaining_accounts; + let maker_accounts = &context.remaining_accounts(); require!( maker_accounts.len() % ACCOUNTS_PER_MAKER == 0, ErrorCode::MissingMakerAccounts @@ -142,7 +142,7 @@ pub fn handle_place_order<'info>( ErrorCode::MakerAccountMismatch ); require!( - maker_order.market == market.key(), + maker_order.market == *market.address(), ErrorCode::MakerAccountMismatch ); } @@ -171,7 +171,7 @@ pub fn handle_place_order<'info>( ErrorCode::MakerOwnerMismatch ); require!( - maker_market_user.market == market.key(), + maker_market_user.market == *market.address(), ErrorCode::MakerAccountMismatch ); @@ -340,17 +340,17 @@ pub fn handle_place_order<'info>( transfer_checked( CpiContext::new_with_signer( - context.accounts.token_program.key(), + context.accounts.token_program.address(), TransferChecked { - from: context.accounts.quote_vault.to_account_info(), - mint: context.accounts.quote_mint.to_account_info(), - to: context.accounts.fee_vault.to_account_info(), - authority: market.to_account_info(), + from: context.accounts.quote_vault.cpi_handle_mut(), + mint: context.accounts.quote_mint.cpi_handle(), + to: context.accounts.fee_vault.cpi_handle_mut(), + authority: market.cpi_handle(), }, signer_seeds, ), total_fee_quote, - context.accounts.quote_mint.decimals, + context.accounts.quote_mint.decimals(), )?; } @@ -388,7 +388,7 @@ pub fn handle_place_order<'info>( side, price, taker_remaining, - context.accounts.owner.key(), + context.accounts.owner.address(), id, timestamp, )?; @@ -397,8 +397,8 @@ pub fn handle_place_order<'info>( }; let order = &mut context.accounts.order; - order.market = market.key(); - order.owner = context.accounts.owner.key(); + order.market = *market.address(); + order.owner = *context.accounts.owner.address(); order.order_id = order_id; order.side = side; order.price = price; @@ -429,7 +429,7 @@ pub fn handle_place_order<'info>( #[derive(Accounts)] #[instruction(side: OrderSide, price: u64, quantity: u64)] -pub struct PlaceOrderAccountConstraints<'info> { +pub struct PlaceOrderAccountConstraints { // `has_one` ties every market-owned account on this struct to the // addresses recorded on the Market PDA. Crucially, without // has_one on base_vault / quote_vault / base_mint / quote_mint a caller @@ -445,7 +445,7 @@ pub struct PlaceOrderAccountConstraints<'info> { has_one = quote_mint @ ErrorCode::InvalidQuoteMint, has_one = order_book @ ErrorCode::InvalidOrderBook, )] - pub market: Account<'info, Market>, + pub market: BorshAccount, // Zero-copy: AccountLoader streams the slab in/out without paying // borsh (de)serialization on every instruction. See order_book.rs for @@ -454,7 +454,7 @@ pub struct PlaceOrderAccountConstraints<'info> { // `has_one = order_book` on `market` is what ties this specific account // to this specific market. #[account(mut)] - pub order_book: AccountLoader<'info, OrderBook>, + pub order_book: AccountLoader, // The order PDA seed uses the book's `next_order_id` *before* this // instruction increments it - i.e. the id this new order will receive. @@ -465,47 +465,47 @@ pub struct PlaceOrderAccountConstraints<'info> { space = Order::DISCRIMINATOR.len() + Order::INIT_SPACE, seeds = [ ORDER_SEED, - market.key().as_ref(), + market.address().as_ref(), order_book.load()?.next_order_id.to_le_bytes().as_ref() ], bump )] - pub order: Account<'info, Order>, + pub order: BorshAccount, #[account( mut, - seeds = [MARKET_USER_SEED, market.key().as_ref(), owner.key().as_ref()], + seeds = [MARKET_USER_SEED, market.address().as_ref(), owner.address().as_ref()], bump = market_user.bump )] - pub market_user: Account<'info, MarketUser>, + pub market_user: BorshAccount, // InterfaceAccount on the stack is ~1 KB each; with 7 of them this struct // blows the 4 KB stack-offset limit on BPF. Boxing moves each to the heap. #[account(mut)] - pub base_vault: Box>, + pub base_vault: Box>, #[account(mut)] - pub quote_vault: Box>, + pub quote_vault: Box>, // Taker fees are routed here. Constrained via `has_one = fee_vault` on // `market` above so the program can trust it without re-checking. #[account(mut)] - pub fee_vault: Box>, + pub fee_vault: Box>, #[account(mut)] - pub user_base_account: Box>, + pub user_base_account: Box>, #[account(mut)] - pub user_quote_account: Box>, + pub user_quote_account: Box>, - pub base_mint: Box>, + pub base_mint: Box>, - pub quote_mint: Box>, + pub quote_mint: Box>, #[account(mut)] - pub owner: Signer<'info>, + pub owner: Signer, - pub token_program: Interface<'info, TokenInterface>, + pub token_program: Interface<'static, TokenInterface>, - pub system_program: Program<'info, System>, + pub system_program: Program, } diff --git a/finance/order-book/anchor/programs/order-book/src/instructions/settle_funds.rs b/finance/order-book/anchor/programs/order-book/src/instructions/settle_funds.rs index 4a9f53ad0..c5b8b8c80 100644 --- a/finance/order-book/anchor/programs/order-book/src/instructions/settle_funds.rs +++ b/finance/order-book/anchor/programs/order-book/src/instructions/settle_funds.rs @@ -6,7 +6,7 @@ use anchor_spl::token_interface::{ use crate::errors::ErrorCode; use crate::state::{Market, MarketUser, MARKET_SEED, MARKET_USER_SEED}; -pub fn handle_settle_funds(context: Context) -> Result<()> { +pub fn handle_settle_funds(context: &mut Context) -> Result<()> { let market_user = &mut context.accounts.market_user; let market = &context.accounts.market; @@ -36,34 +36,34 @@ pub fn handle_settle_funds(context: Context) -> R if base_amount > 0 { transfer_checked( CpiContext::new_with_signer( - context.accounts.token_program.key(), + context.accounts.token_program.address(), TransferChecked { - from: context.accounts.base_vault.to_account_info(), - mint: context.accounts.base_mint.to_account_info(), - to: context.accounts.user_base_account.to_account_info(), - authority: market.to_account_info(), + from: context.accounts.base_vault.cpi_handle_mut(), + mint: context.accounts.base_mint.cpi_handle(), + to: context.accounts.user_base_account.cpi_handle_mut(), + authority: market.cpi_handle(), }, signer_seeds, ), base_amount, - context.accounts.base_mint.decimals, + context.accounts.base_mint.decimals(), )?; } if quote_amount > 0 { transfer_checked( CpiContext::new_with_signer( - context.accounts.token_program.key(), + context.accounts.token_program.address(), TransferChecked { - from: context.accounts.quote_vault.to_account_info(), - mint: context.accounts.quote_mint.to_account_info(), - to: context.accounts.user_quote_account.to_account_info(), - authority: market.to_account_info(), + from: context.accounts.quote_vault.cpi_handle_mut(), + mint: context.accounts.quote_mint.cpi_handle(), + to: context.accounts.user_quote_account.cpi_handle_mut(), + authority: market.cpi_handle(), }, signer_seeds, ), quote_amount, - context.accounts.quote_mint.decimals, + context.accounts.quote_mint.decimals(), )?; } @@ -71,7 +71,7 @@ pub fn handle_settle_funds(context: Context) -> R } #[derive(Accounts)] -pub struct SettleFundsAccountConstraints<'info> { +pub struct SettleFundsAccountConstraints { // `has_one` constraints bind these vaults/mints to the addresses stored // on the Market PDA at initialise_market time. Without them a caller // could substitute the fee_vault (same mint + same authority as @@ -85,34 +85,34 @@ pub struct SettleFundsAccountConstraints<'info> { has_one = base_mint @ ErrorCode::InvalidBaseMint, has_one = quote_mint @ ErrorCode::InvalidQuoteMint, )] - pub market: Account<'info, Market>, + pub market: BorshAccount, #[account( mut, - seeds = [MARKET_USER_SEED, market.key().as_ref(), owner.key().as_ref()], + seeds = [MARKET_USER_SEED, market.address().as_ref(), owner.address().as_ref()], bump = market_user.bump )] - pub market_user: Account<'info, MarketUser>, + pub market_user: BorshAccount, // Boxed for the same reason as in PlaceOrderAccountConstraints - // InterfaceAccount is too large to keep on the BPF stack in bulk. #[account(mut)] - pub base_vault: Box>, + pub base_vault: Box>, #[account(mut)] - pub quote_vault: Box>, + pub quote_vault: Box>, #[account(mut)] - pub user_base_account: Box>, + pub user_base_account: Box>, #[account(mut)] - pub user_quote_account: Box>, + pub user_quote_account: Box>, - pub base_mint: Box>, + pub base_mint: Box>, - pub quote_mint: Box>, + pub quote_mint: Box>, - pub owner: Signer<'info>, + pub owner: Signer, - pub token_program: Interface<'info, TokenInterface>, + pub token_program: Interface<'static, TokenInterface>, } diff --git a/finance/order-book/anchor/programs/order-book/src/lib.rs b/finance/order-book/anchor/programs/order-book/src/lib.rs index edb913309..1a03554a6 100644 --- a/finance/order-book/anchor/programs/order-book/src/lib.rs +++ b/finance/order-book/anchor/programs/order-book/src/lib.rs @@ -16,7 +16,7 @@ pub mod order_book { /// the order book PDA, and the two PDA-authority vaults that hold locked /// funds while orders are open. pub fn initialize_market( - context: Context, + context: &mut Context, fee_basis_points: u16, tick_size: u64, base_lot_size: u64, @@ -35,7 +35,7 @@ pub mod order_book { /// Create a per-user, per-market account that tracks a user's open orders /// and unsettled balances. - pub fn initialize_market_user(context: Context) -> Result<()> { + pub fn initialize_market_user(context: &mut Context) -> Result<()> { instructions::initialize_market_user::handle_initialize_market_user(context) } @@ -51,7 +51,7 @@ pub mod order_book { /// `(maker_order_pda, maker_user_account_pda)`, ordered by the /// book's price-time priority (i.e. best ask first for a taker bid). pub fn place_order<'info>( - context: Context<'info, PlaceOrderAccountConstraints<'info>>, + context: &mut Context<'info, PlaceOrderAccountConstraints<'info>>, side: state::OrderSide, price: u64, quantity: u64, @@ -62,19 +62,19 @@ pub mod order_book { /// Cancel an open (or partially filled) order. Credits the remaining /// locked amount back to the owner's unsettled balance; the actual token /// transfer happens on settle_funds. - pub fn cancel_order(context: Context) -> Result<()> { + pub fn cancel_order(context: &mut Context) -> Result<()> { instructions::cancel_order::handle_cancel_order(context) } /// Move accumulated unsettled balances out of the market vault and into /// the user's token accounts. No-op if both balances are zero. - pub fn settle_funds(context: Context) -> Result<()> { + pub fn settle_funds(context: &mut Context) -> Result<()> { instructions::settle_funds::handle_settle_funds(context) } /// Drain the fee vault into the market authority's token account. /// Authority-gated - only the market's stored `authority` may call this. - pub fn withdraw_fees(context: Context) -> Result<()> { + pub fn withdraw_fees(context: &mut Context) -> Result<()> { instructions::withdraw_fees::handle_withdraw_fees(context) } } diff --git a/finance/order-book/anchor/programs/order-book/src/state/market.rs b/finance/order-book/anchor/programs/order-book/src/state/market.rs index f8db55790..9198a8d66 100644 --- a/finance/order-book/anchor/programs/order-book/src/state/market.rs +++ b/finance/order-book/anchor/programs/order-book/src/state/market.rs @@ -6,26 +6,26 @@ pub const MARKET_SEED: &[u8] = b"market"; // The market PDA itself is the authority of the token vaults, so funds can only // move out via program-signed CPIs (place/cancel/settle). #[derive(InitSpace)] -#[account] +#[account(borsh)] pub struct Market { - pub authority: Pubkey, + pub authority: Address, - pub base_mint: Pubkey, + pub base_mint: Address, - pub quote_mint: Pubkey, + pub quote_mint: Address, - pub base_vault: Pubkey, + pub base_vault: Address, - pub quote_vault: Pubkey, + pub quote_vault: Address, // Dedicated token account (quote mint) that accumulates taker fees. // Kept separate from `quote_vault` so user-owed balances and // market-earned fees cannot be confused. The market PDA signs transfers // out of it, so only program instruction handlers (notably `withdraw_fees`) // can drain it. - pub fee_vault: Pubkey, + pub fee_vault: Address, - pub order_book: Pubkey, + pub order_book: Address, pub fee_basis_points: u16, diff --git a/finance/order-book/anchor/programs/order-book/src/state/market_user.rs b/finance/order-book/anchor/programs/order-book/src/state/market_user.rs index 14d80ac9b..bfef5d9e5 100644 --- a/finance/order-book/anchor/programs/order-book/src/state/market_user.rs +++ b/finance/order-book/anchor/programs/order-book/src/state/market_user.rs @@ -6,11 +6,11 @@ pub const MARKET_USER_SEED: &[u8] = b"market_user"; // to the user (unsettled_*). Settlement moves those amounts from the vaults // to the user's token accounts in settle_funds. #[derive(InitSpace)] -#[account] +#[account(borsh)] pub struct MarketUser { - pub market: Pubkey, + pub market: Address, - pub owner: Pubkey, + pub owner: Address, pub unsettled_base: u64, diff --git a/finance/order-book/anchor/programs/order-book/src/state/order.rs b/finance/order-book/anchor/programs/order-book/src/state/order.rs index 24e4d264e..86ceef9e3 100644 --- a/finance/order-book/anchor/programs/order-book/src/state/order.rs +++ b/finance/order-book/anchor/programs/order-book/src/state/order.rs @@ -17,11 +17,11 @@ pub enum OrderStatus { } #[derive(InitSpace)] -#[account] +#[account(borsh)] pub struct Order { - pub market: Pubkey, + pub market: Address, - pub owner: Pubkey, + pub owner: Address, pub order_id: u64, diff --git a/finance/order-book/anchor/programs/order-book/src/state/order_book.rs b/finance/order-book/anchor/programs/order-book/src/state/order_book.rs index 1786d5ac5..2000a2c91 100644 --- a/finance/order-book/anchor/programs/order-book/src/state/order_book.rs +++ b/finance/order-book/anchor/programs/order-book/src/state/order_book.rs @@ -28,7 +28,7 @@ pub const MAX_ORDERS_PER_SIDE: usize = MAX_TREE_NODES; pub struct OrderBook { /// Market PDA this book belongs to. Constrained onchain via the /// `market` `has_one = order_book` (and vice-versa) bindings. - pub market: Pubkey, + pub market: Address, /// Tree roots for the two sides. Kept on this struct (rather than inside /// the OrderTreeNodes blobs) so each side's `leaf_count` is cheap to read @@ -68,14 +68,14 @@ pub struct RestingOrderView { pub order_id: u64, pub price: u64, pub quantity: u64, - pub owner: Pubkey, + pub owner: Address, } impl OrderBook { /// First-time initialization. Sets the market binding, the order-id /// counter, and stamps each slab with its side tag so the iterator knows /// which way to walk. - pub fn initialize(&mut self, market: Pubkey, bump: u8) { + pub fn initialize(&mut self, market: Address, bump: u8) { self.market = market; self.bids_root = OrderTreeRoot::default(); self.asks_root = OrderTreeRoot::default(); @@ -112,7 +112,7 @@ impl OrderBook { side: OrderSide, price: u64, quantity: u64, - owner: Pubkey, + owner: Address, order_id: u64, timestamp: i64, ) -> Result<()> { diff --git a/finance/order-book/anchor/programs/order-book/src/state/slab/nodes.rs b/finance/order-book/anchor/programs/order-book/src/state/slab/nodes.rs index 2067f6eed..d9838c01b 100644 --- a/finance/order-book/anchor/programs/order-book/src/state/slab/nodes.rs +++ b/finance/order-book/anchor/programs/order-book/src/state/slab/nodes.rs @@ -163,7 +163,7 @@ pub struct LeafNode { /// Owner of this resting order. Same as the corresponding `Order` /// account's `owner`; cached here so the matching loop doesn't have to /// deserialize the maker account just to read the owner. - pub owner: Pubkey, + pub owner: Address, /// Quantity remaining (in base tokens). Decremented as fills consume the /// order; the leaf is removed when it hits 0. @@ -188,7 +188,7 @@ const_assert_eq!(size_of::() % 8, 0); impl LeafNode { pub fn new( key: u128, - owner: Pubkey, + owner: Address, quantity: u64, order_id: u64, timestamp: i64, diff --git a/finance/order-book/anchor/programs/order-book/src/state/slab/ordertree.rs b/finance/order-book/anchor/programs/order-book/src/state/slab/ordertree.rs index ea6e983e1..21641a401 100644 --- a/finance/order-book/anchor/programs/order-book/src/state/slab/ordertree.rs +++ b/finance/order-book/anchor/programs/order-book/src/state/slab/ordertree.rs @@ -278,7 +278,7 @@ impl OrderTreeNodes { let parent_contents = *self .node(parent_handle) .ok_or_else(|| error!(ErrorCode::OrderBookFull))?; - let parent_key = parent_contents.key().unwrap(); + let parent_key = parent_contents.address().unwrap(); // Exact-key collision: only possible if the existing slot is a // leaf (inner nodes' `key` is just a shared prefix). Overwrite diff --git a/finance/perpetual-futures/anchor/programs/mock-switchboard/Cargo.toml b/finance/perpetual-futures/anchor/programs/mock-switchboard/Cargo.toml index 275ec0d52..f199b8d30 100644 --- a/finance/perpetual-futures/anchor/programs/mock-switchboard/Cargo.toml +++ b/finance/perpetual-futures/anchor/programs/mock-switchboard/Cargo.toml @@ -20,7 +20,15 @@ custom-heap = [] custom-panic = [] [dependencies] -anchor-lang = "1.1.2" +anchor-lang = "2.0.0-rc.1" +# The `#[program]` macro expands to `wincode` paths for instruction-data +# (de)serialization, so the crate has to be a direct dependency. +wincode = { version = "0.5", features = ["derive"] } +# anchor-lang 2.0.0-rc.1 is built against wincode 0.5, but solana-address 2.7 +# moved to wincode 0.6. With both in the graph, `Address`'s wincode impls +# belong to the version the account derives are not using and every +# `SchemaRead`/`SchemaWrite` bound fails. 2.6.1 is the last 0.5-line release. +solana-address = ">=2.6, <2.7" [lints.rust] unexpected_cfgs = { level = "warn", check-cfg = ['cfg(target_os, values("solana"))'] } diff --git a/finance/perpetual-futures/anchor/programs/mock-switchboard/src/lib.rs b/finance/perpetual-futures/anchor/programs/mock-switchboard/src/lib.rs index 2a3f79232..a8026d835 100644 --- a/finance/perpetual-futures/anchor/programs/mock-switchboard/src/lib.rs +++ b/finance/perpetual-futures/anchor/programs/mock-switchboard/src/lib.rs @@ -24,13 +24,13 @@ pub mod mock_switchboard { /// Initialize the mock feed with an initial price. The signer becomes the /// authority allowed to push later price updates. pub fn initialize_feed( - context: Context, + context: &mut Context, price: i128, scale: u32, confidence: u64, ) -> Result<()> { let feed = &mut context.accounts.feed; - feed.authority = context.accounts.authority.key(); + feed.authority = *context.accounts.authority.address(); feed.price = price; feed.scale = scale; feed.last_update_slot = Clock::get()?.slot; @@ -43,7 +43,7 @@ pub mod mock_switchboard { /// is an authority-gated write, because the goal is to drive deterministic /// test scenarios. pub fn set_price( - context: Context, + context: &mut Context, price: i128, confidence: u64, ) -> Result<()> { @@ -56,38 +56,38 @@ pub mod mock_switchboard { } #[derive(Accounts)] -pub struct InitializeFeedAccountConstraints<'info> { +pub struct InitializeFeedAccountConstraints { #[account( init, payer = authority, space = MockFeed::DISCRIMINATOR.len() + MockFeed::INIT_SPACE, )] - pub feed: Account<'info, MockFeed>, + pub feed: BorshAccount, #[account(mut)] - pub authority: Signer<'info>, + pub authority: Signer, - pub system_program: Program<'info, System>, + pub system_program: Program, } #[derive(Accounts)] -pub struct SetPriceAccountConstraints<'info> { +pub struct SetPriceAccountConstraints { #[account( mut, has_one = authority, )] - pub feed: Account<'info, MockFeed>, + pub feed: BorshAccount, - pub authority: Signer<'info>, + pub authority: Signer, } /// Mock of a Switchboard On-Demand feed. Real feeds carry many more fields /// (median, range, sample window, signatures) — this is the bare minimum the /// perpetual-futures program needs to do a price comparison. #[derive(InitSpace)] -#[account] +#[account(borsh)] pub struct MockFeed { - pub authority: Pubkey, + pub authority: Address, /// Signed 128-bit fixed-point price. Real Switchboard prices are also i128. pub price: i128, diff --git a/finance/perpetual-futures/anchor/programs/perpetual-futures/Cargo.toml b/finance/perpetual-futures/anchor/programs/perpetual-futures/Cargo.toml index 5c8300a5d..78cc010cc 100644 --- a/finance/perpetual-futures/anchor/programs/perpetual-futures/Cargo.toml +++ b/finance/perpetual-futures/anchor/programs/perpetual-futures/Cargo.toml @@ -14,7 +14,7 @@ cpi = ["no-entrypoint"] no-entrypoint = [] no-idl = [] no-log-ix-name = [] -idl-build = ["anchor-lang/idl-build", "anchor-spl/idl-build"] +idl-build = ["anchor-lang/idl-build"] anchor-debug = [] custom-heap = [] custom-panic = [] @@ -23,8 +23,16 @@ custom-panic = [] # init-if-needed lets add_liquidity create the provider's liquidity-provider # token account on their first deposit. The provider is the payer, so this does # not let one party fund another's rent. -anchor-lang = { version = "1.1.2", features = ["init-if-needed"] } -anchor-spl = "1.1.2" +anchor-lang = "2.0.0-rc.1" +# The `#[program]` macro expands to `wincode` paths for instruction-data +# (de)serialization, so the crate has to be a direct dependency. +wincode = { version = "0.5", features = ["derive"] } +# anchor-lang 2.0.0-rc.1 is built against wincode 0.5, but solana-address 2.7 +# moved to wincode 0.6. With both in the graph, `Address`'s wincode impls +# belong to the version the account derives are not using and every +# `SchemaRead`/`SchemaWrite` bound fails. 2.6.1 is the last 0.5-line release. +solana-address = ">=2.6, <2.7" +anchor-spl = "2.0.0-rc.1" # Not used directly. Declared so Cargo feature unification turns on # `no-entrypoint` for the spl-token that anchor-spl pulls in; without it the # integration-test binary links two `entrypoint` symbols (this program's and diff --git a/finance/perpetual-futures/anchor/programs/perpetual-futures/src/instructions/add_liquidity.rs b/finance/perpetual-futures/anchor/programs/perpetual-futures/src/instructions/add_liquidity.rs index 5b174f7ec..8330e8ef6 100644 --- a/finance/perpetual-futures/anchor/programs/perpetual-futures/src/instructions/add_liquidity.rs +++ b/finance/perpetual-futures/anchor/programs/perpetual-futures/src/instructions/add_liquidity.rs @@ -12,7 +12,7 @@ use crate::instructions::shared::{liquidity_provider_aum, refresh_price_and_fund use crate::state::Pool; pub fn handle_add_liquidity( - context: Context, + context: &mut Context, amount: u64, minimum_shares_out: u64, ) -> Result<()> { @@ -53,27 +53,27 @@ pub fn handle_add_liquidity( transfer_checked( CpiContext::new( - context.accounts.token_program.key(), + context.accounts.token_program.address(), TransferChecked { - from: context.accounts.provider_collateral.to_account_info(), - mint: context.accounts.collateral_mint.to_account_info(), - to: context.accounts.custody_vault.to_account_info(), - authority: context.accounts.provider.to_account_info(), + from: context.accounts.provider_collateral.cpi_handle_mut(), + mint: context.accounts.collateral_mint.cpi_handle(), + to: context.accounts.custody_vault.cpi_handle_mut(), + authority: context.accounts.provider.cpi_handle(), }, ), amount, - context.accounts.collateral_mint.decimals, + context.accounts.collateral_mint.decimals(), )?; - let pool_key = pool.key(); + let pool_key = pool.address(); let authority_seeds: &[&[u8]] = &[AUTHORITY_SEED, pool_key.as_ref(), &[pool.authority_bump]]; mint_to( CpiContext::new_with_signer( - context.accounts.token_program.key(), + context.accounts.token_program.address(), MintTo { - mint: context.accounts.lp_mint.to_account_info(), - to: context.accounts.provider_lp.to_account_info(), - authority: context.accounts.pool_authority.to_account_info(), + mint: context.accounts.lp_mint.cpi_handle_mut(), + to: context.accounts.provider_lp.cpi_handle_mut(), + authority: context.accounts.pool_authority.cpi_handle(), }, &[authority_seeds], ), @@ -84,9 +84,9 @@ pub fn handle_add_liquidity( } #[derive(Accounts)] -pub struct AddLiquidityAccountConstraints<'info> { +pub struct AddLiquidityAccountConstraints { #[account(mut)] - pub provider: Signer<'info>, + pub provider: Signer, #[account( mut, @@ -97,29 +97,29 @@ pub struct AddLiquidityAccountConstraints<'info> { has_one = custody_vault, has_one = oracle_feed, )] - pub pool: Box>, + pub pool: Box>, /// CHECK: PDA authority over the vault and liquidity-provider mint. #[account( - seeds = [AUTHORITY_SEED, pool.key().as_ref()], + seeds = [AUTHORITY_SEED, pool.address().as_ref()], bump = pool.authority_bump, )] - pub pool_authority: UncheckedAccount<'info>, + pub pool_authority: UncheckedAccount, /// CHECK: validated by the `has_one = oracle_feed` constraint on the pool. - pub oracle_feed: UncheckedAccount<'info>, + pub oracle_feed: UncheckedAccount, - pub collateral_mint: Box>, + pub collateral_mint: Box>, #[account(mut)] - pub lp_mint: Box>, + pub lp_mint: Box>, #[account( mut, - seeds = [VAULT_SEED, pool.key().as_ref()], + seeds = [VAULT_SEED, pool.address().as_ref()], bump, )] - pub custody_vault: Box>, + pub custody_vault: Box>, #[account( mut, @@ -127,7 +127,7 @@ pub struct AddLiquidityAccountConstraints<'info> { associated_token::authority = provider, associated_token::token_program = token_program, )] - pub provider_collateral: Box>, + pub provider_collateral: Box>, #[account( init_if_needed, @@ -136,9 +136,9 @@ pub struct AddLiquidityAccountConstraints<'info> { associated_token::authority = provider, associated_token::token_program = token_program, )] - pub provider_lp: Box>, + pub provider_lp: Box>, - pub token_program: Interface<'info, TokenInterface>, - pub associated_token_program: Program<'info, AssociatedToken>, - pub system_program: Program<'info, System>, + pub token_program: Interface<'static, TokenInterface>, + pub associated_token_program: Program, + pub system_program: Program, } diff --git a/finance/perpetual-futures/anchor/programs/perpetual-futures/src/instructions/close_position.rs b/finance/perpetual-futures/anchor/programs/perpetual-futures/src/instructions/close_position.rs index fd50ea002..c748f7e22 100644 --- a/finance/perpetual-futures/anchor/programs/perpetual-futures/src/instructions/close_position.rs +++ b/finance/perpetual-futures/anchor/programs/perpetual-futures/src/instructions/close_position.rs @@ -10,7 +10,7 @@ use crate::instructions::shared::{basis_points_of, refresh_price_and_funding, se use crate::state::{Pool, Position}; pub fn handle_close_position( - context: Context, + context: &mut Context, minimum_payout: u64, ) -> Result<()> { let pool = &mut context.accounts.pool; @@ -66,30 +66,30 @@ pub fn handle_close_position( .checked_add(close_fee) .ok_or(PerpError::MathOverflow)?; - let pool_key = pool.key(); + let pool_key = pool.address(); let authority_seeds: &[&[u8]] = &[AUTHORITY_SEED, pool_key.as_ref(), &[pool.authority_bump]]; transfer_checked( CpiContext::new_with_signer( - context.accounts.token_program.key(), + context.accounts.token_program.address(), TransferChecked { - from: context.accounts.custody_vault.to_account_info(), - mint: context.accounts.collateral_mint.to_account_info(), - to: context.accounts.trader_collateral.to_account_info(), - authority: context.accounts.pool_authority.to_account_info(), + from: context.accounts.custody_vault.cpi_handle_mut(), + mint: context.accounts.collateral_mint.cpi_handle(), + to: context.accounts.trader_collateral.cpi_handle_mut(), + authority: context.accounts.pool_authority.cpi_handle(), }, &[authority_seeds], ), payout, - context.accounts.collateral_mint.decimals, + context.accounts.collateral_mint.decimals(), )?; Ok(()) } #[derive(Accounts)] -pub struct ClosePositionAccountConstraints<'info> { +pub struct ClosePositionAccountConstraints { #[account(mut)] - pub owner: Signer<'info>, + pub owner: Signer, #[account( mut, @@ -99,36 +99,36 @@ pub struct ClosePositionAccountConstraints<'info> { has_one = custody_vault, has_one = oracle_feed, )] - pub pool: Box>, + pub pool: Box>, #[account( mut, close = owner, - seeds = [POSITION_SEED, pool.key().as_ref(), owner.key().as_ref(), position.side.as_seed()], + seeds = [POSITION_SEED, pool.address().as_ref(), owner.address().as_ref(), position.side.as_seed()], bump = position.bump, has_one = owner, has_one = pool, )] - pub position: Box>, + pub position: Box>, /// CHECK: PDA authority over the vault. #[account( - seeds = [AUTHORITY_SEED, pool.key().as_ref()], + seeds = [AUTHORITY_SEED, pool.address().as_ref()], bump = pool.authority_bump, )] - pub pool_authority: UncheckedAccount<'info>, + pub pool_authority: UncheckedAccount, /// CHECK: validated by the `has_one = oracle_feed` constraint on the pool. - pub oracle_feed: UncheckedAccount<'info>, + pub oracle_feed: UncheckedAccount, - pub collateral_mint: Box>, + pub collateral_mint: Box>, #[account( mut, - seeds = [VAULT_SEED, pool.key().as_ref()], + seeds = [VAULT_SEED, pool.address().as_ref()], bump, )] - pub custody_vault: Box>, + pub custody_vault: Box>, #[account( mut, @@ -136,9 +136,9 @@ pub struct ClosePositionAccountConstraints<'info> { associated_token::authority = owner, associated_token::token_program = token_program, )] - pub trader_collateral: Box>, + pub trader_collateral: Box>, - pub token_program: Interface<'info, TokenInterface>, - pub associated_token_program: Program<'info, AssociatedToken>, - pub system_program: Program<'info, System>, + pub token_program: Interface<'static, TokenInterface>, + pub associated_token_program: Program, + pub system_program: Program, } diff --git a/finance/perpetual-futures/anchor/programs/perpetual-futures/src/instructions/collect_fees.rs b/finance/perpetual-futures/anchor/programs/perpetual-futures/src/instructions/collect_fees.rs index e97c68853..1eb23c309 100644 --- a/finance/perpetual-futures/anchor/programs/perpetual-futures/src/instructions/collect_fees.rs +++ b/finance/perpetual-futures/anchor/programs/perpetual-futures/src/instructions/collect_fees.rs @@ -8,7 +8,7 @@ use crate::constants::{AUTHORITY_SEED, POOL_SEED, VAULT_SEED}; use crate::errors::PerpError; use crate::state::Pool; -pub fn handle_collect_fees(context: Context) -> Result<()> { +pub fn handle_collect_fees(context: &mut Context) -> Result<()> { let pool = &mut context.accounts.pool; let amount = pool.protocol_fees; require!(amount > 0, PerpError::NothingToClaim); @@ -16,30 +16,30 @@ pub fn handle_collect_fees(context: Context) -> R // Effects before interaction: zero the balance, then transfer. pool.protocol_fees = 0; - let pool_key = pool.key(); + let pool_key = pool.address(); let authority_seeds: &[&[u8]] = &[AUTHORITY_SEED, pool_key.as_ref(), &[pool.authority_bump]]; transfer_checked( CpiContext::new_with_signer( - context.accounts.token_program.key(), + context.accounts.token_program.address(), TransferChecked { - from: context.accounts.custody_vault.to_account_info(), - mint: context.accounts.collateral_mint.to_account_info(), - to: context.accounts.authority_collateral.to_account_info(), - authority: context.accounts.pool_authority.to_account_info(), + from: context.accounts.custody_vault.cpi_handle_mut(), + mint: context.accounts.collateral_mint.cpi_handle(), + to: context.accounts.authority_collateral.cpi_handle_mut(), + authority: context.accounts.pool_authority.cpi_handle(), }, &[authority_seeds], ), amount, - context.accounts.collateral_mint.decimals, + context.accounts.collateral_mint.decimals(), )?; Ok(()) } #[derive(Accounts)] -pub struct CollectFeesAccountConstraints<'info> { +pub struct CollectFeesAccountConstraints { #[account(mut)] - pub authority: Signer<'info>, + pub authority: Signer, #[account( mut, @@ -49,23 +49,23 @@ pub struct CollectFeesAccountConstraints<'info> { has_one = collateral_mint, has_one = custody_vault, )] - pub pool: Box>, + pub pool: Box>, /// CHECK: PDA authority over the vault. #[account( - seeds = [AUTHORITY_SEED, pool.key().as_ref()], + seeds = [AUTHORITY_SEED, pool.address().as_ref()], bump = pool.authority_bump, )] - pub pool_authority: UncheckedAccount<'info>, + pub pool_authority: UncheckedAccount, - pub collateral_mint: Box>, + pub collateral_mint: Box>, #[account( mut, - seeds = [VAULT_SEED, pool.key().as_ref()], + seeds = [VAULT_SEED, pool.address().as_ref()], bump, )] - pub custody_vault: Box>, + pub custody_vault: Box>, #[account( init_if_needed, @@ -74,9 +74,9 @@ pub struct CollectFeesAccountConstraints<'info> { associated_token::authority = authority, associated_token::token_program = token_program, )] - pub authority_collateral: Box>, + pub authority_collateral: Box>, - pub token_program: Interface<'info, TokenInterface>, - pub associated_token_program: Program<'info, AssociatedToken>, - pub system_program: Program<'info, System>, + pub token_program: Interface<'static, TokenInterface>, + pub associated_token_program: Program, + pub system_program: Program, } diff --git a/finance/perpetual-futures/anchor/programs/perpetual-futures/src/instructions/initialize_pool.rs b/finance/perpetual-futures/anchor/programs/perpetual-futures/src/instructions/initialize_pool.rs index 3699414cb..51fefade0 100644 --- a/finance/perpetual-futures/anchor/programs/perpetual-futures/src/instructions/initialize_pool.rs +++ b/finance/perpetual-futures/anchor/programs/perpetual-futures/src/instructions/initialize_pool.rs @@ -1,4 +1,6 @@ use anchor_lang::prelude::*; +use anchor_spl::token; +use anchor_spl::mint; use anchor_spl::{ associated_token::AssociatedToken, token_interface::{Mint, TokenAccount, TokenInterface}, @@ -33,7 +35,7 @@ pub struct PoolParameters { } pub fn handle_initialize_pool( - context: Context, + context: &mut Context, parameters: PoolParameters, ) -> Result<()> { let denominator = BASIS_POINTS_DENOMINATOR as u16; @@ -76,12 +78,12 @@ pub fn handle_initialize_pool( ); let pool = &mut context.accounts.pool; - pool.authority = context.accounts.authority.key(); - pool.collateral_mint = context.accounts.collateral_mint.key(); - pool.oracle_feed = context.accounts.oracle_feed.key(); + pool.authority = *context.accounts.authority.address(); + pool.collateral_mint = *context.accounts.collateral_mint.address(); + pool.oracle_feed = *context.accounts.oracle_feed.address(); pool.oracle_scale = parameters.oracle_scale; - pool.custody_vault = context.accounts.custody_vault.key(); - pool.lp_mint = context.accounts.lp_mint.key(); + pool.custody_vault = *context.accounts.custody_vault.address(); + pool.lp_mint = *context.accounts.lp_mint.address(); pool.liquidity = 0; pool.reserved_liquidity = 0; pool.total_collateral = 0; @@ -106,57 +108,57 @@ pub fn handle_initialize_pool( } #[derive(Accounts)] -pub struct InitializePoolAccountConstraints<'info> { +pub struct InitializePoolAccountConstraints { #[account(mut)] - pub authority: Signer<'info>, + pub authority: Signer, #[account( init, payer = authority, space = Pool::DISCRIMINATOR.len() + Pool::INIT_SPACE, - seeds = [POOL_SEED, collateral_mint.key().as_ref(), oracle_feed.key().as_ref()], + seeds = [POOL_SEED, collateral_mint.address().as_ref(), oracle_feed.address().as_ref()], bump, )] - pub pool: Box>, + pub pool: Box>, - pub collateral_mint: Box>, + pub collateral_mint: Box>, /// CHECK: The oracle feed account. Its key is stored on the pool and every /// read validates the layout, scale, and freshness; it is never trusted by /// type. Swap for a real Switchboard feed in production. - pub oracle_feed: UncheckedAccount<'info>, + pub oracle_feed: UncheckedAccount, /// CHECK: PDA that owns the vault and the liquidity-provider mint. Holds no /// data; used only to sign vault and mint CPIs. #[account( - seeds = [AUTHORITY_SEED, pool.key().as_ref()], + seeds = [AUTHORITY_SEED, pool.address().as_ref()], bump, )] - pub pool_authority: UncheckedAccount<'info>, + pub pool_authority: UncheckedAccount, #[account( init, payer = authority, - seeds = [LP_MINT_SEED, pool.key().as_ref()], + seeds = [LP_MINT_SEED, pool.address().as_ref()], bump, - mint::decimals = collateral_mint.decimals, + mint::decimals = collateral_mint.decimals(), mint::authority = pool_authority, mint::token_program = token_program, )] - pub lp_mint: Box>, + pub lp_mint: Box>, #[account( init, payer = authority, - seeds = [VAULT_SEED, pool.key().as_ref()], + seeds = [VAULT_SEED, pool.address().as_ref()], bump, token::mint = collateral_mint, token::authority = pool_authority, token::token_program = token_program, )] - pub custody_vault: Box>, + pub custody_vault: Box>, - pub token_program: Interface<'info, TokenInterface>, - pub associated_token_program: Program<'info, AssociatedToken>, - pub system_program: Program<'info, System>, + pub token_program: Interface<'static, TokenInterface>, + pub associated_token_program: Program, + pub system_program: Program, } diff --git a/finance/perpetual-futures/anchor/programs/perpetual-futures/src/instructions/liquidate_position.rs b/finance/perpetual-futures/anchor/programs/perpetual-futures/src/instructions/liquidate_position.rs index 26c87d25e..d8d4aaa09 100644 --- a/finance/perpetual-futures/anchor/programs/perpetual-futures/src/instructions/liquidate_position.rs +++ b/finance/perpetual-futures/anchor/programs/perpetual-futures/src/instructions/liquidate_position.rs @@ -10,7 +10,7 @@ use crate::instructions::shared::{basis_points_of, refresh_price_and_funding, se use crate::state::{Pool, Position}; pub fn handle_liquidate_position( - context: Context, + context: &mut Context, ) -> Result<()> { let pool = &mut context.accounts.pool; let price = refresh_price_and_funding(pool, &context.accounts.oracle_feed)?; @@ -60,40 +60,40 @@ pub fn handle_liquidate_position( .try_into() .map_err(|_| PerpError::MathOverflow)?; - let pool_key = pool.key(); + let pool_key = pool.address(); let authority_seeds: &[&[u8]] = &[AUTHORITY_SEED, pool_key.as_ref(), &[pool.authority_bump]]; if liquidator_payout > 0 { transfer_checked( CpiContext::new_with_signer( - context.accounts.token_program.key(), + context.accounts.token_program.address(), TransferChecked { - from: context.accounts.custody_vault.to_account_info(), - mint: context.accounts.collateral_mint.to_account_info(), - to: context.accounts.liquidator_collateral.to_account_info(), - authority: context.accounts.pool_authority.to_account_info(), + from: context.accounts.custody_vault.cpi_handle_mut(), + mint: context.accounts.collateral_mint.cpi_handle(), + to: context.accounts.liquidator_collateral.cpi_handle_mut(), + authority: context.accounts.pool_authority.cpi_handle(), }, &[authority_seeds], ), liquidator_payout, - context.accounts.collateral_mint.decimals, + context.accounts.collateral_mint.decimals(), )?; } if trader_refund > 0 { transfer_checked( CpiContext::new_with_signer( - context.accounts.token_program.key(), + context.accounts.token_program.address(), TransferChecked { - from: context.accounts.custody_vault.to_account_info(), - mint: context.accounts.collateral_mint.to_account_info(), - to: context.accounts.trader_collateral.to_account_info(), - authority: context.accounts.pool_authority.to_account_info(), + from: context.accounts.custody_vault.cpi_handle_mut(), + mint: context.accounts.collateral_mint.cpi_handle(), + to: context.accounts.trader_collateral.cpi_handle_mut(), + authority: context.accounts.pool_authority.cpi_handle(), }, &[authority_seeds], ), trader_refund, - context.accounts.collateral_mint.decimals, + context.accounts.collateral_mint.decimals(), )?; } @@ -101,14 +101,14 @@ pub fn handle_liquidate_position( } #[derive(Accounts)] -pub struct LiquidatePositionAccountConstraints<'info> { +pub struct LiquidatePositionAccountConstraints { #[account(mut)] - pub liquidator: Signer<'info>, + pub liquidator: Signer, /// CHECK: the position owner, validated by the position's `has_one = owner`. /// Receives the position account's rent and any equity refund. #[account(mut)] - pub owner: UncheckedAccount<'info>, + pub owner: UncheckedAccount, #[account( mut, @@ -118,36 +118,36 @@ pub struct LiquidatePositionAccountConstraints<'info> { has_one = custody_vault, has_one = oracle_feed, )] - pub pool: Box>, + pub pool: Box>, #[account( mut, close = owner, - seeds = [POSITION_SEED, pool.key().as_ref(), owner.key().as_ref(), position.side.as_seed()], + seeds = [POSITION_SEED, pool.address().as_ref(), owner.address().as_ref(), position.side.as_seed()], bump = position.bump, has_one = owner, has_one = pool, )] - pub position: Box>, + pub position: Box>, /// CHECK: PDA authority over the vault. #[account( - seeds = [AUTHORITY_SEED, pool.key().as_ref()], + seeds = [AUTHORITY_SEED, pool.address().as_ref()], bump = pool.authority_bump, )] - pub pool_authority: UncheckedAccount<'info>, + pub pool_authority: UncheckedAccount, /// CHECK: validated by the `has_one = oracle_feed` constraint on the pool. - pub oracle_feed: UncheckedAccount<'info>, + pub oracle_feed: UncheckedAccount, - pub collateral_mint: Box>, + pub collateral_mint: Box>, #[account( mut, - seeds = [VAULT_SEED, pool.key().as_ref()], + seeds = [VAULT_SEED, pool.address().as_ref()], bump, )] - pub custody_vault: Box>, + pub custody_vault: Box>, #[account( mut, @@ -155,7 +155,7 @@ pub struct LiquidatePositionAccountConstraints<'info> { associated_token::authority = owner, associated_token::token_program = token_program, )] - pub trader_collateral: Box>, + pub trader_collateral: Box>, #[account( init_if_needed, @@ -164,9 +164,9 @@ pub struct LiquidatePositionAccountConstraints<'info> { associated_token::authority = liquidator, associated_token::token_program = token_program, )] - pub liquidator_collateral: Box>, + pub liquidator_collateral: Box>, - pub token_program: Interface<'info, TokenInterface>, - pub associated_token_program: Program<'info, AssociatedToken>, - pub system_program: Program<'info, System>, + pub token_program: Interface<'static, TokenInterface>, + pub associated_token_program: Program, + pub system_program: Program, } diff --git a/finance/perpetual-futures/anchor/programs/perpetual-futures/src/instructions/open_position.rs b/finance/perpetual-futures/anchor/programs/perpetual-futures/src/instructions/open_position.rs index 8c3ec8dc9..003fb970f 100644 --- a/finance/perpetual-futures/anchor/programs/perpetual-futures/src/instructions/open_position.rs +++ b/finance/perpetual-futures/anchor/programs/perpetual-futures/src/instructions/open_position.rs @@ -10,7 +10,7 @@ use crate::instructions::shared::{basis_points_of, refresh_price_and_funding, sc use crate::state::{Pool, Position, Side}; pub fn handle_open_position( - context: Context, + context: &mut Context, side: Side, collateral_amount: u64, size: u64, @@ -66,8 +66,8 @@ pub fn handle_open_position( // Effects: record the position and the pool's new aggregates before moving // any tokens. let position = &mut context.accounts.position; - position.owner = context.accounts.owner.key(); - position.pool = pool.key(); + position.owner = *context.accounts.owner.address(); + position.pool = *pool.address(); position.side = side; position.collateral = net_collateral; position.size = size; @@ -110,16 +110,16 @@ pub fn handle_open_position( transfer_checked( CpiContext::new( - context.accounts.token_program.key(), + context.accounts.token_program.address(), TransferChecked { - from: context.accounts.trader_collateral.to_account_info(), - mint: context.accounts.collateral_mint.to_account_info(), - to: context.accounts.custody_vault.to_account_info(), - authority: context.accounts.owner.to_account_info(), + from: context.accounts.trader_collateral.cpi_handle_mut(), + mint: context.accounts.collateral_mint.cpi_handle(), + to: context.accounts.custody_vault.cpi_handle_mut(), + authority: context.accounts.owner.cpi_handle(), }, ), collateral_amount, - context.accounts.collateral_mint.decimals, + context.accounts.collateral_mint.decimals(), )?; Ok(()) @@ -127,9 +127,9 @@ pub fn handle_open_position( #[derive(Accounts)] #[instruction(side: Side)] -pub struct OpenPositionAccountConstraints<'info> { +pub struct OpenPositionAccountConstraints { #[account(mut)] - pub owner: Signer<'info>, + pub owner: Signer, #[account( mut, @@ -139,28 +139,28 @@ pub struct OpenPositionAccountConstraints<'info> { has_one = custody_vault, has_one = oracle_feed, )] - pub pool: Box>, + pub pool: Box>, #[account( init, payer = owner, space = Position::DISCRIMINATOR.len() + Position::INIT_SPACE, - seeds = [POSITION_SEED, pool.key().as_ref(), owner.key().as_ref(), side.as_seed()], + seeds = [POSITION_SEED, pool.address().as_ref(), owner.address().as_ref(), side.as_seed()], bump, )] - pub position: Box>, + pub position: Box>, /// CHECK: validated by the `has_one = oracle_feed` constraint on the pool. - pub oracle_feed: UncheckedAccount<'info>, + pub oracle_feed: UncheckedAccount, - pub collateral_mint: Box>, + pub collateral_mint: Box>, #[account( mut, - seeds = [VAULT_SEED, pool.key().as_ref()], + seeds = [VAULT_SEED, pool.address().as_ref()], bump, )] - pub custody_vault: Box>, + pub custody_vault: Box>, #[account( mut, @@ -168,9 +168,9 @@ pub struct OpenPositionAccountConstraints<'info> { associated_token::authority = owner, associated_token::token_program = token_program, )] - pub trader_collateral: Box>, + pub trader_collateral: Box>, - pub token_program: Interface<'info, TokenInterface>, - pub associated_token_program: Program<'info, AssociatedToken>, - pub system_program: Program<'info, System>, + pub token_program: Interface<'static, TokenInterface>, + pub associated_token_program: Program, + pub system_program: Program, } diff --git a/finance/perpetual-futures/anchor/programs/perpetual-futures/src/instructions/remove_liquidity.rs b/finance/perpetual-futures/anchor/programs/perpetual-futures/src/instructions/remove_liquidity.rs index 2426c556c..73ff3d07e 100644 --- a/finance/perpetual-futures/anchor/programs/perpetual-futures/src/instructions/remove_liquidity.rs +++ b/finance/perpetual-futures/anchor/programs/perpetual-futures/src/instructions/remove_liquidity.rs @@ -12,7 +12,7 @@ use crate::instructions::shared::{liquidity_provider_aum, refresh_price_and_fund use crate::state::Pool; pub fn handle_remove_liquidity( - context: Context, + context: &mut Context, shares: u64, minimum_amount_out: u64, ) -> Result<()> { @@ -58,40 +58,40 @@ pub fn handle_remove_liquidity( burn( CpiContext::new( - context.accounts.token_program.key(), + context.accounts.token_program.address(), Burn { - mint: context.accounts.lp_mint.to_account_info(), - from: context.accounts.provider_lp.to_account_info(), - authority: context.accounts.provider.to_account_info(), + mint: context.accounts.lp_mint.cpi_handle_mut(), + from: context.accounts.provider_lp.cpi_handle_mut(), + authority: context.accounts.provider.cpi_handle(), }, ), shares, )?; - let pool_key = pool.key(); + let pool_key = pool.address(); let authority_seeds: &[&[u8]] = &[AUTHORITY_SEED, pool_key.as_ref(), &[pool.authority_bump]]; transfer_checked( CpiContext::new_with_signer( - context.accounts.token_program.key(), + context.accounts.token_program.address(), TransferChecked { - from: context.accounts.custody_vault.to_account_info(), - mint: context.accounts.collateral_mint.to_account_info(), - to: context.accounts.provider_collateral.to_account_info(), - authority: context.accounts.pool_authority.to_account_info(), + from: context.accounts.custody_vault.cpi_handle_mut(), + mint: context.accounts.collateral_mint.cpi_handle(), + to: context.accounts.provider_collateral.cpi_handle_mut(), + authority: context.accounts.pool_authority.cpi_handle(), }, &[authority_seeds], ), amount_out, - context.accounts.collateral_mint.decimals, + context.accounts.collateral_mint.decimals(), )?; Ok(()) } #[derive(Accounts)] -pub struct RemoveLiquidityAccountConstraints<'info> { +pub struct RemoveLiquidityAccountConstraints { #[account(mut)] - pub provider: Signer<'info>, + pub provider: Signer, #[account( mut, @@ -102,29 +102,29 @@ pub struct RemoveLiquidityAccountConstraints<'info> { has_one = custody_vault, has_one = oracle_feed, )] - pub pool: Box>, + pub pool: Box>, /// CHECK: PDA authority over the vault and liquidity-provider mint. #[account( - seeds = [AUTHORITY_SEED, pool.key().as_ref()], + seeds = [AUTHORITY_SEED, pool.address().as_ref()], bump = pool.authority_bump, )] - pub pool_authority: UncheckedAccount<'info>, + pub pool_authority: UncheckedAccount, /// CHECK: validated by the `has_one = oracle_feed` constraint on the pool. - pub oracle_feed: UncheckedAccount<'info>, + pub oracle_feed: UncheckedAccount, - pub collateral_mint: Box>, + pub collateral_mint: Box>, #[account(mut)] - pub lp_mint: Box>, + pub lp_mint: Box>, #[account( mut, - seeds = [VAULT_SEED, pool.key().as_ref()], + seeds = [VAULT_SEED, pool.address().as_ref()], bump, )] - pub custody_vault: Box>, + pub custody_vault: Box>, #[account( mut, @@ -132,7 +132,7 @@ pub struct RemoveLiquidityAccountConstraints<'info> { associated_token::authority = provider, associated_token::token_program = token_program, )] - pub provider_collateral: Box>, + pub provider_collateral: Box>, #[account( mut, @@ -140,9 +140,9 @@ pub struct RemoveLiquidityAccountConstraints<'info> { associated_token::authority = provider, associated_token::token_program = token_program, )] - pub provider_lp: Box>, + pub provider_lp: Box>, - pub token_program: Interface<'info, TokenInterface>, - pub associated_token_program: Program<'info, AssociatedToken>, - pub system_program: Program<'info, System>, + pub token_program: Interface<'static, TokenInterface>, + pub associated_token_program: Program, + pub system_program: Program, } diff --git a/finance/perpetual-futures/anchor/programs/perpetual-futures/src/instructions/shared.rs b/finance/perpetual-futures/anchor/programs/perpetual-futures/src/instructions/shared.rs index e8e322092..2fb9ae7f3 100644 --- a/finance/perpetual-futures/anchor/programs/perpetual-futures/src/instructions/shared.rs +++ b/finance/perpetual-futures/anchor/programs/perpetual-futures/src/instructions/shared.rs @@ -213,7 +213,7 @@ pub fn basis_points_of(amount: u64, basis_points: u16) -> Result { /// price, then bring the pool's funding index up to the current slot, so the /// settlement that follows uses fresh numbers for both. Centralized so no /// handler can settle a position against a stale funding index. -pub fn refresh_price_and_funding(pool: &mut Pool, oracle_feed: &AccountInfo) -> Result { +pub fn refresh_price_and_funding(pool: &mut Pool, oracle_feed: &AccountView) -> Result { let price = crate::state::oracle::read_oracle_price( oracle_feed, pool.oracle_scale, diff --git a/finance/perpetual-futures/anchor/programs/perpetual-futures/src/lib.rs b/finance/perpetual-futures/anchor/programs/perpetual-futures/src/lib.rs index 31e19e4c7..0b0ce5678 100644 --- a/finance/perpetual-futures/anchor/programs/perpetual-futures/src/lib.rs +++ b/finance/perpetual-futures/anchor/programs/perpetual-futures/src/lib.rs @@ -20,7 +20,7 @@ pub mod perpetual_futures { /// oracle feed. Sets the trading parameters and creates the custody vault /// and liquidity-provider mint. pub fn initialize_pool( - context: Context, + context: &mut Context, parameters: PoolParameters, ) -> Result<()> { instructions::handle_initialize_pool(context, parameters) @@ -29,7 +29,7 @@ pub mod perpetual_futures { /// Deposit collateral into the pool and receive liquidity-provider shares. /// `minimum_shares_out` is slippage protection; pass `0` to opt out. pub fn add_liquidity( - context: Context, + context: &mut Context, amount: u64, minimum_shares_out: u64, ) -> Result<()> { @@ -39,7 +39,7 @@ pub mod perpetual_futures { /// Burn liquidity-provider shares and withdraw the matching collateral. /// `minimum_amount_out` is slippage protection; pass `0` to opt out. pub fn remove_liquidity( - context: Context, + context: &mut Context, shares: u64, minimum_amount_out: u64, ) -> Result<()> { @@ -50,7 +50,7 @@ pub mod perpetual_futures { /// oracle price. `acceptable_price` bounds the fill (longs reject above it, /// shorts reject below it); pass `0` to opt out. pub fn open_position( - context: Context, + context: &mut Context, side: Side, collateral_amount: u64, size: u64, @@ -63,7 +63,7 @@ pub mod perpetual_futures { /// and the close fee. `minimum_payout` is slippage protection; pass `0` to /// opt out. pub fn close_position( - context: Context, + context: &mut Context, minimum_payout: u64, ) -> Result<()> { instructions::handle_close_position(context, minimum_payout) @@ -71,12 +71,12 @@ pub mod perpetual_futures { /// Permissionlessly close a position whose equity has fallen to or below /// the maintenance margin. The caller earns the liquidation fee. - pub fn liquidate_position(context: Context) -> Result<()> { + pub fn liquidate_position(context: &mut Context) -> Result<()> { instructions::handle_liquidate_position(context) } /// Pool authority sweeps the accumulated protocol fees from the vault. - pub fn collect_fees(context: Context) -> Result<()> { + pub fn collect_fees(context: &mut Context) -> Result<()> { instructions::handle_collect_fees(context) } } diff --git a/finance/perpetual-futures/anchor/programs/perpetual-futures/src/state/oracle.rs b/finance/perpetual-futures/anchor/programs/perpetual-futures/src/state/oracle.rs index e05b7ce32..127c7b667 100644 --- a/finance/perpetual-futures/anchor/programs/perpetual-futures/src/state/oracle.rs +++ b/finance/perpetual-futures/anchor/programs/perpetual-futures/src/state/oracle.rs @@ -6,7 +6,7 @@ use crate::errors::PerpError; // Byte layout of the feed account this program reads. It matches the // `mock_switchboard::MockFeed` account: an 8-byte Anchor discriminator followed -// by `authority: Pubkey (32)`, `price: i128 (16)`, `scale: u32 (4)`, +// by `authority: Address (32)`, `price: i128 (16)`, `scale: u32 (4)`, // `last_update_slot: u64 (8)`, `confidence: u64 (8)`. // // We read the raw bytes rather than deserializing the mock account type so this @@ -40,7 +40,7 @@ const FEED_MINIMUM_LENGTH: usize = CONFIDENCE_OFFSET + 8; /// non-positive price, a feed whose scale differs from the pool's pinned scale, /// and a price whose confidence band exceeds `max_confidence_bps` of the price. pub fn read_oracle_price( - feed: &AccountInfo, + feed: &AccountView, expected_scale: u32, max_confidence_bps: u16, ) -> Result { diff --git a/finance/perpetual-futures/anchor/programs/perpetual-futures/src/state/pool.rs b/finance/perpetual-futures/anchor/programs/perpetual-futures/src/state/pool.rs index eae178c51..4b068a98c 100644 --- a/finance/perpetual-futures/anchor/programs/perpetual-futures/src/state/pool.rs +++ b/finance/perpetual-futures/anchor/programs/perpetual-futures/src/state/pool.rs @@ -6,26 +6,26 @@ use anchor_lang::prelude::*; /// /// Money fields are raw base units of the collateral token. The pool never /// assumes decimals — `transfer_checked` carries them through every CPI. -#[account] +#[account(borsh)] #[derive(InitSpace)] pub struct Pool { /// Admin: configures the pool and sweeps protocol fees. Not a custody /// escape hatch — it cannot touch liquidity-provider or trader funds. - pub authority: Pubkey, + pub authority: Address, - pub collateral_mint: Pubkey, + pub collateral_mint: Address, /// Oracle feed this market reads its price from. Stored so handlers can /// reject any substituted feed account. - pub oracle_feed: Pubkey, + pub oracle_feed: Address, /// Decimal places the oracle price is quoted in. Pinned at creation so a /// feed that silently changes scale is rejected rather than mis-read. pub oracle_scale: u32, - pub custody_vault: Pubkey, + pub custody_vault: Address, - pub lp_mint: Pubkey, + pub lp_mint: Address, /// Liquidity-provider-owned assets, in collateral base units. Grows with /// deposits, trader losses, fees-to-LPs; shrinks with withdrawals and diff --git a/finance/perpetual-futures/anchor/programs/perpetual-futures/src/state/position.rs b/finance/perpetual-futures/anchor/programs/perpetual-futures/src/state/position.rs index 29a0d7080..664985367 100644 --- a/finance/perpetual-futures/anchor/programs/perpetual-futures/src/state/position.rs +++ b/finance/perpetual-futures/anchor/programs/perpetual-futures/src/state/position.rs @@ -19,12 +19,12 @@ impl Side { } /// A single trader's leveraged position. One PDA per (pool, owner, side). -#[account] +#[account(borsh)] #[derive(InitSpace)] pub struct Position { - pub owner: Pubkey, + pub owner: Address, - pub pool: Pubkey, + pub pool: Address, pub side: Side, diff --git a/finance/prop-amm/anchor/programs/mock-switchboard/Cargo.toml b/finance/prop-amm/anchor/programs/mock-switchboard/Cargo.toml index 5374a001c..047c5c133 100644 --- a/finance/prop-amm/anchor/programs/mock-switchboard/Cargo.toml +++ b/finance/prop-amm/anchor/programs/mock-switchboard/Cargo.toml @@ -20,7 +20,15 @@ custom-heap = [] custom-panic = [] [dependencies] -anchor-lang = "1.1.2" +anchor-lang = "2.0.0-rc.1" +# The `#[program]` macro expands to `wincode` paths for instruction-data +# (de)serialization, so the crate has to be a direct dependency. +wincode = { version = "0.5", features = ["derive"] } +# anchor-lang 2.0.0-rc.1 is built against wincode 0.5, but solana-address 2.7 +# moved to wincode 0.6. With both in the graph, `Address`'s wincode impls +# belong to the version the account derives are not using and every +# `SchemaRead`/`SchemaWrite` bound fails. 2.6.1 is the last 0.5-line release. +solana-address = ">=2.6, <2.7" [lints.rust] unexpected_cfgs = { level = "warn", check-cfg = ['cfg(target_os, values("solana"))'] } diff --git a/finance/prop-amm/anchor/programs/mock-switchboard/src/lib.rs b/finance/prop-amm/anchor/programs/mock-switchboard/src/lib.rs index 66d29a659..8c314c624 100644 --- a/finance/prop-amm/anchor/programs/mock-switchboard/src/lib.rs +++ b/finance/prop-amm/anchor/programs/mock-switchboard/src/lib.rs @@ -24,13 +24,13 @@ pub mod mock_switchboard { /// Initialize the mock feed with an initial price. The signer becomes the /// authority allowed to push later price updates. pub fn initialize_feed( - context: Context, + context: &mut Context, price: i128, scale: u32, confidence: u64, ) -> Result<()> { let feed = &mut context.accounts.feed; - feed.authority = context.accounts.authority.key(); + feed.authority = *context.accounts.authority.address(); feed.price = price; feed.scale = scale; feed.last_update_slot = Clock::get()?.slot; @@ -43,7 +43,7 @@ pub mod mock_switchboard { /// is an authority-gated write, because the goal is to drive deterministic /// test scenarios. pub fn set_price( - context: Context, + context: &mut Context, price: i128, confidence: u64, ) -> Result<()> { @@ -56,38 +56,38 @@ pub mod mock_switchboard { } #[derive(Accounts)] -pub struct InitializeFeedAccountConstraints<'info> { +pub struct InitializeFeedAccountConstraints { #[account( init, payer = authority, space = MockFeed::DISCRIMINATOR.len() + MockFeed::INIT_SPACE, )] - pub feed: Account<'info, MockFeed>, + pub feed: BorshAccount, #[account(mut)] - pub authority: Signer<'info>, + pub authority: Signer, - pub system_program: Program<'info, System>, + pub system_program: Program, } #[derive(Accounts)] -pub struct SetPriceAccountConstraints<'info> { +pub struct SetPriceAccountConstraints { #[account( mut, has_one = authority, )] - pub feed: Account<'info, MockFeed>, + pub feed: BorshAccount, - pub authority: Signer<'info>, + pub authority: Signer, } /// Mock of a Switchboard On-Demand feed. Real feeds carry many more fields /// (median, range, sample window, signatures) — this is the bare minimum the /// prop-amm program needs to price a quote. #[derive(InitSpace)] -#[account] +#[account(borsh)] pub struct MockFeed { - pub authority: Pubkey, + pub authority: Address, /// Signed 128-bit fixed-point price. Real Switchboard prices are also i128. pub price: i128, diff --git a/finance/prop-amm/anchor/programs/prop-amm/Cargo.toml b/finance/prop-amm/anchor/programs/prop-amm/Cargo.toml index fb8a4f9a5..9bf25f995 100644 --- a/finance/prop-amm/anchor/programs/prop-amm/Cargo.toml +++ b/finance/prop-amm/anchor/programs/prop-amm/Cargo.toml @@ -14,7 +14,7 @@ cpi = ["no-entrypoint"] no-entrypoint = [] no-idl = [] no-log-ix-name = [] -idl-build = ["anchor-lang/idl-build", "anchor-spl/idl-build"] +idl-build = ["anchor-lang/idl-build"] anchor-debug = [] custom-heap = [] custom-panic = [] @@ -22,8 +22,16 @@ custom-panic = [] [dependencies] # init-if-needed: swap creates the trader's destination token account when it # doesn't exist yet, so a first-time buyer needs no separate setup transaction. -anchor-lang = { version = "1.1.2", features = ["init-if-needed"] } -anchor-spl = "1.1.2" +anchor-lang = "2.0.0-rc.1" +# The `#[program]` macro expands to `wincode` paths for instruction-data +# (de)serialization, so the crate has to be a direct dependency. +wincode = { version = "0.5", features = ["derive"] } +# anchor-lang 2.0.0-rc.1 is built against wincode 0.5, but solana-address 2.7 +# moved to wincode 0.6. With both in the graph, `Address`'s wincode impls +# belong to the version the account derives are not using and every +# `SchemaRead`/`SchemaWrite` bound fails. 2.6.1 is the last 0.5-line release. +solana-address = ">=2.6, <2.7" +anchor-spl = "2.0.0-rc.1" # For the LastRestartSlot sysvar (not re-exported by anchor-lang): the oracle # reader rejects prices from before a cluster restart. Same major as the # solana-sysvar anchor-lang itself uses, so only one copy is compiled in. diff --git a/finance/prop-amm/anchor/programs/prop-amm/src/instructions/deposit_inventory.rs b/finance/prop-amm/anchor/programs/prop-amm/src/instructions/deposit_inventory.rs index 7cd7b6817..67bec9f4c 100644 --- a/finance/prop-amm/anchor/programs/prop-amm/src/instructions/deposit_inventory.rs +++ b/finance/prop-amm/anchor/programs/prop-amm/src/instructions/deposit_inventory.rs @@ -8,7 +8,7 @@ use crate::errors::PropAmmError; use crate::state::Market; pub fn handle_deposit_inventory( - context: Context, + context: &mut Context, base_amount: u64, quote_amount: u64, ) -> Result<()> { @@ -20,32 +20,32 @@ pub fn handle_deposit_inventory( if base_amount > 0 { transfer_checked( CpiContext::new( - context.accounts.token_program.key(), + context.accounts.token_program.address(), TransferChecked { - from: context.accounts.operator_base.to_account_info(), - mint: context.accounts.base_mint.to_account_info(), - to: context.accounts.base_vault.to_account_info(), - authority: context.accounts.operator.to_account_info(), + from: context.accounts.operator_base.cpi_handle_mut(), + mint: context.accounts.base_mint.cpi_handle(), + to: context.accounts.base_vault.cpi_handle_mut(), + authority: context.accounts.operator.cpi_handle(), }, ), base_amount, - context.accounts.base_mint.decimals, + context.accounts.base_mint.decimals(), )?; } if quote_amount > 0 { transfer_checked( CpiContext::new( - context.accounts.token_program.key(), + context.accounts.token_program.address(), TransferChecked { - from: context.accounts.operator_quote.to_account_info(), - mint: context.accounts.quote_mint.to_account_info(), - to: context.accounts.quote_vault.to_account_info(), - authority: context.accounts.operator.to_account_info(), + from: context.accounts.operator_quote.cpi_handle_mut(), + mint: context.accounts.quote_mint.cpi_handle(), + to: context.accounts.quote_vault.cpi_handle_mut(), + authority: context.accounts.operator.cpi_handle(), }, ), quote_amount, - context.accounts.quote_mint.decimals, + context.accounts.quote_mint.decimals(), )?; } @@ -53,11 +53,11 @@ pub fn handle_deposit_inventory( } #[derive(Accounts)] -pub struct DepositInventoryAccountConstraints<'info> { +pub struct DepositInventoryAccountConstraints { // `has_one = operator` on the market is the whole access control: only the // firm's key can stock the market. #[account(mut)] - pub operator: Signer<'info>, + pub operator: Signer, #[account( seeds = [MARKET_SEED, market.base_mint.as_ref(), market.quote_mint.as_ref()], @@ -68,25 +68,25 @@ pub struct DepositInventoryAccountConstraints<'info> { has_one = base_vault, has_one = quote_vault, )] - pub market: Box>, + pub market: Box>, - pub base_mint: Box>, + pub base_mint: Box>, - pub quote_mint: Box>, + pub quote_mint: Box>, #[account( mut, - seeds = [BASE_VAULT_SEED, market.key().as_ref()], + seeds = [BASE_VAULT_SEED, market.address().as_ref()], bump, )] - pub base_vault: Box>, + pub base_vault: Box>, #[account( mut, - seeds = [QUOTE_VAULT_SEED, market.key().as_ref()], + seeds = [QUOTE_VAULT_SEED, market.address().as_ref()], bump, )] - pub quote_vault: Box>, + pub quote_vault: Box>, #[account( mut, @@ -94,7 +94,7 @@ pub struct DepositInventoryAccountConstraints<'info> { associated_token::authority = operator, associated_token::token_program = token_program, )] - pub operator_base: Box>, + pub operator_base: Box>, #[account( mut, @@ -102,7 +102,7 @@ pub struct DepositInventoryAccountConstraints<'info> { associated_token::authority = operator, associated_token::token_program = token_program, )] - pub operator_quote: Box>, + pub operator_quote: Box>, - pub token_program: Interface<'info, TokenInterface>, + pub token_program: Interface<'static, TokenInterface>, } diff --git a/finance/prop-amm/anchor/programs/prop-amm/src/instructions/initialize_market.rs b/finance/prop-amm/anchor/programs/prop-amm/src/instructions/initialize_market.rs index 056fc96ac..c4f9f0e6d 100644 --- a/finance/prop-amm/anchor/programs/prop-amm/src/instructions/initialize_market.rs +++ b/finance/prop-amm/anchor/programs/prop-amm/src/instructions/initialize_market.rs @@ -1,4 +1,5 @@ use anchor_lang::prelude::*; +use anchor_spl::token; use anchor_spl::{ associated_token::AssociatedToken, token_interface::{Mint, TokenAccount, TokenInterface}, @@ -25,14 +26,14 @@ pub struct MarketParameters { } pub fn handle_initialize_market( - context: Context, + context: &mut Context, parameters: MarketParameters, ) -> Result<()> { let denominator = BASIS_POINTS_DENOMINATOR as u16; // A market quoting the same token against itself prices nothing. require_keys_neq!( - context.accounts.base_mint.key(), - context.accounts.quote_mint.key(), + context.accounts.base_mint.address(), + context.accounts.quote_mint.address(), PropAmmError::InvalidParameter ); // Zero spread means quoting the oracle price for free while paying adverse @@ -51,15 +52,15 @@ pub fn handle_initialize_market( ); let market = &mut context.accounts.market; - market.operator = context.accounts.operator.key(); - market.base_mint = context.accounts.base_mint.key(); - market.quote_mint = context.accounts.quote_mint.key(); - market.oracle_feed = context.accounts.oracle_feed.key(); - market.base_vault = context.accounts.base_vault.key(); - market.quote_vault = context.accounts.quote_vault.key(); + market.operator = *context.accounts.operator.address(); + market.base_mint = *context.accounts.base_mint.address(); + market.quote_mint = *context.accounts.quote_mint.address(); + market.oracle_feed = *context.accounts.oracle_feed.address(); + market.base_vault = *context.accounts.base_vault.address(); + market.quote_vault = *context.accounts.quote_vault.address(); market.oracle_scale = parameters.oracle_scale; - market.base_decimals = context.accounts.base_mint.decimals; - market.quote_decimals = context.accounts.quote_mint.decimals; + market.base_decimals = context.accounts.base_mint.decimals(); + market.quote_decimals = context.accounts.quote_mint.decimals(); market.spread_bps = parameters.spread_bps; market.max_confidence_bps = parameters.max_confidence_bps; market.paused = false; @@ -70,9 +71,9 @@ pub fn handle_initialize_market( } #[derive(Accounts)] -pub struct InitializeMarketAccountConstraints<'info> { +pub struct InitializeMarketAccountConstraints { #[account(mut)] - pub operator: Signer<'info>, + pub operator: Signer, // One market per pair: the deployment IS the firm. A real prop AMM is a // closed program deployed by the market-making firm itself, so there is no @@ -81,51 +82,51 @@ pub struct InitializeMarketAccountConstraints<'info> { init, payer = operator, space = Market::DISCRIMINATOR.len() + Market::INIT_SPACE, - seeds = [MARKET_SEED, base_mint.key().as_ref(), quote_mint.key().as_ref()], + seeds = [MARKET_SEED, base_mint.address().as_ref(), quote_mint.address().as_ref()], bump, )] - pub market: Box>, + pub market: Box>, - pub base_mint: Box>, + pub base_mint: Box>, - pub quote_mint: Box>, + pub quote_mint: Box>, /// CHECK: The oracle feed account. Its key is stored on the market and /// every read validates the layout, scale, and freshness; it is never /// trusted by type. Swap for a real Switchboard feed in production. - pub oracle_feed: UncheckedAccount<'info>, + pub oracle_feed: UncheckedAccount, /// CHECK: PDA that owns both vaults. Holds no data; used only to sign /// vault CPIs. #[account( - seeds = [AUTHORITY_SEED, market.key().as_ref()], + seeds = [AUTHORITY_SEED, market.address().as_ref()], bump, )] - pub market_authority: UncheckedAccount<'info>, + pub market_authority: UncheckedAccount, #[account( init, payer = operator, - seeds = [BASE_VAULT_SEED, market.key().as_ref()], + seeds = [BASE_VAULT_SEED, market.address().as_ref()], bump, token::mint = base_mint, token::authority = market_authority, token::token_program = token_program, )] - pub base_vault: Box>, + pub base_vault: Box>, #[account( init, payer = operator, - seeds = [QUOTE_VAULT_SEED, market.key().as_ref()], + seeds = [QUOTE_VAULT_SEED, market.address().as_ref()], bump, token::mint = quote_mint, token::authority = market_authority, token::token_program = token_program, )] - pub quote_vault: Box>, + pub quote_vault: Box>, - pub token_program: Interface<'info, TokenInterface>, - pub associated_token_program: Program<'info, AssociatedToken>, - pub system_program: Program<'info, System>, + pub token_program: Interface<'static, TokenInterface>, + pub associated_token_program: Program, + pub system_program: Program, } diff --git a/finance/prop-amm/anchor/programs/prop-amm/src/instructions/set_quote.rs b/finance/prop-amm/anchor/programs/prop-amm/src/instructions/set_quote.rs index 0b00ce17c..b571fb062 100644 --- a/finance/prop-amm/anchor/programs/prop-amm/src/instructions/set_quote.rs +++ b/finance/prop-amm/anchor/programs/prop-amm/src/instructions/set_quote.rs @@ -12,7 +12,7 @@ use crate::state::Market; /// widen the spread or stop quoting entirely. Onchain prop AMMs do exactly /// this — during fast markets their quotes vanish and return minutes later. pub fn handle_set_quote( - context: Context, + context: &mut Context, spread_bps: u16, paused: bool, ) -> Result<()> { @@ -31,8 +31,8 @@ pub fn handle_set_quote( } #[derive(Accounts)] -pub struct SetQuoteAccountConstraints<'info> { - pub operator: Signer<'info>, +pub struct SetQuoteAccountConstraints { + pub operator: Signer, #[account( mut, @@ -40,5 +40,5 @@ pub struct SetQuoteAccountConstraints<'info> { bump = market.bump, has_one = operator, )] - pub market: Box>, + pub market: Box>, } diff --git a/finance/prop-amm/anchor/programs/prop-amm/src/instructions/swap.rs b/finance/prop-amm/anchor/programs/prop-amm/src/instructions/swap.rs index ea0f26f97..c20b10380 100644 --- a/finance/prop-amm/anchor/programs/prop-amm/src/instructions/swap.rs +++ b/finance/prop-amm/anchor/programs/prop-amm/src/instructions/swap.rs @@ -18,7 +18,7 @@ use crate::state::{Direction, Market}; /// you — a curve AMM's reserves are its pricing input, a prop AMM's inventory /// is just its ammunition. pub fn handle_swap( - context: Context, + context: &mut Context, direction: Direction, amount_in: u64, minimum_amount_out: u64, @@ -98,12 +98,12 @@ pub fn handle_swap( let (vault_out_balance, out_mint_decimals) = match direction { Direction::BuyBase => ( - context.accounts.base_vault.amount, - context.accounts.base_mint.decimals, + context.accounts.base_vault.amount(), + context.accounts.base_mint.decimals(), ), Direction::SellBase => ( - context.accounts.quote_vault.amount, - context.accounts.quote_mint.decimals, + context.accounts.quote_vault.amount(), + context.accounts.quote_mint.decimals(), ), }; require!( @@ -111,7 +111,7 @@ pub fn handle_swap( PropAmmError::InsufficientInventory ); - let market_key = market.key(); + let market_key = market.address(); let authority_seeds: &[&[u8]] = &[ AUTHORITY_SEED, market_key.as_ref(), @@ -123,25 +123,25 @@ pub fn handle_swap( Direction::BuyBase => { transfer_checked( CpiContext::new( - context.accounts.token_program.key(), + context.accounts.token_program.address(), TransferChecked { - from: context.accounts.trader_quote.to_account_info(), - mint: context.accounts.quote_mint.to_account_info(), - to: context.accounts.quote_vault.to_account_info(), - authority: context.accounts.trader.to_account_info(), + from: context.accounts.trader_quote.cpi_handle_mut(), + mint: context.accounts.quote_mint.cpi_handle(), + to: context.accounts.quote_vault.cpi_handle_mut(), + authority: context.accounts.trader.cpi_handle(), }, ), amount_in, - context.accounts.quote_mint.decimals, + context.accounts.quote_mint.decimals(), )?; transfer_checked( CpiContext::new_with_signer( - context.accounts.token_program.key(), + context.accounts.token_program.address(), TransferChecked { - from: context.accounts.base_vault.to_account_info(), - mint: context.accounts.base_mint.to_account_info(), - to: context.accounts.trader_base.to_account_info(), - authority: context.accounts.market_authority.to_account_info(), + from: context.accounts.base_vault.cpi_handle_mut(), + mint: context.accounts.base_mint.cpi_handle(), + to: context.accounts.trader_base.cpi_handle_mut(), + authority: context.accounts.market_authority.cpi_handle(), }, &[authority_seeds], ), @@ -152,25 +152,25 @@ pub fn handle_swap( Direction::SellBase => { transfer_checked( CpiContext::new( - context.accounts.token_program.key(), + context.accounts.token_program.address(), TransferChecked { - from: context.accounts.trader_base.to_account_info(), - mint: context.accounts.base_mint.to_account_info(), - to: context.accounts.base_vault.to_account_info(), - authority: context.accounts.trader.to_account_info(), + from: context.accounts.trader_base.cpi_handle_mut(), + mint: context.accounts.base_mint.cpi_handle(), + to: context.accounts.base_vault.cpi_handle_mut(), + authority: context.accounts.trader.cpi_handle(), }, ), amount_in, - context.accounts.base_mint.decimals, + context.accounts.base_mint.decimals(), )?; transfer_checked( CpiContext::new_with_signer( - context.accounts.token_program.key(), + context.accounts.token_program.address(), TransferChecked { - from: context.accounts.quote_vault.to_account_info(), - mint: context.accounts.quote_mint.to_account_info(), - to: context.accounts.trader_quote.to_account_info(), - authority: context.accounts.market_authority.to_account_info(), + from: context.accounts.quote_vault.cpi_handle_mut(), + mint: context.accounts.quote_mint.cpi_handle(), + to: context.accounts.trader_quote.cpi_handle_mut(), + authority: context.accounts.market_authority.cpi_handle(), }, &[authority_seeds], ), @@ -184,9 +184,9 @@ pub fn handle_swap( } #[derive(Accounts)] -pub struct SwapAccountConstraints<'info> { +pub struct SwapAccountConstraints { #[account(mut)] - pub trader: Signer<'info>, + pub trader: Signer, #[account( seeds = [MARKET_SEED, market.base_mint.as_ref(), market.quote_mint.as_ref()], @@ -197,35 +197,35 @@ pub struct SwapAccountConstraints<'info> { has_one = base_vault, has_one = quote_vault, )] - pub market: Box>, + pub market: Box>, /// CHECK: PDA authority over both vaults; holds no data, only signs. #[account( - seeds = [AUTHORITY_SEED, market.key().as_ref()], + seeds = [AUTHORITY_SEED, market.address().as_ref()], bump = market.authority_bump, )] - pub market_authority: UncheckedAccount<'info>, + pub market_authority: UncheckedAccount, /// CHECK: validated by the `has_one = oracle_feed` constraint on the market. - pub oracle_feed: UncheckedAccount<'info>, + pub oracle_feed: UncheckedAccount, - pub base_mint: Box>, + pub base_mint: Box>, - pub quote_mint: Box>, + pub quote_mint: Box>, #[account( mut, - seeds = [BASE_VAULT_SEED, market.key().as_ref()], + seeds = [BASE_VAULT_SEED, market.address().as_ref()], bump, )] - pub base_vault: Box>, + pub base_vault: Box>, #[account( mut, - seeds = [QUOTE_VAULT_SEED, market.key().as_ref()], + seeds = [QUOTE_VAULT_SEED, market.address().as_ref()], bump, )] - pub quote_vault: Box>, + pub quote_vault: Box>, #[account( init_if_needed, @@ -234,7 +234,7 @@ pub struct SwapAccountConstraints<'info> { associated_token::authority = trader, associated_token::token_program = token_program, )] - pub trader_base: Box>, + pub trader_base: Box>, #[account( init_if_needed, @@ -243,9 +243,9 @@ pub struct SwapAccountConstraints<'info> { associated_token::authority = trader, associated_token::token_program = token_program, )] - pub trader_quote: Box>, + pub trader_quote: Box>, - pub token_program: Interface<'info, TokenInterface>, - pub associated_token_program: Program<'info, AssociatedToken>, - pub system_program: Program<'info, System>, + pub token_program: Interface<'static, TokenInterface>, + pub associated_token_program: Program, + pub system_program: Program, } diff --git a/finance/prop-amm/anchor/programs/prop-amm/src/instructions/withdraw_inventory.rs b/finance/prop-amm/anchor/programs/prop-amm/src/instructions/withdraw_inventory.rs index 6caa3649c..06a56e19f 100644 --- a/finance/prop-amm/anchor/programs/prop-amm/src/instructions/withdraw_inventory.rs +++ b/finance/prop-amm/anchor/programs/prop-amm/src/instructions/withdraw_inventory.rs @@ -14,7 +14,7 @@ use crate::state::Market; /// providers. The capital being quoted is the firm's own, so its exit needs no /// waterfall, no share burn, and no pro-rata math. pub fn handle_withdraw_inventory( - context: Context, + context: &mut Context, base_amount: u64, quote_amount: u64, ) -> Result<()> { @@ -23,16 +23,16 @@ pub fn handle_withdraw_inventory( PropAmmError::ZeroAmount ); require!( - base_amount <= context.accounts.base_vault.amount, + base_amount <= context.accounts.base_vault.amount(), PropAmmError::InsufficientInventory ); require!( - quote_amount <= context.accounts.quote_vault.amount, + quote_amount <= context.accounts.quote_vault.amount(), PropAmmError::InsufficientInventory ); let market = &context.accounts.market; - let market_key = market.key(); + let market_key = market.address(); let authority_seeds: &[&[u8]] = &[ AUTHORITY_SEED, market_key.as_ref(), @@ -42,34 +42,34 @@ pub fn handle_withdraw_inventory( if base_amount > 0 { transfer_checked( CpiContext::new_with_signer( - context.accounts.token_program.key(), + context.accounts.token_program.address(), TransferChecked { - from: context.accounts.base_vault.to_account_info(), - mint: context.accounts.base_mint.to_account_info(), - to: context.accounts.operator_base.to_account_info(), - authority: context.accounts.market_authority.to_account_info(), + from: context.accounts.base_vault.cpi_handle_mut(), + mint: context.accounts.base_mint.cpi_handle(), + to: context.accounts.operator_base.cpi_handle_mut(), + authority: context.accounts.market_authority.cpi_handle(), }, &[authority_seeds], ), base_amount, - context.accounts.base_mint.decimals, + context.accounts.base_mint.decimals(), )?; } if quote_amount > 0 { transfer_checked( CpiContext::new_with_signer( - context.accounts.token_program.key(), + context.accounts.token_program.address(), TransferChecked { - from: context.accounts.quote_vault.to_account_info(), - mint: context.accounts.quote_mint.to_account_info(), - to: context.accounts.operator_quote.to_account_info(), - authority: context.accounts.market_authority.to_account_info(), + from: context.accounts.quote_vault.cpi_handle_mut(), + mint: context.accounts.quote_mint.cpi_handle(), + to: context.accounts.operator_quote.cpi_handle_mut(), + authority: context.accounts.market_authority.cpi_handle(), }, &[authority_seeds], ), quote_amount, - context.accounts.quote_mint.decimals, + context.accounts.quote_mint.decimals(), )?; } @@ -77,9 +77,9 @@ pub fn handle_withdraw_inventory( } #[derive(Accounts)] -pub struct WithdrawInventoryAccountConstraints<'info> { +pub struct WithdrawInventoryAccountConstraints { #[account(mut)] - pub operator: Signer<'info>, + pub operator: Signer, #[account( seeds = [MARKET_SEED, market.base_mint.as_ref(), market.quote_mint.as_ref()], @@ -90,32 +90,32 @@ pub struct WithdrawInventoryAccountConstraints<'info> { has_one = base_vault, has_one = quote_vault, )] - pub market: Box>, + pub market: Box>, /// CHECK: PDA authority over both vaults; holds no data, only signs. #[account( - seeds = [AUTHORITY_SEED, market.key().as_ref()], + seeds = [AUTHORITY_SEED, market.address().as_ref()], bump = market.authority_bump, )] - pub market_authority: UncheckedAccount<'info>, + pub market_authority: UncheckedAccount, - pub base_mint: Box>, + pub base_mint: Box>, - pub quote_mint: Box>, + pub quote_mint: Box>, #[account( mut, - seeds = [BASE_VAULT_SEED, market.key().as_ref()], + seeds = [BASE_VAULT_SEED, market.address().as_ref()], bump, )] - pub base_vault: Box>, + pub base_vault: Box>, #[account( mut, - seeds = [QUOTE_VAULT_SEED, market.key().as_ref()], + seeds = [QUOTE_VAULT_SEED, market.address().as_ref()], bump, )] - pub quote_vault: Box>, + pub quote_vault: Box>, #[account( mut, @@ -123,7 +123,7 @@ pub struct WithdrawInventoryAccountConstraints<'info> { associated_token::authority = operator, associated_token::token_program = token_program, )] - pub operator_base: Box>, + pub operator_base: Box>, #[account( mut, @@ -131,7 +131,7 @@ pub struct WithdrawInventoryAccountConstraints<'info> { associated_token::authority = operator, associated_token::token_program = token_program, )] - pub operator_quote: Box>, + pub operator_quote: Box>, - pub token_program: Interface<'info, TokenInterface>, + pub token_program: Interface<'static, TokenInterface>, } diff --git a/finance/prop-amm/anchor/programs/prop-amm/src/lib.rs b/finance/prop-amm/anchor/programs/prop-amm/src/lib.rs index 0706dfb43..ff0f5e72f 100644 --- a/finance/prop-amm/anchor/programs/prop-amm/src/lib.rs +++ b/finance/prop-amm/anchor/programs/prop-amm/src/lib.rs @@ -31,7 +31,7 @@ pub mod prop_amm { /// The signer becomes the market's operator: the only party allowed to /// move inventory or change the quote. pub fn initialize_market( - context: Context, + context: &mut Context, parameters: MarketParameters, ) -> Result<()> { instructions::handle_initialize_market(context, parameters) @@ -40,7 +40,7 @@ pub mod prop_amm { /// Operator moves inventory into the market's vaults. Either amount may be /// zero, but not both. pub fn deposit_inventory( - context: Context, + context: &mut Context, base_amount: u64, quote_amount: u64, ) -> Result<()> { @@ -51,7 +51,7 @@ pub mod prop_amm { /// it, at any time. The capital is the operator's own; nobody else has a /// claim on it. pub fn withdraw_inventory( - context: Context, + context: &mut Context, base_amount: u64, quote_amount: u64, ) -> Result<()> { @@ -62,7 +62,7 @@ pub mod prop_amm { /// during volatility is not an emergency measure for a market maker; it is /// Tuesday. pub fn set_quote( - context: Context, + context: &mut Context, spread_bps: u16, paused: bool, ) -> Result<()> { @@ -73,7 +73,7 @@ pub mod prop_amm { /// spread, or sell it at oracle minus spread. Permissionless. /// `minimum_amount_out` is slippage protection; pass `0` to opt out. pub fn swap( - context: Context, + context: &mut Context, direction: Direction, amount_in: u64, minimum_amount_out: u64, diff --git a/finance/prop-amm/anchor/programs/prop-amm/src/state/market.rs b/finance/prop-amm/anchor/programs/prop-amm/src/state/market.rs index 8aac7c7b0..545315ceb 100644 --- a/finance/prop-amm/anchor/programs/prop-amm/src/state/market.rs +++ b/finance/prop-amm/anchor/programs/prop-amm/src/state/market.rs @@ -19,25 +19,25 @@ pub enum Direction { /// on. The price comes from the oracle; the vault balances only bound how much /// of a fill is possible. And because nobody but the operator has a claim on /// the vaults, the token balances themselves are the complete accounting. -#[account] +#[account(borsh)] #[derive(InitSpace)] pub struct Market { /// The market-making firm. Deposits and withdraws inventory, sets the /// spread, pauses quoting. Cannot touch anyone else's funds, because the /// market never holds anyone else's funds. - pub operator: Pubkey, + pub operator: Address, - pub base_mint: Pubkey, + pub base_mint: Address, - pub quote_mint: Pubkey, + pub quote_mint: Address, /// Oracle feed this market quotes from. Stored so handlers can reject any /// substituted feed account. - pub oracle_feed: Pubkey, + pub oracle_feed: Address, - pub base_vault: Pubkey, + pub base_vault: Address, - pub quote_vault: Pubkey, + pub quote_vault: Address, /// Decimal places the oracle price is quoted in. Pinned at creation so a /// feed that silently changes scale is rejected rather than mis-read. diff --git a/finance/prop-amm/anchor/programs/prop-amm/src/state/oracle.rs b/finance/prop-amm/anchor/programs/prop-amm/src/state/oracle.rs index 83479816a..d252f06d2 100644 --- a/finance/prop-amm/anchor/programs/prop-amm/src/state/oracle.rs +++ b/finance/prop-amm/anchor/programs/prop-amm/src/state/oracle.rs @@ -6,7 +6,7 @@ use crate::errors::PropAmmError; // Byte layout of the feed account this program reads. It matches the // `mock_switchboard::MockFeed` account: an 8-byte Anchor discriminator followed -// by `authority: Pubkey (32)`, `price: i128 (16)`, `scale: u32 (4)`, +// by `authority: Address (32)`, `price: i128 (16)`, `scale: u32 (4)`, // `last_update_slot: u64 (8)`, `confidence: u64 (8)`. // // We read the raw bytes rather than deserializing the mock account type so this @@ -42,7 +42,7 @@ const FEED_MINIMUM_LENGTH: usize = CONFIDENCE_OFFSET + 8; /// scale, and a price whose confidence band exceeds `max_confidence_bps` of /// the price. pub fn read_oracle_price( - feed: &AccountInfo, + feed: &AccountView, expected_scale: u32, max_confidence_bps: u16, ) -> Result { diff --git a/finance/token-fundraiser/anchor/programs/fundraiser/Cargo.toml b/finance/token-fundraiser/anchor/programs/fundraiser/Cargo.toml index 66991a7cf..4e12e6c47 100644 --- a/finance/token-fundraiser/anchor/programs/fundraiser/Cargo.toml +++ b/finance/token-fundraiser/anchor/programs/fundraiser/Cargo.toml @@ -14,14 +14,22 @@ cpi = ["no-entrypoint"] no-entrypoint = [] no-idl = [] no-log-ix-name = [] -idl-build = ["anchor-lang/idl-build", "anchor-spl/idl-build"] +idl-build = ["anchor-lang/idl-build"] anchor-debug = [] custom-heap = [] custom-panic = [] [dependencies] -anchor-lang = { version = "1.1.2", features = ["init-if-needed"] } -anchor-spl = "1.1.2" +anchor-lang = { version = "2.0.0-rc.1", features = ["compat"] } +# The `#[program]` macro expands to `wincode` paths for instruction-data +# (de)serialization, so the crate has to be a direct dependency. +wincode = { version = "0.5", features = ["derive"] } +# anchor-lang 2.0.0-rc.1 is built against wincode 0.5, but solana-address 2.7 +# moved to wincode 0.6. With both in the graph, `Address`'s wincode impls +# belong to the version the account derives are not using and every +# `SchemaRead`/`SchemaWrite` bound fails. 2.6.1 is the last 0.5-line release. +solana-address = ">=2.6, <2.7" +anchor-spl = "2.0.0-rc.1" [dev-dependencies] litesvm = "0.13.1" diff --git a/finance/token-fundraiser/anchor/programs/fundraiser/src/instructions/checker.rs b/finance/token-fundraiser/anchor/programs/fundraiser/src/instructions/checker.rs index 333bae498..6ce070440 100644 --- a/finance/token-fundraiser/anchor/programs/fundraiser/src/instructions/checker.rs +++ b/finance/token-fundraiser/anchor/programs/fundraiser/src/instructions/checker.rs @@ -10,19 +10,19 @@ use anchor_spl::{ use crate::{state::Fundraiser, FundraiserError}; #[derive(Accounts)] -pub struct CheckContributionsAccountConstraints<'info> { +pub struct CheckContributionsAccountConstraints { #[account(mut)] - pub maker: Signer<'info>, + pub maker: Signer, - pub mint_to_raise: InterfaceAccount<'info, Mint>, + pub mint_to_raise: InterfaceAccount, #[account( mut, - seeds = [b"fundraiser".as_ref(), maker.key().as_ref()], + seeds = [b"fundraiser".as_ref(), maker.address().as_ref()], bump = fundraiser.bump, close = maker, )] - pub fundraiser: Account<'info, Fundraiser>, + pub fundraiser: BorshAccount, #[account( mut, @@ -30,7 +30,7 @@ pub struct CheckContributionsAccountConstraints<'info> { associated_token::authority = fundraiser, associated_token::token_program = token_program, )] - pub vault: InterfaceAccount<'info, TokenAccount>, + pub vault: InterfaceAccount, #[account( init_if_needed, @@ -39,13 +39,13 @@ pub struct CheckContributionsAccountConstraints<'info> { associated_token::authority = maker, associated_token::token_program = token_program, )] - pub maker_ata: InterfaceAccount<'info, TokenAccount>, + pub maker_ata: InterfaceAccount, - pub token_program: Interface<'info, TokenInterface>, + pub token_program: Interface<'static, TokenInterface>, - pub system_program: Program<'info, System>, + pub system_program: Program, - pub associated_token_program: Program<'info, AssociatedToken>, + pub associated_token_program: Program, } pub fn handle_check_contributions( @@ -62,36 +62,36 @@ pub fn handle_check_contributions( // its seeds. let signer_seeds: [&[&[u8]]; 1] = [&[ b"fundraiser".as_ref(), - accounts.maker.to_account_info().key.as_ref(), + accounts.maker.cpi_handle_mut().address().as_ref(), &[accounts.fundraiser.bump], ]]; // Drain the whole vault (including any direct donations) to the maker. let transfer_accounts = TransferChecked { - from: accounts.vault.to_account_info(), - mint: accounts.mint_to_raise.to_account_info(), - to: accounts.maker_ata.to_account_info(), - authority: accounts.fundraiser.to_account_info(), + from: accounts.vault.cpi_handle_mut(), + mint: accounts.mint_to_raise.cpi_handle(), + to: accounts.maker_ata.cpi_handle_mut(), + authority: accounts.fundraiser.cpi_handle(), }; let transfer_context = CpiContext::new_with_signer( - accounts.token_program.key(), + accounts.token_program.address(), transfer_accounts, &signer_seeds, ); transfer_checked( transfer_context, - accounts.vault.amount, - accounts.mint_to_raise.decimals, + accounts.vault.amount(), + accounts.mint_to_raise.decimals(), )?; // Close the empty vault so its rent goes back to the maker. let close_accounts = CloseAccount { - account: accounts.vault.to_account_info(), - destination: accounts.maker.to_account_info(), - authority: accounts.fundraiser.to_account_info(), + account: accounts.vault.cpi_handle_mut(), + destination: accounts.maker.cpi_handle_mut(), + authority: accounts.fundraiser.cpi_handle(), }; let close_context = CpiContext::new_with_signer( - accounts.token_program.key(), + accounts.token_program.address(), close_accounts, &signer_seeds, ); diff --git a/finance/token-fundraiser/anchor/programs/fundraiser/src/instructions/close.rs b/finance/token-fundraiser/anchor/programs/fundraiser/src/instructions/close.rs index c888cbc26..455c5596f 100644 --- a/finance/token-fundraiser/anchor/programs/fundraiser/src/instructions/close.rs +++ b/finance/token-fundraiser/anchor/programs/fundraiser/src/instructions/close.rs @@ -10,20 +10,20 @@ use anchor_spl::{ use crate::{state::Fundraiser, FundraiserError, SECONDS_TO_DAYS}; #[derive(Accounts)] -pub struct CloseFundraiserAccountConstraints<'info> { +pub struct CloseFundraiserAccountConstraints { #[account(mut)] - pub maker: Signer<'info>, + pub maker: Signer, - pub mint_to_raise: InterfaceAccount<'info, Mint>, + pub mint_to_raise: InterfaceAccount, #[account( mut, has_one = mint_to_raise, - seeds = [b"fundraiser".as_ref(), maker.key().as_ref()], + seeds = [b"fundraiser".as_ref(), maker.address().as_ref()], bump = fundraiser.bump, close = maker, )] - pub fundraiser: Account<'info, Fundraiser>, + pub fundraiser: BorshAccount, #[account( mut, @@ -31,7 +31,7 @@ pub struct CloseFundraiserAccountConstraints<'info> { associated_token::authority = fundraiser, associated_token::token_program = token_program, )] - pub vault: InterfaceAccount<'info, TokenAccount>, + pub vault: InterfaceAccount, #[account( init_if_needed, @@ -40,13 +40,13 @@ pub struct CloseFundraiserAccountConstraints<'info> { associated_token::authority = maker, associated_token::token_program = token_program, )] - pub maker_ata: InterfaceAccount<'info, TokenAccount>, + pub maker_ata: InterfaceAccount, - pub token_program: Interface<'info, TokenInterface>, + pub token_program: Interface<'static, TokenInterface>, - pub system_program: Program<'info, System>, + pub system_program: Program, - pub associated_token_program: Program<'info, AssociatedToken>, + pub associated_token_program: Program, } /// Retires a failed fundraiser so the maker can raise again. @@ -87,41 +87,41 @@ pub fn handle_close_fundraiser(accounts: &mut CloseFundraiserAccountConstraints) // its seeds. let signer_seeds: [&[&[u8]]; 1] = [&[ b"fundraiser".as_ref(), - accounts.maker.to_account_info().key.as_ref(), + accounts.maker.cpi_handle_mut().address().as_ref(), &[accounts.fundraiser.bump], ]]; // Refunds have already drained every tracked contribution, so anything // left in the vault is a direct donation; sweep it to the maker rather // than burn it with the account. - if accounts.vault.amount > 0 { + if accounts.vault.amount() > 0 { let transfer_accounts = TransferChecked { - from: accounts.vault.to_account_info(), - mint: accounts.mint_to_raise.to_account_info(), - to: accounts.maker_ata.to_account_info(), - authority: accounts.fundraiser.to_account_info(), + from: accounts.vault.cpi_handle_mut(), + mint: accounts.mint_to_raise.cpi_handle(), + to: accounts.maker_ata.cpi_handle_mut(), + authority: accounts.fundraiser.cpi_handle(), }; let transfer_context = CpiContext::new_with_signer( - accounts.token_program.key(), + accounts.token_program.address(), transfer_accounts, &signer_seeds, ); transfer_checked( transfer_context, - accounts.vault.amount, - accounts.mint_to_raise.decimals, + accounts.vault.amount(), + accounts.mint_to_raise.decimals(), )?; } // Close the empty vault so its rent goes back to the maker. The // fundraiser account itself is closed by its close = maker constraint. let close_accounts = CloseAccount { - account: accounts.vault.to_account_info(), - destination: accounts.maker.to_account_info(), - authority: accounts.fundraiser.to_account_info(), + account: accounts.vault.cpi_handle_mut(), + destination: accounts.maker.cpi_handle_mut(), + authority: accounts.fundraiser.cpi_handle(), }; let close_context = CpiContext::new_with_signer( - accounts.token_program.key(), + accounts.token_program.address(), close_accounts, &signer_seeds, ); diff --git a/finance/token-fundraiser/anchor/programs/fundraiser/src/instructions/contribute.rs b/finance/token-fundraiser/anchor/programs/fundraiser/src/instructions/contribute.rs index 67e73d2f6..da8ffa926 100644 --- a/finance/token-fundraiser/anchor/programs/fundraiser/src/instructions/contribute.rs +++ b/finance/token-fundraiser/anchor/programs/fundraiser/src/instructions/contribute.rs @@ -9,11 +9,11 @@ use crate::{ }; #[derive(Accounts)] -pub struct ContributeAccountConstraints<'info> { +pub struct ContributeAccountConstraints { #[account(mut)] - pub contributor: Signer<'info>, + pub contributor: Signer, - pub mint_to_raise: InterfaceAccount<'info, Mint>, + pub mint_to_raise: InterfaceAccount, #[account( mut, @@ -21,16 +21,16 @@ pub struct ContributeAccountConstraints<'info> { seeds = [b"fundraiser".as_ref(), fundraiser.maker.as_ref()], bump = fundraiser.bump, )] - pub fundraiser: Account<'info, Fundraiser>, + pub fundraiser: BorshAccount, #[account( init_if_needed, payer = contributor, - seeds = [b"contributor", fundraiser.key().as_ref(), contributor.key().as_ref()], + seeds = [b"contributor", fundraiser.address().as_ref(), contributor.address().as_ref()], bump, space = Contributor::DISCRIMINATOR.len() + Contributor::INIT_SPACE, )] - pub contributor_account: Account<'info, Contributor>, + pub contributor_account: BorshAccount, #[account( mut, @@ -38,7 +38,7 @@ pub struct ContributeAccountConstraints<'info> { associated_token::authority = contributor, associated_token::token_program = token_program, )] - pub contributor_ata: InterfaceAccount<'info, TokenAccount>, + pub contributor_ata: InterfaceAccount, #[account( mut, @@ -46,11 +46,11 @@ pub struct ContributeAccountConstraints<'info> { associated_token::authority = fundraiser, associated_token::token_program = token_program, )] - pub vault: InterfaceAccount<'info, TokenAccount>, + pub vault: InterfaceAccount, - pub token_program: Interface<'info, TokenInterface>, + pub token_program: Interface<'static, TokenInterface>, - pub system_program: Program<'info, System>, + pub system_program: Program, } /// Caps a single contributor at MAX_CONTRIBUTION_PERCENTAGE percent of the @@ -72,7 +72,7 @@ pub fn handle_contribute( ) -> Result<()> { // The minimum contribution is one major unit, which is 10^decimals minor units. let one_major_unit = 10_u64 - .checked_pow(accounts.mint_to_raise.decimals as u32) + .checked_pow(accounts.mint_to_raise.decimals() as u32) .ok_or(FundraiserError::MathOverflow)?; require!( amount >= one_major_unit, @@ -124,13 +124,13 @@ pub fn handle_contribute( // Transfer the funds from the contributor to the vault. let cpi_accounts = TransferChecked { - from: accounts.contributor_ata.to_account_info(), - mint: accounts.mint_to_raise.to_account_info(), - to: accounts.vault.to_account_info(), - authority: accounts.contributor.to_account_info(), + from: accounts.contributor_ata.cpi_handle_mut(), + mint: accounts.mint_to_raise.cpi_handle(), + to: accounts.vault.cpi_handle_mut(), + authority: accounts.contributor.cpi_handle(), }; - let cpi_context = CpiContext::new(accounts.token_program.key(), cpi_accounts); - transfer_checked(cpi_context, amount, accounts.mint_to_raise.decimals)?; + let cpi_context = CpiContext::new(accounts.token_program.address(), cpi_accounts); + transfer_checked(cpi_context, amount, accounts.mint_to_raise.decimals())?; Ok(()) } diff --git a/finance/token-fundraiser/anchor/programs/fundraiser/src/instructions/initialize_fundraiser.rs b/finance/token-fundraiser/anchor/programs/fundraiser/src/instructions/initialize_fundraiser.rs index b105d17a5..125bf1a38 100644 --- a/finance/token-fundraiser/anchor/programs/fundraiser/src/instructions/initialize_fundraiser.rs +++ b/finance/token-fundraiser/anchor/programs/fundraiser/src/instructions/initialize_fundraiser.rs @@ -7,20 +7,20 @@ use anchor_spl::{ use crate::{state::Fundraiser, FundraiserError, MIN_AMOUNT_TO_RAISE}; #[derive(Accounts)] -pub struct InitializeFundraiserAccountConstraints<'info> { +pub struct InitializeFundraiserAccountConstraints { #[account(mut)] - pub maker: Signer<'info>, + pub maker: Signer, - pub mint_to_raise: InterfaceAccount<'info, Mint>, + pub mint_to_raise: InterfaceAccount, #[account( init, payer = maker, - seeds = [b"fundraiser", maker.key().as_ref()], + seeds = [b"fundraiser", maker.address().as_ref()], bump, space = Fundraiser::DISCRIMINATOR.len() + Fundraiser::INIT_SPACE, )] - pub fundraiser: Account<'info, Fundraiser>, + pub fundraiser: BorshAccount, #[account( init, @@ -29,13 +29,13 @@ pub struct InitializeFundraiserAccountConstraints<'info> { associated_token::authority = fundraiser, associated_token::token_program = token_program, )] - pub vault: InterfaceAccount<'info, TokenAccount>, + pub vault: InterfaceAccount, - pub system_program: Program<'info, System>, + pub system_program: Program, - pub token_program: Interface<'info, TokenInterface>, + pub token_program: Interface<'static, TokenInterface>, - pub associated_token_program: Program<'info, AssociatedToken>, + pub associated_token_program: Program, } pub fn handle_initialize_fundraiser( @@ -47,7 +47,7 @@ pub fn handle_initialize_fundraiser( // The target must be at least MIN_AMOUNT_TO_RAISE major units, expressed // in minor units: MIN_AMOUNT_TO_RAISE * 10^decimals. let one_major_unit = 10_u64 - .checked_pow(accounts.mint_to_raise.decimals as u32) + .checked_pow(accounts.mint_to_raise.decimals() as u32) .ok_or(FundraiserError::MathOverflow)?; let minimum_amount_to_raise = MIN_AMOUNT_TO_RAISE .checked_mul(one_major_unit) @@ -57,9 +57,9 @@ pub fn handle_initialize_fundraiser( FundraiserError::InvalidAmount ); - accounts.fundraiser.set_inner(Fundraiser { - maker: accounts.maker.key(), - mint_to_raise: accounts.mint_to_raise.key(), + *accounts.fundraiser = (Fundraiser { + maker: *accounts.maker.address(), + mint_to_raise: *accounts.mint_to_raise.address(), amount_to_raise: amount, current_amount: 0, time_started: Clock::get()?.unix_timestamp, diff --git a/finance/token-fundraiser/anchor/programs/fundraiser/src/instructions/refund.rs b/finance/token-fundraiser/anchor/programs/fundraiser/src/instructions/refund.rs index 61fb2babe..db1702e81 100644 --- a/finance/token-fundraiser/anchor/programs/fundraiser/src/instructions/refund.rs +++ b/finance/token-fundraiser/anchor/programs/fundraiser/src/instructions/refund.rs @@ -9,29 +9,29 @@ use crate::{ }; #[derive(Accounts)] -pub struct RefundAccountConstraints<'info> { +pub struct RefundAccountConstraints { #[account(mut)] - pub contributor: Signer<'info>, + pub contributor: Signer, - pub maker: SystemAccount<'info>, + pub maker: SystemAccount, - pub mint_to_raise: InterfaceAccount<'info, Mint>, + pub mint_to_raise: InterfaceAccount, #[account( mut, has_one = mint_to_raise, - seeds = [b"fundraiser", maker.key().as_ref()], + seeds = [b"fundraiser", maker.address().as_ref()], bump = fundraiser.bump, )] - pub fundraiser: Account<'info, Fundraiser>, + pub fundraiser: BorshAccount, #[account( mut, - seeds = [b"contributor", fundraiser.key().as_ref(), contributor.key().as_ref()], + seeds = [b"contributor", fundraiser.address().as_ref(), contributor.address().as_ref()], bump = contributor_account.bump, close = contributor, )] - pub contributor_account: Account<'info, Contributor>, + pub contributor_account: BorshAccount, #[account( mut, @@ -39,7 +39,7 @@ pub struct RefundAccountConstraints<'info> { associated_token::authority = contributor, associated_token::token_program = token_program, )] - pub contributor_ata: InterfaceAccount<'info, TokenAccount>, + pub contributor_ata: InterfaceAccount, #[account( mut, @@ -47,11 +47,11 @@ pub struct RefundAccountConstraints<'info> { associated_token::authority = fundraiser, associated_token::token_program = token_program, )] - pub vault: InterfaceAccount<'info, TokenAccount>, + pub vault: InterfaceAccount, - pub token_program: Interface<'info, TokenInterface>, + pub token_program: Interface<'static, TokenInterface>, - pub system_program: Program<'info, System>, + pub system_program: Program, } pub fn handle_refund(accounts: &mut RefundAccountConstraints) -> Result<()> { @@ -88,25 +88,25 @@ pub fn handle_refund(accounts: &mut RefundAccountConstraints) -> Result<()> { // Transfer the funds from the vault back to the contributor. The vault is // owned by the fundraiser PDA, so the CPI is signed with its seeds. let cpi_accounts = TransferChecked { - from: accounts.vault.to_account_info(), - mint: accounts.mint_to_raise.to_account_info(), - to: accounts.contributor_ata.to_account_info(), - authority: accounts.fundraiser.to_account_info(), + from: accounts.vault.cpi_handle_mut(), + mint: accounts.mint_to_raise.cpi_handle(), + to: accounts.contributor_ata.cpi_handle_mut(), + authority: accounts.fundraiser.cpi_handle(), }; let signer_seeds: [&[&[u8]]; 1] = [&[ b"fundraiser".as_ref(), - accounts.maker.to_account_info().key.as_ref(), + accounts.maker.cpi_handle_mut().address().as_ref(), &[accounts.fundraiser.bump], ]]; let cpi_context = CpiContext::new_with_signer( - accounts.token_program.key(), + accounts.token_program.address(), cpi_accounts, &signer_seeds, ); transfer_checked( cpi_context, refund_amount, - accounts.mint_to_raise.decimals, + accounts.mint_to_raise.decimals(), )?; Ok(()) diff --git a/finance/token-fundraiser/anchor/programs/fundraiser/src/lib.rs b/finance/token-fundraiser/anchor/programs/fundraiser/src/lib.rs index ccbee926a..26ddae74f 100644 --- a/finance/token-fundraiser/anchor/programs/fundraiser/src/lib.rs +++ b/finance/token-fundraiser/anchor/programs/fundraiser/src/lib.rs @@ -16,7 +16,7 @@ pub mod fundraiser { use super::*; pub fn initialize_fundraiser( - mut context: Context, + mut context: &mut Context, amount: u64, duration: u16, ) -> Result<()> { @@ -26,7 +26,7 @@ pub mod fundraiser { } pub fn contribute( - mut context: Context, + mut context: &mut Context, amount: u64, ) -> Result<()> { handle_contribute(&mut context.accounts, amount, &context.bumps)?; @@ -35,20 +35,20 @@ pub mod fundraiser { } pub fn check_contributions( - mut context: Context, + mut context: &mut Context, ) -> Result<()> { handle_check_contributions(&mut context.accounts)?; Ok(()) } - pub fn refund(mut context: Context) -> Result<()> { + pub fn refund(mut context: &mut Context) -> Result<()> { handle_refund(&mut context.accounts)?; Ok(()) } - pub fn close_fundraiser(mut context: Context) -> Result<()> { + pub fn close_fundraiser(mut context: &mut Context) -> Result<()> { handle_close_fundraiser(&mut context.accounts)?; Ok(()) diff --git a/finance/token-fundraiser/anchor/programs/fundraiser/src/state/contributor.rs b/finance/token-fundraiser/anchor/programs/fundraiser/src/state/contributor.rs index 33b0c00e4..616d570e2 100644 --- a/finance/token-fundraiser/anchor/programs/fundraiser/src/state/contributor.rs +++ b/finance/token-fundraiser/anchor/programs/fundraiser/src/state/contributor.rs @@ -1,6 +1,6 @@ use anchor_lang::prelude::*; -#[account] +#[account(borsh)] #[derive(InitSpace)] pub struct Contributor { pub amount: u64, diff --git a/finance/token-fundraiser/anchor/programs/fundraiser/src/state/fundraiser.rs b/finance/token-fundraiser/anchor/programs/fundraiser/src/state/fundraiser.rs index cab7d1c60..1a7ad324b 100644 --- a/finance/token-fundraiser/anchor/programs/fundraiser/src/state/fundraiser.rs +++ b/finance/token-fundraiser/anchor/programs/fundraiser/src/state/fundraiser.rs @@ -1,10 +1,10 @@ use anchor_lang::prelude::*; -#[account] +#[account(borsh)] #[derive(InitSpace)] pub struct Fundraiser { - pub maker: Pubkey, - pub mint_to_raise: Pubkey, + pub maker: Address, + pub mint_to_raise: Address, pub amount_to_raise: u64, pub current_amount: u64, pub time_started: i64, diff --git a/finance/token-swap/anchor/programs/token-swap/Cargo.toml b/finance/token-swap/anchor/programs/token-swap/Cargo.toml index 18f746194..e7041985c 100644 --- a/finance/token-swap/anchor/programs/token-swap/Cargo.toml +++ b/finance/token-swap/anchor/programs/token-swap/Cargo.toml @@ -14,14 +14,22 @@ cpi = ["no-entrypoint"] no-entrypoint = [] no-idl = [] no-log-ix-name = [] -idl-build = ["anchor-lang/idl-build", "anchor-spl/idl-build"] +idl-build = ["anchor-lang/idl-build"] anchor-debug = [] custom-heap = [] custom-panic = [] [dependencies] -anchor-lang = { version = "1.1.2", features = ["init-if-needed"] } -anchor-spl = { version = "1.1.2", features = ["metadata", "spl-token-interface"] } +anchor-lang = { version = "2.0.0-rc.1", features = ["compat"] } +# The `#[program]` macro expands to `wincode` paths for instruction-data +# (de)serialization, so the crate has to be a direct dependency. +wincode = { version = "0.5", features = ["derive"] } +# anchor-lang 2.0.0-rc.1 is built against wincode 0.5, but solana-address 2.7 +# moved to wincode 0.6. With both in the graph, `Address`'s wincode impls +# belong to the version the account derives are not using and every +# `SchemaRead`/`SchemaWrite` bound fails. 2.6.1 is the last 0.5-line release. +solana-address = ">=2.6, <2.7" +anchor-spl = { version = "2.0.0-rc.1", features = ["metadata"] } # `fixed` removed: all financial math is now u128 + checked_*, matching how # production Solana AMMs (Orca, Raydium, Meteora, Saber) do it. Floats / # fixed-point types are not used for money in this program. diff --git a/finance/token-swap/anchor/programs/token-swap/src/instructions/admin/claim_admin_fees.rs b/finance/token-swap/anchor/programs/token-swap/src/instructions/admin/claim_admin_fees.rs index 78ddb1795..726741646 100644 --- a/finance/token-swap/anchor/programs/token-swap/src/instructions/admin/claim_admin_fees.rs +++ b/finance/token-swap/anchor/programs/token-swap/src/instructions/admin/claim_admin_fees.rs @@ -1,4 +1,5 @@ use anchor_lang::prelude::*; +use anchor_spl::token; use anchor_spl::token_interface::{self, Mint, TokenAccount, TokenInterface, TransferChecked}; use crate::{ @@ -19,7 +20,7 @@ use crate::{ /// `Signer` constraint on `admin` together mean only the address stored in /// `Config.admin` can call this. Any other signer will be rejected by /// Anchor's built-in `has_one` check. -pub fn handle_claim_admin_fees(context: Context) -> Result<()> { +pub fn handle_claim_admin_fees(context: &mut Context) -> Result<()> { let owed_a = context.accounts.pool_config.admin_fees_owed_a; let owed_b = context.accounts.pool_config.admin_fees_owed_b; @@ -37,8 +38,8 @@ pub fn handle_claim_admin_fees(context: Context 0 { token_interface::transfer_checked( CpiContext::new_with_signer( - context.accounts.token_program.key(), + context.accounts.token_program.address(), TransferChecked { - from: context.accounts.pool_a.to_account_info(), - mint: context.accounts.mint_a.to_account_info(), - to: context.accounts.admin_token_a.to_account_info(), - authority: context.accounts.pool_authority.to_account_info(), + from: context.accounts.pool_a.cpi_handle_mut(), + mint: context.accounts.mint_a.cpi_handle(), + to: context.accounts.admin_token_a.cpi_handle_mut(), + authority: context.accounts.pool_authority.cpi_handle(), }, signer_seeds, ), owed_a, - context.accounts.mint_a.decimals, + context.accounts.mint_a.decimals(), )?; } if owed_b > 0 { token_interface::transfer_checked( CpiContext::new_with_signer( - context.accounts.token_program.key(), + context.accounts.token_program.address(), TransferChecked { - from: context.accounts.pool_b.to_account_info(), - mint: context.accounts.mint_b.to_account_info(), - to: context.accounts.admin_token_b.to_account_info(), - authority: context.accounts.pool_authority.to_account_info(), + from: context.accounts.pool_b.cpi_handle_mut(), + mint: context.accounts.mint_b.cpi_handle(), + to: context.accounts.admin_token_b.cpi_handle_mut(), + authority: context.accounts.pool_authority.cpi_handle(), }, signer_seeds, ), owed_b, - context.accounts.mint_b.decimals, + context.accounts.mint_b.decimals(), )?; } @@ -98,43 +99,43 @@ pub fn handle_claim_admin_fees(context: Context { +pub struct ClaimAdminFeesAccountConstraints { #[account( seeds = [CONFIG_SEED], bump, has_one = admin, )] - pub config: Account<'info, Config>, + pub config: BorshAccount, #[account( mut, seeds = [ pool_config.config.as_ref(), - pool_config.mint_a.key().as_ref(), - pool_config.mint_b.key().as_ref(), + pool_config.mint_a.address().as_ref(), + pool_config.mint_b.address().as_ref(), ], bump, has_one = config, has_one = mint_a, has_one = mint_b, )] - pub pool_config: Account<'info, PoolConfig>, + pub pool_config: BorshAccount, /// CHECK: PDA that owns the pool reserves; signs the outbound transfers. #[account( seeds = [ pool_config.config.as_ref(), - mint_a.key().as_ref(), - mint_b.key().as_ref(), + mint_a.address().as_ref(), + mint_b.address().as_ref(), AUTHORITY_SEED, ], bump, )] - pub pool_authority: UncheckedAccount<'info>, + pub pool_authority: UncheckedAccount, - pub mint_a: Box>, + pub mint_a: Box>, - pub mint_b: Box>, + pub mint_b: Box>, /// The pool's token-A reserve. The admin's owed token-A fees are paid out /// of this account. @@ -144,7 +145,7 @@ pub struct ClaimAdminFeesAccountConstraints<'info> { associated_token::authority = pool_authority, associated_token::token_program = token_program, )] - pub pool_a: Box>, + pub pool_a: Box>, /// The pool's token-B reserve. The admin's owed token-B fees are paid out /// of this account. @@ -154,11 +155,11 @@ pub struct ClaimAdminFeesAccountConstraints<'info> { associated_token::authority = pool_authority, associated_token::token_program = token_program, )] - pub pool_b: Box>, + pub pool_b: Box>, /// Must match the address stored in `Config.admin` (enforced by /// `has_one = admin` above). - pub admin: Signer<'info>, + pub admin: Signer, /// Admin's token-A receiving account. Must already exist; the admin is /// expected to create it themselves before calling. Keeps this handler @@ -169,7 +170,7 @@ pub struct ClaimAdminFeesAccountConstraints<'info> { token::authority = admin, token::token_program = token_program, )] - pub admin_token_a: Box>, + pub admin_token_a: Box>, /// Admin's token-B receiving account. Same constraints as `admin_token_a`. #[account( @@ -178,7 +179,7 @@ pub struct ClaimAdminFeesAccountConstraints<'info> { token::authority = admin, token::token_program = token_program, )] - pub admin_token_b: Box>, + pub admin_token_b: Box>, - pub token_program: Interface<'info, TokenInterface>, + pub token_program: Interface<'static, TokenInterface>, } diff --git a/finance/token-swap/anchor/programs/token-swap/src/instructions/deposit_liquidity.rs b/finance/token-swap/anchor/programs/token-swap/src/instructions/deposit_liquidity.rs index a6d97ff26..5b5cd3931 100644 --- a/finance/token-swap/anchor/programs/token-swap/src/instructions/deposit_liquidity.rs +++ b/finance/token-swap/anchor/programs/token-swap/src/instructions/deposit_liquidity.rs @@ -28,7 +28,7 @@ fn integer_sqrt(n: u128) -> u128 { } pub fn handle_deposit_liquidity( - context: Context, + context: &mut Context, amount_a: u64, amount_b: u64, minimum_lp_tokens_out: u64, @@ -37,8 +37,8 @@ pub fn handle_deposit_liquidity( // silently clamped to the available balance, which broke slippage protection // for callers building on top - they expected their input amount to be the // amount actually deposited. - if amount_a > context.accounts.token_a.amount - || amount_b > context.accounts.token_b.amount + if amount_a > context.accounts.token_a.amount() + || amount_b > context.accounts.token_b.amount() { return err!(AmmError::InsufficientBalance); } @@ -69,11 +69,11 @@ pub fn handle_deposit_liquidity( // checked_sub: admin_fees_owed is an invariant subset of the vault balance; // a raw `-` would wrap silently on a BPF release build if that ever broke. let effective_pool_a = pool_a - .amount + .amount() .checked_sub(pool_config.admin_fees_owed_a) .ok_or(AmmError::MathOverflow)?; let effective_pool_b = pool_b - .amount + .amount() .checked_sub(pool_config.admin_fees_owed_b) .ok_or(AmmError::MathOverflow)?; // Defining pool creation like this allows attackers to frontrun pool creation with bad ratios @@ -198,48 +198,48 @@ pub fn handle_deposit_liquidity( // decimal-mismatch bugs (and is the modern recommended path). token_interface::transfer_checked( CpiContext::new( - context.accounts.token_program.key(), + context.accounts.token_program.address(), TransferChecked { - from: context.accounts.token_a.to_account_info(), - mint: context.accounts.mint_a.to_account_info(), - to: context.accounts.pool_a.to_account_info(), - authority: context.accounts.depositor.to_account_info(), + from: context.accounts.token_a.cpi_handle_mut(), + mint: context.accounts.mint_a.cpi_handle(), + to: context.accounts.pool_a.cpi_handle_mut(), + authority: context.accounts.depositor.cpi_handle(), }, ), amount_a, - context.accounts.mint_a.decimals, + context.accounts.mint_a.decimals(), )?; token_interface::transfer_checked( CpiContext::new( - context.accounts.token_program.key(), + context.accounts.token_program.address(), TransferChecked { - from: context.accounts.token_b.to_account_info(), - mint: context.accounts.mint_b.to_account_info(), - to: context.accounts.pool_b.to_account_info(), - authority: context.accounts.depositor.to_account_info(), + from: context.accounts.token_b.cpi_handle_mut(), + mint: context.accounts.mint_b.cpi_handle(), + to: context.accounts.pool_b.cpi_handle_mut(), + authority: context.accounts.depositor.cpi_handle(), }, ), amount_b, - context.accounts.mint_b.decimals, + context.accounts.mint_b.decimals(), )?; // Mint the liquidity to user let authority_bump = context.bumps.pool_authority; let authority_seeds = &[ &context.accounts.pool_config.config.to_bytes(), - &context.accounts.mint_a.key().to_bytes(), - &context.accounts.mint_b.key().to_bytes(), + &context.accounts.mint_a.address().to_bytes(), + &context.accounts.mint_b.address().to_bytes(), AUTHORITY_SEED, &[authority_bump], ]; let signer_seeds = &[&authority_seeds[..]]; token_interface::mint_to( CpiContext::new_with_signer( - context.accounts.token_program.key(), + context.accounts.token_program.address(), MintTo { - mint: context.accounts.liquidity_provider_mint.to_account_info(), - to: context.accounts.liquidity_provider_token.to_account_info(), - authority: context.accounts.pool_authority.to_account_info(), + mint: context.accounts.liquidity_provider_mint.cpi_handle_mut(), + to: context.accounts.liquidity_provider_token.cpi_handle_mut(), + authority: context.accounts.pool_authority.cpi_handle(), }, signer_seeds, ), @@ -250,49 +250,49 @@ pub fn handle_deposit_liquidity( } #[derive(Accounts)] -pub struct DepositLiquidityAccountConstraints<'info> { +pub struct DepositLiquidityAccountConstraints { #[account( seeds = [ pool_config.config.as_ref(), - pool_config.mint_a.key().as_ref(), - pool_config.mint_b.key().as_ref(), + pool_config.mint_a.address().as_ref(), + pool_config.mint_b.address().as_ref(), ], bump, has_one = mint_a, has_one = mint_b, )] - pub pool_config: Box>, + pub pool_config: Box>, /// CHECK: Read only authority #[account( seeds = [ pool_config.config.as_ref(), - mint_a.key().as_ref(), - mint_b.key().as_ref(), + mint_a.address().as_ref(), + mint_b.address().as_ref(), AUTHORITY_SEED, ], bump, )] - pub pool_authority: UncheckedAccount<'info>, + pub pool_authority: UncheckedAccount, /// The account paying for all rents - pub depositor: Signer<'info>, + pub depositor: Signer, #[account( mut, seeds = [ pool_config.config.as_ref(), - mint_a.key().as_ref(), - mint_b.key().as_ref(), + mint_a.address().as_ref(), + mint_b.address().as_ref(), LIQUIDITY_SEED, ], bump, )] - pub liquidity_provider_mint: Box>, + pub liquidity_provider_mint: Box>, - pub mint_a: Box>, + pub mint_a: Box>, - pub mint_b: Box>, + pub mint_b: Box>, #[account( mut, @@ -300,7 +300,7 @@ pub struct DepositLiquidityAccountConstraints<'info> { associated_token::authority = pool_authority, associated_token::token_program = token_program, )] - pub pool_a: Box>, + pub pool_a: Box>, #[account( mut, @@ -308,7 +308,7 @@ pub struct DepositLiquidityAccountConstraints<'info> { associated_token::authority = pool_authority, associated_token::token_program = token_program, )] - pub pool_b: Box>, + pub pool_b: Box>, #[account( init_if_needed, @@ -317,7 +317,7 @@ pub struct DepositLiquidityAccountConstraints<'info> { associated_token::authority = depositor, associated_token::token_program = token_program, )] - pub liquidity_provider_token: Box>, + pub liquidity_provider_token: Box>, #[account( mut, @@ -325,7 +325,7 @@ pub struct DepositLiquidityAccountConstraints<'info> { associated_token::authority = depositor, associated_token::token_program = token_program, )] - pub token_a: Box>, + pub token_a: Box>, #[account( mut, @@ -333,14 +333,14 @@ pub struct DepositLiquidityAccountConstraints<'info> { associated_token::authority = depositor, associated_token::token_program = token_program, )] - pub token_b: Box>, + pub token_b: Box>, /// The account paying for all rents #[account(mut)] - pub payer: Signer<'info>, + pub payer: Signer, /// Solana ecosystem accounts - pub token_program: Interface<'info, TokenInterface>, - pub associated_token_program: Program<'info, AssociatedToken>, - pub system_program: Program<'info, System>, + pub token_program: Interface<'static, TokenInterface>, + pub associated_token_program: Program, + pub system_program: Program, } diff --git a/finance/token-swap/anchor/programs/token-swap/src/instructions/initialize_config.rs b/finance/token-swap/anchor/programs/token-swap/src/instructions/initialize_config.rs index b5739d3e5..5d6413c95 100644 --- a/finance/token-swap/anchor/programs/token-swap/src/instructions/initialize_config.rs +++ b/finance/token-swap/anchor/programs/token-swap/src/instructions/initialize_config.rs @@ -7,13 +7,13 @@ use crate::{ }; pub fn handle_initialize_config( - context: Context, + context: &mut Context, fee: u16, admin_share_bps: u16, ) -> Result<()> { let bump = context.bumps.config; let config = &mut context.accounts.config; - config.admin = context.accounts.admin.key(); + config.admin = *context.accounts.admin.address(); config.fee = fee; config.admin_share_bps = admin_share_bps; config.bump = bump; @@ -23,7 +23,7 @@ pub fn handle_initialize_config( #[derive(Accounts)] #[instruction(fee: u16, admin_share_bps: u16)] -pub struct InitializeConfigAccountConstraints<'info> { +pub struct InitializeConfigAccountConstraints { #[account( init, payer = payer, @@ -33,16 +33,16 @@ pub struct InitializeConfigAccountConstraints<'info> { constraint = (fee as u64) < BASIS_POINTS_DIVISOR @ AmmError::InvalidFee, constraint = (admin_share_bps as u64) < BASIS_POINTS_DIVISOR @ AmmError::AdminShareTooHigh, )] - pub config: Account<'info, Config>, + pub config: BorshAccount, /// The admin of the AMM /// CHECK: Read only, delegatable creation - pub admin: UncheckedAccount<'info>, + pub admin: UncheckedAccount, /// The account paying for all rents #[account(mut)] - pub payer: Signer<'info>, + pub payer: Signer, /// Solana ecosystem accounts - pub system_program: Program<'info, System>, + pub system_program: Program, } diff --git a/finance/token-swap/anchor/programs/token-swap/src/instructions/initialize_pool.rs b/finance/token-swap/anchor/programs/token-swap/src/instructions/initialize_pool.rs index 4a869c743..1a530dbbc 100644 --- a/finance/token-swap/anchor/programs/token-swap/src/instructions/initialize_pool.rs +++ b/finance/token-swap/anchor/programs/token-swap/src/instructions/initialize_pool.rs @@ -1,4 +1,5 @@ use anchor_lang::prelude::*; +use anchor_spl::mint; use anchor_spl::{ associated_token::AssociatedToken, token_interface::{Mint, TokenAccount, TokenInterface}, @@ -10,69 +11,69 @@ use crate::{ state::{Config, PoolConfig}, }; -pub fn handle_initialize_pool(context: Context) -> Result<()> { +pub fn handle_initialize_pool(context: &mut Context) -> Result<()> { let bump = context.bumps.pool_config; let pool_config = &mut context.accounts.pool_config; - pool_config.config = context.accounts.config.key(); - pool_config.mint_a = context.accounts.mint_a.key(); - pool_config.mint_b = context.accounts.mint_b.key(); + pool_config.config = *context.accounts.config.address(); + pool_config.mint_a = *context.accounts.mint_a.address(); + pool_config.mint_b = *context.accounts.mint_b.address(); pool_config.bump = bump; Ok(()) } #[derive(Accounts)] -pub struct InitializePoolAccountConstraints<'info> { +pub struct InitializePoolAccountConstraints { #[account( seeds = [CONFIG_SEED], bump, )] - pub config: Box>, + pub config: Box>, #[account( init, payer = payer, space = PoolConfig::DISCRIMINATOR.len() + PoolConfig::INIT_SPACE, seeds = [ - config.key().as_ref(), - mint_a.key().as_ref(), - mint_b.key().as_ref(), + config.address().as_ref(), + mint_a.address().as_ref(), + mint_b.address().as_ref(), ], bump, - constraint = mint_a.key() < mint_b.key() @ AmmError::InvalidMintOrder, + constraint = mint_a.address() < mint_b.address() @ AmmError::InvalidMintOrder, )] - pub pool_config: Box>, + pub pool_config: Box>, /// CHECK: Read only authority #[account( seeds = [ - config.key().as_ref(), - mint_a.key().as_ref(), - mint_b.key().as_ref(), + config.address().as_ref(), + mint_a.address().as_ref(), + mint_b.address().as_ref(), AUTHORITY_SEED, ], bump, )] - pub pool_authority: UncheckedAccount<'info>, + pub pool_authority: UncheckedAccount, #[account( init, payer = payer, seeds = [ - config.key().as_ref(), - mint_a.key().as_ref(), - mint_b.key().as_ref(), + config.address().as_ref(), + mint_a.address().as_ref(), + mint_b.address().as_ref(), LIQUIDITY_SEED, ], bump, mint::decimals = 6, mint::authority = pool_authority, )] - pub liquidity_provider_mint: Box>, + pub liquidity_provider_mint: Box>, - pub mint_a: Box>, + pub mint_a: Box>, - pub mint_b: Box>, + pub mint_b: Box>, #[account( init, @@ -81,7 +82,7 @@ pub struct InitializePoolAccountConstraints<'info> { associated_token::authority = pool_authority, associated_token::token_program = token_program, )] - pub pool_a: Box>, + pub pool_a: Box>, #[account( init, @@ -90,14 +91,14 @@ pub struct InitializePoolAccountConstraints<'info> { associated_token::authority = pool_authority, associated_token::token_program = token_program, )] - pub pool_b: Box>, + pub pool_b: Box>, /// The account paying for all rents #[account(mut)] - pub payer: Signer<'info>, + pub payer: Signer, /// Solana ecosystem accounts - pub token_program: Interface<'info, TokenInterface>, - pub associated_token_program: Program<'info, AssociatedToken>, - pub system_program: Program<'info, System>, + pub token_program: Interface<'static, TokenInterface>, + pub associated_token_program: Program, + pub system_program: Program, } diff --git a/finance/token-swap/anchor/programs/token-swap/src/instructions/swap_tokens.rs b/finance/token-swap/anchor/programs/token-swap/src/instructions/swap_tokens.rs index c9b4b2b8e..9969ed9e7 100644 --- a/finance/token-swap/anchor/programs/token-swap/src/instructions/swap_tokens.rs +++ b/finance/token-swap/anchor/programs/token-swap/src/instructions/swap_tokens.rs @@ -11,7 +11,7 @@ use crate::{ }; pub fn handle_swap_tokens( - context: Context, + context: &mut Context, input_is_token_a: bool, input_amount: u64, min_output_amount: u64, @@ -21,10 +21,10 @@ pub fn handle_swap_tokens( // for callers - their min_output_amount is computed against the requested // input, not the clamped one, so the trade could succeed with worse terms // than expected. - if input_is_token_a && input_amount > context.accounts.token_a.amount { + if input_is_token_a && input_amount > context.accounts.token_a.amount() { return err!(AmmError::InsufficientBalance); } - if !input_is_token_a && input_amount > context.accounts.token_b.amount { + if !input_is_token_a && input_amount > context.accounts.token_b.amount() { return err!(AmmError::InsufficientBalance); } // Split the trading fee between LPs and the admin. The full fee is taken @@ -72,11 +72,11 @@ pub fn handle_swap_tokens( // but a raw `-` would wrap silently on a BPF release build if that invariant // were ever violated, handing the curve a giant effective reserve. let effective_pool_a = pool_a - .amount + .amount() .checked_sub(pool_config.admin_fees_owed_a) .ok_or(AmmError::MathOverflow)?; let effective_pool_b = pool_b - .amount + .amount() .checked_sub(pool_config.admin_fees_owed_b) .ok_or(AmmError::MathOverflow)?; @@ -144,8 +144,8 @@ pub fn handle_swap_tokens( // to_bytes() returns an owned [u8; 32] copy so there are no borrow conflicts. let authority_bump = context.bumps.pool_authority; let config_bytes = context.accounts.pool_config.config.to_bytes(); - let mint_a_bytes = context.accounts.mint_a.key().to_bytes(); - let mint_b_bytes = context.accounts.mint_b.key().to_bytes(); + let mint_a_bytes = context.accounts.mint_a.address().to_bytes(); + let mint_b_bytes = context.accounts.mint_b.address().to_bytes(); // Effects: update admin_fees before CPIs (Checks-Effects-Interactions). // The fee always comes off the input side, so the admin's claim accumulates @@ -177,58 +177,58 @@ pub fn handle_swap_tokens( if input_is_token_a { token_interface::transfer_checked( CpiContext::new( - context.accounts.token_program.key(), + context.accounts.token_program.address(), TransferChecked { - from: context.accounts.token_a.to_account_info(), - mint: context.accounts.mint_a.to_account_info(), - to: context.accounts.pool_a.to_account_info(), - authority: context.accounts.trader.to_account_info(), + from: context.accounts.token_a.cpi_handle_mut(), + mint: context.accounts.mint_a.cpi_handle(), + to: context.accounts.pool_a.cpi_handle_mut(), + authority: context.accounts.trader.cpi_handle(), }, ), input_amount, - context.accounts.mint_a.decimals, + context.accounts.mint_a.decimals(), )?; token_interface::transfer_checked( CpiContext::new_with_signer( - context.accounts.token_program.key(), + context.accounts.token_program.address(), TransferChecked { - from: context.accounts.pool_b.to_account_info(), - mint: context.accounts.mint_b.to_account_info(), - to: context.accounts.token_b.to_account_info(), - authority: context.accounts.pool_authority.to_account_info(), + from: context.accounts.pool_b.cpi_handle_mut(), + mint: context.accounts.mint_b.cpi_handle(), + to: context.accounts.token_b.cpi_handle_mut(), + authority: context.accounts.pool_authority.cpi_handle(), }, signer_seeds, ), output, - context.accounts.mint_b.decimals, + context.accounts.mint_b.decimals(), )?; } else { token_interface::transfer_checked( CpiContext::new_with_signer( - context.accounts.token_program.key(), + context.accounts.token_program.address(), TransferChecked { - from: context.accounts.pool_a.to_account_info(), - mint: context.accounts.mint_a.to_account_info(), - to: context.accounts.token_a.to_account_info(), - authority: context.accounts.pool_authority.to_account_info(), + from: context.accounts.pool_a.cpi_handle_mut(), + mint: context.accounts.mint_a.cpi_handle(), + to: context.accounts.token_a.cpi_handle_mut(), + authority: context.accounts.pool_authority.cpi_handle(), }, signer_seeds, ), output, - context.accounts.mint_a.decimals, + context.accounts.mint_a.decimals(), )?; token_interface::transfer_checked( CpiContext::new( - context.accounts.token_program.key(), + context.accounts.token_program.address(), TransferChecked { - from: context.accounts.token_b.to_account_info(), - mint: context.accounts.mint_b.to_account_info(), - to: context.accounts.pool_b.to_account_info(), - authority: context.accounts.trader.to_account_info(), + from: context.accounts.token_b.cpi_handle_mut(), + mint: context.accounts.mint_b.cpi_handle(), + to: context.accounts.pool_b.cpi_handle_mut(), + authority: context.accounts.trader.cpi_handle(), }, ), input_amount, - context.accounts.mint_b.decimals, + context.accounts.mint_b.decimals(), )?; } @@ -257,13 +257,13 @@ pub fn handle_swap_tokens( let effective_pool_a_after = context .accounts .pool_a - .amount + .amount() .checked_sub(pool_config.admin_fees_owed_a) .ok_or(AmmError::MathOverflow)?; let effective_pool_b_after = context .accounts .pool_b - .amount + .amount() .checked_sub(pool_config.admin_fees_owed_b) .ok_or(AmmError::MathOverflow)?; let new_invariant = (effective_pool_a_after as u128) @@ -275,45 +275,45 @@ pub fn handle_swap_tokens( } #[derive(Accounts)] -pub struct SwapTokensAccountConstraints<'info> { +pub struct SwapTokensAccountConstraints { #[account( seeds = [CONFIG_SEED], bump, )] - pub config: Account<'info, Config>, + pub config: BorshAccount, #[account( mut, seeds = [ pool_config.config.as_ref(), - pool_config.mint_a.key().as_ref(), - pool_config.mint_b.key().as_ref(), + pool_config.mint_a.address().as_ref(), + pool_config.mint_b.address().as_ref(), ], bump, has_one = config, has_one = mint_a, has_one = mint_b, )] - pub pool_config: Account<'info, PoolConfig>, + pub pool_config: BorshAccount, /// CHECK: Read only authority #[account( seeds = [ pool_config.config.as_ref(), - mint_a.key().as_ref(), - mint_b.key().as_ref(), + mint_a.address().as_ref(), + mint_b.address().as_ref(), AUTHORITY_SEED, ], bump, )] - pub pool_authority: UncheckedAccount<'info>, + pub pool_authority: UncheckedAccount, /// The account doing the swap - pub trader: Signer<'info>, + pub trader: Signer, - pub mint_a: Box>, + pub mint_a: Box>, - pub mint_b: Box>, + pub mint_b: Box>, #[account( mut, @@ -321,7 +321,7 @@ pub struct SwapTokensAccountConstraints<'info> { associated_token::authority = pool_authority, associated_token::token_program = token_program, )] - pub pool_a: Box>, + pub pool_a: Box>, #[account( mut, @@ -329,7 +329,7 @@ pub struct SwapTokensAccountConstraints<'info> { associated_token::authority = pool_authority, associated_token::token_program = token_program, )] - pub pool_b: Box>, + pub pool_b: Box>, #[account( init_if_needed, @@ -338,7 +338,7 @@ pub struct SwapTokensAccountConstraints<'info> { associated_token::authority = trader, associated_token::token_program = token_program, )] - pub token_a: Box>, + pub token_a: Box>, #[account( init_if_needed, @@ -347,14 +347,14 @@ pub struct SwapTokensAccountConstraints<'info> { associated_token::authority = trader, associated_token::token_program = token_program, )] - pub token_b: Box>, + pub token_b: Box>, /// The account paying for all rents #[account(mut)] - pub payer: Signer<'info>, + pub payer: Signer, /// Solana ecosystem accounts - pub token_program: Interface<'info, TokenInterface>, - pub associated_token_program: Program<'info, AssociatedToken>, - pub system_program: Program<'info, System>, + pub token_program: Interface<'static, TokenInterface>, + pub associated_token_program: Program, + pub system_program: Program, } diff --git a/finance/token-swap/anchor/programs/token-swap/src/instructions/withdraw_liquidity.rs b/finance/token-swap/anchor/programs/token-swap/src/instructions/withdraw_liquidity.rs index db8ef01e9..896a2955b 100644 --- a/finance/token-swap/anchor/programs/token-swap/src/instructions/withdraw_liquidity.rs +++ b/finance/token-swap/anchor/programs/token-swap/src/instructions/withdraw_liquidity.rs @@ -11,7 +11,7 @@ use crate::{ }; pub fn handle_withdraw_liquidity( - context: Context, + context: &mut Context, amount: u64, minimum_token_a_out: u64, minimum_token_b_out: u64, @@ -19,8 +19,8 @@ pub fn handle_withdraw_liquidity( let authority_bump = context.bumps.pool_authority; let authority_seeds = &[ &context.accounts.pool_config.config.to_bytes(), - &context.accounts.mint_a.key().to_bytes(), - &context.accounts.mint_b.key().to_bytes(), + &context.accounts.mint_a.address().to_bytes(), + &context.accounts.mint_b.address().to_bytes(), AUTHORITY_SEED, &[authority_bump], ]; @@ -36,13 +36,13 @@ pub fn handle_withdraw_liquidity( let effective_pool_a = context .accounts .pool_a - .amount + .amount() .checked_sub(pool_config.admin_fees_owed_a) .ok_or(AmmError::MathOverflow)?; let effective_pool_b = context .accounts .pool_b - .amount + .amount() .checked_sub(pool_config.admin_fees_owed_b) .ok_or(AmmError::MathOverflow)?; @@ -94,43 +94,43 @@ pub fn handle_withdraw_liquidity( // transfer_checked verifies the mint + decimals at the token program. token_interface::transfer_checked( CpiContext::new_with_signer( - context.accounts.token_program.key(), + context.accounts.token_program.address(), TransferChecked { - from: context.accounts.pool_a.to_account_info(), - mint: context.accounts.mint_a.to_account_info(), - to: context.accounts.token_a.to_account_info(), - authority: context.accounts.pool_authority.to_account_info(), + from: context.accounts.pool_a.cpi_handle_mut(), + mint: context.accounts.mint_a.cpi_handle(), + to: context.accounts.token_a.cpi_handle_mut(), + authority: context.accounts.pool_authority.cpi_handle(), }, signer_seeds, ), amount_a, - context.accounts.mint_a.decimals, + context.accounts.mint_a.decimals(), )?; token_interface::transfer_checked( CpiContext::new_with_signer( - context.accounts.token_program.key(), + context.accounts.token_program.address(), TransferChecked { - from: context.accounts.pool_b.to_account_info(), - mint: context.accounts.mint_b.to_account_info(), - to: context.accounts.token_b.to_account_info(), - authority: context.accounts.pool_authority.to_account_info(), + from: context.accounts.pool_b.cpi_handle_mut(), + mint: context.accounts.mint_b.cpi_handle(), + to: context.accounts.token_b.cpi_handle_mut(), + authority: context.accounts.pool_authority.cpi_handle(), }, signer_seeds, ), amount_b, - context.accounts.mint_b.decimals, + context.accounts.mint_b.decimals(), )?; // Burn the liquidity tokens // It will fail if the amount is invalid token_interface::burn( CpiContext::new( - context.accounts.token_program.key(), + context.accounts.token_program.address(), Burn { - mint: context.accounts.liquidity_provider_mint.to_account_info(), - from: context.accounts.liquidity_provider_token.to_account_info(), - authority: context.accounts.withdrawer.to_account_info(), + mint: context.accounts.liquidity_provider_mint.cpi_handle_mut(), + from: context.accounts.liquidity_provider_token.cpi_handle_mut(), + authority: context.accounts.withdrawer.cpi_handle(), }, ), amount, @@ -140,56 +140,56 @@ pub fn handle_withdraw_liquidity( } #[derive(Accounts)] -pub struct WithdrawLiquidityAccountConstraints<'info> { +pub struct WithdrawLiquidityAccountConstraints { #[account( seeds = [CONFIG_SEED], bump, )] - pub config: Account<'info, Config>, + pub config: BorshAccount, #[account( seeds = [ pool_config.config.as_ref(), - pool_config.mint_a.key().as_ref(), - pool_config.mint_b.key().as_ref(), + pool_config.mint_a.address().as_ref(), + pool_config.mint_b.address().as_ref(), ], bump, has_one = mint_a, has_one = mint_b, )] - pub pool_config: Account<'info, PoolConfig>, + pub pool_config: BorshAccount, /// CHECK: Read only authority #[account( seeds = [ pool_config.config.as_ref(), - mint_a.key().as_ref(), - mint_b.key().as_ref(), + mint_a.address().as_ref(), + mint_b.address().as_ref(), AUTHORITY_SEED, ], bump, )] - pub pool_authority: UncheckedAccount<'info>, + pub pool_authority: UncheckedAccount, - pub withdrawer: Signer<'info>, + pub withdrawer: Signer, #[account( mut, seeds = [ pool_config.config.as_ref(), - mint_a.key().as_ref(), - mint_b.key().as_ref(), + mint_a.address().as_ref(), + mint_b.address().as_ref(), LIQUIDITY_SEED, ], bump, )] - pub liquidity_provider_mint: Box>, + pub liquidity_provider_mint: Box>, #[account(mut)] - pub mint_a: Box>, + pub mint_a: Box>, #[account(mut)] - pub mint_b: Box>, + pub mint_b: Box>, #[account( mut, @@ -197,7 +197,7 @@ pub struct WithdrawLiquidityAccountConstraints<'info> { associated_token::authority = pool_authority, associated_token::token_program = token_program, )] - pub pool_a: Box>, + pub pool_a: Box>, #[account( mut, @@ -205,7 +205,7 @@ pub struct WithdrawLiquidityAccountConstraints<'info> { associated_token::authority = pool_authority, associated_token::token_program = token_program, )] - pub pool_b: Box>, + pub pool_b: Box>, #[account( mut, @@ -213,7 +213,7 @@ pub struct WithdrawLiquidityAccountConstraints<'info> { associated_token::authority = withdrawer, associated_token::token_program = token_program, )] - pub liquidity_provider_token: Box>, + pub liquidity_provider_token: Box>, #[account( init_if_needed, @@ -222,7 +222,7 @@ pub struct WithdrawLiquidityAccountConstraints<'info> { associated_token::authority = withdrawer, associated_token::token_program = token_program, )] - pub token_a: Box>, + pub token_a: Box>, #[account( init_if_needed, @@ -231,14 +231,14 @@ pub struct WithdrawLiquidityAccountConstraints<'info> { associated_token::authority = withdrawer, associated_token::token_program = token_program, )] - pub token_b: Box>, + pub token_b: Box>, /// The account paying for all rents #[account(mut)] - pub payer: Signer<'info>, + pub payer: Signer, /// Solana ecosystem accounts - pub token_program: Interface<'info, TokenInterface>, - pub associated_token_program: Program<'info, AssociatedToken>, - pub system_program: Program<'info, System>, + pub token_program: Interface<'static, TokenInterface>, + pub associated_token_program: Program, + pub system_program: Program, } diff --git a/finance/token-swap/anchor/programs/token-swap/src/lib.rs b/finance/token-swap/anchor/programs/token-swap/src/lib.rs index 780516598..061d4345a 100644 --- a/finance/token-swap/anchor/programs/token-swap/src/lib.rs +++ b/finance/token-swap/anchor/programs/token-swap/src/lib.rs @@ -13,19 +13,19 @@ pub mod swap_example { use super::*; pub fn initialize_config( - context: Context, + context: &mut Context, fee: u16, admin_share_bps: u16, ) -> Result<()> { instructions::handle_initialize_config(context, fee, admin_share_bps) } - pub fn initialize_pool(context: Context) -> Result<()> { + pub fn initialize_pool(context: &mut Context) -> Result<()> { instructions::handle_initialize_pool(context) } pub fn deposit_liquidity( - context: Context, + context: &mut Context, amount_a: u64, amount_b: u64, minimum_lp_tokens_out: u64, @@ -39,7 +39,7 @@ pub mod swap_example { } pub fn withdraw_liquidity( - context: Context, + context: &mut Context, amount: u64, minimum_token_a_out: u64, minimum_token_b_out: u64, @@ -53,7 +53,7 @@ pub mod swap_example { } pub fn swap_tokens( - context: Context, + context: &mut Context, input_is_token_a: bool, input_amount: u64, min_output_amount: u64, @@ -66,7 +66,7 @@ pub mod swap_example { ) } - pub fn claim_admin_fees(context: Context) -> Result<()> { + pub fn claim_admin_fees(context: &mut Context) -> Result<()> { instructions::handle_claim_admin_fees(context) } } diff --git a/finance/token-swap/anchor/programs/token-swap/src/state/config.rs b/finance/token-swap/anchor/programs/token-swap/src/state/config.rs index ff005d9d0..06a7dfc97 100644 --- a/finance/token-swap/anchor/programs/token-swap/src/state/config.rs +++ b/finance/token-swap/anchor/programs/token-swap/src/state/config.rs @@ -8,11 +8,11 @@ use anchor_lang::prelude::*; /// program-level config is global by construction). Parameterising the config /// by an `id` was leftover complexity from the original example; removing it /// makes the onchain layout simpler and matches realistic deployment. -#[account] +#[account(borsh)] #[derive(Default, InitSpace)] pub struct Config { /// Account that has admin authority over the AMM. - pub admin: Pubkey, + pub admin: Address, /// The trading fee taken on each swap, in basis points (out of 10_000). /// diff --git a/finance/token-swap/anchor/programs/token-swap/src/state/pool_config.rs b/finance/token-swap/anchor/programs/token-swap/src/state/pool_config.rs index d39a93e59..f5a443624 100644 --- a/finance/token-swap/anchor/programs/token-swap/src/state/pool_config.rs +++ b/finance/token-swap/anchor/programs/token-swap/src/state/pool_config.rs @@ -13,19 +13,19 @@ use anchor_lang::prelude::*; /// `admin_fees_owed_b`). Those fees physically sit in the existing `pool_a` / /// `pool_b` reserves; the accumulators are a *virtual* obligation against /// those balances. LP-facing math (deposit, withdraw, swap curve) uses -/// `pool_X.amount - admin_fees_owed_X` so the admin's owed slice is not +/// `pool_X.amount() - admin_fees_owed_X` so the admin's owed slice is not /// counted toward LP yield. -#[account] +#[account(borsh)] #[derive(Default, InitSpace)] pub struct PoolConfig { /// Address of the parent `Config` account this pool belongs to. - pub config: Pubkey, + pub config: Address, /// Mint of token A. - pub mint_a: Pubkey, + pub mint_a: Address, /// Mint of token B. - pub mint_b: Pubkey, + pub mint_b: Address, /// Admin's accumulated fee claim on token A, in base units. Sits /// physically in `pool_a` but is excluded from the LP curve and from diff --git a/finance/vault-strategy/anchor/programs/mock-swap-router/Cargo.toml b/finance/vault-strategy/anchor/programs/mock-swap-router/Cargo.toml index b9968e2d7..c446a4ddc 100644 --- a/finance/vault-strategy/anchor/programs/mock-swap-router/Cargo.toml +++ b/finance/vault-strategy/anchor/programs/mock-swap-router/Cargo.toml @@ -14,14 +14,22 @@ cpi = ["no-entrypoint"] no-entrypoint = [] no-idl = [] no-log-ix-name = [] -idl-build = ["anchor-lang/idl-build", "anchor-spl/idl-build"] +idl-build = ["anchor-lang/idl-build"] anchor-debug = [] custom-heap = [] custom-panic = [] [dependencies] -anchor-lang = { version = "1.1.2", features = ["init-if-needed"] } -anchor-spl = "1.1.2" +anchor-lang = "2.0.0-rc.1" +# The `#[program]` macro expands to `wincode` paths for instruction-data +# (de)serialization, so the crate has to be a direct dependency. +wincode = { version = "0.5", features = ["derive"] } +# anchor-lang 2.0.0-rc.1 is built against wincode 0.5, but solana-address 2.7 +# moved to wincode 0.6. With both in the graph, `Address`'s wincode impls +# belong to the version the account derives are not using and every +# `SchemaRead`/`SchemaWrite` bound fails. 2.6.1 is the last 0.5-line release. +solana-address = ">=2.6, <2.7" +anchor-spl = "2.0.0-rc.1" [dev-dependencies] litesvm = "0.13.1" diff --git a/finance/vault-strategy/anchor/programs/mock-swap-router/src/instructions/initialize_router.rs b/finance/vault-strategy/anchor/programs/mock-swap-router/src/instructions/initialize_router.rs index 125ea422f..3c6856984 100644 --- a/finance/vault-strategy/anchor/programs/mock-swap-router/src/instructions/initialize_router.rs +++ b/finance/vault-strategy/anchor/programs/mock-swap-router/src/instructions/initialize_router.rs @@ -4,11 +4,11 @@ use anchor_spl::token_interface::{Mint, TokenInterface}; use crate::state::RouterConfig; #[derive(Accounts)] -pub struct InitializeRouterAccountConstraints<'info> { +pub struct InitializeRouterAccountConstraints { #[account(mut)] - pub authority: Signer<'info>, + pub authority: Signer, - pub usdc_mint: InterfaceAccount<'info, Mint>, + pub usdc_mint: InterfaceAccount, #[account( init, @@ -17,26 +17,26 @@ pub struct InitializeRouterAccountConstraints<'info> { seeds = [b"router_config"], bump )] - pub router_config: Account<'info, RouterConfig>, + pub router_config: BorshAccount, /// CHECK: PDA used as mint authority only - no data stored #[account( seeds = [b"router_authority"], bump )] - pub router_authority: UncheckedAccount<'info>, + pub router_authority: UncheckedAccount, - pub token_program: Interface<'info, TokenInterface>, - pub system_program: Program<'info, System>, + pub token_program: Interface<'static, TokenInterface>, + pub system_program: Program, } pub fn handle_initialize_router( - context: Context, - _usdc_mint: Pubkey, + context: &mut Context, + _usdc_mint: Address, ) -> Result<()> { - context.accounts.router_config.set_inner(RouterConfig { - authority: context.accounts.authority.key(), - usdc_mint: context.accounts.usdc_mint.key(), + *context.accounts.router_config = (RouterConfig { + authority: *context.accounts.authority.address(), + usdc_mint: *context.accounts.usdc_mint.address(), bump: context.bumps.router_config, }); Ok(()) diff --git a/finance/vault-strategy/anchor/programs/mock-swap-router/src/instructions/set_rate.rs b/finance/vault-strategy/anchor/programs/mock-swap-router/src/instructions/set_rate.rs index 09c5d9548..d0eb3d8fa 100644 --- a/finance/vault-strategy/anchor/programs/mock-swap-router/src/instructions/set_rate.rs +++ b/finance/vault-strategy/anchor/programs/mock-swap-router/src/instructions/set_rate.rs @@ -7,36 +7,36 @@ use anchor_spl::{ use crate::state::{AssetRate, RouterConfig}; #[derive(Accounts)] -pub struct SetRateAccountConstraints<'info> { +pub struct SetRateAccountConstraints { #[account(mut)] - pub authority: Signer<'info>, + pub authority: Signer, #[account( has_one = authority, seeds = [b"router_config"], bump = router_config.bump )] - pub router_config: Account<'info, RouterConfig>, + pub router_config: BorshAccount, - pub asset_mint: InterfaceAccount<'info, Mint>, + pub asset_mint: InterfaceAccount, - pub usdc_mint: InterfaceAccount<'info, Mint>, + pub usdc_mint: InterfaceAccount, #[account( init_if_needed, payer = authority, space = AssetRate::DISCRIMINATOR.len() + AssetRate::INIT_SPACE, - seeds = [b"rate", asset_mint.key().as_ref()], + seeds = [b"rate", asset_mint.address().as_ref()], bump )] - pub asset_rate: Account<'info, AssetRate>, + pub asset_rate: BorshAccount, /// CHECK: PDA used as mint authority only #[account( seeds = [b"router_authority"], bump )] - pub router_authority: UncheckedAccount<'info>, + pub router_authority: UncheckedAccount, #[account( init_if_needed, @@ -45,20 +45,20 @@ pub struct SetRateAccountConstraints<'info> { associated_token::authority = router_authority, associated_token::token_program = token_program )] - pub router_usdc_treasury: InterfaceAccount<'info, TokenAccount>, + pub router_usdc_treasury: InterfaceAccount, - pub associated_token_program: Program<'info, AssociatedToken>, - pub token_program: Interface<'info, TokenInterface>, - pub system_program: Program<'info, System>, + pub associated_token_program: Program, + pub token_program: Interface<'static, TokenInterface>, + pub system_program: Program, } pub fn handle_set_rate( - context: Context, - _mint: Pubkey, + context: &mut Context, + _mint: Address, usdc_per_token: u64, ) -> Result<()> { - context.accounts.asset_rate.set_inner(AssetRate { - mint: context.accounts.asset_mint.key(), + *context.accounts.asset_rate = (AssetRate { + mint: *context.accounts.asset_mint.address(), usdc_per_token, bump: context.bumps.asset_rate, }); diff --git a/finance/vault-strategy/anchor/programs/mock-swap-router/src/instructions/swap_asset_for_usdc.rs b/finance/vault-strategy/anchor/programs/mock-swap-router/src/instructions/swap_asset_for_usdc.rs index 66d33f376..17f7f3313 100644 --- a/finance/vault-strategy/anchor/programs/mock-swap-router/src/instructions/swap_asset_for_usdc.rs +++ b/finance/vault-strategy/anchor/programs/mock-swap-router/src/instructions/swap_asset_for_usdc.rs @@ -10,25 +10,25 @@ use crate::error::RouterError; use crate::state::{AssetRate, RouterConfig}; #[derive(Accounts)] -pub struct SwapAssetForUsdcAccountConstraints<'info> { - pub caller: Signer<'info>, +pub struct SwapAssetForUsdcAccountConstraints { + pub caller: Signer, #[account( seeds = [b"router_config"], bump = router_config.bump )] - pub router_config: Account<'info, RouterConfig>, + pub router_config: BorshAccount, #[account( - constraint = asset_rate.mint == asset_mint.key() @ RouterError::InvalidAssetMint + constraint = asset_rate.mint == *asset_mint.address() @ RouterError::InvalidAssetMint )] - pub asset_rate: Account<'info, AssetRate>, + pub asset_rate: BorshAccount, - #[account(constraint = usdc_mint.key() == router_config.usdc_mint @ RouterError::WrongUsdcMint)] - pub usdc_mint: Box>, + #[account(constraint = usdc_mint.address() == router_config.usdc_mint @ RouterError::WrongUsdcMint)] + pub usdc_mint: Box>, #[account(mut)] - pub asset_mint: Box>, + pub asset_mint: Box>, /// Caller's asset token account - asset tokens are burned from here #[account( @@ -37,7 +37,7 @@ pub struct SwapAssetForUsdcAccountConstraints<'info> { associated_token::authority = caller, associated_token::token_program = token_program )] - pub caller_asset_account: Box>, + pub caller_asset_account: Box>, /// Caller's USDC account - receives the USDC #[account( @@ -46,7 +46,7 @@ pub struct SwapAssetForUsdcAccountConstraints<'info> { associated_token::authority = caller, associated_token::token_program = token_program )] - pub caller_usdc_account: Box>, + pub caller_usdc_account: Box>, /// Router's USDC treasury - sends the USDC #[account( @@ -55,22 +55,22 @@ pub struct SwapAssetForUsdcAccountConstraints<'info> { associated_token::authority = router_authority, associated_token::token_program = token_program )] - pub router_usdc_treasury: Box>, + pub router_usdc_treasury: Box>, /// CHECK: PDA used as treasury authority - validated by seeds constraint #[account( seeds = [b"router_authority"], bump )] - pub router_authority: UncheckedAccount<'info>, + pub router_authority: UncheckedAccount, - pub associated_token_program: Program<'info, AssociatedToken>, - pub token_program: Interface<'info, TokenInterface>, - pub system_program: Program<'info, System>, + pub associated_token_program: Program, + pub token_program: Interface<'static, TokenInterface>, + pub system_program: Program, } pub fn handle_swap_asset_for_usdc( - context: Context, + context: &mut Context, asset_amount_in: u64, minimum_usdc_out: u64, ) -> Result<()> { @@ -87,12 +87,12 @@ pub fn handle_swap_asset_for_usdc( // Burn asset tokens from caller let burn_accounts = Burn { - mint: context.accounts.asset_mint.to_account_info(), - from: context.accounts.caller_asset_account.to_account_info(), - authority: context.accounts.caller.to_account_info(), + mint: context.accounts.asset_mint.cpi_handle_mut(), + from: context.accounts.caller_asset_account.cpi_handle_mut(), + authority: context.accounts.caller.cpi_handle(), }; burn( - CpiContext::new(context.accounts.token_program.key(), burn_accounts), + CpiContext::new(context.accounts.token_program.address(), burn_accounts), asset_amount_in, )?; @@ -101,19 +101,19 @@ pub fn handle_swap_asset_for_usdc( let signer_seeds: &[&[&[u8]]] = &[&[b"router_authority", &[router_authority_bump]]]; let transfer_accounts = TransferChecked { - from: context.accounts.router_usdc_treasury.to_account_info(), - mint: context.accounts.usdc_mint.to_account_info(), - to: context.accounts.caller_usdc_account.to_account_info(), - authority: context.accounts.router_authority.to_account_info(), + from: context.accounts.router_usdc_treasury.cpi_handle_mut(), + mint: context.accounts.usdc_mint.cpi_handle(), + to: context.accounts.caller_usdc_account.cpi_handle_mut(), + authority: context.accounts.router_authority.cpi_handle(), }; transfer_checked( CpiContext::new_with_signer( - context.accounts.token_program.key(), + context.accounts.token_program.address(), transfer_accounts, signer_seeds, ), usdc_out, - context.accounts.usdc_mint.decimals, + context.accounts.usdc_mint.decimals(), )?; Ok(()) diff --git a/finance/vault-strategy/anchor/programs/mock-swap-router/src/instructions/swap_usdc_for_asset.rs b/finance/vault-strategy/anchor/programs/mock-swap-router/src/instructions/swap_usdc_for_asset.rs index 784d16b7f..6e6032a1f 100644 --- a/finance/vault-strategy/anchor/programs/mock-swap-router/src/instructions/swap_usdc_for_asset.rs +++ b/finance/vault-strategy/anchor/programs/mock-swap-router/src/instructions/swap_usdc_for_asset.rs @@ -10,25 +10,25 @@ use crate::error::RouterError; use crate::state::{AssetRate, RouterConfig}; #[derive(Accounts)] -pub struct SwapUsdcForAssetAccountConstraints<'info> { +pub struct SwapUsdcForAssetAccountConstraints { /// The caller - e.g. the vault strategy PDA (can be a signer or a PDA signer via CPI) - pub caller: Signer<'info>, + pub caller: Signer, #[account( seeds = [b"router_config"], bump = router_config.bump )] - pub router_config: Account<'info, RouterConfig>, + pub router_config: BorshAccount, #[account( - constraint = asset_rate.mint == asset_mint.key() @ RouterError::InvalidAssetMint + constraint = asset_rate.mint == *asset_mint.address() @ RouterError::InvalidAssetMint )] - pub asset_rate: Account<'info, AssetRate>, + pub asset_rate: BorshAccount, - pub usdc_mint: Box>, + pub usdc_mint: Box>, #[account(mut)] - pub asset_mint: Box>, + pub asset_mint: Box>, /// Caller's USDC token account - USDC flows from here to the treasury #[account( @@ -37,7 +37,7 @@ pub struct SwapUsdcForAssetAccountConstraints<'info> { associated_token::authority = caller, associated_token::token_program = token_program )] - pub caller_usdc_account: Box>, + pub caller_usdc_account: Box>, /// Caller's asset token account - minted asset tokens land here #[account( @@ -46,7 +46,7 @@ pub struct SwapUsdcForAssetAccountConstraints<'info> { associated_token::authority = caller, associated_token::token_program = token_program )] - pub caller_asset_account: Box>, + pub caller_asset_account: Box>, /// Router's USDC treasury - receives the USDC payment #[account( @@ -55,22 +55,22 @@ pub struct SwapUsdcForAssetAccountConstraints<'info> { associated_token::authority = router_authority, associated_token::token_program = token_program )] - pub router_usdc_treasury: Box>, + pub router_usdc_treasury: Box>, /// CHECK: PDA used as mint authority - validated by seeds constraint #[account( seeds = [b"router_authority"], bump )] - pub router_authority: UncheckedAccount<'info>, + pub router_authority: UncheckedAccount, - pub associated_token_program: Program<'info, AssociatedToken>, - pub token_program: Interface<'info, TokenInterface>, - pub system_program: Program<'info, System>, + pub associated_token_program: Program, + pub token_program: Interface<'static, TokenInterface>, + pub system_program: Program, } pub fn handle_swap_usdc_for_asset( - context: Context, + context: &mut Context, usdc_amount_in: u64, minimum_asset_out: u64, ) -> Result<()> { @@ -91,25 +91,25 @@ pub fn handle_swap_usdc_for_asset( // Transfer USDC from caller to router treasury let transfer_accounts = TransferChecked { - from: context.accounts.caller_usdc_account.to_account_info(), - mint: context.accounts.usdc_mint.to_account_info(), - to: context.accounts.router_usdc_treasury.to_account_info(), - authority: context.accounts.caller.to_account_info(), + from: context.accounts.caller_usdc_account.cpi_handle_mut(), + mint: context.accounts.usdc_mint.cpi_handle(), + to: context.accounts.router_usdc_treasury.cpi_handle_mut(), + authority: context.accounts.caller.cpi_handle(), }; - let cpi_ctx = CpiContext::new(context.accounts.token_program.key(), transfer_accounts); - transfer_checked(cpi_ctx, usdc_amount_in, context.accounts.usdc_mint.decimals)?; + let cpi_ctx = CpiContext::new(context.accounts.token_program.address(), transfer_accounts); + transfer_checked(cpi_ctx, usdc_amount_in, context.accounts.usdc_mint.decimals())?; // Mint asset tokens to caller - router_authority PDA signs let router_authority_bump = context.bumps.router_authority; let signer_seeds: &[&[&[u8]]] = &[&[b"router_authority", &[router_authority_bump]]]; let mint_accounts = MintTo { - mint: context.accounts.asset_mint.to_account_info(), - to: context.accounts.caller_asset_account.to_account_info(), - authority: context.accounts.router_authority.to_account_info(), + mint: context.accounts.asset_mint.cpi_handle_mut(), + to: context.accounts.caller_asset_account.cpi_handle_mut(), + authority: context.accounts.router_authority.cpi_handle(), }; let cpi_ctx = CpiContext::new_with_signer( - context.accounts.token_program.key(), + context.accounts.token_program.address(), mint_accounts, signer_seeds, ); diff --git a/finance/vault-strategy/anchor/programs/mock-swap-router/src/lib.rs b/finance/vault-strategy/anchor/programs/mock-swap-router/src/lib.rs index 7ee358266..e2950e736 100644 --- a/finance/vault-strategy/anchor/programs/mock-swap-router/src/lib.rs +++ b/finance/vault-strategy/anchor/programs/mock-swap-router/src/lib.rs @@ -14,22 +14,22 @@ pub mod mock_swap_router { use super::*; pub fn initialize_router( - context: Context, - usdc_mint: Pubkey, + context: &mut Context, + usdc_mint: Address, ) -> Result<()> { instructions::initialize_router::handle_initialize_router(context, usdc_mint) } pub fn set_rate( - context: Context, - mint: Pubkey, + context: &mut Context, + mint: Address, usdc_per_token: u64, ) -> Result<()> { instructions::set_rate::handle_set_rate(context, mint, usdc_per_token) } pub fn swap_usdc_for_asset( - context: Context, + context: &mut Context, usdc_amount_in: u64, minimum_asset_out: u64, ) -> Result<()> { @@ -41,7 +41,7 @@ pub mod mock_swap_router { } pub fn swap_asset_for_usdc( - context: Context, + context: &mut Context, asset_amount_in: u64, minimum_usdc_out: u64, ) -> Result<()> { diff --git a/finance/vault-strategy/anchor/programs/mock-swap-router/src/state/asset_rate.rs b/finance/vault-strategy/anchor/programs/mock-swap-router/src/state/asset_rate.rs index 332ebfa8b..003a23dab 100644 --- a/finance/vault-strategy/anchor/programs/mock-swap-router/src/state/asset_rate.rs +++ b/finance/vault-strategy/anchor/programs/mock-swap-router/src/state/asset_rate.rs @@ -1,9 +1,9 @@ use anchor_lang::prelude::*; -#[account] +#[account(borsh)] #[derive(InitSpace)] pub struct AssetRate { - pub mint: Pubkey, + pub mint: Address, /// USDC base units per token base unit. /// e.g. 250 means 1 token base unit = 250 USDC base units /// (so 1.0 TSLAx = $250 when both have 6 decimals) diff --git a/finance/vault-strategy/anchor/programs/mock-swap-router/src/state/router_config.rs b/finance/vault-strategy/anchor/programs/mock-swap-router/src/state/router_config.rs index 308a2e4aa..68ffc9165 100644 --- a/finance/vault-strategy/anchor/programs/mock-swap-router/src/state/router_config.rs +++ b/finance/vault-strategy/anchor/programs/mock-swap-router/src/state/router_config.rs @@ -1,9 +1,9 @@ use anchor_lang::prelude::*; -#[account] +#[account(borsh)] #[derive(InitSpace)] pub struct RouterConfig { - pub authority: Pubkey, - pub usdc_mint: Pubkey, + pub authority: Address, + pub usdc_mint: Address, pub bump: u8, } diff --git a/finance/vault-strategy/anchor/programs/vault-strategy/Cargo.toml b/finance/vault-strategy/anchor/programs/vault-strategy/Cargo.toml index 917c27183..be180589f 100644 --- a/finance/vault-strategy/anchor/programs/vault-strategy/Cargo.toml +++ b/finance/vault-strategy/anchor/programs/vault-strategy/Cargo.toml @@ -14,14 +14,22 @@ cpi = ["no-entrypoint"] no-entrypoint = [] no-idl = [] no-log-ix-name = [] -idl-build = ["anchor-lang/idl-build", "anchor-spl/idl-build"] +idl-build = ["anchor-lang/idl-build"] anchor-debug = [] custom-heap = [] custom-panic = [] [dependencies] -anchor-lang = { version = "1.1.2", features = ["init-if-needed"] } -anchor-spl = "1.1.2" +anchor-lang = { version = "2.0.0-rc.1", features = ["compat"] } +# The `#[program]` macro expands to `wincode` paths for instruction-data +# (de)serialization, so the crate has to be a direct dependency. +wincode = { version = "0.5", features = ["derive"] } +# anchor-lang 2.0.0-rc.1 is built against wincode 0.5, but solana-address 2.7 +# moved to wincode 0.6. With both in the graph, `Address`'s wincode impls +# belong to the version the account derives are not using and every +# `SchemaRead`/`SchemaWrite` bound fails. 2.6.1 is the last 0.5-line release. +solana-address = ">=2.6, <2.7" +anchor-spl = "2.0.0-rc.1" mock-swap-router = { path = "../mock-swap-router", features = ["cpi"] } [dev-dependencies] diff --git a/finance/vault-strategy/anchor/programs/vault-strategy/src/instructions/add_asset.rs b/finance/vault-strategy/anchor/programs/vault-strategy/src/instructions/add_asset.rs index dc21ac1df..8ff4688c4 100644 --- a/finance/vault-strategy/anchor/programs/vault-strategy/src/instructions/add_asset.rs +++ b/finance/vault-strategy/anchor/programs/vault-strategy/src/instructions/add_asset.rs @@ -8,9 +8,9 @@ use crate::error::VaultError; use crate::state::{ApprovedAsset, AssetConfig, Registry, Strategy, MAX_ASSETS}; #[derive(Accounts)] -pub struct AddAssetAccountConstraints<'info> { +pub struct AddAssetAccountConstraints { #[account(mut)] - pub manager: Signer<'info>, + pub manager: Signer, #[account( mut, @@ -19,28 +19,28 @@ pub struct AddAssetAccountConstraints<'info> { seeds = [b"strategy", strategy.index.to_le_bytes().as_ref()], bump = strategy.bump )] - pub strategy: Box>, + pub strategy: Box>, - pub registry: Box>, + pub registry: Box>, - pub asset_mint: Box>, + pub asset_mint: Box>, /// Proof the mint is approved, and the source of its official price feed. /// Seeds tie it to this registry and this mint; existence means approved. #[account( - seeds = [b"approved_asset", registry.key().as_ref(), asset_mint.key().as_ref()], + seeds = [b"approved_asset", registry.address().as_ref(), asset_mint.address().as_ref()], bump = approved_asset.bump )] - pub approved_asset: Box>, + pub approved_asset: Box>, #[account( init, payer = manager, space = AssetConfig::DISCRIMINATOR.len() + AssetConfig::INIT_SPACE, - seeds = [b"asset", strategy.key().as_ref(), &[strategy.asset_count]], + seeds = [b"asset", strategy.address().as_ref(), &[strategy.asset_count]], bump )] - pub asset_config: Box>, + pub asset_config: Box>, /// Strategy-owned vault for this asset. #[account( @@ -50,15 +50,15 @@ pub struct AddAssetAccountConstraints<'info> { associated_token::authority = strategy, associated_token::token_program = token_program )] - pub vault_asset: Box>, + pub vault_asset: Box>, - pub associated_token_program: Program<'info, AssociatedToken>, - pub token_program: Interface<'info, TokenInterface>, - pub system_program: Program<'info, System>, + pub associated_token_program: Program, + pub token_program: Interface<'static, TokenInterface>, + pub system_program: Program, } pub fn handle_add_asset( - context: Context, + context: &mut Context, weight_bps: u16, ) -> Result<()> { let strategy = &mut context.accounts.strategy; @@ -72,13 +72,13 @@ pub fn handle_add_asset( let index = strategy.asset_count; - context.accounts.asset_config.set_inner(AssetConfig { - strategy: strategy.key(), + *context.accounts.asset_config = (AssetConfig { + strategy: strategy.address(), index, - mint: context.accounts.asset_mint.key(), + mint: context.accounts.asset_mint.address(), // Copied from the registry entry, never supplied by the manager. price_feed: context.accounts.approved_asset.price_feed, - vault: context.accounts.vault_asset.key(), + vault: context.accounts.vault_asset.address(), weight_bps, bump: context.bumps.asset_config, }); diff --git a/finance/vault-strategy/anchor/programs/vault-strategy/src/instructions/approve_asset.rs b/finance/vault-strategy/anchor/programs/vault-strategy/src/instructions/approve_asset.rs index 0dd06af72..e2af0d3c1 100644 --- a/finance/vault-strategy/anchor/programs/vault-strategy/src/instructions/approve_asset.rs +++ b/finance/vault-strategy/anchor/programs/vault-strategy/src/instructions/approve_asset.rs @@ -4,38 +4,38 @@ use anchor_spl::token_interface::Mint; use crate::state::{ApprovedAsset, Registry}; #[derive(Accounts)] -pub struct ApproveAssetAccountConstraints<'info> { +pub struct ApproveAssetAccountConstraints { #[account(mut)] - pub authority: Signer<'info>, + pub authority: Signer, #[account( has_one = authority, - seeds = [b"registry", authority.key().as_ref()], + seeds = [b"registry", authority.address().as_ref()], bump = registry.bump )] - pub registry: Account<'info, Registry>, + pub registry: BorshAccount, - pub asset_mint: InterfaceAccount<'info, Mint>, + pub asset_mint: InterfaceAccount, #[account( init, payer = authority, space = ApprovedAsset::DISCRIMINATOR.len() + ApprovedAsset::INIT_SPACE, - seeds = [b"approved_asset", registry.key().as_ref(), asset_mint.key().as_ref()], + seeds = [b"approved_asset", registry.address().as_ref(), asset_mint.address().as_ref()], bump )] - pub approved_asset: Account<'info, ApprovedAsset>, + pub approved_asset: BorshAccount, - pub system_program: Program<'info, System>, + pub system_program: Program, } pub fn handle_approve_asset( - context: Context, - price_feed: Pubkey, + context: &mut Context, + price_feed: Address, ) -> Result<()> { - context.accounts.approved_asset.set_inner(ApprovedAsset { - registry: context.accounts.registry.key(), - mint: context.accounts.asset_mint.key(), + *context.accounts.approved_asset = (ApprovedAsset { + registry: context.accounts.registry.address(), + mint: context.accounts.asset_mint.address(), price_feed, bump: context.bumps.approved_asset, }); diff --git a/finance/vault-strategy/anchor/programs/vault-strategy/src/instructions/collect_fees.rs b/finance/vault-strategy/anchor/programs/vault-strategy/src/instructions/collect_fees.rs index 04e964d02..280e64d23 100644 --- a/finance/vault-strategy/anchor/programs/vault-strategy/src/instructions/collect_fees.rs +++ b/finance/vault-strategy/anchor/programs/vault-strategy/src/instructions/collect_fees.rs @@ -10,9 +10,9 @@ use crate::state::Strategy; const SECONDS_PER_YEAR: u64 = 31_536_000; #[derive(Accounts)] -pub struct CollectFeesAccountConstraints<'info> { +pub struct CollectFeesAccountConstraints { /// CHECK: manager is stored in strategy; we only read their pubkey for derivation - pub manager: UncheckedAccount<'info>, + pub manager: UncheckedAccount, #[account( mut, @@ -20,14 +20,14 @@ pub struct CollectFeesAccountConstraints<'info> { seeds = [b"strategy", strategy.index.to_le_bytes().as_ref()], bump = strategy.bump )] - pub strategy: Account<'info, Strategy>, + pub strategy: BorshAccount, #[account( mut, - seeds = [b"share_mint", strategy.key().as_ref()], + seeds = [b"share_mint", strategy.address().as_ref()], bump )] - pub share_mint: InterfaceAccount<'info, Mint>, + pub share_mint: InterfaceAccount, /// Manager's share token account - receives fee shares #[account( @@ -37,17 +37,17 @@ pub struct CollectFeesAccountConstraints<'info> { associated_token::authority = manager, associated_token::token_program = token_program )] - pub manager_share_account: InterfaceAccount<'info, TokenAccount>, + pub manager_share_account: InterfaceAccount, #[account(mut)] - pub payer: Signer<'info>, + pub payer: Signer, - pub associated_token_program: Program<'info, AssociatedToken>, - pub token_program: Interface<'info, TokenInterface>, - pub system_program: Program<'info, System>, + pub associated_token_program: Program, + pub token_program: Interface<'static, TokenInterface>, + pub system_program: Program, } -pub fn handle_collect_fees(context: Context) -> Result<()> { +pub fn handle_collect_fees(context: &mut Context) -> Result<()> { let clock = Clock::get()?; let current_ts = clock.unix_timestamp; let last_ts = context.accounts.strategy.last_fee_accrual_timestamp; @@ -90,12 +90,12 @@ pub fn handle_collect_fees(context: Context) -> R let signer_seeds: &[&[&[u8]]] = &[&[b"strategy", index_bytes.as_ref(), &[strategy_bump]]]; let mint_accounts = MintTo { - mint: context.accounts.share_mint.to_account_info(), - to: context.accounts.manager_share_account.to_account_info(), - authority: context.accounts.strategy.to_account_info(), + mint: context.accounts.share_mint.cpi_handle_mut(), + to: context.accounts.manager_share_account.cpi_handle_mut(), + authority: context.accounts.strategy.cpi_handle_mut(), }; let cpi_ctx = CpiContext::new_with_signer( - context.accounts.token_program.key(), + context.accounts.token_program.address(), mint_accounts, signer_seeds, ); diff --git a/finance/vault-strategy/anchor/programs/vault-strategy/src/instructions/deposit.rs b/finance/vault-strategy/anchor/programs/vault-strategy/src/instructions/deposit.rs index 1a31f6c58..635496ce4 100644 --- a/finance/vault-strategy/anchor/programs/vault-strategy/src/instructions/deposit.rs +++ b/finance/vault-strategy/anchor/programs/vault-strategy/src/instructions/deposit.rs @@ -12,9 +12,9 @@ use crate::oracle::{asset_value_in_usdc, load_price, read_token_amount, PYTH_PRI use crate::state::{AssetConfig, Strategy}; #[derive(Accounts)] -pub struct DepositAccountConstraints<'info> { +pub struct DepositAccountConstraints { #[account(mut)] - pub depositor: Signer<'info>, + pub depositor: Signer, #[account( mut, @@ -22,16 +22,16 @@ pub struct DepositAccountConstraints<'info> { seeds = [b"strategy", strategy.index.to_le_bytes().as_ref()], bump = strategy.bump )] - pub strategy: Box>, + pub strategy: Box>, #[account( mut, - seeds = [b"share_mint", strategy.key().as_ref()], + seeds = [b"share_mint", strategy.address().as_ref()], bump )] - pub share_mint: Box>, + pub share_mint: Box>, - pub usdc_mint: Box>, + pub usdc_mint: Box>, #[account( mut, @@ -39,7 +39,7 @@ pub struct DepositAccountConstraints<'info> { associated_token::authority = depositor, associated_token::token_program = token_program )] - pub depositor_usdc_account: Box>, + pub depositor_usdc_account: Box>, #[account( init_if_needed, @@ -48,7 +48,7 @@ pub struct DepositAccountConstraints<'info> { associated_token::authority = depositor, associated_token::token_program = token_program )] - pub depositor_share_account: Box>, + pub depositor_share_account: Box>, #[account( mut, @@ -56,28 +56,28 @@ pub struct DepositAccountConstraints<'info> { associated_token::authority = strategy, associated_token::token_program = token_program )] - pub vault_usdc: Box>, + pub vault_usdc: Box>, /// CHECK: Router config PDA from the mock-swap-router program #[account(mut)] - pub router_config: UncheckedAccount<'info>, + pub router_config: UncheckedAccount, /// CHECK: Router USDC treasury ATA #[account(mut)] - pub router_usdc_treasury: UncheckedAccount<'info>, + pub router_usdc_treasury: UncheckedAccount, /// CHECK: Router authority PDA from the mock-swap-router program #[account(mut)] - pub router_authority: UncheckedAccount<'info>, + pub router_authority: UncheckedAccount, #[account( - constraint = swap_router_program.key() == strategy.swap_router @ VaultError::InvalidSwapRouter + constraint = swap_router_program.address() == strategy.swap_router @ VaultError::InvalidSwapRouter )] - pub swap_router_program: Program<'info, mock_swap_router::program::MockSwapRouter>, + pub swap_router_program: Program, - pub associated_token_program: Program<'info, AssociatedToken>, - pub token_program: Interface<'info, TokenInterface>, - pub system_program: Program<'info, System>, + pub associated_token_program: Program, + pub token_program: Interface<'static, TokenInterface>, + pub system_program: Program, // remaining_accounts: for each asset index 0..asset_count, in order: // [asset_config, vault, asset_mint, asset_rate, price_feed] } @@ -89,7 +89,7 @@ pub struct DepositAccountConstraints<'info> { /// through the registered router, so a depositor's money is invested in the same /// transaction they put it in (only sub-cent rounding dust can remain as USDC). pub fn handle_deposit<'info>( - context: Context<'info, DepositAccountConstraints<'info>>, + context: &mut Context<'info, DepositAccountConstraints<'info>>, usdc_amount: u64, minimum_shares: u64, ) -> Result<()> { @@ -101,12 +101,12 @@ pub fn handle_deposit<'info>( VaultError::StrategyNotFullyAllocated ); - let vault_usdc_amount = context.accounts.vault_usdc.amount; + let vault_usdc_amount = context.accounts.vault_usdc.amount(); let total_shares = context.accounts.strategy.total_shares; - let usdc_decimals = context.accounts.usdc_mint.decimals; + let usdc_decimals = context.accounts.usdc_mint.decimals(); let strategy_index = context.accounts.strategy.index; let strategy_bump = context.accounts.strategy.bump; - let strategy_key = context.accounts.strategy.key(); + let strategy_key = context.accounts.strategy.address(); let max_slippage_bps = context.accounts.strategy.max_slippage_bps; let asset_count = context.accounts.strategy.asset_count as usize; @@ -115,7 +115,7 @@ pub fn handle_deposit<'info>( // Net asset value over the complete asset set. The assets are exactly indices // 0..asset_count, so requiring five accounts per index, in order, each with a // matching index, makes it impossible to omit an asset and understate NAV. - let remaining = context.remaining_accounts; + let remaining = context.remaining_accounts(); require!( remaining.len() == asset_count * 5, VaultError::IncompleteAssetAccounts @@ -139,7 +139,7 @@ pub fn handle_deposit<'info>( VaultError::InvalidAssetAccount ); require_keys_eq!( - vault_account.key(), + vault_account.address(), config.vault, VaultError::InvalidAssetAccount ); @@ -173,12 +173,12 @@ pub fn handle_deposit<'info>( // Pull the depositor's USDC into the strategy's USDC vault. let transfer_accounts = TransferChecked { - from: context.accounts.depositor_usdc_account.to_account_info(), - mint: context.accounts.usdc_mint.to_account_info(), - to: context.accounts.vault_usdc.to_account_info(), - authority: context.accounts.depositor.to_account_info(), + from: context.accounts.depositor_usdc_account.cpi_handle_mut(), + mint: context.accounts.usdc_mint.cpi_handle_mut(), + to: context.accounts.vault_usdc.cpi_handle_mut(), + authority: context.accounts.depositor.cpi_handle_mut(), }; - let cpi_ctx = CpiContext::new(context.accounts.token_program.key(), transfer_accounts); + let cpi_ctx = CpiContext::new(context.accounts.token_program.address(), transfer_accounts); transfer_checked(cpi_ctx, usdc_amount, usdc_decimals)?; let index_bytes = strategy_index.to_le_bytes(); @@ -197,7 +197,7 @@ pub fn handle_deposit<'info>( let config = AssetConfig::load_checked(config_account)?; require_keys_eq!( - mint_account.key(), + mint_account.address(), config.mint, VaultError::InvalidAssetAccount ); @@ -233,21 +233,21 @@ pub fn handle_deposit<'info>( .map_err(|_| VaultError::MathOverflow)?; let cpi_accounts = RouterSwapAccounts { - caller: context.accounts.strategy.to_account_info(), - router_config: context.accounts.router_config.to_account_info(), + caller: context.accounts.strategy.cpi_handle_mut(), + router_config: context.accounts.router_config.cpi_handle_mut(), asset_rate: rate_account.clone(), - usdc_mint: context.accounts.usdc_mint.to_account_info(), + usdc_mint: context.accounts.usdc_mint.cpi_handle_mut(), asset_mint: mint_account.clone(), - caller_usdc_account: context.accounts.vault_usdc.to_account_info(), + caller_usdc_account: context.accounts.vault_usdc.cpi_handle_mut(), caller_asset_account: vault_account.clone(), - router_usdc_treasury: context.accounts.router_usdc_treasury.to_account_info(), - router_authority: context.accounts.router_authority.to_account_info(), - associated_token_program: context.accounts.associated_token_program.to_account_info(), - token_program: context.accounts.token_program.to_account_info(), - system_program: context.accounts.system_program.to_account_info(), + router_usdc_treasury: context.accounts.router_usdc_treasury.cpi_handle_mut(), + router_authority: context.accounts.router_authority.cpi_handle_mut(), + associated_token_program: context.accounts.associated_token_program.cpi_handle_mut(), + token_program: context.accounts.token_program.cpi_handle_mut(), + system_program: context.accounts.system_program.cpi_handle_mut(), }; let cpi_ctx = CpiContext::new_with_signer( - context.accounts.swap_router_program.key(), + context.accounts.swap_router_program.address(), cpi_accounts, signer_seeds, ); @@ -256,12 +256,12 @@ pub fn handle_deposit<'info>( // Mint the shares last, with the strategy PDA signing as the share mint authority. let mint_accounts = MintTo { - mint: context.accounts.share_mint.to_account_info(), - to: context.accounts.depositor_share_account.to_account_info(), - authority: context.accounts.strategy.to_account_info(), + mint: context.accounts.share_mint.cpi_handle_mut(), + to: context.accounts.depositor_share_account.cpi_handle_mut(), + authority: context.accounts.strategy.cpi_handle_mut(), }; let cpi_ctx = CpiContext::new_with_signer( - context.accounts.token_program.key(), + context.accounts.token_program.address(), mint_accounts, signer_seeds, ); diff --git a/finance/vault-strategy/anchor/programs/vault-strategy/src/instructions/initialize_registry.rs b/finance/vault-strategy/anchor/programs/vault-strategy/src/instructions/initialize_registry.rs index c0479af3e..03bbdf578 100644 --- a/finance/vault-strategy/anchor/programs/vault-strategy/src/instructions/initialize_registry.rs +++ b/finance/vault-strategy/anchor/programs/vault-strategy/src/instructions/initialize_registry.rs @@ -3,27 +3,27 @@ use anchor_lang::prelude::*; use crate::state::Registry; #[derive(Accounts)] -pub struct InitializeRegistryAccountConstraints<'info> { +pub struct InitializeRegistryAccountConstraints { #[account(mut)] - pub authority: Signer<'info>, + pub authority: Signer, #[account( init, payer = authority, space = Registry::DISCRIMINATOR.len() + Registry::INIT_SPACE, - seeds = [b"registry", authority.key().as_ref()], + seeds = [b"registry", authority.address().as_ref()], bump )] - pub registry: Account<'info, Registry>, + pub registry: BorshAccount, - pub system_program: Program<'info, System>, + pub system_program: Program, } pub fn handle_initialize_registry( - context: Context, + context: &mut Context, ) -> Result<()> { - context.accounts.registry.set_inner(Registry { - authority: context.accounts.authority.key(), + *context.accounts.registry = (Registry { + authority: context.accounts.authority.address(), bump: context.bumps.registry, }); Ok(()) diff --git a/finance/vault-strategy/anchor/programs/vault-strategy/src/instructions/initialize_strategy.rs b/finance/vault-strategy/anchor/programs/vault-strategy/src/instructions/initialize_strategy.rs index 0cf96dd44..eb83c1dd5 100644 --- a/finance/vault-strategy/anchor/programs/vault-strategy/src/instructions/initialize_strategy.rs +++ b/finance/vault-strategy/anchor/programs/vault-strategy/src/instructions/initialize_strategy.rs @@ -1,4 +1,5 @@ use anchor_lang::prelude::*; +use anchor_spl::mint; use anchor_spl::{ associated_token::AssociatedToken, token_interface::{Mint, TokenAccount, TokenInterface}, @@ -21,14 +22,14 @@ pub const MAX_SLIPPAGE_BPS: u16 = 1_000; #[derive(Accounts)] #[instruction(index: u64)] -pub struct InitializeStrategyAccountConstraints<'info> { +pub struct InitializeStrategyAccountConstraints { #[account(mut)] - pub manager: Signer<'info>, + pub manager: Signer, - pub usdc_mint: InterfaceAccount<'info, Mint>, + pub usdc_mint: InterfaceAccount, /// Registry whose approved assets this strategy may hold. - pub registry: Account<'info, Registry>, + pub registry: BorshAccount, #[account( init, @@ -37,7 +38,7 @@ pub struct InitializeStrategyAccountConstraints<'info> { seeds = [b"strategy", index.to_le_bytes().as_ref()], bump )] - pub strategy: Box>, + pub strategy: Box>, #[account( init, @@ -46,10 +47,10 @@ pub struct InitializeStrategyAccountConstraints<'info> { mint::authority = strategy, mint::freeze_authority = strategy, mint::token_program = token_program, - seeds = [b"share_mint", strategy.key().as_ref()], + seeds = [b"share_mint", strategy.address().as_ref()], bump )] - pub share_mint: Box>, + pub share_mint: Box>, /// Vault's USDC token account - strategy PDA is the authority #[account( @@ -59,19 +60,19 @@ pub struct InitializeStrategyAccountConstraints<'info> { associated_token::authority = strategy, associated_token::token_program = token_program )] - pub vault_usdc: Box>, + pub vault_usdc: Box>, - pub associated_token_program: Program<'info, AssociatedToken>, - pub token_program: Interface<'info, TokenInterface>, - pub system_program: Program<'info, System>, + pub associated_token_program: Program, + pub token_program: Interface<'static, TokenInterface>, + pub system_program: Program, } pub fn handle_initialize_strategy( - context: Context, + context: &mut Context, index: u64, fee_bps: u16, max_slippage_bps: u16, - swap_router: Pubkey, + swap_router: Address, ) -> Result<()> { require!(fee_bps <= MAX_FEE_BPS, VaultError::FeeTooHigh); require!( @@ -81,12 +82,12 @@ pub fn handle_initialize_strategy( let clock = Clock::get()?; - context.accounts.strategy.set_inner(Strategy { + *context.accounts.strategy = (Strategy { index, - manager: context.accounts.manager.key(), - registry: context.accounts.registry.key(), - share_mint: context.accounts.share_mint.key(), - usdc_mint: context.accounts.usdc_mint.key(), + manager: context.accounts.manager.address(), + registry: context.accounts.registry.address(), + share_mint: context.accounts.share_mint.address(), + usdc_mint: context.accounts.usdc_mint.address(), swap_router, fee_bps, max_slippage_bps, diff --git a/finance/vault-strategy/anchor/programs/vault-strategy/src/instructions/rebalance.rs b/finance/vault-strategy/anchor/programs/vault-strategy/src/instructions/rebalance.rs index b75e1b1ae..b19432fd7 100644 --- a/finance/vault-strategy/anchor/programs/vault-strategy/src/instructions/rebalance.rs +++ b/finance/vault-strategy/anchor/programs/vault-strategy/src/instructions/rebalance.rs @@ -13,8 +13,8 @@ use crate::oracle::{load_price, PYTH_PRICE_PRECISION}; use crate::state::{AssetConfig, Strategy}; #[derive(Accounts)] -pub struct RebalanceAccountConstraints<'info> { - pub manager: Signer<'info>, +pub struct RebalanceAccountConstraints { + pub manager: Signer, #[account( mut, @@ -23,37 +23,37 @@ pub struct RebalanceAccountConstraints<'info> { seeds = [b"strategy", strategy.index.to_le_bytes().as_ref()], bump = strategy.bump )] - pub strategy: Box>, + pub strategy: Box>, - pub usdc_mint: Box>, + pub usdc_mint: Box>, #[account(mut)] - pub sell_mint: Box>, + pub sell_mint: Box>, #[account(mut)] - pub buy_mint: Box>, + pub buy_mint: Box>, #[account( - constraint = sell_config.strategy == strategy.key() @ VaultError::InvalidAssetAccount, - constraint = sell_config.mint == sell_mint.key() @ VaultError::AssetNotFound, - constraint = sell_config.vault == vault_sell.key() @ VaultError::InvalidAssetAccount, + constraint = sell_config.strategy == strategy.address() @ VaultError::InvalidAssetAccount, + constraint = sell_config.mint == sell_mint.address() @ VaultError::AssetNotFound, + constraint = sell_config.vault == vault_sell.address() @ VaultError::InvalidAssetAccount, )] - pub sell_config: Box>, + pub sell_config: Box>, #[account( - constraint = buy_config.strategy == strategy.key() @ VaultError::InvalidAssetAccount, - constraint = buy_config.mint == buy_mint.key() @ VaultError::AssetNotFound, - constraint = buy_config.vault == vault_buy.key() @ VaultError::InvalidAssetAccount, + constraint = buy_config.strategy == strategy.address() @ VaultError::InvalidAssetAccount, + constraint = buy_config.mint == buy_mint.address() @ VaultError::AssetNotFound, + constraint = buy_config.vault == vault_buy.address() @ VaultError::InvalidAssetAccount, )] - pub buy_config: Box>, + pub buy_config: Box>, /// CHECK: Pyth feed - validated against sell asset's registered feed - #[account(constraint = sell_price_feed.key() == sell_config.price_feed @ VaultError::InvalidPriceFeed)] - pub sell_price_feed: UncheckedAccount<'info>, + #[account(constraint = sell_price_feed.address() == sell_config.price_feed @ VaultError::InvalidPriceFeed)] + pub sell_price_feed: UncheckedAccount, /// CHECK: Pyth feed - validated against buy asset's registered feed - #[account(constraint = buy_price_feed.key() == buy_config.price_feed @ VaultError::InvalidPriceFeed)] - pub buy_price_feed: UncheckedAccount<'info>, + #[account(constraint = buy_price_feed.address() == buy_config.price_feed @ VaultError::InvalidPriceFeed)] + pub buy_price_feed: UncheckedAccount, #[account( mut, @@ -61,7 +61,7 @@ pub struct RebalanceAccountConstraints<'info> { associated_token::authority = strategy, associated_token::token_program = token_program )] - pub vault_sell: Box>, + pub vault_sell: Box>, #[account( mut, @@ -69,7 +69,7 @@ pub struct RebalanceAccountConstraints<'info> { associated_token::authority = strategy, associated_token::token_program = token_program )] - pub vault_buy: Box>, + pub vault_buy: Box>, #[account( mut, @@ -77,41 +77,41 @@ pub struct RebalanceAccountConstraints<'info> { associated_token::authority = strategy, associated_token::token_program = token_program )] - pub vault_usdc: Box>, + pub vault_usdc: Box>, - pub sell_rate: Account<'info, AssetRate>, + pub sell_rate: BorshAccount, - pub buy_rate: Account<'info, AssetRate>, + pub buy_rate: BorshAccount, /// CHECK: Router config PDA #[account(mut)] - pub router_config: UncheckedAccount<'info>, + pub router_config: UncheckedAccount, /// CHECK: Router USDC treasury ATA #[account(mut)] - pub router_usdc_treasury: UncheckedAccount<'info>, + pub router_usdc_treasury: UncheckedAccount, /// CHECK: Router authority PDA #[account(mut)] - pub router_authority: UncheckedAccount<'info>, + pub router_authority: UncheckedAccount, #[account( - constraint = swap_router_program.key() == strategy.swap_router @ VaultError::InvalidSwapRouter + constraint = swap_router_program.address() == strategy.swap_router @ VaultError::InvalidSwapRouter )] - pub swap_router_program: Program<'info, mock_swap_router::program::MockSwapRouter>, + pub swap_router_program: Program, - pub associated_token_program: Program<'info, AssociatedToken>, - pub token_program: Interface<'info, TokenInterface>, - pub system_program: Program<'info, System>, + pub associated_token_program: Program, + pub token_program: Interface<'static, TokenInterface>, + pub system_program: Program, } pub fn handle_rebalance( - context: Context, + context: &mut Context, sell_amount: u64, usdc_to_invest: u64, ) -> Result<()> { require!( - context.accounts.sell_mint.key() != context.accounts.buy_mint.key(), + context.accounts.sell_mint.address() != context.accounts.buy_mint.address(), VaultError::SameMint ); @@ -165,22 +165,22 @@ pub fn handle_rebalance( // Step 1: sell basket token -> USDC let sell_cpi_accounts = RouterSellAccounts { - caller: context.accounts.strategy.to_account_info(), - router_config: context.accounts.router_config.to_account_info(), - asset_rate: context.accounts.sell_rate.to_account_info(), - usdc_mint: context.accounts.usdc_mint.to_account_info(), - asset_mint: context.accounts.sell_mint.to_account_info(), - caller_asset_account: context.accounts.vault_sell.to_account_info(), - caller_usdc_account: context.accounts.vault_usdc.to_account_info(), - router_usdc_treasury: context.accounts.router_usdc_treasury.to_account_info(), - router_authority: context.accounts.router_authority.to_account_info(), - associated_token_program: context.accounts.associated_token_program.to_account_info(), - token_program: context.accounts.token_program.to_account_info(), - system_program: context.accounts.system_program.to_account_info(), + caller: context.accounts.strategy.cpi_handle_mut(), + router_config: context.accounts.router_config.cpi_handle_mut(), + asset_rate: context.accounts.sell_rate.cpi_handle_mut(), + usdc_mint: context.accounts.usdc_mint.cpi_handle_mut(), + asset_mint: context.accounts.sell_mint.cpi_handle_mut(), + caller_asset_account: context.accounts.vault_sell.cpi_handle_mut(), + caller_usdc_account: context.accounts.vault_usdc.cpi_handle_mut(), + router_usdc_treasury: context.accounts.router_usdc_treasury.cpi_handle_mut(), + router_authority: context.accounts.router_authority.cpi_handle_mut(), + associated_token_program: context.accounts.associated_token_program.cpi_handle_mut(), + token_program: context.accounts.token_program.cpi_handle_mut(), + system_program: context.accounts.system_program.cpi_handle_mut(), }; mock_swap_router::cpi::swap_asset_for_usdc( CpiContext::new_with_signer( - context.accounts.swap_router_program.key(), + context.accounts.swap_router_program.address(), sell_cpi_accounts, signer_seeds, ), @@ -190,22 +190,22 @@ pub fn handle_rebalance( // Step 2: buy basket token with USDC let buy_cpi_accounts = RouterBuyAccounts { - caller: context.accounts.strategy.to_account_info(), - router_config: context.accounts.router_config.to_account_info(), - asset_rate: context.accounts.buy_rate.to_account_info(), - usdc_mint: context.accounts.usdc_mint.to_account_info(), - asset_mint: context.accounts.buy_mint.to_account_info(), - caller_usdc_account: context.accounts.vault_usdc.to_account_info(), - caller_asset_account: context.accounts.vault_buy.to_account_info(), - router_usdc_treasury: context.accounts.router_usdc_treasury.to_account_info(), - router_authority: context.accounts.router_authority.to_account_info(), - associated_token_program: context.accounts.associated_token_program.to_account_info(), - token_program: context.accounts.token_program.to_account_info(), - system_program: context.accounts.system_program.to_account_info(), + caller: context.accounts.strategy.cpi_handle_mut(), + router_config: context.accounts.router_config.cpi_handle_mut(), + asset_rate: context.accounts.buy_rate.cpi_handle_mut(), + usdc_mint: context.accounts.usdc_mint.cpi_handle_mut(), + asset_mint: context.accounts.buy_mint.cpi_handle_mut(), + caller_usdc_account: context.accounts.vault_usdc.cpi_handle_mut(), + caller_asset_account: context.accounts.vault_buy.cpi_handle_mut(), + router_usdc_treasury: context.accounts.router_usdc_treasury.cpi_handle_mut(), + router_authority: context.accounts.router_authority.cpi_handle_mut(), + associated_token_program: context.accounts.associated_token_program.cpi_handle_mut(), + token_program: context.accounts.token_program.cpi_handle_mut(), + system_program: context.accounts.system_program.cpi_handle_mut(), }; mock_swap_router::cpi::swap_usdc_for_asset( CpiContext::new_with_signer( - context.accounts.swap_router_program.key(), + context.accounts.swap_router_program.address(), buy_cpi_accounts, signer_seeds, ), diff --git a/finance/vault-strategy/anchor/programs/vault-strategy/src/instructions/set_weight.rs b/finance/vault-strategy/anchor/programs/vault-strategy/src/instructions/set_weight.rs index d0cbd6b05..5460cfee8 100644 --- a/finance/vault-strategy/anchor/programs/vault-strategy/src/instructions/set_weight.rs +++ b/finance/vault-strategy/anchor/programs/vault-strategy/src/instructions/set_weight.rs @@ -4,8 +4,8 @@ use crate::error::VaultError; use crate::state::{AssetConfig, Strategy}; #[derive(Accounts)] -pub struct SetWeightAccountConstraints<'info> { - pub manager: Signer<'info>, +pub struct SetWeightAccountConstraints { + pub manager: Signer, #[account( mut, @@ -13,13 +13,13 @@ pub struct SetWeightAccountConstraints<'info> { seeds = [b"strategy", strategy.index.to_le_bytes().as_ref()], bump = strategy.bump )] - pub strategy: Box>, + pub strategy: Box>, #[account( mut, - constraint = asset_config.strategy == strategy.key() @ VaultError::InvalidAssetAccount, + constraint = asset_config.strategy == strategy.address() @ VaultError::InvalidAssetAccount, )] - pub asset_config: Box>, + pub asset_config: Box>, } /// Change an asset's target weight. Setting it to zero retires the asset: deposits @@ -28,7 +28,7 @@ pub struct SetWeightAccountConstraints<'info> { /// contiguous 0..asset_count range the valuation handlers depend on stays intact. /// Funds do not move here; this only edits the target the manager trades toward. pub fn handle_set_weight( - context: Context, + context: &mut Context, weight_bps: u16, ) -> Result<()> { let strategy = &mut context.accounts.strategy; diff --git a/finance/vault-strategy/anchor/programs/vault-strategy/src/instructions/withdraw.rs b/finance/vault-strategy/anchor/programs/vault-strategy/src/instructions/withdraw.rs index a15235d4d..87cc2bb42 100644 --- a/finance/vault-strategy/anchor/programs/vault-strategy/src/instructions/withdraw.rs +++ b/finance/vault-strategy/anchor/programs/vault-strategy/src/instructions/withdraw.rs @@ -11,9 +11,9 @@ use crate::oracle::{read_mint_decimals, read_token_amount, read_token_mint_and_o use crate::state::{AssetConfig, Strategy}; #[derive(Accounts)] -pub struct WithdrawAccountConstraints<'info> { +pub struct WithdrawAccountConstraints { #[account(mut)] - pub user: Signer<'info>, + pub user: Signer, #[account( mut, @@ -21,16 +21,16 @@ pub struct WithdrawAccountConstraints<'info> { seeds = [b"strategy", strategy.index.to_le_bytes().as_ref()], bump = strategy.bump )] - pub strategy: Box>, + pub strategy: Box>, #[account( mut, - seeds = [b"share_mint", strategy.key().as_ref()], + seeds = [b"share_mint", strategy.address().as_ref()], bump )] - pub share_mint: Box>, + pub share_mint: Box>, - pub usdc_mint: Box>, + pub usdc_mint: Box>, #[account( mut, @@ -38,7 +38,7 @@ pub struct WithdrawAccountConstraints<'info> { associated_token::authority = user, associated_token::token_program = token_program )] - pub user_share_account: Box>, + pub user_share_account: Box>, #[account( init_if_needed, @@ -47,7 +47,7 @@ pub struct WithdrawAccountConstraints<'info> { associated_token::authority = user, associated_token::token_program = token_program )] - pub user_usdc_account: Box>, + pub user_usdc_account: Box>, #[account( mut, @@ -55,18 +55,18 @@ pub struct WithdrawAccountConstraints<'info> { associated_token::authority = strategy, associated_token::token_program = token_program )] - pub vault_usdc: Box>, + pub vault_usdc: Box>, - pub associated_token_program: Program<'info, AssociatedToken>, - pub token_program: Interface<'info, TokenInterface>, - pub system_program: Program<'info, System>, + pub associated_token_program: Program, + pub token_program: Interface<'static, TokenInterface>, + pub system_program: Program, // remaining_accounts: for each asset index 0..asset_count, in order: // [asset_config, vault, mint, user_token_account] // The user's asset token accounts must already exist. } pub fn handle_withdraw<'info>( - context: Context<'info, WithdrawAccountConstraints<'info>>, + context: &mut Context<'info, WithdrawAccountConstraints<'info>>, shares_to_burn: u64, min_usdc_out: u64, ) -> Result<()> { @@ -75,16 +75,16 @@ pub fn handle_withdraw<'info>( let total_shares = context.accounts.strategy.total_shares; require!(total_shares > 0, VaultError::ZeroTotalShares); - let vault_usdc_amount = context.accounts.vault_usdc.amount; - let usdc_decimals = context.accounts.usdc_mint.decimals; + let vault_usdc_amount = context.accounts.vault_usdc.amount(); + let usdc_decimals = context.accounts.usdc_mint.decimals(); let strategy_index = context.accounts.strategy.index; let strategy_bump = context.accounts.strategy.bump; - let strategy_key = context.accounts.strategy.key(); - let user_key = context.accounts.user.key(); + let strategy_key = context.accounts.strategy.address(); + let user_key = context.accounts.user.address(); let asset_count = context.accounts.strategy.asset_count as usize; require!( - context.remaining_accounts.len() == asset_count * 4, + context.remaining_accounts().len() == asset_count * 4, VaultError::IncompleteAssetAccounts ); @@ -110,14 +110,14 @@ pub fn handle_withdraw<'info>( // Hoist owned account-info handles for every CPI up front, so the asset loop // can borrow remaining_accounts without also re-borrowing `context.accounts` // (Account is invariant over its lifetime, which otherwise fails to unify). - let strategy_info = context.accounts.strategy.to_account_info(); - let share_mint_info = context.accounts.share_mint.to_account_info(); - let usdc_mint_info = context.accounts.usdc_mint.to_account_info(); - let vault_usdc_info = context.accounts.vault_usdc.to_account_info(); - let user_info = context.accounts.user.to_account_info(); - let user_share_info = context.accounts.user_share_account.to_account_info(); - let user_usdc_info = context.accounts.user_usdc_account.to_account_info(); - let token_program_key = context.accounts.token_program.key(); + let strategy_info = context.accounts.strategy.cpi_handle_mut(); + let share_mint_info = context.accounts.share_mint.cpi_handle_mut(); + let usdc_mint_info = context.accounts.usdc_mint.cpi_handle_mut(); + let vault_usdc_info = context.accounts.vault_usdc.cpi_handle_mut(); + let user_info = context.accounts.user.cpi_handle_mut(); + let user_share_info = context.accounts.user_share_account.cpi_handle_mut(); + let user_usdc_info = context.accounts.user_usdc_account.cpi_handle_mut(); + let token_program_key = context.accounts.token_program.address(); // Burn the user's shares. let burn_accounts = Burn { @@ -146,7 +146,7 @@ pub fn handle_withdraw<'info>( } // Each basket asset, paid in kind, proportional to shares burned. - let remaining = context.remaining_accounts; + let remaining = context.remaining_accounts(); for i in 0..asset_count { let config_ai = &remaining[i * 4]; let vault_ai = &remaining[i * 4 + 1]; @@ -161,11 +161,11 @@ pub fn handle_withdraw<'info>( ); require!(config.index as usize == i, VaultError::InvalidAssetAccount); require_keys_eq!( - vault_ai.key(), + vault_ai.address(), config.vault, VaultError::InvalidAssetAccount ); - require_keys_eq!(mint_ai.key(), config.mint, VaultError::InvalidAssetAccount); + require_keys_eq!(mint_ai.address(), config.mint, VaultError::InvalidAssetAccount); let (recipient_mint, recipient_owner) = read_token_mint_and_owner(user_ata_ai)?; require_keys_eq!(recipient_owner, user_key, VaultError::InvalidRecipient); @@ -181,9 +181,9 @@ pub fn handle_withdraw<'info>( if amount > 0 { let decimals = read_mint_decimals(mint_ai)?; let transfer_accounts = TransferChecked { - from: vault_ai.to_account_info(), - mint: mint_ai.to_account_info(), - to: user_ata_ai.to_account_info(), + from: vault_ai.cpi_handle_mut(), + mint: mint_ai.cpi_handle_mut(), + to: user_ata_ai.cpi_handle_mut(), authority: strategy_info.clone(), }; transfer_checked( diff --git a/finance/vault-strategy/anchor/programs/vault-strategy/src/lib.rs b/finance/vault-strategy/anchor/programs/vault-strategy/src/lib.rs index 4773503cb..9f3e82e97 100644 --- a/finance/vault-strategy/anchor/programs/vault-strategy/src/lib.rs +++ b/finance/vault-strategy/anchor/programs/vault-strategy/src/lib.rs @@ -17,15 +17,15 @@ pub mod vault_strategy { /// Create the curator record for an approved-asset set, owned by `authority` /// (not a manager). The set itself lives in per-asset ApprovedAsset accounts. pub fn initialize_registry( - context: Context, + context: &mut Context, ) -> Result<()> { instructions::initialize_registry::handle_initialize_registry(context) } /// Approve a mint and bind it to its official price feed. Registry authority only. pub fn approve_asset( - context: Context, - price_feed: Pubkey, + context: &mut Context, + price_feed: Address, ) -> Result<()> { instructions::approve_asset::handle_approve_asset(context, price_feed) } @@ -33,11 +33,11 @@ pub mod vault_strategy { /// Open a strategy at a caller-chosen index, e.g. index 0 derives the PDA /// from seeds `"strategy" + 0`. Manager pays and becomes the strategy's manager. pub fn initialize_strategy( - context: Context, + context: &mut Context, index: u64, fee_bps: u16, max_slippage_bps: u16, - swap_router: Pubkey, + swap_router: Address, ) -> Result<()> { instructions::initialize_strategy::handle_initialize_strategy( context, @@ -49,32 +49,32 @@ pub mod vault_strategy { } /// Add a curator-approved asset to the strategy at the next index. Manager only. - pub fn add_asset(context: Context, weight_bps: u16) -> Result<()> { + pub fn add_asset(context: &mut Context, weight_bps: u16) -> Result<()> { instructions::add_asset::handle_add_asset(context, weight_bps) } /// Change an asset's target weight, or set it to zero to retire it. Manager only. pub fn set_weight( - context: Context, + context: &mut Context, weight_bps: u16, ) -> Result<()> { instructions::set_weight::handle_set_weight(context, weight_bps) } pub fn deposit<'info>( - context: Context<'info, DepositAccountConstraints<'info>>, + context: &mut Context<'info, DepositAccountConstraints<'info>>, usdc_amount: u64, minimum_shares: u64, ) -> Result<()> { instructions::deposit::handle_deposit(context, usdc_amount, minimum_shares) } - pub fn collect_fees(context: Context) -> Result<()> { + pub fn collect_fees(context: &mut Context) -> Result<()> { instructions::collect_fees::handle_collect_fees(context) } pub fn withdraw<'info>( - context: Context<'info, WithdrawAccountConstraints<'info>>, + context: &mut Context<'info, WithdrawAccountConstraints<'info>>, shares_to_burn: u64, min_usdc_out: u64, ) -> Result<()> { @@ -82,7 +82,7 @@ pub mod vault_strategy { } pub fn rebalance( - context: Context, + context: &mut Context, sell_amount: u64, usdc_to_invest: u64, ) -> Result<()> { diff --git a/finance/vault-strategy/anchor/programs/vault-strategy/src/oracle.rs b/finance/vault-strategy/anchor/programs/vault-strategy/src/oracle.rs index cfa41e11f..43eeb5fb5 100644 --- a/finance/vault-strategy/anchor/programs/vault-strategy/src/oracle.rs +++ b/finance/vault-strategy/anchor/programs/vault-strategy/src/oracle.rs @@ -17,9 +17,9 @@ const MAX_PRICE_AGE_SECONDS: i64 = 60; /// shared by the Classic Token Program and the Token Extensions Program, so this /// reads either. const TOKEN_AMOUNT_OFFSET: usize = 64; -/// `owner` Pubkey is at bytes 32..64. +/// `owner` Address is at bytes 32..64. const TOKEN_OWNER_OFFSET: usize = 32; -/// `mint` Pubkey is at bytes 0..32. +/// `mint` Address is at bytes 0..32. const TOKEN_MINT_OFFSET: usize = 0; fn read_pyth_raw(account_data: &[u8]) -> Result<(i64, i64)> { @@ -41,9 +41,9 @@ fn read_pyth_raw(account_data: &[u8]) -> Result<(i64, i64)> { /// Validate a price feed account against the one the strategy registered, then /// return its positive, fresh price as u128. `now` is the current unix timestamp. -pub fn load_price(price_feed: &AccountInfo, expected_key: &Pubkey, now: i64) -> Result { +pub fn load_price(price_feed: &AccountView, expected_key: &Address, now: i64) -> Result { require_keys_eq!( - price_feed.key(), + price_feed.address(), *expected_key, VaultError::InvalidPriceFeed ); @@ -63,7 +63,7 @@ pub fn load_price(price_feed: &AccountInfo, expected_key: &Pubkey, now: i64) -> } /// Read the `amount` field of a token account from its raw data. -pub fn read_token_amount(account: &AccountInfo) -> Result { +pub fn read_token_amount(account: &AccountView) -> Result { let data = account.try_borrow_data()?; if data.len() < TOKEN_AMOUNT_OFFSET + 8 { return err!(VaultError::InvalidVaultAccount); @@ -77,7 +77,7 @@ pub fn read_token_amount(account: &AccountInfo) -> Result { /// Read the `decimals` byte of a mint account. Offset 44 in the Mint layout /// (mint_authority option 36 + supply 8), shared by both token programs. -pub fn read_mint_decimals(account: &AccountInfo) -> Result { +pub fn read_mint_decimals(account: &AccountView) -> Result { let data = account.try_borrow_data()?; const MINT_DECIMALS_OFFSET: usize = 44; if data.len() <= MINT_DECIMALS_OFFSET { @@ -87,14 +87,14 @@ pub fn read_mint_decimals(account: &AccountInfo) -> Result { } /// Read the `mint` and `owner` Pubkeys of a token account from its raw data. -pub fn read_token_mint_and_owner(account: &AccountInfo) -> Result<(Pubkey, Pubkey)> { +pub fn read_token_mint_and_owner(account: &AccountView) -> Result<(Address, Address)> { let data = account.try_borrow_data()?; if data.len() < TOKEN_OWNER_OFFSET + 32 { return err!(VaultError::InvalidVaultAccount); } - let mint = Pubkey::try_from(&data[TOKEN_MINT_OFFSET..TOKEN_MINT_OFFSET + 32]) + let mint = Address::try_from(&data[TOKEN_MINT_OFFSET..TOKEN_MINT_OFFSET + 32]) .map_err(|_| VaultError::InvalidVaultAccount)?; - let owner = Pubkey::try_from(&data[TOKEN_OWNER_OFFSET..TOKEN_OWNER_OFFSET + 32]) + let owner = Address::try_from(&data[TOKEN_OWNER_OFFSET..TOKEN_OWNER_OFFSET + 32]) .map_err(|_| VaultError::InvalidVaultAccount)?; Ok((mint, owner)) } diff --git a/finance/vault-strategy/anchor/programs/vault-strategy/src/state/registry.rs b/finance/vault-strategy/anchor/programs/vault-strategy/src/state/registry.rs index ffee0bb73..1fc465c5a 100644 --- a/finance/vault-strategy/anchor/programs/vault-strategy/src/state/registry.rs +++ b/finance/vault-strategy/anchor/programs/vault-strategy/src/state/registry.rs @@ -9,10 +9,10 @@ use anchor_lang::prelude::*; /// real assets (and which official price feed) are safe, and a manager only /// chooses among them. This is what stops a manager from listing a token they /// mint themselves, or pairing a real mint with a feed they control. -#[account] +#[account(borsh)] #[derive(InitSpace)] pub struct Registry { - pub authority: Pubkey, + pub authority: Address, pub bump: u8, } @@ -22,11 +22,11 @@ pub struct Registry { /// address and fails if no account is there. Created only by the registry /// authority; add_asset copies `price_feed` from here so the manager never /// supplies the feed. -#[account] +#[account(borsh)] #[derive(InitSpace)] pub struct ApprovedAsset { - pub registry: Pubkey, - pub mint: Pubkey, - pub price_feed: Pubkey, + pub registry: Address, + pub mint: Address, + pub price_feed: Address, pub bump: u8, } diff --git a/finance/vault-strategy/anchor/programs/vault-strategy/src/state/strategy.rs b/finance/vault-strategy/anchor/programs/vault-strategy/src/state/strategy.rs index 05dc893a3..c09f785cd 100644 --- a/finance/vault-strategy/anchor/programs/vault-strategy/src/state/strategy.rs +++ b/finance/vault-strategy/anchor/programs/vault-strategy/src/state/strategy.rs @@ -15,18 +15,18 @@ pub const MAX_ASSETS: u8 = 16; /// e.g. seeds `"strategy" + 0`, so strategies are addressed by a simple counter /// rather than by the manager's key. The index is stored here so every handler /// can re-derive the PDA to sign for the vaults and share mint. -#[account] +#[account(borsh)] #[derive(InitSpace)] pub struct Strategy { /// Index used as the PDA seed, e.g. 0 for the first strategy. pub index: u64, - pub manager: Pubkey, + pub manager: Address, /// Registry whose curator approves assets. add_asset only accepts mints /// approved in this registry. - pub registry: Pubkey, - pub share_mint: Pubkey, - pub usdc_mint: Pubkey, - pub swap_router: Pubkey, + pub registry: Address, + pub share_mint: Address, + pub usdc_mint: Address, + pub swap_router: Address, /// Annual management fee in basis points (e.g. 100 = 1%). pub fee_bps: u16, /// Maximum tolerated deviation, in basis points, between a swap's output and @@ -46,17 +46,17 @@ pub struct Strategy { /// index, so the full set is the contiguous range 0..asset_count: any handler /// computing net asset value re-derives every index and refuses to proceed if an /// asset account is missing. -#[account] +#[account(borsh)] #[derive(InitSpace)] pub struct AssetConfig { - pub strategy: Pubkey, + pub strategy: Address, pub index: u8, - pub mint: Pubkey, + pub mint: Address, /// Pyth PriceUpdateV2 account, copied from the registry's ApprovedAsset at /// add time so the manager cannot substitute a feed they control. - pub price_feed: Pubkey, + pub price_feed: Address, /// Strategy-owned associated token account holding this asset. - pub vault: Pubkey, + pub vault: Address, /// Target share of the strategy's value in basis points. deposit deploys at these /// weights (the sum across assets must reach 10000 before deposits open), and the /// manager maintains them against price drift with rebalance. @@ -68,7 +68,7 @@ impl AssetConfig { /// Deserialize an AssetConfig passed via remaining_accounts to an owned value, /// verifying it is owned by this program and has the right discriminator. /// Avoids the lifetime invariance of `Account::try_from` on borrowed infos. - pub fn load_checked(account: &AccountInfo) -> Result { + pub fn load_checked(account: &AccountView) -> Result { require_keys_eq!( *account.owner, crate::ID, diff --git a/tokens/create-token/anchor/programs/create-token/Cargo.toml b/tokens/create-token/anchor/programs/create-token/Cargo.toml index ca65253b2..6ccd329cd 100644 --- a/tokens/create-token/anchor/programs/create-token/Cargo.toml +++ b/tokens/create-token/anchor/programs/create-token/Cargo.toml @@ -14,15 +14,23 @@ cpi = ["no-entrypoint"] no-entrypoint = [] no-idl = [] no-log-ix-name = [] -idl-build = ["anchor-lang/idl-build", "anchor-spl/idl-build"] +idl-build = ["anchor-lang/idl-build"] anchor-debug = [] custom-heap = [] custom-panic = [] [dependencies] -anchor-lang = "1.1.2" -anchor-spl = { version = "1.1.2", features = ["metadata"] } +anchor-lang = "2.0.0-rc.1" +# The `#[program]` macro expands to `wincode` paths for instruction-data +# (de)serialization, so the crate has to be a direct dependency. +wincode = { version = "0.5", features = ["derive"] } +# anchor-lang 2.0.0-rc.1 is built against wincode 0.5, but solana-address 2.7 +# moved to wincode 0.6. With both in the graph, `Address`'s wincode impls +# belong to the version the account derives are not using and every +# `SchemaRead`/`SchemaWrite` bound fails. 2.6.1 is the last 0.5-line release. +solana-address = ">=2.6, <2.7" +anchor-spl = { version = "2.0.0-rc.1", features = ["metadata"] } [dev-dependencies] litesvm = "0.13.1" diff --git a/tokens/create-token/anchor/programs/create-token/src/lib.rs b/tokens/create-token/anchor/programs/create-token/src/lib.rs index 59173b779..3a12ab26d 100644 --- a/tokens/create-token/anchor/programs/create-token/src/lib.rs +++ b/tokens/create-token/anchor/programs/create-token/src/lib.rs @@ -5,7 +5,8 @@ use { create_metadata_accounts_v3, mpl_token_metadata::types::DataV2, CreateMetadataAccountsV3, Metadata, }, - token::{Mint, Token}, + mint::{self, Mint}, + token::Token, }, }; @@ -16,7 +17,7 @@ pub mod create_token { use super::*; pub fn create_token_mint( - context: Context, + context: &mut Context, _token_decimals: u8, token_name: String, token_symbol: String, @@ -25,22 +26,28 @@ pub mod create_token { msg!("Creating metadata account..."); msg!( "Metadata account address: {}", - &context.accounts.metadata_account.key() + &context.accounts.metadata_account.address() ); // Cross Program Invocation (CPI) // Invoking the create_metadata_account_v3 instruction on the token metadata program + // `payer` fills three CPI slots (payer, mint authority, update + // authority). v2's typed handles enforce borrow exclusivity at compile + // time, so the two read-only slots are built from a copy of the + // `AccountView` — it still points at the same underlying account. + let payer_view = *context.accounts.payer.account(); + create_metadata_accounts_v3( CpiContext::new( - context.accounts.token_metadata_program.key(), + context.accounts.token_metadata_program.address(), CreateMetadataAccountsV3 { - metadata: context.accounts.metadata_account.to_account_info(), - mint: context.accounts.mint_account.to_account_info(), - mint_authority: context.accounts.payer.to_account_info(), - update_authority: context.accounts.payer.to_account_info(), - payer: context.accounts.payer.to_account_info(), - system_program: context.accounts.system_program.to_account_info(), - rent: context.accounts.rent.to_account_info(), + metadata: context.accounts.metadata_account.cpi_handle_mut(), + mint: context.accounts.mint_account.cpi_handle(), + mint_authority: CpiHandle::readonly(&payer_view), + update_authority: CpiHandle::readonly(&payer_view), + payer: context.accounts.payer.cpi_handle_mut(), + system_program: context.accounts.system_program.cpi_handle(), + update_authority_is_signer: true, }, ), DataV2 { @@ -53,7 +60,6 @@ pub mod create_token { uses: None, }, false, // Is mutable - true, // Update authority is signer None, // Collection details )?; @@ -65,29 +71,29 @@ pub mod create_token { #[derive(Accounts)] #[instruction(_token_decimals: u8)] -pub struct CreateTokenMintAccountConstraints<'info> { +pub struct CreateTokenMintAccountConstraints { #[account(mut)] - pub payer: Signer<'info>, + pub payer: Signer, /// CHECK: Validate address by deriving pda #[account( mut, - seeds = [b"metadata", token_metadata_program.key().as_ref(), mint_account.key().as_ref()], + seeds = [b"metadata", token_metadata_program.address().as_ref(), mint_account.address().as_ref()], bump, - seeds::program = token_metadata_program.key(), + seeds::program = token_metadata_program.address(), )] - pub metadata_account: UncheckedAccount<'info>, + pub metadata_account: UncheckedAccount, // Create new mint account #[account( init, payer = payer, mint::decimals = _token_decimals, - mint::authority = payer.key(), + mint::authority = payer, )] - pub mint_account: Account<'info, Mint>, + pub mint_account: Account, - pub token_metadata_program: Program<'info, Metadata>, - pub token_program: Program<'info, Token>, - pub system_program: Program<'info, System>, - pub rent: Sysvar<'info, Rent>, + pub token_metadata_program: Program, + pub token_program: Program, + pub system_program: Program, + pub rent: Sysvar, } diff --git a/tokens/external-delegate-token-master/anchor/programs/external-delegate-token-master/Cargo.toml b/tokens/external-delegate-token-master/anchor/programs/external-delegate-token-master/Cargo.toml index a1ccd537b..e67b57e71 100644 --- a/tokens/external-delegate-token-master/anchor/programs/external-delegate-token-master/Cargo.toml +++ b/tokens/external-delegate-token-master/anchor/programs/external-delegate-token-master/Cargo.toml @@ -14,14 +14,22 @@ cpi = ["no-entrypoint"] no-entrypoint = [] no-idl = [] no-log-ix-name = [] -idl-build = ["anchor-lang/idl-build", "anchor-spl/idl-build"] +idl-build = ["anchor-lang/idl-build"] anchor-debug = [] custom-heap = [] custom-panic = [] [dependencies] -anchor-lang = { version = "1.1.2", features = ["init-if-needed"] } -anchor-spl = { version = "1.1.2", features = ["metadata"] } +anchor-lang = "2.0.0-rc.1" +# The `#[program]` macro expands to `wincode` paths for instruction-data +# (de)serialization, so the crate has to be a direct dependency. +wincode = { version = "0.5", features = ["derive"] } +# anchor-lang 2.0.0-rc.1 is built against wincode 0.5, but solana-address 2.7 +# moved to wincode 0.6. With both in the graph, `Address`'s wincode impls +# belong to the version the account derives are not using and every +# `SchemaRead`/`SchemaWrite` bound fails. 2.6.1 is the last 0.5-line release. +solana-address = ">=2.6, <2.7" +anchor-spl = { version = "2.0.0-rc.1", features = ["metadata"] } sha3 = "0.10.8" solana-secp256k1-recover = "2.0.0" diff --git a/tokens/external-delegate-token-master/anchor/programs/external-delegate-token-master/src/instructions/authority_transfer.rs b/tokens/external-delegate-token-master/anchor/programs/external-delegate-token-master/src/instructions/authority_transfer.rs index 9fc507eea..b309b2178 100644 --- a/tokens/external-delegate-token-master/anchor/programs/external-delegate-token-master/src/instructions/authority_transfer.rs +++ b/tokens/external-delegate-token-master/anchor/programs/external-delegate-token-master/src/instructions/authority_transfer.rs @@ -6,48 +6,48 @@ use anchor_spl::token_interface::{ use crate::UserAccount; #[derive(Accounts)] -pub struct AuthorityTransferAccountConstraints<'info> { +pub struct AuthorityTransferAccountConstraints { #[account(has_one = authority)] - pub user_account: Account<'info, UserAccount>, + pub user_account: BorshAccount, - pub authority: Signer<'info>, + pub authority: Signer, - pub mint: InterfaceAccount<'info, Mint>, + pub mint: InterfaceAccount, #[account(mut)] - pub user_token_account: InterfaceAccount<'info, TokenAccount>, + pub user_token_account: InterfaceAccount, #[account(mut)] - pub recipient_token_account: InterfaceAccount<'info, TokenAccount>, + pub recipient_token_account: InterfaceAccount, #[account( - seeds = [user_account.key().as_ref()], + seeds = [user_account.address().as_ref()], bump, )] - pub user_pda: SystemAccount<'info>, + pub user_pda: SystemAccount, - pub token_program: Interface<'info, TokenInterface>, + pub token_program: Interface<'static, TokenInterface>, } -pub fn handler(context: Context, amount: u64) -> Result<()> { +pub fn handler(context: &mut Context, amount: u64) -> Result<()> { let transfer_accounts = TransferChecked { - from: context.accounts.user_token_account.to_account_info(), - mint: context.accounts.mint.to_account_info(), - to: context.accounts.recipient_token_account.to_account_info(), - authority: context.accounts.user_pda.to_account_info(), + from: context.accounts.user_token_account.cpi_handle_mut(), + mint: context.accounts.mint.cpi_handle(), + to: context.accounts.recipient_token_account.cpi_handle_mut(), + authority: context.accounts.user_pda.cpi_handle(), }; transfer_checked( CpiContext::new_with_signer( - context.accounts.token_program.key(), + context.accounts.token_program.address(), transfer_accounts, &[&[ - context.accounts.user_account.key().as_ref(), + context.accounts.user_account.address().as_ref(), &[context.bumps.user_pda], ]], ), amount, - context.accounts.mint.decimals, + context.accounts.mint.decimals(), )?; Ok(()) diff --git a/tokens/external-delegate-token-master/anchor/programs/external-delegate-token-master/src/instructions/initialize.rs b/tokens/external-delegate-token-master/anchor/programs/external-delegate-token-master/src/instructions/initialize.rs index 12890e6e8..3b566d5ab 100644 --- a/tokens/external-delegate-token-master/anchor/programs/external-delegate-token-master/src/instructions/initialize.rs +++ b/tokens/external-delegate-token-master/anchor/programs/external-delegate-token-master/src/instructions/initialize.rs @@ -3,23 +3,23 @@ use anchor_lang::prelude::*; use crate::UserAccount; #[derive(Accounts)] -pub struct InitializeAccountConstraints<'info> { +pub struct InitializeAccountConstraints { #[account( init, payer = authority, space = UserAccount::DISCRIMINATOR.len() + UserAccount::INIT_SPACE, )] - pub user_account: Account<'info, UserAccount>, + pub user_account: BorshAccount, #[account(mut)] - pub authority: Signer<'info>, + pub authority: Signer, - pub system_program: Program<'info, System>, + pub system_program: Program, } -pub fn handler(context: Context) -> Result<()> { +pub fn handler(context: &mut Context) -> Result<()> { let user_account = &mut context.accounts.user_account; - user_account.authority = context.accounts.authority.key(); + user_account.authority = *context.accounts.authority.address(); user_account.ethereum_address = [0; 20]; user_account.nonce = 0; Ok(()) diff --git a/tokens/external-delegate-token-master/anchor/programs/external-delegate-token-master/src/instructions/set_ethereum_address.rs b/tokens/external-delegate-token-master/anchor/programs/external-delegate-token-master/src/instructions/set_ethereum_address.rs index 1967fc1b4..cb65388df 100644 --- a/tokens/external-delegate-token-master/anchor/programs/external-delegate-token-master/src/instructions/set_ethereum_address.rs +++ b/tokens/external-delegate-token-master/anchor/programs/external-delegate-token-master/src/instructions/set_ethereum_address.rs @@ -3,15 +3,15 @@ use anchor_lang::prelude::*; use crate::UserAccount; #[derive(Accounts)] -pub struct SetEthereumAddressAccountConstraints<'info> { +pub struct SetEthereumAddressAccountConstraints { #[account(mut, has_one = authority)] - pub user_account: Account<'info, UserAccount>, + pub user_account: BorshAccount, - pub authority: Signer<'info>, + pub authority: Signer, } pub fn handler( - context: Context, + context: &mut Context, ethereum_address: [u8; 20], ) -> Result<()> { let user_account = &mut context.accounts.user_account; diff --git a/tokens/external-delegate-token-master/anchor/programs/external-delegate-token-master/src/instructions/transfer_tokens.rs b/tokens/external-delegate-token-master/anchor/programs/external-delegate-token-master/src/instructions/transfer_tokens.rs index 4ab2c8631..f277837c4 100644 --- a/tokens/external-delegate-token-master/anchor/programs/external-delegate-token-master/src/instructions/transfer_tokens.rs +++ b/tokens/external-delegate-token-master/anchor/programs/external-delegate-token-master/src/instructions/transfer_tokens.rs @@ -6,43 +6,43 @@ use anchor_spl::token_interface::{ use crate::{build_transfer_authorization_message, verify_ethereum_signature, ErrorCode, UserAccount}; #[derive(Accounts)] -pub struct TransferTokensAccountConstraints<'info> { +pub struct TransferTokensAccountConstraints { #[account(mut, has_one = authority)] - pub user_account: Account<'info, UserAccount>, + pub user_account: BorshAccount, - pub authority: Signer<'info>, + pub authority: Signer, - pub mint: InterfaceAccount<'info, Mint>, + pub mint: InterfaceAccount, #[account(mut)] - pub user_token_account: InterfaceAccount<'info, TokenAccount>, + pub user_token_account: InterfaceAccount, #[account(mut)] - pub recipient_token_account: InterfaceAccount<'info, TokenAccount>, + pub recipient_token_account: InterfaceAccount, #[account( - seeds = [user_account.key().as_ref()], + seeds = [user_account.address().as_ref()], bump, )] - pub user_pda: SystemAccount<'info>, + pub user_pda: SystemAccount, - pub token_program: Interface<'info, TokenInterface>, + pub token_program: Interface<'static, TokenInterface>, } pub fn handler( - context: Context, + context: &mut Context, amount: u64, signature: [u8; 65], ) -> Result<()> { let user_account = &context.accounts.user_account; - let user_account_key = user_account.key(); + let user_account_key = user_account.address(); // Rebuild the authorized message onchain so the signature commits to // this exact transfer (amount, recipient, and the current nonce). let message = build_transfer_authorization_message( &user_account_key, amount, - &context.accounts.recipient_token_account.key(), + &context.accounts.recipient_token_account.address(), user_account.nonce, ); @@ -60,20 +60,20 @@ pub fn handler( .ok_or(ErrorCode::NonceOverflow)?; let transfer_accounts = TransferChecked { - from: context.accounts.user_token_account.to_account_info(), - mint: context.accounts.mint.to_account_info(), - to: context.accounts.recipient_token_account.to_account_info(), - authority: context.accounts.user_pda.to_account_info(), + from: context.accounts.user_token_account.cpi_handle_mut(), + mint: context.accounts.mint.cpi_handle(), + to: context.accounts.recipient_token_account.cpi_handle_mut(), + authority: context.accounts.user_pda.cpi_handle(), }; transfer_checked( CpiContext::new_with_signer( - context.accounts.token_program.key(), + context.accounts.token_program.address(), transfer_accounts, &[&[user_account_key.as_ref(), &[context.bumps.user_pda]]], ), amount, - context.accounts.mint.decimals, + context.accounts.mint.decimals(), )?; Ok(()) diff --git a/tokens/external-delegate-token-master/anchor/programs/external-delegate-token-master/src/lib.rs b/tokens/external-delegate-token-master/anchor/programs/external-delegate-token-master/src/lib.rs index 27aed7a12..e1c21713f 100644 --- a/tokens/external-delegate-token-master/anchor/programs/external-delegate-token-master/src/lib.rs +++ b/tokens/external-delegate-token-master/anchor/programs/external-delegate-token-master/src/lib.rs @@ -11,19 +11,19 @@ declare_id!("FYPkt5VWMvtyWZDMGCwoKFkE3wXTzphicTpnNGuHWVbD"); pub mod external_delegate_token_master { use super::*; - pub fn initialize(context: Context) -> Result<()> { + pub fn initialize(context: &mut Context) -> Result<()> { instructions::initialize::handler(context) } pub fn set_ethereum_address( - context: Context, + context: &mut Context, ethereum_address: [u8; 20], ) -> Result<()> { instructions::set_ethereum_address::handler(context, ethereum_address) } pub fn transfer_tokens( - context: Context, + context: &mut Context, amount: u64, signature: [u8; 65], ) -> Result<()> { @@ -31,17 +31,17 @@ pub mod external_delegate_token_master { } pub fn authority_transfer( - context: Context, + context: &mut Context, amount: u64, ) -> Result<()> { instructions::authority_transfer::handler(context, amount) } } -#[account] +#[account(borsh)] #[derive(InitSpace)] pub struct UserAccount { - pub authority: Pubkey, + pub authority: Address, pub ethereum_address: [u8; 20], /// Strictly increasing counter committed into every signed transfer /// authorization, so each Ethereum signature executes exactly once. @@ -63,9 +63,9 @@ pub enum ErrorCode { /// account's stored nonce, a signature is valid for exactly one /// (amount, recipient, nonce) execution and cannot be replayed. pub fn build_transfer_authorization_message( - user_account: &Pubkey, + user_account: &Address, amount: u64, - recipient_token_account: &Pubkey, + recipient_token_account: &Address, nonce: u64, ) -> [u8; 32] { let mut hasher = Keccak256::new(); diff --git a/tokens/nft-minter/anchor/programs/nft-minter/Cargo.toml b/tokens/nft-minter/anchor/programs/nft-minter/Cargo.toml index c721aa3d8..c528c3b77 100644 --- a/tokens/nft-minter/anchor/programs/nft-minter/Cargo.toml +++ b/tokens/nft-minter/anchor/programs/nft-minter/Cargo.toml @@ -14,14 +14,22 @@ cpi = ["no-entrypoint"] no-entrypoint = [] no-idl = [] no-log-ix-name = [] -idl-build = ["anchor-lang/idl-build", "anchor-spl/idl-build"] +idl-build = ["anchor-lang/idl-build"] anchor-debug = [] custom-heap = [] custom-panic = [] [dependencies] -anchor-lang = { version = "1.1.2", features = ["init-if-needed"] } -anchor-spl = { version = "1.1.2", features = ["metadata"] } +anchor-lang = "2.0.0-rc.1" +# The `#[program]` macro expands to `wincode` paths for instruction-data +# (de)serialization, so the crate has to be a direct dependency. +wincode = { version = "0.5", features = ["derive"] } +# anchor-lang 2.0.0-rc.1 is built against wincode 0.5, but solana-address 2.7 +# moved to wincode 0.6. With both in the graph, `Address`'s wincode impls +# belong to the version the account derives are not using and every +# `SchemaRead`/`SchemaWrite` bound fails. 2.6.1 is the last 0.5-line release. +solana-address = ">=2.6, <2.7" +anchor-spl = { version = "2.0.0-rc.1", features = ["metadata"] } [dev-dependencies] litesvm = "0.13.1" diff --git a/tokens/nft-minter/anchor/programs/nft-minter/src/lib.rs b/tokens/nft-minter/anchor/programs/nft-minter/src/lib.rs index 134a07cbb..edc362e5e 100644 --- a/tokens/nft-minter/anchor/programs/nft-minter/src/lib.rs +++ b/tokens/nft-minter/anchor/programs/nft-minter/src/lib.rs @@ -18,7 +18,7 @@ pub mod nft_minter { use super::*; pub fn mint_nft( - context: Context, + context: &mut Context, nft_name: String, nft_symbol: String, nft_uri: String, @@ -28,11 +28,11 @@ pub mod nft_minter { // Invoking the mint_to instruction on the token program mint_to( CpiContext::new( - context.accounts.token_program.key(), + context.accounts.token_program.address(), MintTo { - mint: context.accounts.mint_account.to_account_info(), - to: context.accounts.associated_token_account.to_account_info(), - authority: context.accounts.payer.to_account_info(), + mint: context.accounts.mint_account.cpi_handle_mut(), + to: context.accounts.associated_token_account.cpi_handle_mut(), + authority: context.accounts.payer.cpi_handle(), }, ), 1, @@ -43,15 +43,15 @@ pub mod nft_minter { // Invoking the create_metadata_account_v3 instruction on the token metadata program create_metadata_accounts_v3( CpiContext::new( - context.accounts.token_metadata_program.key(), + context.accounts.token_metadata_program.address(), CreateMetadataAccountsV3 { - metadata: context.accounts.metadata_account.to_account_info(), - mint: context.accounts.mint_account.to_account_info(), - mint_authority: context.accounts.payer.to_account_info(), - update_authority: context.accounts.payer.to_account_info(), - payer: context.accounts.payer.to_account_info(), - system_program: context.accounts.system_program.to_account_info(), - rent: context.accounts.rent.to_account_info(), + metadata: context.accounts.metadata_account.cpi_handle_mut(), + mint: context.accounts.mint_account.cpi_handle(), + mint_authority: context.accounts.payer.cpi_handle(), + update_authority: context.accounts.payer.cpi_handle(), + payer: context.accounts.payer.cpi_handle_mut(), + system_program: context.accounts.system_program.cpi_handle(), + update_authority_is_signer: true, }, ), DataV2 { @@ -73,17 +73,17 @@ pub mod nft_minter { // Invoking the create_master_edition_v3 instruction on the token metadata program create_master_edition_v3( CpiContext::new( - context.accounts.token_metadata_program.key(), + context.accounts.token_metadata_program.address(), CreateMasterEditionV3 { - edition: context.accounts.edition_account.to_account_info(), - mint: context.accounts.mint_account.to_account_info(), - update_authority: context.accounts.payer.to_account_info(), - mint_authority: context.accounts.payer.to_account_info(), - payer: context.accounts.payer.to_account_info(), - metadata: context.accounts.metadata_account.to_account_info(), - token_program: context.accounts.token_program.to_account_info(), - system_program: context.accounts.system_program.to_account_info(), - rent: context.accounts.rent.to_account_info(), + edition: context.accounts.edition_account.cpi_handle_mut(), + mint: context.accounts.mint_account.cpi_handle_mut(), + update_authority: context.accounts.payer.cpi_handle(), + mint_authority: context.accounts.payer.cpi_handle(), + payer: context.accounts.payer.cpi_handle_mut(), + metadata: context.accounts.metadata_account.cpi_handle_mut(), + token_program: context.accounts.token_program.cpi_handle(), + system_program: context.accounts.system_program.cpi_handle(), + update_authority_is_signer: true, }, ), None, // Max Supply @@ -96,37 +96,37 @@ pub mod nft_minter { } #[derive(Accounts)] -pub struct MintNftAccountConstraints<'info> { +pub struct MintNftAccountConstraints { #[account(mut)] - pub payer: Signer<'info>, + pub payer: Signer, /// CHECK: Validate address by deriving pda #[account( mut, - seeds = [b"metadata", token_metadata_program.key().as_ref(), mint_account.key().as_ref()], + seeds = [b"metadata", token_metadata_program.address().as_ref(), mint_account.address().as_ref()], bump, - seeds::program = token_metadata_program.key(), + seeds::program = token_metadata_program.address(), )] - pub metadata_account: UncheckedAccount<'info>, + pub metadata_account: UncheckedAccount, /// CHECK: Validate address by deriving pda #[account( mut, - seeds = [b"metadata", token_metadata_program.key().as_ref(), mint_account.key().as_ref(), b"edition"], + seeds = [b"metadata", token_metadata_program.address().as_ref(), mint_account.address().as_ref(), b"edition"], bump, - seeds::program = token_metadata_program.key(), + seeds::program = token_metadata_program.address(), )] - pub edition_account: UncheckedAccount<'info>, + pub edition_account: UncheckedAccount, // Create new mint account, NFTs have 0 decimals #[account( init, payer = payer, mint::decimals = 0, - mint::authority = payer.key(), - mint::freeze_authority = payer.key(), + mint::authority = payer, + mint::freeze_authority = payer, )] - pub mint_account: Account<'info, Mint>, + pub mint_account: Account, // Create associated token account, if needed // This is the account that will hold the NFT @@ -136,11 +136,11 @@ pub struct MintNftAccountConstraints<'info> { associated_token::mint = mint_account, associated_token::authority = payer, )] - pub associated_token_account: Account<'info, TokenAccount>, + pub associated_token_account: Account, - pub token_program: Program<'info, Token>, - pub token_metadata_program: Program<'info, Metadata>, - pub associated_token_program: Program<'info, AssociatedToken>, - pub system_program: Program<'info, System>, - pub rent: Sysvar<'info, Rent>, + pub token_program: Program, + pub token_metadata_program: Program, + pub associated_token_program: Program, + pub system_program: Program, + pub rent: Sysvar, } diff --git a/tokens/nft-operations/anchor/programs/mint-nft/Cargo.toml b/tokens/nft-operations/anchor/programs/mint-nft/Cargo.toml index e277d36a5..ed8eb4d18 100644 --- a/tokens/nft-operations/anchor/programs/mint-nft/Cargo.toml +++ b/tokens/nft-operations/anchor/programs/mint-nft/Cargo.toml @@ -14,14 +14,22 @@ no-idl = [] no-log-ix-name = [] cpi = ["no-entrypoint"] default = [] -idl-build = ["anchor-lang/idl-build", "anchor-spl/idl-build"] +idl-build = ["anchor-lang/idl-build"] anchor-debug = [] custom-heap = [] custom-panic = [] [dependencies] -anchor-lang = { version = "1.1.2", features = ["init-if-needed"] } -anchor-spl = { version = "1.1.2", features = ["metadata"] } +anchor-lang = { version = "2.0.0-rc.1", features = ["compat"] } +# The `#[program]` macro expands to `wincode` paths for instruction-data +# (de)serialization, so the crate has to be a direct dependency. +wincode = { version = "0.5", features = ["derive"] } +# anchor-lang 2.0.0-rc.1 is built against wincode 0.5, but solana-address 2.7 +# moved to wincode 0.6. With both in the graph, `Address`'s wincode impls +# belong to the version the account derives are not using and every +# `SchemaRead`/`SchemaWrite` bound fails. 2.6.1 is the last 0.5-line release. +solana-address = ">=2.6, <2.7" +anchor-spl = { version = "2.0.0-rc.1", features = ["metadata"] } [dev-dependencies] litesvm = "0.13.1" diff --git a/tokens/nft-operations/anchor/programs/mint-nft/src/instructions/create_collection.rs b/tokens/nft-operations/anchor/programs/mint-nft/src/instructions/create_collection.rs index 6fd2722cc..e4ed10d33 100644 --- a/tokens/nft-operations/anchor/programs/mint-nft/src/instructions/create_collection.rs +++ b/tokens/nft-operations/anchor/programs/mint-nft/src/instructions/create_collection.rs @@ -1,4 +1,5 @@ use anchor_lang::prelude::*; +use anchor_spl::mint; use anchor_spl::{ associated_token::AssociatedToken, metadata::Metadata, @@ -17,9 +18,9 @@ use anchor_spl::metadata::mpl_token_metadata::{ use super::validate_metadata_strings; #[derive(Accounts)] -pub struct CreateCollectionAccountConstraints<'info> { +pub struct CreateCollectionAccountConstraints { #[account(mut)] - user: Signer<'info>, + user: Signer, #[account( init, @@ -28,22 +29,22 @@ pub struct CreateCollectionAccountConstraints<'info> { mint::authority = mint_authority, mint::freeze_authority = mint_authority, )] - mint: Account<'info, Mint>, + mint: Account, #[account( seeds = [b"authority"], bump, )] /// CHECK: This account is not initialized and is being used for signing purposes only - pub mint_authority: UncheckedAccount<'info>, + pub mint_authority: UncheckedAccount, #[account(mut)] /// CHECK: This account will be initialized by the metaplex program - metadata: UncheckedAccount<'info>, + metadata: UncheckedAccount, #[account(mut)] /// CHECK: This account will be initialized by the metaplex program - master_edition: UncheckedAccount<'info>, + master_edition: UncheckedAccount, #[account( init, @@ -51,12 +52,12 @@ pub struct CreateCollectionAccountConstraints<'info> { associated_token::mint = mint, associated_token::authority = user )] - destination: Account<'info, TokenAccount>, + destination: Account, - system_program: Program<'info, System>, - token_program: Program<'info, Token>, - associated_token_program: Program<'info, AssociatedToken>, - token_metadata_program: Program<'info, Metadata>, + system_program: Program, + token_program: Program, + associated_token_program: Program, + token_metadata_program: Program, } /// Creates a collection NFT with caller-supplied metadata. @@ -72,30 +73,30 @@ pub fn handle_create_collection( ) -> Result<()> { validate_metadata_strings(&name, &symbol, &uri)?; - let metadata = &accounts.metadata.to_account_info(); - let master_edition = &accounts.master_edition.to_account_info(); - let mint = &accounts.mint.to_account_info(); - let authority = &accounts.mint_authority.to_account_info(); - let payer = &accounts.user.to_account_info(); - let system_program = &accounts.system_program.to_account_info(); - let spl_token_program = &accounts.token_program.to_account_info(); - let spl_metadata_program = &accounts.token_metadata_program.to_account_info(); + let metadata = &accounts.metadata.cpi_handle_mut(); + let master_edition = &accounts.master_edition.cpi_handle_mut(); + let mint = &accounts.mint.cpi_handle_mut(); + let authority = &accounts.mint_authority.cpi_handle_mut(); + let payer = &accounts.user.cpi_handle_mut(); + let system_program = &accounts.system_program.cpi_handle_mut(); + let spl_token_program = &accounts.token_program.cpi_handle_mut(); + let spl_metadata_program = &accounts.token_metadata_program.cpi_handle_mut(); let seeds = &[&b"authority"[..], &[bumps.mint_authority]]; let signer_seeds = &[&seeds[..]]; let cpi_accounts = MintTo { - mint: accounts.mint.to_account_info(), - to: accounts.destination.to_account_info(), - authority: accounts.mint_authority.to_account_info(), + mint: accounts.mint.cpi_handle_mut(), + to: accounts.destination.cpi_handle_mut(), + authority: accounts.mint_authority.cpi_handle(), }; let cpi_ctx = - CpiContext::new_with_signer(accounts.token_program.key(), cpi_accounts, signer_seeds); + CpiContext::new_with_signer(accounts.token_program.address(), cpi_accounts, signer_seeds); mint_to(cpi_ctx, 1)?; msg!("Collection NFT minted!"); let creator = vec![Creator { - address: accounts.mint_authority.key(), + address: *accounts.mint_authority.address(), verified: true, share: 100, }]; diff --git a/tokens/nft-operations/anchor/programs/mint-nft/src/instructions/mint_nft.rs b/tokens/nft-operations/anchor/programs/mint-nft/src/instructions/mint_nft.rs index 04023aada..77caa0b43 100644 --- a/tokens/nft-operations/anchor/programs/mint-nft/src/instructions/mint_nft.rs +++ b/tokens/nft-operations/anchor/programs/mint-nft/src/instructions/mint_nft.rs @@ -1,4 +1,5 @@ use anchor_lang::prelude::*; +use anchor_spl::mint; use anchor_spl::{ associated_token::AssociatedToken, metadata::Metadata, @@ -17,9 +18,9 @@ use anchor_spl::metadata::mpl_token_metadata::{ use super::validate_metadata_strings; #[derive(Accounts)] -pub struct MintNftAccountConstraints<'info> { +pub struct MintNftAccountConstraints { #[account(mut)] - pub owner: Signer<'info>, + pub owner: Signer, #[account( init, @@ -28,7 +29,7 @@ pub struct MintNftAccountConstraints<'info> { mint::authority = mint_authority, mint::freeze_authority = mint_authority, )] - pub mint: Account<'info, Mint>, + pub mint: Account, #[account( init, @@ -36,30 +37,30 @@ pub struct MintNftAccountConstraints<'info> { associated_token::mint = mint, associated_token::authority = owner )] - pub destination: Account<'info, TokenAccount>, + pub destination: Account, #[account(mut)] /// CHECK: This account will be initialized by the metaplex program - pub metadata: UncheckedAccount<'info>, + pub metadata: UncheckedAccount, #[account(mut)] /// CHECK: This account will be initialized by the metaplex program - pub master_edition: UncheckedAccount<'info>, + pub master_edition: UncheckedAccount, #[account( seeds = [b"authority"], bump, )] /// CHECK: This is account is not initialized and is being used for signing purposes only - pub mint_authority: UncheckedAccount<'info>, + pub mint_authority: UncheckedAccount, #[account(mut)] - pub collection_mint: Account<'info, Mint>, + pub collection_mint: Account, - 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>, + pub system_program: Program, + pub token_program: Program, + pub associated_token_program: Program, + pub token_metadata_program: Program, } /// Mints an NFT into the collection with caller-supplied metadata. @@ -76,30 +77,30 @@ pub fn handle_mint_nft( ) -> Result<()> { validate_metadata_strings(&name, &symbol, &uri)?; - let metadata = &accounts.metadata.to_account_info(); - let master_edition = &accounts.master_edition.to_account_info(); - let mint = &accounts.mint.to_account_info(); - let authority = &accounts.mint_authority.to_account_info(); - let payer = &accounts.owner.to_account_info(); - let system_program = &accounts.system_program.to_account_info(); - let spl_token_program = &accounts.token_program.to_account_info(); - let spl_metadata_program = &accounts.token_metadata_program.to_account_info(); + let metadata = &accounts.metadata.cpi_handle_mut(); + let master_edition = &accounts.master_edition.cpi_handle_mut(); + let mint = &accounts.mint.cpi_handle_mut(); + let authority = &accounts.mint_authority.cpi_handle_mut(); + let payer = &accounts.owner.cpi_handle_mut(); + let system_program = &accounts.system_program.cpi_handle_mut(); + let spl_token_program = &accounts.token_program.cpi_handle_mut(); + let spl_metadata_program = &accounts.token_metadata_program.cpi_handle_mut(); let seeds = &[&b"authority"[..], &[bumps.mint_authority]]; let signer_seeds = &[&seeds[..]]; let cpi_accounts = MintTo { - mint: accounts.mint.to_account_info(), - to: accounts.destination.to_account_info(), - authority: accounts.mint_authority.to_account_info(), + mint: accounts.mint.cpi_handle_mut(), + to: accounts.destination.cpi_handle_mut(), + authority: accounts.mint_authority.cpi_handle(), }; let cpi_ctx = - CpiContext::new_with_signer(accounts.token_program.key(), cpi_accounts, signer_seeds); + CpiContext::new_with_signer(accounts.token_program.address(), cpi_accounts, signer_seeds); mint_to(cpi_ctx, 1)?; msg!("NFT minted!"); let creator = vec![Creator { - address: accounts.mint_authority.key(), + address: *accounts.mint_authority.address(), verified: true, share: 100, }]; @@ -124,7 +125,7 @@ pub fn handle_mint_nft( creators: Some(creator), collection: Some(Collection { verified: false, - key: accounts.collection_mint.key(), + key: *accounts.collection_mint.address(), }), uses: None, }, diff --git a/tokens/nft-operations/anchor/programs/mint-nft/src/instructions/verify_collection.rs b/tokens/nft-operations/anchor/programs/mint-nft/src/instructions/verify_collection.rs index 4748ec254..667b243db 100644 --- a/tokens/nft-operations/anchor/programs/mint-nft/src/instructions/verify_collection.rs +++ b/tokens/nft-operations/anchor/programs/mint-nft/src/instructions/verify_collection.rs @@ -13,43 +13,43 @@ use anchor_spl::{ metadata::Metadata, }; // In Anchor 1.0, sysvar::instructions::ID moved - use the well-known address directly -const INSTRUCTIONS_SYSVAR_ID: Pubkey = anchor_lang::solana_program::pubkey::pubkey!("Sysvar1nstructions1111111111111111111111111"); +const INSTRUCTIONS_SYSVAR_ID: Address = anchor_lang::solana_program::pubkey::pubkey!("Sysvar1nstructions1111111111111111111111111"); #[derive(Accounts)] -pub struct VerifyCollectionMintAccountConstraints<'info> { - pub authority: Signer<'info>, +pub struct VerifyCollectionMintAccountConstraints { + pub authority: Signer, #[account(mut)] - pub metadata: Account<'info, MetadataAccount>, - pub mint: Account<'info, Mint>, + pub metadata: Account, + pub mint: Account, #[account( seeds = [b"authority"], bump, )] /// CHECK: This account is not initialized and is being used for signing purposes only - pub mint_authority: UncheckedAccount<'info>, - pub collection_mint: Account<'info, Mint>, + pub mint_authority: UncheckedAccount, + pub collection_mint: Account, #[account(mut)] - pub collection_metadata: Account<'info, MetadataAccount>, - pub collection_master_edition: Account<'info, MasterEditionAccount>, - pub system_program: Program<'info, System>, + pub collection_metadata: Account, + pub collection_master_edition: Account, + pub system_program: Program, #[account(address = INSTRUCTIONS_SYSVAR_ID)] /// CHECK: Sysvar instruction account that is being checked with an address constraint - pub sysvar_instruction: UncheckedAccount<'info>, - pub token_metadata_program: Program<'info, Metadata>, + pub sysvar_instruction: UncheckedAccount, + pub token_metadata_program: Program, } pub fn handle_verify_collection( accounts: &mut VerifyCollectionMintAccountConstraints, bumps: &VerifyCollectionMintAccountConstraintsBumps, ) -> Result<()> { - let metadata = &accounts.metadata.to_account_info(); - let authority = &accounts.mint_authority.to_account_info(); - let collection_mint = &accounts.collection_mint.to_account_info(); - let collection_metadata = &accounts.collection_metadata.to_account_info(); - let collection_master_edition = &accounts.collection_master_edition.to_account_info(); - let system_program = &accounts.system_program.to_account_info(); - let sysvar_instructions = &accounts.sysvar_instruction.to_account_info(); - let spl_metadata_program = &accounts.token_metadata_program.to_account_info(); + let metadata = &accounts.metadata.cpi_handle_mut(); + let authority = &accounts.mint_authority.cpi_handle_mut(); + let collection_mint = &accounts.collection_mint.cpi_handle_mut(); + let collection_metadata = &accounts.collection_metadata.cpi_handle_mut(); + let collection_master_edition = &accounts.collection_master_edition.cpi_handle_mut(); + let system_program = &accounts.system_program.cpi_handle_mut(); + let sysvar_instructions = &accounts.sysvar_instruction.cpi_handle_mut(); + let spl_metadata_program = &accounts.token_metadata_program.cpi_handle_mut(); let seeds = &[ &b"authority"[..], diff --git a/tokens/nft-operations/anchor/programs/mint-nft/src/lib.rs b/tokens/nft-operations/anchor/programs/mint-nft/src/lib.rs index 2e1c3eac5..a26577160 100644 --- a/tokens/nft-operations/anchor/programs/mint-nft/src/lib.rs +++ b/tokens/nft-operations/anchor/programs/mint-nft/src/lib.rs @@ -14,7 +14,7 @@ pub mod mint_nft { /// Create a collection NFT with the given metadata. pub fn create_collection( - mut context: Context, + mut context: &mut Context, name: String, symbol: String, uri: String, @@ -30,7 +30,7 @@ pub mod mint_nft { /// Mint an NFT into the collection with the given metadata. pub fn mint_nft( - mut context: Context, + mut context: &mut Context, name: String, symbol: String, uri: String, @@ -40,7 +40,7 @@ pub mod mint_nft { /// Verify an NFT as a member of the collection. pub fn verify_collection( - mut context: Context, + mut context: &mut Context, ) -> Result<()> { instructions::verify_collection::handle_verify_collection( &mut context.accounts, diff --git a/tokens/pda-mint-authority/anchor/programs/token-minter/Cargo.toml b/tokens/pda-mint-authority/anchor/programs/token-minter/Cargo.toml index 42928e358..3e2b487ad 100644 --- a/tokens/pda-mint-authority/anchor/programs/token-minter/Cargo.toml +++ b/tokens/pda-mint-authority/anchor/programs/token-minter/Cargo.toml @@ -14,14 +14,22 @@ cpi = ["no-entrypoint"] no-entrypoint = [] no-idl = [] no-log-ix-name = [] -idl-build = ["anchor-lang/idl-build", "anchor-spl/idl-build"] +idl-build = ["anchor-lang/idl-build"] anchor-debug = [] custom-heap = [] custom-panic = [] [dependencies] -anchor-lang = { version = "1.1.2", features = ["init-if-needed"] } -anchor-spl = { version = "1.1.2", features = ["metadata"] } +anchor-lang = "2.0.0-rc.1" +# The `#[program]` macro expands to `wincode` paths for instruction-data +# (de)serialization, so the crate has to be a direct dependency. +wincode = { version = "0.5", features = ["derive"] } +# anchor-lang 2.0.0-rc.1 is built against wincode 0.5, but solana-address 2.7 +# moved to wincode 0.6. With both in the graph, `Address`'s wincode impls +# belong to the version the account derives are not using and every +# `SchemaRead`/`SchemaWrite` bound fails. 2.6.1 is the last 0.5-line release. +solana-address = ">=2.6, <2.7" +anchor-spl = { version = "2.0.0-rc.1", features = ["metadata"] } [dev-dependencies] litesvm = "0.13.1" diff --git a/tokens/pda-mint-authority/anchor/programs/token-minter/src/instructions/create.rs b/tokens/pda-mint-authority/anchor/programs/token-minter/src/instructions/create.rs index 75891fe9f..21b0f275f 100644 --- a/tokens/pda-mint-authority/anchor/programs/token-minter/src/instructions/create.rs +++ b/tokens/pda-mint-authority/anchor/programs/token-minter/src/instructions/create.rs @@ -7,14 +7,15 @@ use { create_metadata_accounts_v3, mpl_token_metadata::types::DataV2, CreateMetadataAccountsV3, Metadata, }, - token::{Mint, Token}, + mint::{self, Mint}, + token::{self, Token}, }, }; #[derive(Accounts)] -pub struct CreateTokenAccountConstraints<'info> { +pub struct CreateTokenAccountConstraints { #[account(mut)] - pub payer: Signer<'info>, + pub payer: Signer, // Create mint account // Same PDA as address of the account and mint/freeze authority @@ -24,29 +25,29 @@ pub struct CreateTokenAccountConstraints<'info> { bump, payer = payer, mint::decimals = 9, - mint::authority = mint_account.key(), - mint::freeze_authority = mint_account.key(), + mint::authority = mint_account, + mint::freeze_authority = mint_account, )] - pub mint_account: Account<'info, Mint>, + pub mint_account: Account, /// CHECK: Validate address by deriving pda #[account( mut, - seeds = [b"metadata", token_metadata_program.key().as_ref(), mint_account.key().as_ref()], + seeds = [b"metadata", token_metadata_program.address().as_ref(), mint_account.address().as_ref()], bump, - seeds::program = token_metadata_program.key(), + seeds::program = token_metadata_program.address(), )] - pub metadata_account: UncheckedAccount<'info>, + pub metadata_account: UncheckedAccount, - pub token_program: Program<'info, Token>, - pub token_metadata_program: Program<'info, Metadata>, - pub system_program: Program<'info, System>, - pub rent: Sysvar<'info, Rent>, + pub token_program: Program, + pub token_metadata_program: Program, + pub system_program: Program, + pub rent: Sysvar, } pub fn handle_create_token( - context: Context, + context: &mut Context, token_name: String, token_symbol: String, token_uri: String, @@ -60,15 +61,15 @@ pub fn handle_create_token( // Invoking the create_metadata_account_v3 instruction on the token metadata program create_metadata_accounts_v3( CpiContext::new( - context.accounts.token_metadata_program.key(), + context.accounts.token_metadata_program.address(), CreateMetadataAccountsV3 { - metadata: context.accounts.metadata_account.to_account_info(), - mint: context.accounts.mint_account.to_account_info(), - mint_authority: context.accounts.mint_account.to_account_info(), // PDA is mint authority - update_authority: context.accounts.mint_account.to_account_info(), // PDA is update authority - payer: context.accounts.payer.to_account_info(), - system_program: context.accounts.system_program.to_account_info(), - rent: context.accounts.rent.to_account_info(), + metadata: context.accounts.metadata_account.cpi_handle_mut(), + mint: context.accounts.mint_account.cpi_handle(), + mint_authority: context.accounts.mint_account.cpi_handle(), // PDA is mint authority + update_authority: context.accounts.mint_account.cpi_handle(), // PDA is update authority + payer: context.accounts.payer.cpi_handle_mut(), + system_program: context.accounts.system_program.cpi_handle(), + update_authority_is_signer: true, }, ) .with_signer(signer_seeds), diff --git a/tokens/pda-mint-authority/anchor/programs/token-minter/src/instructions/mint.rs b/tokens/pda-mint-authority/anchor/programs/token-minter/src/instructions/mint.rs index eedae32c9..e5fe9b33a 100644 --- a/tokens/pda-mint-authority/anchor/programs/token-minter/src/instructions/mint.rs +++ b/tokens/pda-mint-authority/anchor/programs/token-minter/src/instructions/mint.rs @@ -7,9 +7,9 @@ use { }; #[derive(Accounts)] -pub struct MintTokenAccountConstraints<'info> { +pub struct MintTokenAccountConstraints { #[account(mut)] - pub payer: Signer<'info>, + pub payer: Signer, // Mint account address is a PDA #[account( @@ -17,7 +17,7 @@ pub struct MintTokenAccountConstraints<'info> { seeds = [b"mint"], bump )] - pub mint_account: Account<'info, Mint>, + pub mint_account: Account, // Create Associated Token Account, if needed // This is the account that will hold the minted tokens @@ -27,11 +27,11 @@ pub struct MintTokenAccountConstraints<'info> { associated_token::mint = mint_account, associated_token::authority = payer, )] - pub associated_token_account: Account<'info, TokenAccount>, + pub associated_token_account: Account, - pub token_program: Program<'info, Token>, - pub associated_token_program: Program<'info, AssociatedToken>, - pub system_program: Program<'info, System>, + pub token_program: Program, + pub associated_token_program: Program, + pub system_program: Program, } /// Mints `amount` tokens to the payer's associated token account, signed by @@ -41,14 +41,14 @@ pub struct MintTokenAccountConstraints<'info> { /// on). Clients convert from major units, e.g. 1 token with 9 decimals is /// `1 * 10u64.pow(9)` minor units. pub fn handle_mint_token( - context: Context, + context: &mut Context, amount: u64, ) -> Result<()> { msg!("Minting token to associated token account..."); - msg!("Mint: {}", &context.accounts.mint_account.key()); + msg!("Mint: {}", &context.accounts.mint_account.address()); msg!( "Token Address: {}", - &context.accounts.associated_token_account.key() + &context.accounts.associated_token_account.address() ); // PDA signer seeds @@ -57,11 +57,11 @@ pub fn handle_mint_token( // Invoke the mint_to instruction on the token program mint_to( CpiContext::new( - context.accounts.token_program.key(), + context.accounts.token_program.address(), MintTo { - mint: context.accounts.mint_account.to_account_info(), - to: context.accounts.associated_token_account.to_account_info(), - authority: context.accounts.mint_account.to_account_info(), // PDA mint authority, required as signer + mint: context.accounts.mint_account.cpi_handle_mut(), + to: context.accounts.associated_token_account.cpi_handle_mut(), + authority: context.accounts.mint_account.cpi_handle(), // PDA mint authority, required as signer }, ) .with_signer(signer_seeds), // using PDA to sign diff --git a/tokens/pda-mint-authority/anchor/programs/token-minter/src/lib.rs b/tokens/pda-mint-authority/anchor/programs/token-minter/src/lib.rs index fb5ba6795..d60398ee3 100644 --- a/tokens/pda-mint-authority/anchor/programs/token-minter/src/lib.rs +++ b/tokens/pda-mint-authority/anchor/programs/token-minter/src/lib.rs @@ -9,7 +9,7 @@ pub mod token_minter { use super::*; pub fn create_token( - context: Context, + context: &mut Context, token_name: String, token_symbol: String, token_uri: String, @@ -19,7 +19,7 @@ pub mod token_minter { /// Mint `amount` minor units of the token to the payer. pub fn mint_token( - context: Context, + context: &mut Context, amount: u64, ) -> Result<()> { mint::handle_mint_token(context, amount) diff --git a/tokens/token-extensions/basics/anchor/programs/basics/Cargo.toml b/tokens/token-extensions/basics/anchor/programs/basics/Cargo.toml index fff167965..595ef823e 100644 --- a/tokens/token-extensions/basics/anchor/programs/basics/Cargo.toml +++ b/tokens/token-extensions/basics/anchor/programs/basics/Cargo.toml @@ -14,14 +14,22 @@ cpi = ["no-entrypoint"] no-entrypoint = [] no-idl = [] no-log-ix-name = [] -idl-build = ["anchor-lang/idl-build", "anchor-spl/idl-build"] +idl-build = ["anchor-lang/idl-build"] anchor-debug = [] custom-heap = [] custom-panic = [] [dependencies] -anchor-spl = "1.1.2" -anchor-lang = { version = "1.1.2", features= ["init-if-needed"]} +anchor-spl = "2.0.0-rc.1" +anchor-lang = "2.0.0-rc.1" +# The `#[program]` macro expands to `wincode` paths for instruction-data +# (de)serialization, so the crate has to be a direct dependency. +wincode = { version = "0.5", features = ["derive"] } +# anchor-lang 2.0.0-rc.1 is built against wincode 0.5, but solana-address 2.7 +# moved to wincode 0.6. With both in the graph, `Address`'s wincode impls +# belong to the version the account derives are not using and every +# `SchemaRead`/`SchemaWrite` bound fails. 2.6.1 is the last 0.5-line release. +solana-address = ">=2.6, <2.7" [dev-dependencies] litesvm = "0.13.1" diff --git a/tokens/token-extensions/basics/anchor/programs/basics/src/instructions/create_associated_token_account.rs b/tokens/token-extensions/basics/anchor/programs/basics/src/instructions/create_associated_token_account.rs index 9c00f4be1..b28a32b14 100644 --- a/tokens/token-extensions/basics/anchor/programs/basics/src/instructions/create_associated_token_account.rs +++ b/tokens/token-extensions/basics/anchor/programs/basics/src/instructions/create_associated_token_account.rs @@ -3,23 +3,23 @@ use anchor_spl::associated_token::AssociatedToken; use anchor_spl::token_interface::{Mint, TokenAccount, TokenInterface}; #[derive(Accounts)] -pub struct CreateAssociatedTokenAccountAccountConstraints<'info> { +pub struct CreateAssociatedTokenAccountAccountConstraints { #[account(mut)] - pub signer: Signer<'info>, - pub mint: InterfaceAccount<'info, Mint>, + pub signer: Signer, + pub mint: InterfaceAccount, #[account( init, associated_token::mint = mint, payer = signer, associated_token::authority = signer, )] - pub token_account: InterfaceAccount<'info, TokenAccount>, - pub system_program: Program<'info, System>, - pub token_program: Interface<'info, TokenInterface>, - pub associated_token_program: Program<'info, AssociatedToken>, + pub token_account: InterfaceAccount, + pub system_program: Program, + pub token_program: Interface<'static, TokenInterface>, + pub associated_token_program: Program, } -pub fn handler(_context: Context) -> Result<()> { +pub fn handler(_context: &mut Context) -> Result<()> { msg!("Create Associated Token Account"); Ok(()) } diff --git a/tokens/token-extensions/basics/anchor/programs/basics/src/instructions/create_token.rs b/tokens/token-extensions/basics/anchor/programs/basics/src/instructions/create_token.rs index bc08105ad..9e0f6707d 100644 --- a/tokens/token-extensions/basics/anchor/programs/basics/src/instructions/create_token.rs +++ b/tokens/token-extensions/basics/anchor/programs/basics/src/instructions/create_token.rs @@ -1,25 +1,26 @@ use anchor_lang::prelude::*; +use anchor_spl::mint; use anchor_spl::token_interface::{Mint, TokenInterface}; #[derive(Accounts)] #[instruction(token_name: String)] -pub struct CreateTokenAccountConstraints<'info> { +pub struct CreateTokenAccountConstraints { #[account(mut)] - pub signer: Signer<'info>, + pub signer: Signer, #[account( init, payer = signer, mint::decimals = 6, - mint::authority = signer.key(), - seeds = [b"token-2022-token", signer.key().as_ref(), token_name.as_bytes()], + mint::authority = signer, + seeds = [b"token-2022-token", signer.address().as_ref(), token_name.as_bytes()], bump, )] - pub mint: InterfaceAccount<'info, Mint>, - pub system_program: Program<'info, System>, - pub token_program: Interface<'info, TokenInterface>, + pub mint: InterfaceAccount, + pub system_program: Program, + pub token_program: Interface<'static, TokenInterface>, } -pub fn handler(_context: Context, _token_name: String) -> Result<()> { +pub fn handler(_context: &mut Context, _token_name: String) -> Result<()> { msg!("Create Token"); Ok(()) } diff --git a/tokens/token-extensions/basics/anchor/programs/basics/src/instructions/create_token_account.rs b/tokens/token-extensions/basics/anchor/programs/basics/src/instructions/create_token_account.rs index d5e87541e..cfe3976f8 100644 --- a/tokens/token-extensions/basics/anchor/programs/basics/src/instructions/create_token_account.rs +++ b/tokens/token-extensions/basics/anchor/programs/basics/src/instructions/create_token_account.rs @@ -1,25 +1,26 @@ use anchor_lang::prelude::*; +use anchor_spl::token; use anchor_spl::token_interface::{Mint, TokenAccount, TokenInterface}; #[derive(Accounts)] -pub struct CreateTokenAccountAccountConstraints<'info> { +pub struct CreateTokenAccountAccountConstraints { #[account(mut)] - pub signer: Signer<'info>, - pub mint: InterfaceAccount<'info, Mint>, + pub signer: Signer, + pub mint: InterfaceAccount, #[account( init, token::mint = mint, token::authority = signer, payer = signer, - seeds = [b"token-2022-token-account", signer.key().as_ref(), mint.key().as_ref()], + seeds = [b"token-2022-token-account", signer.address().as_ref(), mint.address().as_ref()], bump, )] - pub token_account: InterfaceAccount<'info, TokenAccount>, - pub system_program: Program<'info, System>, - pub token_program: Interface<'info, TokenInterface>, + pub token_account: InterfaceAccount, + pub system_program: Program, + pub token_program: Interface<'static, TokenInterface>, } -pub fn handler(_context: Context) -> Result<()> { +pub fn handler(_context: &mut Context) -> Result<()> { msg!("Create Token Account"); Ok(()) } diff --git a/tokens/token-extensions/basics/anchor/programs/basics/src/instructions/mint_token.rs b/tokens/token-extensions/basics/anchor/programs/basics/src/instructions/mint_token.rs index 26798f219..3d5f249fd 100644 --- a/tokens/token-extensions/basics/anchor/programs/basics/src/instructions/mint_token.rs +++ b/tokens/token-extensions/basics/anchor/programs/basics/src/instructions/mint_token.rs @@ -2,23 +2,23 @@ use anchor_lang::prelude::*; use anchor_spl::token_interface::{self, Mint, MintTo, TokenAccount, TokenInterface}; #[derive(Accounts)] -pub struct MintTokenAccountConstraints<'info> { +pub struct MintTokenAccountConstraints { #[account(mut)] - pub signer: Signer<'info>, + pub signer: Signer, #[account(mut)] - pub mint: InterfaceAccount<'info, Mint>, + pub mint: InterfaceAccount, #[account(mut)] - pub receiver: InterfaceAccount<'info, TokenAccount>, - pub token_program: Interface<'info, TokenInterface>, + pub receiver: InterfaceAccount, + pub token_program: Interface<'static, TokenInterface>, } -pub fn handler(context: Context, amount: u64) -> Result<()> { +pub fn handler(context: &mut Context, amount: u64) -> Result<()> { let cpi_accounts = MintTo { - mint: context.accounts.mint.to_account_info().clone(), - to: context.accounts.receiver.to_account_info().clone(), - authority: context.accounts.signer.to_account_info(), + mint: context.accounts.mint.cpi_handle_mut().clone(), + to: context.accounts.receiver.cpi_handle_mut().clone(), + authority: context.accounts.signer.cpi_handle(), }; - let cpi_program = context.accounts.token_program.key(); + let cpi_program = context.accounts.token_program.address(); let cpi_context = CpiContext::new(cpi_program, cpi_accounts); token_interface::mint_to(cpi_context, amount)?; msg!("Mint Token"); diff --git a/tokens/token-extensions/basics/anchor/programs/basics/src/instructions/transfer_token.rs b/tokens/token-extensions/basics/anchor/programs/basics/src/instructions/transfer_token.rs index 57ce4168a..10c9518f6 100644 --- a/tokens/token-extensions/basics/anchor/programs/basics/src/instructions/transfer_token.rs +++ b/tokens/token-extensions/basics/anchor/programs/basics/src/instructions/transfer_token.rs @@ -3,36 +3,36 @@ use anchor_spl::associated_token::AssociatedToken; use anchor_spl::token_interface::{self, Mint, TokenAccount, TokenInterface, TransferChecked}; #[derive(Accounts)] -pub struct TransferTokenAccountConstraints<'info> { +pub struct TransferTokenAccountConstraints { #[account(mut)] - pub signer: Signer<'info>, + pub signer: Signer, #[account(mut)] - pub from: InterfaceAccount<'info, TokenAccount>, - pub to: SystemAccount<'info>, + pub from: InterfaceAccount, + pub to: SystemAccount, #[account( init, associated_token::mint = mint, payer = signer, associated_token::authority = to )] - pub to_ata: InterfaceAccount<'info, TokenAccount>, + pub to_ata: InterfaceAccount, #[account(mut)] - pub mint: InterfaceAccount<'info, Mint>, - pub token_program: Interface<'info, TokenInterface>, - pub system_program: Program<'info, System>, - pub associated_token_program: Program<'info, AssociatedToken>, + pub mint: InterfaceAccount, + pub token_program: Interface<'static, TokenInterface>, + pub system_program: Program, + pub associated_token_program: Program, } -pub fn handler(context: Context, amount: u64) -> Result<()> { +pub fn handler(context: &mut Context, amount: u64) -> Result<()> { let cpi_accounts = TransferChecked { - from: context.accounts.from.to_account_info().clone(), - mint: context.accounts.mint.to_account_info().clone(), - to: context.accounts.to_ata.to_account_info().clone(), - authority: context.accounts.signer.to_account_info(), + from: context.accounts.from.cpi_handle_mut().clone(), + mint: context.accounts.mint.cpi_handle().clone(), + to: context.accounts.to_ata.cpi_handle_mut().clone(), + authority: context.accounts.signer.cpi_handle(), }; - let cpi_program = context.accounts.token_program.key(); + let cpi_program = context.accounts.token_program.address(); let cpi_context = CpiContext::new(cpi_program, cpi_accounts); - token_interface::transfer_checked(cpi_context, amount, context.accounts.mint.decimals)?; + token_interface::transfer_checked(cpi_context, amount, context.accounts.mint.decimals())?; msg!("Transfer Token"); Ok(()) } diff --git a/tokens/token-extensions/basics/anchor/programs/basics/src/lib.rs b/tokens/token-extensions/basics/anchor/programs/basics/src/lib.rs index 0eb3bbf0b..8c86d1db5 100644 --- a/tokens/token-extensions/basics/anchor/programs/basics/src/lib.rs +++ b/tokens/token-extensions/basics/anchor/programs/basics/src/lib.rs @@ -10,25 +10,25 @@ pub mod anchor { use super::*; - pub fn create_token(context: Context, token_name: String) -> Result<()> { + pub fn create_token(context: &mut Context, token_name: String) -> Result<()> { instructions::create_token::handler(context, token_name) } - pub fn create_token_account(context: Context) -> Result<()> { + pub fn create_token_account(context: &mut Context) -> Result<()> { instructions::create_token_account::handler(context) } pub fn create_associated_token_account( - context: Context, + context: &mut Context, ) -> Result<()> { instructions::create_associated_token_account::handler(context) } - pub fn transfer_token(context: Context, amount: u64) -> Result<()> { + pub fn transfer_token(context: &mut Context, amount: u64) -> Result<()> { instructions::transfer_token::handler(context, amount) } - pub fn mint_token(context: Context, amount: u64) -> Result<()> { + pub fn mint_token(context: &mut Context, amount: u64) -> Result<()> { instructions::mint_token::handler(context, amount) } } diff --git a/tokens/token-extensions/cpi-guard/anchor/programs/cpi-guard/Cargo.toml b/tokens/token-extensions/cpi-guard/anchor/programs/cpi-guard/Cargo.toml index 6f37da297..b684815f9 100644 --- a/tokens/token-extensions/cpi-guard/anchor/programs/cpi-guard/Cargo.toml +++ b/tokens/token-extensions/cpi-guard/anchor/programs/cpi-guard/Cargo.toml @@ -14,14 +14,22 @@ cpi = ["no-entrypoint"] no-entrypoint = [] no-idl = [] no-log-ix-name = [] -idl-build = ["anchor-lang/idl-build", "anchor-spl/idl-build"] +idl-build = ["anchor-lang/idl-build"] anchor-debug = [] custom-heap = [] custom-panic = [] [dependencies] -anchor-lang = { version = "1.1.2", features = ["init-if-needed"] } -anchor-spl = "1.1.2" +anchor-lang = "2.0.0-rc.1" +# The `#[program]` macro expands to `wincode` paths for instruction-data +# (de)serialization, so the crate has to be a direct dependency. +wincode = { version = "0.5", features = ["derive"] } +# anchor-lang 2.0.0-rc.1 is built against wincode 0.5, but solana-address 2.7 +# moved to wincode 0.6. With both in the graph, `Address`'s wincode impls +# belong to the version the account derives are not using and every +# `SchemaRead`/`SchemaWrite` bound fails. 2.6.1 is the last 0.5-line release. +solana-address = ">=2.6, <2.7" +anchor-spl = "2.0.0-rc.1" [dev-dependencies] litesvm = "0.13.1" diff --git a/tokens/token-extensions/cpi-guard/anchor/programs/cpi-guard/src/lib.rs b/tokens/token-extensions/cpi-guard/anchor/programs/cpi-guard/src/lib.rs index 23f51a37f..00396949b 100644 --- a/tokens/token-extensions/cpi-guard/anchor/programs/cpi-guard/src/lib.rs +++ b/tokens/token-extensions/cpi-guard/anchor/programs/cpi-guard/src/lib.rs @@ -1,4 +1,5 @@ use anchor_lang::prelude::*; +use anchor_spl::token; use anchor_spl::{ token_2022::{transfer_checked, TransferChecked}, token_interface::{Mint, Token2022, TokenAccount}, @@ -12,34 +13,34 @@ declare_id!("6tU3MEowU6oxxeDZLSxEwzcEZsZrhBJsfUR6xECvShid"); pub mod cpi_guard { use super::*; - pub fn cpi_transfer(context: Context) -> Result<()> { + pub fn cpi_transfer(context: &mut Context) -> Result<()> { transfer_checked( CpiContext::new( - context.accounts.token_program.key(), + context.accounts.token_program.address(), TransferChecked { - from: context.accounts.sender_token_account.to_account_info(), - mint: context.accounts.mint_account.to_account_info(), - to: context.accounts.recipient_token_account.to_account_info(), - authority: context.accounts.sender.to_account_info(), + from: context.accounts.sender_token_account.cpi_handle_mut(), + mint: context.accounts.mint_account.cpi_handle(), + to: context.accounts.recipient_token_account.cpi_handle_mut(), + authority: context.accounts.sender.cpi_handle(), }, ), 1, - context.accounts.mint_account.decimals, + context.accounts.mint_account.decimals(), )?; Ok(()) } } #[derive(Accounts)] -pub struct CpiTransferAccountConstraints<'info> { +pub struct CpiTransferAccountConstraints { #[account(mut)] - pub sender: Signer<'info>, + pub sender: Signer, #[account( mut, token::mint = mint_account )] - pub sender_token_account: InterfaceAccount<'info, TokenAccount>, + pub sender_token_account: InterfaceAccount, #[account( init_if_needed, payer = sender, @@ -49,8 +50,8 @@ pub struct CpiTransferAccountConstraints<'info> { token::authority = recipient_token_account, token::token_program = token_program )] - pub recipient_token_account: InterfaceAccount<'info, TokenAccount>, - pub mint_account: InterfaceAccount<'info, Mint>, - pub token_program: Program<'info, Token2022>, - pub system_program: Program<'info, System>, + pub recipient_token_account: InterfaceAccount, + pub mint_account: InterfaceAccount, + pub token_program: Program, + pub system_program: Program, } diff --git a/tokens/token-extensions/default-account-state/anchor/programs/default-account-state/Cargo.toml b/tokens/token-extensions/default-account-state/anchor/programs/default-account-state/Cargo.toml index 4073392e0..23e284f69 100644 --- a/tokens/token-extensions/default-account-state/anchor/programs/default-account-state/Cargo.toml +++ b/tokens/token-extensions/default-account-state/anchor/programs/default-account-state/Cargo.toml @@ -14,14 +14,22 @@ cpi = ["no-entrypoint"] no-entrypoint = [] no-idl = [] no-log-ix-name = [] -idl-build = ["anchor-lang/idl-build", "anchor-spl/idl-build"] +idl-build = ["anchor-lang/idl-build"] anchor-debug = [] custom-heap = [] custom-panic = [] [dependencies] -anchor-lang = "1.1.2" -anchor-spl = "1.1.2" +anchor-lang = "2.0.0-rc.1" +# The `#[program]` macro expands to `wincode` paths for instruction-data +# (de)serialization, so the crate has to be a direct dependency. +wincode = { version = "0.5", features = ["derive"] } +# anchor-lang 2.0.0-rc.1 is built against wincode 0.5, but solana-address 2.7 +# moved to wincode 0.6. With both in the graph, `Address`'s wincode impls +# belong to the version the account derives are not using and every +# `SchemaRead`/`SchemaWrite` bound fails. 2.6.1 is the last 0.5-line release. +solana-address = ">=2.6, <2.7" +anchor-spl = "2.0.0-rc.1" [dev-dependencies] litesvm = "0.13.1" diff --git a/tokens/token-extensions/default-account-state/anchor/programs/default-account-state/src/instructions/initialize.rs b/tokens/token-extensions/default-account-state/anchor/programs/default-account-state/src/instructions/initialize.rs index 939241aba..5044e43ed 100644 --- a/tokens/token-extensions/default-account-state/anchor/programs/default-account-state/src/instructions/initialize.rs +++ b/tokens/token-extensions/default-account-state/anchor/programs/default-account-state/src/instructions/initialize.rs @@ -12,49 +12,48 @@ use anchor_spl::{ }; #[derive(Accounts)] -pub struct InitializeAccountConstraints<'info> { +pub struct InitializeAccountConstraints { #[account(mut)] - pub payer: Signer<'info>, + pub payer: Signer, #[account(mut)] - pub mint_account: Signer<'info>, + pub mint_account: Signer, - pub token_program: Program<'info, Token2022>, - pub system_program: Program<'info, System>, + pub token_program: Program, + pub system_program: Program, } // There is currently not an anchor constraint to automatically initialize the DefaultAccountState extension // We can manually create and initialize the mint account via CPIs in the instruction handler -pub fn handler(context: Context) -> Result<()> { +pub fn handler(context: &mut Context) -> Result<()> { // Calculate space required for mint and extension data let mint_size = ExtensionType::try_calculate_account_len::(&[ ExtensionType::DefaultAccountState, ])?; // Calculate minimum lamports required for size of mint account with extensions - let lamports = (Rent::get()?).minimum_balance(mint_size); + let lamports = Rent::get()?.try_minimum_balance(mint_size)?; // Invoke System Program to create new account with space for mint and extension data create_account( CpiContext::new( - context.accounts.system_program.key(), + context.accounts.system_program.address(), CreateAccount { - from: context.accounts.payer.to_account_info(), - to: context.accounts.mint_account.to_account_info(), + from: context.accounts.payer.cpi_handle_mut(), + to: context.accounts.mint_account.cpi_handle_mut(), }, ), lamports, // Lamports mint_size as u64, // Space - &context.accounts.token_program.key(), // Owner Program + &context.accounts.token_program.address(), // Owner Program )?; // Initialize the NonTransferable extension // This instruction must come before the instruction to initialize the mint data default_account_state_initialize( CpiContext::new( - context.accounts.token_program.key(), + context.accounts.token_program.address(), DefaultAccountStateInitialize { - token_program_id: context.accounts.token_program.to_account_info(), - mint: context.accounts.mint_account.to_account_info(), + mint: context.accounts.mint_account.cpi_handle_mut(), }, ), &AccountState::Frozen, // default frozen token accounts @@ -63,14 +62,14 @@ pub fn handler(context: Context) -> Result<()> { // Initialize the standard mint account data initialize_mint2( CpiContext::new( - context.accounts.token_program.key(), + context.accounts.token_program.address(), InitializeMint2 { - mint: context.accounts.mint_account.to_account_info(), + mint: context.accounts.mint_account.cpi_handle_mut(), }, ), 2, // decimals - &context.accounts.payer.key(), // mint authority - Some(&context.accounts.payer.key()), // freeze authority + &context.accounts.payer.address(), // mint authority + Some(&context.accounts.payer.address()), // freeze authority )?; Ok(()) } diff --git a/tokens/token-extensions/default-account-state/anchor/programs/default-account-state/src/instructions/update_default_state.rs b/tokens/token-extensions/default-account-state/anchor/programs/default-account-state/src/instructions/update_default_state.rs index 7ed0ec0dc..9323ccf15 100644 --- a/tokens/token-extensions/default-account-state/anchor/programs/default-account-state/src/instructions/update_default_state.rs +++ b/tokens/token-extensions/default-account-state/anchor/programs/default-account-state/src/instructions/update_default_state.rs @@ -1,4 +1,5 @@ use anchor_lang::prelude::*; +use anchor_spl::mint; use anchor_spl::token_interface::{ default_account_state_update, DefaultAccountStateUpdate, Mint, Token2022, }; @@ -6,21 +7,21 @@ use anchor_spl::token_interface::{ use crate::AnchorAccountState; #[derive(Accounts)] -pub struct UpdateDefaultStateAccountConstraints<'info> { +pub struct UpdateDefaultStateAccountConstraints { #[account(mut)] - pub freeze_authority: Signer<'info>, + pub freeze_authority: Signer, #[account( mut, mint::freeze_authority = freeze_authority, )] - pub mint_account: InterfaceAccount<'info, Mint>, + pub mint_account: InterfaceAccount, - pub token_program: Program<'info, Token2022>, - pub system_program: Program<'info, System>, + pub token_program: Program, + pub system_program: Program, } pub fn handler( - context: Context, + context: &mut Context, account_state: AnchorAccountState, ) -> Result<()> { // Convert AnchorAccountState to spl_token_2022::state::AccountState @@ -28,11 +29,10 @@ pub fn handler( default_account_state_update( CpiContext::new( - context.accounts.token_program.key(), + context.accounts.token_program.address(), DefaultAccountStateUpdate { - token_program_id: context.accounts.token_program.to_account_info(), - mint: context.accounts.mint_account.to_account_info(), - freeze_authority: context.accounts.freeze_authority.to_account_info(), + mint: context.accounts.mint_account.cpi_handle_mut(), + freeze_authority: context.accounts.freeze_authority.cpi_handle(), }, ), &account_state, diff --git a/tokens/token-extensions/default-account-state/anchor/programs/default-account-state/src/lib.rs b/tokens/token-extensions/default-account-state/anchor/programs/default-account-state/src/lib.rs index 8d6ef76c6..4cafa685e 100644 --- a/tokens/token-extensions/default-account-state/anchor/programs/default-account-state/src/lib.rs +++ b/tokens/token-extensions/default-account-state/anchor/programs/default-account-state/src/lib.rs @@ -10,12 +10,12 @@ declare_id!("5LdYbHiUsFxVG8bfqoeBkhBYMRmWZb3BoLuABgYW7coB"); pub mod default_account_state { use super::*; - pub fn initialize(context: Context) -> Result<()> { + pub fn initialize(context: &mut Context) -> Result<()> { instructions::initialize::handler(context) } pub fn update_default_state( - context: Context, + context: &mut Context, account_state: AnchorAccountState, ) -> Result<()> { instructions::update_default_state::handler(context, account_state) diff --git a/tokens/token-extensions/group/anchor/programs/group/Cargo.toml b/tokens/token-extensions/group/anchor/programs/group/Cargo.toml index 1081814e8..d6c91ba9b 100644 --- a/tokens/token-extensions/group/anchor/programs/group/Cargo.toml +++ b/tokens/token-extensions/group/anchor/programs/group/Cargo.toml @@ -14,14 +14,22 @@ cpi = ["no-entrypoint"] no-entrypoint = [] no-idl = [] no-log-ix-name = [] -idl-build = ["anchor-lang/idl-build", "anchor-spl/idl-build"] +idl-build = ["anchor-lang/idl-build"] anchor-debug = [] custom-heap = [] custom-panic = [] [dependencies] -anchor-lang = "1.1.2" -anchor-spl = "1.1.2" +anchor-lang = "2.0.0-rc.1" +# The `#[program]` macro expands to `wincode` paths for instruction-data +# (de)serialization, so the crate has to be a direct dependency. +wincode = { version = "0.5", features = ["derive"] } +# anchor-lang 2.0.0-rc.1 is built against wincode 0.5, but solana-address 2.7 +# moved to wincode 0.6. With both in the graph, `Address`'s wincode impls +# belong to the version the account derives are not using and every +# `SchemaRead`/`SchemaWrite` bound fails. 2.6.1 is the last 0.5-line release. +solana-address = ">=2.6, <2.7" +anchor-spl = "2.0.0-rc.1" [dev-dependencies] litesvm = "0.13.1" diff --git a/tokens/token-extensions/group/anchor/programs/group/src/instructions/test_initialize_group.rs b/tokens/token-extensions/group/anchor/programs/group/src/instructions/test_initialize_group.rs index d7b992a0d..3c958a065 100644 --- a/tokens/token-extensions/group/anchor/programs/group/src/instructions/test_initialize_group.rs +++ b/tokens/token-extensions/group/anchor/programs/group/src/instructions/test_initialize_group.rs @@ -1,4 +1,5 @@ use anchor_lang::prelude::*; +use anchor_spl::mint; use anchor_spl::token_2022::spl_token_2022::extension::group_pointer::GroupPointer; use anchor_spl::token_interface::{ spl_token_2022::{ @@ -9,9 +10,9 @@ use anchor_spl::token_interface::{ }; #[derive(Accounts)] -pub struct InitializeGroupAccountConstraints<'info> { +pub struct InitializeGroupAccountConstraints { #[account(mut)] - pub payer: Signer<'info>, + pub payer: Signer, #[account( init, @@ -24,13 +25,13 @@ pub struct InitializeGroupAccountConstraints<'info> { extensions::group_pointer::authority = mint_account, extensions::group_pointer::group_address = mint_account, )] - pub mint_account: InterfaceAccount<'info, Mint>, - pub token_program: Program<'info, Token2022>, - pub system_program: Program<'info, System>, + pub mint_account: InterfaceAccount, + pub token_program: Program, + pub system_program: Program, } fn check_mint_data(accounts: &mut InitializeGroupAccountConstraints) -> Result<()> { - let mint = &accounts.mint_account.to_account_info(); + let mint = &accounts.mint_account.cpi_handle_mut(); let mint_data = mint.data.borrow(); let mint_with_extension = StateWithExtensions::::unpack(&mint_data)?; let extension_data = mint_with_extension.get_extension::()?; @@ -40,7 +41,7 @@ fn check_mint_data(accounts: &mut InitializeGroupAccountConstraints) -> Result<( Ok(()) } -pub fn handler(mut context: Context) -> Result<()> { +pub fn handler(mut context: &mut Context) -> Result<()> { check_mint_data(&mut context.accounts)?; // // Token Group and Token Member extensions features not enabled yet on the Token2022 program @@ -50,16 +51,16 @@ pub fn handler(mut context: Context) -> Resul // let signer_seeds: &[&[&[u8]]] = &[&[b"group", &[context.bumps.mint_account]]]; // token_group_initialize( // CpiContext::new( - // context.accounts.token_program.to_account_info(), + // context.accounts.token_program.cpi_handle_mut(), // TokenGroupInitialize { - // token_program_id: context.accounts.token_program.to_account_info(), - // group: context.accounts.mint_account.to_account_info(), - // mint: context.accounts.mint_account.to_account_info(), - // mint_authority: context.accounts.mint_account.to_account_info(), + // token_program_id: context.accounts.token_program.cpi_handle_mut(), + // group: context.accounts.mint_account.cpi_handle_mut(), + // mint: context.accounts.mint_account.cpi_handle_mut(), + // mint_authority: context.accounts.mint_account.cpi_handle_mut(), // }, // ) // .with_signer(signer_seeds), - // Some(context.accounts.payer.key()), // update_authority + // Some(context.accounts.payer.address()), // update_authority // 10, // max_size // )?; Ok(()) diff --git a/tokens/token-extensions/group/anchor/programs/group/src/lib.rs b/tokens/token-extensions/group/anchor/programs/group/src/lib.rs index 4c2219231..895270caf 100644 --- a/tokens/token-extensions/group/anchor/programs/group/src/lib.rs +++ b/tokens/token-extensions/group/anchor/programs/group/src/lib.rs @@ -10,7 +10,7 @@ pub mod group { use super::*; - pub fn test_initialize_group(context: Context) -> Result<()> { + pub fn test_initialize_group(context: &mut Context) -> Result<()> { instructions::test_initialize_group::handler(context) } } diff --git a/tokens/token-extensions/immutable-owner/anchor/programs/immutable-owner/Cargo.toml b/tokens/token-extensions/immutable-owner/anchor/programs/immutable-owner/Cargo.toml index ab8bfba85..a7531d126 100644 --- a/tokens/token-extensions/immutable-owner/anchor/programs/immutable-owner/Cargo.toml +++ b/tokens/token-extensions/immutable-owner/anchor/programs/immutable-owner/Cargo.toml @@ -14,14 +14,22 @@ cpi = ["no-entrypoint"] no-entrypoint = [] no-idl = [] no-log-ix-name = [] -idl-build = ["anchor-lang/idl-build", "anchor-spl/idl-build"] +idl-build = ["anchor-lang/idl-build"] anchor-debug = [] custom-heap = [] custom-panic = [] [dependencies] -anchor-lang = "1.1.2" -anchor-spl = "1.1.2" +anchor-lang = "2.0.0-rc.1" +# The `#[program]` macro expands to `wincode` paths for instruction-data +# (de)serialization, so the crate has to be a direct dependency. +wincode = { version = "0.5", features = ["derive"] } +# anchor-lang 2.0.0-rc.1 is built against wincode 0.5, but solana-address 2.7 +# moved to wincode 0.6. With both in the graph, `Address`'s wincode impls +# belong to the version the account derives are not using and every +# `SchemaRead`/`SchemaWrite` bound fails. 2.6.1 is the last 0.5-line release. +solana-address = ">=2.6, <2.7" +anchor-spl = "2.0.0-rc.1" [dev-dependencies] litesvm = "0.13.1" diff --git a/tokens/token-extensions/immutable-owner/anchor/programs/immutable-owner/src/lib.rs b/tokens/token-extensions/immutable-owner/anchor/programs/immutable-owner/src/lib.rs index e21a5e718..95badb006 100644 --- a/tokens/token-extensions/immutable-owner/anchor/programs/immutable-owner/src/lib.rs +++ b/tokens/token-extensions/immutable-owner/anchor/programs/immutable-owner/src/lib.rs @@ -17,45 +17,44 @@ pub mod immutable_owner { // There is currently not an anchor constraint to automatically initialize the ImmutableOwner extension // We can manually create and initialize the token account via CPIs in the instruction handler - pub fn initialize(context: Context) -> Result<()> { + pub fn initialize(context: &mut Context) -> Result<()> { // Calculate space required for token and extension data let token_account_size = ExtensionType::try_calculate_account_len::(&[ ExtensionType::ImmutableOwner, ])?; // Calculate minimum lamports required for size of token account with extensions - let lamports = (Rent::get()?).minimum_balance(token_account_size); + let lamports = Rent::get()?.try_minimum_balance(token_account_size)?; // Invoke System Program to create new account with space for token account and extension data create_account( CpiContext::new( - context.accounts.system_program.key(), + context.accounts.system_program.address(), CreateAccount { - from: context.accounts.payer.to_account_info(), - to: context.accounts.token_account.to_account_info(), + from: context.accounts.payer.cpi_handle_mut(), + to: context.accounts.token_account.cpi_handle_mut(), }, ), lamports, // Lamports token_account_size as u64, // Space - &context.accounts.token_program.key(), // Owner Program + &context.accounts.token_program.address(), // Owner Program )?; // Initialize the token account with the immutable owner extension immutable_owner_initialize(CpiContext::new( - context.accounts.token_program.key(), + context.accounts.token_program.address(), ImmutableOwnerInitialize { - token_program_id: context.accounts.token_program.to_account_info(), - token_account: context.accounts.token_account.to_account_info(), + token_account: context.accounts.token_account.cpi_handle_mut(), }, ))?; // Initialize the standard token account data initialize_account3(CpiContext::new( - context.accounts.token_program.key(), + context.accounts.token_program.address(), InitializeAccount3 { - account: context.accounts.token_account.to_account_info(), - mint: context.accounts.mint_account.to_account_info(), - authority: context.accounts.payer.to_account_info(), + account: context.accounts.token_account.cpi_handle_mut(), + mint: context.accounts.mint_account.cpi_handle(), + authority: context.accounts.payer.cpi_handle(), }, ))?; Ok(()) @@ -63,13 +62,13 @@ pub mod immutable_owner { } #[derive(Accounts)] -pub struct InitializeAccountConstraints<'info> { +pub struct InitializeAccountConstraints { #[account(mut)] - pub payer: Signer<'info>, + pub payer: Signer, #[account(mut)] - pub token_account: Signer<'info>, - pub mint_account: InterfaceAccount<'info, Mint>, - pub token_program: Program<'info, Token2022>, - pub system_program: Program<'info, System>, + pub token_account: Signer, + pub mint_account: InterfaceAccount, + pub token_program: Program, + pub system_program: Program, } diff --git a/tokens/token-extensions/interest-bearing/anchor/programs/interest-bearing/Cargo.toml b/tokens/token-extensions/interest-bearing/anchor/programs/interest-bearing/Cargo.toml index b7a89a7c3..64866ee57 100644 --- a/tokens/token-extensions/interest-bearing/anchor/programs/interest-bearing/Cargo.toml +++ b/tokens/token-extensions/interest-bearing/anchor/programs/interest-bearing/Cargo.toml @@ -14,15 +14,23 @@ cpi = ["no-entrypoint"] no-entrypoint = [] no-idl = [] no-log-ix-name = [] -idl-build = ["anchor-lang/idl-build", "anchor-spl/idl-build"] +idl-build = ["anchor-lang/idl-build"] anchor-debug = [] custom-heap = [] custom-panic = [] [dependencies] -anchor-lang = "1.1.2" -anchor-spl = "1.1.2" +anchor-lang = "2.0.0-rc.1" +# The `#[program]` macro expands to `wincode` paths for instruction-data +# (de)serialization, so the crate has to be a direct dependency. +wincode = { version = "0.5", features = ["derive"] } +# anchor-lang 2.0.0-rc.1 is built against wincode 0.5, but solana-address 2.7 +# moved to wincode 0.6. With both in the graph, `Address`'s wincode impls +# belong to the version the account derives are not using and every +# `SchemaRead`/`SchemaWrite` bound fails. 2.6.1 is the last 0.5-line release. +solana-address = ">=2.6, <2.7" +anchor-spl = "2.0.0-rc.1" [dev-dependencies] litesvm = "0.13.1" diff --git a/tokens/token-extensions/interest-bearing/anchor/programs/interest-bearing/src/instructions/initialize.rs b/tokens/token-extensions/interest-bearing/anchor/programs/interest-bearing/src/instructions/initialize.rs index cb34219a7..08f2ea99c 100644 --- a/tokens/token-extensions/interest-bearing/anchor/programs/interest-bearing/src/instructions/initialize.rs +++ b/tokens/token-extensions/interest-bearing/anchor/programs/interest-bearing/src/instructions/initialize.rs @@ -12,69 +12,68 @@ use anchor_spl::{ use crate::check_mint_data; #[derive(Accounts)] -pub struct InitializeAccountConstraints<'info> { +pub struct InitializeAccountConstraints { #[account(mut)] - pub payer: Signer<'info>, + pub payer: Signer, #[account(mut)] - pub mint_account: Signer<'info>, + pub mint_account: Signer, - pub token_program: Program<'info, Token2022>, - pub system_program: Program<'info, System>, + pub token_program: Program, + pub system_program: Program, } -pub fn handler(context: Context, rate: i16) -> Result<()> { +pub fn handler(context: &mut Context, rate: i16) -> Result<()> { // Calculate space required for mint and extension data let mint_size = ExtensionType::try_calculate_account_len::(&[ ExtensionType::InterestBearingConfig, ])?; // Calculate minimum lamports required for size of mint account with extensions - let lamports = (Rent::get()?).minimum_balance(mint_size); + let lamports = Rent::get()?.try_minimum_balance(mint_size)?; // Invoke System Program to create new account with space for mint and extension data create_account( CpiContext::new( - context.accounts.system_program.key(), + context.accounts.system_program.address(), CreateAccount { - from: context.accounts.payer.to_account_info(), - to: context.accounts.mint_account.to_account_info(), + from: context.accounts.payer.cpi_handle_mut(), + to: context.accounts.mint_account.cpi_handle_mut(), }, ), lamports, // Lamports mint_size as u64, // Space - &context.accounts.token_program.key(), // Owner Program + &context.accounts.token_program.address(), // Owner Program )?; // Initialize the InterestBearingConfig extension // This instruction must come before the instruction to initialize the mint data interest_bearing_mint_initialize( CpiContext::new( - context.accounts.token_program.key(), + context.accounts.token_program.address(), InterestBearingMintInitialize { - token_program_id: context.accounts.token_program.to_account_info(), - mint: context.accounts.mint_account.to_account_info(), + mint: context.accounts.mint_account.cpi_handle_mut(), }, ), - Some(context.accounts.payer.key()), + Some(context.accounts.payer.address()), rate, )?; // Initialize the standard mint account data initialize_mint2( CpiContext::new( - context.accounts.token_program.key(), + context.accounts.token_program.address(), InitializeMint2 { - mint: context.accounts.mint_account.to_account_info(), + mint: context.accounts.mint_account.cpi_handle_mut(), }, ), 2, // decimals - &context.accounts.payer.key(), // mint authority - Some(&context.accounts.payer.key()), // freeze authority + &context.accounts.payer.address(), // mint authority + Some(&context.accounts.payer.address()), // freeze authority )?; check_mint_data( - &context.accounts.mint_account.to_account_info(), - &context.accounts.payer.key(), + &context.accounts.mint_account.cpi_handle_mut(), + &context.accounts.payer.address(), )?; Ok(()) } diff --git a/tokens/token-extensions/interest-bearing/anchor/programs/interest-bearing/src/instructions/update_rate.rs b/tokens/token-extensions/interest-bearing/anchor/programs/interest-bearing/src/instructions/update_rate.rs index 87da63389..362ffd57b 100644 --- a/tokens/token-extensions/interest-bearing/anchor/programs/interest-bearing/src/instructions/update_rate.rs +++ b/tokens/token-extensions/interest-bearing/anchor/programs/interest-bearing/src/instructions/update_rate.rs @@ -6,32 +6,31 @@ use anchor_spl::token_interface::{ use crate::check_mint_data; #[derive(Accounts)] -pub struct UpdateRateAccountConstraints<'info> { +pub struct UpdateRateAccountConstraints { #[account(mut)] - pub authority: Signer<'info>, + pub authority: Signer, #[account(mut)] - pub mint_account: InterfaceAccount<'info, Mint>, + pub mint_account: InterfaceAccount, - pub token_program: Program<'info, Token2022>, - pub system_program: Program<'info, System>, + pub token_program: Program, + pub system_program: Program, } -pub fn handler(context: Context, rate: i16) -> Result<()> { +pub fn handler(context: &mut Context, rate: i16) -> Result<()> { interest_bearing_mint_update_rate( CpiContext::new( - context.accounts.token_program.key(), + context.accounts.token_program.address(), InterestBearingMintUpdateRate { - token_program_id: context.accounts.token_program.to_account_info(), - mint: context.accounts.mint_account.to_account_info(), - rate_authority: context.accounts.authority.to_account_info(), + mint: context.accounts.mint_account.cpi_handle_mut(), + rate_authority: context.accounts.authority.cpi_handle(), }, ), rate, )?; check_mint_data( - &context.accounts.mint_account.to_account_info(), - &context.accounts.authority.key(), + &context.accounts.mint_account.cpi_handle_mut(), + &context.accounts.authority.address(), )?; Ok(()) } diff --git a/tokens/token-extensions/interest-bearing/anchor/programs/interest-bearing/src/lib.rs b/tokens/token-extensions/interest-bearing/anchor/programs/interest-bearing/src/lib.rs index b09782186..fffe7e2d6 100644 --- a/tokens/token-extensions/interest-bearing/anchor/programs/interest-bearing/src/lib.rs +++ b/tokens/token-extensions/interest-bearing/anchor/programs/interest-bearing/src/lib.rs @@ -18,16 +18,16 @@ pub mod interest_bearing { use super::*; - pub fn initialize(context: Context, rate: i16) -> Result<()> { + pub fn initialize(context: &mut Context, rate: i16) -> Result<()> { instructions::initialize::handler(context, rate) } - pub fn update_rate(context: Context, rate: i16) -> Result<()> { + pub fn update_rate(context: &mut Context, rate: i16) -> Result<()> { instructions::update_rate::handler(context, rate) } } -pub fn check_mint_data(mint_account_info: &AccountInfo, authority_key: &Pubkey) -> Result<()> { +pub fn check_mint_data(mint_account_info: &AccountView, authority_key: &Address) -> Result<()> { let mint_data = mint_account_info.data.borrow(); let mint_with_extension = StateWithExtensions::::unpack(&mint_data)?; let extension_data = mint_with_extension.get_extension::()?; diff --git a/tokens/token-extensions/memo-transfer/anchor/programs/memo-transfer/Cargo.toml b/tokens/token-extensions/memo-transfer/anchor/programs/memo-transfer/Cargo.toml index 2b821be8d..86f284f02 100644 --- a/tokens/token-extensions/memo-transfer/anchor/programs/memo-transfer/Cargo.toml +++ b/tokens/token-extensions/memo-transfer/anchor/programs/memo-transfer/Cargo.toml @@ -14,14 +14,22 @@ cpi = ["no-entrypoint"] no-entrypoint = [] no-idl = [] no-log-ix-name = [] -idl-build = ["anchor-lang/idl-build", "anchor-spl/idl-build"] +idl-build = ["anchor-lang/idl-build"] anchor-debug = [] custom-heap = [] custom-panic = [] [dependencies] -anchor-lang = "1.1.2" -anchor-spl = "1.1.2" +anchor-lang = "2.0.0-rc.1" +# The `#[program]` macro expands to `wincode` paths for instruction-data +# (de)serialization, so the crate has to be a direct dependency. +wincode = { version = "0.5", features = ["derive"] } +# anchor-lang 2.0.0-rc.1 is built against wincode 0.5, but solana-address 2.7 +# moved to wincode 0.6. With both in the graph, `Address`'s wincode impls +# belong to the version the account derives are not using and every +# `SchemaRead`/`SchemaWrite` bound fails. 2.6.1 is the last 0.5-line release. +solana-address = ">=2.6, <2.7" +anchor-spl = "2.0.0-rc.1" [dev-dependencies] litesvm = "0.13.1" diff --git a/tokens/token-extensions/memo-transfer/anchor/programs/memo-transfer/src/instructions/disable.rs b/tokens/token-extensions/memo-transfer/anchor/programs/memo-transfer/src/instructions/disable.rs index f38442c0d..0a8b6999c 100644 --- a/tokens/token-extensions/memo-transfer/anchor/programs/memo-transfer/src/instructions/disable.rs +++ b/tokens/token-extensions/memo-transfer/anchor/programs/memo-transfer/src/instructions/disable.rs @@ -1,26 +1,26 @@ use anchor_lang::prelude::*; +use anchor_spl::token; use anchor_spl::token_interface::{memo_transfer_disable, MemoTransfer, Token2022, TokenAccount}; #[derive(Accounts)] -pub struct DisableAccountConstraints<'info> { +pub struct DisableAccountConstraints { #[account(mut)] - pub owner: Signer<'info>, + pub owner: Signer, #[account( mut, token::authority = owner, )] - pub token_account: InterfaceAccount<'info, TokenAccount>, - pub token_program: Program<'info, Token2022>, + pub token_account: InterfaceAccount, + pub token_program: Program, } -pub fn handler(context: Context) -> Result<()> { +pub fn handler(context: &mut Context) -> Result<()> { memo_transfer_disable(CpiContext::new( - context.accounts.token_program.key(), + context.accounts.token_program.address(), MemoTransfer { - token_program_id: context.accounts.token_program.to_account_info(), - account: context.accounts.token_account.to_account_info(), - owner: context.accounts.owner.to_account_info(), + account: context.accounts.token_account.cpi_handle_mut(), + owner: context.accounts.owner.cpi_handle(), }, ))?; Ok(()) diff --git a/tokens/token-extensions/memo-transfer/anchor/programs/memo-transfer/src/instructions/initialize.rs b/tokens/token-extensions/memo-transfer/anchor/programs/memo-transfer/src/instructions/initialize.rs index b465e5423..fc68fb039 100644 --- a/tokens/token-extensions/memo-transfer/anchor/programs/memo-transfer/src/instructions/initialize.rs +++ b/tokens/token-extensions/memo-transfer/anchor/programs/memo-transfer/src/instructions/initialize.rs @@ -10,57 +10,56 @@ use anchor_spl::{ }; #[derive(Accounts)] -pub struct InitializeAccountConstraints<'info> { +pub struct InitializeAccountConstraints { #[account(mut)] - pub payer: Signer<'info>, + pub payer: Signer, #[account(mut)] - pub token_account: Signer<'info>, - pub mint_account: InterfaceAccount<'info, Mint>, - pub token_program: Program<'info, Token2022>, - pub system_program: Program<'info, System>, + pub token_account: Signer, + pub mint_account: InterfaceAccount, + pub token_program: Program, + pub system_program: Program, } -pub fn handler(context: Context) -> Result<()> { +pub fn handler(context: &mut Context) -> Result<()> { // Calculate space required for token and extension data let token_account_size = ExtensionType::try_calculate_account_len::(&[ExtensionType::MemoTransfer])?; // Calculate minimum lamports required for size of token account with extensions - let lamports = (Rent::get()?).minimum_balance(token_account_size); + let lamports = Rent::get()?.try_minimum_balance(token_account_size)?; // Invoke System Program to create new account with space for token account and extension data create_account( CpiContext::new( - context.accounts.system_program.key(), + context.accounts.system_program.address(), CreateAccount { - from: context.accounts.payer.to_account_info(), - to: context.accounts.token_account.to_account_info(), + from: context.accounts.payer.cpi_handle_mut(), + to: context.accounts.token_account.cpi_handle_mut(), }, ), lamports, // Lamports token_account_size as u64, // Space - &context.accounts.token_program.key(), // Owner Program + &context.accounts.token_program.address(), // Owner Program )?; // Initialize the standard token account data initialize_account3(CpiContext::new( - context.accounts.token_program.key(), + context.accounts.token_program.address(), InitializeAccount3 { - account: context.accounts.token_account.to_account_info(), - mint: context.accounts.mint_account.to_account_info(), - authority: context.accounts.payer.to_account_info(), + account: context.accounts.token_account.cpi_handle_mut(), + mint: context.accounts.mint_account.cpi_handle(), + authority: context.accounts.payer.cpi_handle(), }, ))?; // Initialize the memo transfer extension // This instruction must come after the token account initialization memo_transfer_initialize(CpiContext::new( - context.accounts.token_program.key(), + context.accounts.token_program.address(), MemoTransfer { - token_program_id: context.accounts.token_program.to_account_info(), - account: context.accounts.token_account.to_account_info(), - owner: context.accounts.payer.to_account_info(), + account: context.accounts.token_account.cpi_handle_mut(), + owner: context.accounts.payer.cpi_handle(), }, ))?; Ok(()) diff --git a/tokens/token-extensions/memo-transfer/anchor/programs/memo-transfer/src/lib.rs b/tokens/token-extensions/memo-transfer/anchor/programs/memo-transfer/src/lib.rs index d9e71f050..d6b31d665 100644 --- a/tokens/token-extensions/memo-transfer/anchor/programs/memo-transfer/src/lib.rs +++ b/tokens/token-extensions/memo-transfer/anchor/programs/memo-transfer/src/lib.rs @@ -9,11 +9,11 @@ declare_id!("5BQyC7y2Pc283woThq11uZRqsgcRbBRLKz4yQ8BJadi2"); pub mod memo_transfer { use super::*; - pub fn initialize(context: Context) -> Result<()> { + pub fn initialize(context: &mut Context) -> Result<()> { instructions::initialize::handler(context) } - pub fn disable(context: Context) -> Result<()> { + pub fn disable(context: &mut Context) -> Result<()> { instructions::disable::handler(context) } } diff --git a/tokens/token-extensions/metadata/anchor/programs/metadata/Cargo.toml b/tokens/token-extensions/metadata/anchor/programs/metadata/Cargo.toml index d686173a3..424caed88 100644 --- a/tokens/token-extensions/metadata/anchor/programs/metadata/Cargo.toml +++ b/tokens/token-extensions/metadata/anchor/programs/metadata/Cargo.toml @@ -14,14 +14,22 @@ cpi = ["no-entrypoint"] no-entrypoint = [] no-idl = [] no-log-ix-name = [] -idl-build = ["anchor-lang/idl-build", "anchor-spl/idl-build"] +idl-build = ["anchor-lang/idl-build"] anchor-debug = [] custom-heap = [] custom-panic = [] [dependencies] -anchor-lang = "1.1.2" -anchor-spl = "1.1.2" +anchor-lang = "2.0.0-rc.1" +# The `#[program]` macro expands to `wincode` paths for instruction-data +# (de)serialization, so the crate has to be a direct dependency. +wincode = { version = "0.5", features = ["derive"] } +# anchor-lang 2.0.0-rc.1 is built against wincode 0.5, but solana-address 2.7 +# moved to wincode 0.6. With both in the graph, `Address`'s wincode impls +# belong to the version the account derives are not using and every +# `SchemaRead`/`SchemaWrite` bound fails. 2.6.1 is the last 0.5-line release. +solana-address = ">=2.6, <2.7" +anchor-spl = "2.0.0-rc.1" spl-token-metadata-interface = "0.8.0" spl-type-length-value = "0.9.1" diff --git a/tokens/token-extensions/metadata/anchor/programs/metadata/src/instructions/emit.rs b/tokens/token-extensions/metadata/anchor/programs/metadata/src/instructions/emit.rs index b849ab82b..71a445977 100644 --- a/tokens/token-extensions/metadata/anchor/programs/metadata/src/instructions/emit.rs +++ b/tokens/token-extensions/metadata/anchor/programs/metadata/src/instructions/emit.rs @@ -4,24 +4,24 @@ use anchor_spl::token_interface::{Mint, Token2022}; use spl_token_metadata_interface::instruction::emit; #[derive(Accounts)] -pub struct EmitAccountConstraints<'info> { - pub mint_account: InterfaceAccount<'info, Mint>, - pub token_program: Program<'info, Token2022>, +pub struct EmitAccountConstraints { + pub mint_account: InterfaceAccount, + pub token_program: Program, } // Invoke the emit instruction from spl_token_metadata_interface directly // There is not an anchor CpiContext for this instruction -pub fn process_emit(context: Context) -> Result<()> { +pub fn process_emit(context: &mut Context) -> Result<()> { invoke( &emit( - &context.accounts.token_program.key(), // token program id - &context.accounts.mint_account.key(), // "metadata" account + &context.accounts.token_program.address(), // token program id + &context.accounts.mint_account.address(), // "metadata" account None, None, ), &[ - context.accounts.token_program.to_account_info(), - context.accounts.mint_account.to_account_info(), + context.accounts.token_program.cpi_handle(), + context.accounts.mint_account.cpi_handle(), ], )?; Ok(()) diff --git a/tokens/token-extensions/metadata/anchor/programs/metadata/src/instructions/initialize.rs b/tokens/token-extensions/metadata/anchor/programs/metadata/src/instructions/initialize.rs index 740b61bae..f883c31d6 100644 --- a/tokens/token-extensions/metadata/anchor/programs/metadata/src/instructions/initialize.rs +++ b/tokens/token-extensions/metadata/anchor/programs/metadata/src/instructions/initialize.rs @@ -1,4 +1,5 @@ use anchor_lang::prelude::*; +use anchor_spl::mint; use anchor_lang::system_program::{transfer, Transfer}; use anchor_spl::token_interface::{ token_metadata_initialize, Mint, Token2022, TokenMetadataInitialize, @@ -7,9 +8,9 @@ use spl_token_metadata_interface::state::TokenMetadata; use spl_type_length_value::variable_len_pack::VariableLenPack; #[derive(Accounts)] -pub struct InitializeAccountConstraints<'info> { +pub struct InitializeAccountConstraints { #[account(mut)] - pub payer: Signer<'info>, + pub payer: Signer, #[account( init, @@ -19,12 +20,12 @@ pub struct InitializeAccountConstraints<'info> { extensions::metadata_pointer::authority = payer, extensions::metadata_pointer::metadata_address = mint_account, )] - pub mint_account: InterfaceAccount<'info, Mint>, - pub token_program: Program<'info, Token2022>, - pub system_program: Program<'info, System>, + pub mint_account: InterfaceAccount, + pub token_program: Program, + pub system_program: Program, } -pub fn process_initialize(context: Context, args: TokenMetadataArgs) -> Result<()> { +pub fn process_initialize(context: &mut Context, args: TokenMetadataArgs) -> Result<()> { let TokenMetadataArgs { name, symbol, uri } = args; // Define token metadata @@ -39,15 +40,15 @@ pub fn process_initialize(context: Context, args: let data_len = 4 + token_metadata.get_packed_len()?; // Calculate lamports required for the additional metadata - let lamports = Rent::get()?.minimum_balance(data_len); + let lamports = Rent::get()?.try_minimum_balance(data_len)?; // Transfer additional lamports to mint account transfer( CpiContext::new( - context.accounts.system_program.key(), + context.accounts.system_program.address(), Transfer { - from: context.accounts.payer.to_account_info(), - to: context.accounts.mint_account.to_account_info(), + from: context.accounts.payer.cpi_handle_mut(), + to: context.accounts.mint_account.cpi_handle_mut(), }, ), lamports, @@ -56,13 +57,13 @@ pub fn process_initialize(context: Context, args: // Initialize token metadata token_metadata_initialize( CpiContext::new( - context.accounts.token_program.key(), + context.accounts.token_program.address(), TokenMetadataInitialize { - program_id: context.accounts.token_program.to_account_info(), - mint: context.accounts.mint_account.to_account_info(), - metadata: context.accounts.mint_account.to_account_info(), - mint_authority: context.accounts.payer.to_account_info(), - update_authority: context.accounts.payer.to_account_info(), + program_id: context.accounts.token_program.cpi_handle_mut(), + mint: context.accounts.mint_account.cpi_handle(), + metadata: context.accounts.mint_account.cpi_handle_mut(), + mint_authority: context.accounts.payer.cpi_handle(), + update_authority: context.accounts.payer.cpi_handle(), }, ), name, diff --git a/tokens/token-extensions/metadata/anchor/programs/metadata/src/instructions/remove_key.rs b/tokens/token-extensions/metadata/anchor/programs/metadata/src/instructions/remove_key.rs index 0114a4b26..d378fef0d 100644 --- a/tokens/token-extensions/metadata/anchor/programs/metadata/src/instructions/remove_key.rs +++ b/tokens/token-extensions/metadata/anchor/programs/metadata/src/instructions/remove_key.rs @@ -4,34 +4,34 @@ use anchor_spl::token_interface::{Mint, Token2022}; use spl_token_metadata_interface::instruction::remove_key; #[derive(Accounts)] -pub struct RemoveKeyAccountConstraints<'info> { +pub struct RemoveKeyAccountConstraints { #[account(mut)] - pub update_authority: Signer<'info>, + pub update_authority: Signer, #[account( mut, extensions::metadata_pointer::metadata_address = mint_account, )] - pub mint_account: InterfaceAccount<'info, Mint>, - pub token_program: Program<'info, Token2022>, - pub system_program: Program<'info, System>, + pub mint_account: InterfaceAccount, + pub token_program: Program, + pub system_program: Program, } // Invoke the remove_key instruction from spl_token_metadata_interface directly // There is not an anchor CpiContext for this instruction -pub fn process_remove_key(context: Context, key: String) -> Result<()> { +pub fn process_remove_key(context: &mut Context, key: String) -> Result<()> { invoke( &remove_key( - &context.accounts.token_program.key(), // token program id - &context.accounts.mint_account.key(), // "metadata" account - &context.accounts.update_authority.key(), // update authority + &context.accounts.token_program.address(), // token program id + &context.accounts.mint_account.address(), // "metadata" account + &context.accounts.update_authority.address(), // update authority key, // key to remove true, // idempotent flag, if true transaction will not fail if key does not exist ), &[ - context.accounts.token_program.to_account_info(), - context.accounts.mint_account.to_account_info(), - context.accounts.update_authority.to_account_info(), + context.accounts.token_program.cpi_handle(), + context.accounts.mint_account.cpi_handle(), + context.accounts.update_authority.cpi_handle(), ], )?; Ok(()) diff --git a/tokens/token-extensions/metadata/anchor/programs/metadata/src/instructions/update_authority.rs b/tokens/token-extensions/metadata/anchor/programs/metadata/src/instructions/update_authority.rs index ded7b65f7..083d13258 100644 --- a/tokens/token-extensions/metadata/anchor/programs/metadata/src/instructions/update_authority.rs +++ b/tokens/token-extensions/metadata/anchor/programs/metadata/src/instructions/update_authority.rs @@ -5,37 +5,37 @@ use anchor_spl::token_interface::{ }; #[derive(Accounts)] -pub struct UpdateAuthorityAccountConstraints<'info> { - pub current_authority: Signer<'info>, - pub new_authority: Option>, +pub struct UpdateAuthorityAccountConstraints { + pub current_authority: Signer, + pub new_authority: Option, #[account( mut, extensions::metadata_pointer::metadata_address = mint_account, )] - pub mint_account: InterfaceAccount<'info, Mint>, - pub token_program: Program<'info, Token2022>, - pub system_program: Program<'info, System>, + pub mint_account: InterfaceAccount, + pub token_program: Program, + pub system_program: Program, } -pub fn process_update_authority(context: Context) -> Result<()> { +pub fn process_update_authority(context: &mut Context) -> Result<()> { let new_authority_key = match &context.accounts.new_authority { - Some(account) => OptionalNonZeroPubkey::try_from(Some(account.key()))?, + Some(account) => OptionalNonZeroPubkey::try_from(Some(account.address()))?, None => OptionalNonZeroPubkey::try_from(None)?, }; // Change update authority token_metadata_update_authority( CpiContext::new( - context.accounts.token_program.key(), + context.accounts.token_program.address(), TokenMetadataUpdateAuthority { - program_id: context.accounts.token_program.to_account_info(), - metadata: context.accounts.mint_account.to_account_info(), - current_authority: context.accounts.current_authority.to_account_info(), + program_id: context.accounts.token_program.cpi_handle_mut(), + metadata: context.accounts.mint_account.cpi_handle_mut(), + current_authority: context.accounts.current_authority.cpi_handle(), // new authority isn't actually needed as account in the CPI // using current_authority as a placeholder to satisfy the struct - new_authority: context.accounts.current_authority.to_account_info(), + new_authority: context.accounts.current_authority.cpi_handle_mut(), }, ), new_authority_key, diff --git a/tokens/token-extensions/metadata/anchor/programs/metadata/src/instructions/update_field.rs b/tokens/token-extensions/metadata/anchor/programs/metadata/src/instructions/update_field.rs index 754fb8091..c8d219fc1 100644 --- a/tokens/token-extensions/metadata/anchor/programs/metadata/src/instructions/update_field.rs +++ b/tokens/token-extensions/metadata/anchor/programs/metadata/src/instructions/update_field.rs @@ -10,20 +10,20 @@ use anchor_spl::{ use spl_token_metadata_interface::state::{Field, TokenMetadata}; #[derive(Accounts)] -pub struct UpdateFieldAccountConstraints<'info> { +pub struct UpdateFieldAccountConstraints { #[account(mut)] - pub authority: Signer<'info>, + pub authority: Signer, #[account( mut, extensions::metadata_pointer::metadata_address = mint_account, )] - pub mint_account: InterfaceAccount<'info, Mint>, - pub token_program: Program<'info, Token2022>, - pub system_program: Program<'info, System>, + pub mint_account: InterfaceAccount, + pub token_program: Program, + pub system_program: Program, } -pub fn process_update_field(context: Context, args: UpdateFieldArgs) -> Result<()> { +pub fn process_update_field(context: &mut Context, args: UpdateFieldArgs) -> Result<()> { let UpdateFieldArgs { field, value } = args; // Convert to Field type from spl_token_metadata_interface @@ -32,7 +32,7 @@ pub fn process_update_field(context: Context, arg let (current_lamports, required_lamports) = { // Get the current state of the mint account - let mint = &context.accounts.mint_account.to_account_info(); + let mint = &context.accounts.mint_account.cpi_handle_mut(); let buffer = mint.try_borrow_data()?; let state = PodStateWithExtensions::::unpack(&buffer)?; @@ -46,7 +46,7 @@ pub fn process_update_field(context: Context, arg state.try_get_new_account_len_for_variable_len_extension(&token_metadata)?; // Calculate the required lamports for the new account length - let required_lamports = Rent::get()?.minimum_balance(new_account_len); + let required_lamports = Rent::get()?.try_minimum_balance(new_account_len)?; // Get the current lamports of the mint account let current_lamports = mint.lamports(); @@ -61,10 +61,10 @@ pub fn process_update_field(context: Context, arg let lamport_difference = required_lamports - current_lamports; transfer( CpiContext::new( - context.accounts.system_program.key(), + context.accounts.system_program.address(), Transfer { - from: context.accounts.authority.to_account_info(), - to: context.accounts.mint_account.to_account_info(), + from: context.accounts.authority.cpi_handle_mut(), + to: context.accounts.mint_account.cpi_handle_mut(), }, ), lamport_difference, @@ -78,11 +78,11 @@ pub fn process_update_field(context: Context, arg // Update token metadata token_metadata_update_field( CpiContext::new( - context.accounts.token_program.key(), + context.accounts.token_program.address(), TokenMetadataUpdateField { - program_id: context.accounts.token_program.to_account_info(), - metadata: context.accounts.mint_account.to_account_info(), - update_authority: context.accounts.authority.to_account_info(), + program_id: context.accounts.token_program.cpi_handle_mut(), + metadata: context.accounts.mint_account.cpi_handle_mut(), + update_authority: context.accounts.authority.cpi_handle(), }, ), field, diff --git a/tokens/token-extensions/metadata/anchor/programs/metadata/src/lib.rs b/tokens/token-extensions/metadata/anchor/programs/metadata/src/lib.rs index e086f704c..158e336e5 100644 --- a/tokens/token-extensions/metadata/anchor/programs/metadata/src/lib.rs +++ b/tokens/token-extensions/metadata/anchor/programs/metadata/src/lib.rs @@ -11,23 +11,23 @@ declare_id!("BJHEDXSQfD9kBFvhw8ZCGmPFRihzvbMoxoHUKpXdpn4D"); pub mod metadata { use super::*; - pub fn initialize(context: Context, args: TokenMetadataArgs) -> Result<()> { + pub fn initialize(context: &mut Context, args: TokenMetadataArgs) -> Result<()> { process_initialize(context, args) } - pub fn update_field(context: Context, args: UpdateFieldArgs) -> Result<()> { + pub fn update_field(context: &mut Context, args: UpdateFieldArgs) -> Result<()> { process_update_field(context, args) } - pub fn remove_key(context: Context, key: String) -> Result<()> { + pub fn remove_key(context: &mut Context, key: String) -> Result<()> { process_remove_key(context, key) } - pub fn emit(context: Context) -> Result<()> { + pub fn emit(context: &mut Context) -> Result<()> { process_emit(context) } - pub fn update_authority(context: Context) -> Result<()> { + pub fn update_authority(context: &mut Context) -> Result<()> { process_update_authority(context) } } diff --git a/tokens/token-extensions/mint-close-authority/anchor/programs/mint-close-authority/Cargo.toml b/tokens/token-extensions/mint-close-authority/anchor/programs/mint-close-authority/Cargo.toml index 0c4bc0de7..8c031c522 100644 --- a/tokens/token-extensions/mint-close-authority/anchor/programs/mint-close-authority/Cargo.toml +++ b/tokens/token-extensions/mint-close-authority/anchor/programs/mint-close-authority/Cargo.toml @@ -14,11 +14,19 @@ cpi = ["no-entrypoint"] no-entrypoint = [] no-idl = [] no-log-ix-name = [] -idl-build = ["anchor-lang/idl-build", "anchor-spl/idl-build"] +idl-build = ["anchor-lang/idl-build"] [dependencies] -anchor-lang = "1.1.2" -anchor-spl = "1.1.2" +anchor-lang = "2.0.0-rc.1" +# The `#[program]` macro expands to `wincode` paths for instruction-data +# (de)serialization, so the crate has to be a direct dependency. +wincode = { version = "0.5", features = ["derive"] } +# anchor-lang 2.0.0-rc.1 is built against wincode 0.5, but solana-address 2.7 +# moved to wincode 0.6. With both in the graph, `Address`'s wincode impls +# belong to the version the account derives are not using and every +# `SchemaRead`/`SchemaWrite` bound fails. 2.6.1 is the last 0.5-line release. +solana-address = ">=2.6, <2.7" +anchor-spl = "2.0.0-rc.1" [dev-dependencies] litesvm = "0.13.1" diff --git a/tokens/token-extensions/mint-close-authority/anchor/programs/mint-close-authority/src/instructions/close.rs b/tokens/token-extensions/mint-close-authority/anchor/programs/mint-close-authority/src/instructions/close.rs index f87c3ccb3..8b8da31cb 100644 --- a/tokens/token-extensions/mint-close-authority/anchor/programs/mint-close-authority/src/instructions/close.rs +++ b/tokens/token-extensions/mint-close-authority/anchor/programs/mint-close-authority/src/instructions/close.rs @@ -5,27 +5,27 @@ use anchor_spl::{ }; #[derive(Accounts)] -pub struct CloseAccountConstraints<'info> { +pub struct CloseAccountConstraints { #[account(mut)] - pub authority: Signer<'info>, + pub authority: Signer, #[account( mut, extensions::close_authority::authority = authority, )] - pub mint_account: InterfaceAccount<'info, Mint>, - pub token_program: Program<'info, Token2022>, + pub mint_account: InterfaceAccount, + pub token_program: Program, } -pub fn handler(context: Context) -> Result<()> { +pub fn handler(context: &mut Context) -> Result<()> { // cpi to token extensions programs to close mint account // alternatively, this can also be done in the client close_account(CpiContext::new( - context.accounts.token_program.key(), + context.accounts.token_program.address(), CloseAccount { - account: context.accounts.mint_account.to_account_info(), - destination: context.accounts.authority.to_account_info(), - authority: context.accounts.authority.to_account_info(), + account: context.accounts.mint_account.cpi_handle_mut(), + destination: context.accounts.authority.cpi_handle_mut(), + authority: context.accounts.authority.cpi_handle(), }, ))?; Ok(()) diff --git a/tokens/token-extensions/mint-close-authority/anchor/programs/mint-close-authority/src/instructions/initialize.rs b/tokens/token-extensions/mint-close-authority/anchor/programs/mint-close-authority/src/instructions/initialize.rs index ba2f2a47a..759866321 100644 --- a/tokens/token-extensions/mint-close-authority/anchor/programs/mint-close-authority/src/instructions/initialize.rs +++ b/tokens/token-extensions/mint-close-authority/anchor/programs/mint-close-authority/src/instructions/initialize.rs @@ -1,4 +1,5 @@ use anchor_lang::prelude::*; +use anchor_spl::mint; use anchor_spl::token_interface::{ spl_pod::optional_keys::OptionalNonZeroPubkey, spl_token_2022::{ @@ -12,9 +13,9 @@ use anchor_spl::token_interface::{ }; #[derive(Accounts)] -pub struct InitializeAccountConstraints<'info> { +pub struct InitializeAccountConstraints { #[account(mut)] - pub payer: Signer<'info>, + pub payer: Signer, #[account( init, @@ -23,26 +24,26 @@ pub struct InitializeAccountConstraints<'info> { mint::authority = payer, extensions::close_authority::authority = payer, )] - pub mint_account: InterfaceAccount<'info, Mint>, - pub token_program: Program<'info, Token2022>, - pub system_program: Program<'info, System>, + pub mint_account: InterfaceAccount, + pub token_program: Program, + pub system_program: Program, } -pub fn handler(mut context: Context) -> Result<()> { +pub fn handler(mut context: &mut Context) -> Result<()> { handle_check_mint_data(&mut context.accounts)?; Ok(()) } // helper to check mint data, and demonstrate how to read mint extension data within a program fn handle_check_mint_data(accounts: &mut InitializeAccountConstraints) -> Result<()> { - let mint = &accounts.mint_account.to_account_info(); + let mint = &accounts.mint_account.cpi_handle_mut(); let mint_data = mint.data.borrow(); let mint_with_extension = StateWithExtensions::::unpack(&mint_data)?; let extension_data = mint_with_extension.get_extension::()?; assert_eq!( extension_data.close_authority, - OptionalNonZeroPubkey::try_from(Some(accounts.payer.key()))? + OptionalNonZeroPubkey::try_from(Some(accounts.payer.address()))? ); msg!("{:?}", extension_data); diff --git a/tokens/token-extensions/mint-close-authority/anchor/programs/mint-close-authority/src/lib.rs b/tokens/token-extensions/mint-close-authority/anchor/programs/mint-close-authority/src/lib.rs index c5ac6ba42..eabebf8d5 100644 --- a/tokens/token-extensions/mint-close-authority/anchor/programs/mint-close-authority/src/lib.rs +++ b/tokens/token-extensions/mint-close-authority/anchor/programs/mint-close-authority/src/lib.rs @@ -9,11 +9,11 @@ declare_id!("AcfQLsYKuzprcCNH1n96pKKgAbAnZchwpbr3gbVN742n"); pub mod mint_close_authority { use super::*; - pub fn initialize(context: Context) -> Result<()> { + pub fn initialize(context: &mut Context) -> Result<()> { instructions::initialize::handler(context) } - pub fn close(context: Context) -> Result<()> { + pub fn close(context: &mut Context) -> Result<()> { instructions::close::handler(context) } } diff --git a/tokens/token-extensions/nft-meta-data-pointer/anchor-example/anchor/programs/extension_nft/Cargo.toml b/tokens/token-extensions/nft-meta-data-pointer/anchor-example/anchor/programs/extension_nft/Cargo.toml index 4e71abdec..1f3c355b8 100644 --- a/tokens/token-extensions/nft-meta-data-pointer/anchor-example/anchor/programs/extension_nft/Cargo.toml +++ b/tokens/token-extensions/nft-meta-data-pointer/anchor-example/anchor/programs/extension_nft/Cargo.toml @@ -14,15 +14,23 @@ cpi = ["no-entrypoint"] no-entrypoint = [] no-idl = [] no-log-ix-name = [] -idl-build = ["anchor-lang/idl-build", "anchor-spl/idl-build"] +idl-build = ["anchor-lang/idl-build"] anchor-debug = [] custom-heap = [] custom-panic = [] [dependencies] -anchor-lang = { version = "1.1.2", features = ["init-if-needed"] } -anchor-spl = { version = "1.1.2" } +anchor-lang = { version = "2.0.0-rc.1", features = ["compat"] } +# The `#[program]` macro expands to `wincode` paths for instruction-data +# (de)serialization, so the crate has to be a direct dependency. +wincode = { version = "0.5", features = ["derive"] } +# anchor-lang 2.0.0-rc.1 is built against wincode 0.5, but solana-address 2.7 +# moved to wincode 0.6. With both in the graph, `Address`'s wincode impls +# belong to the version the account derives are not using and every +# `SchemaRead`/`SchemaWrite` bound fails. 2.6.1 is the last 0.5-line release. +solana-address = ">=2.6, <2.7" +anchor-spl = { version = "2.0.0-rc.1" } # session-keys 3.1.1 is the first release that supports Anchor >=0.28,<2.0 # (so it builds against Anchor 1.0). Earlier 2.x releases pin Anchor <=0.30 # and fail to compile against the Anchor 1.0 / Solana 3.x API. Provides the diff --git a/tokens/token-extensions/nft-meta-data-pointer/anchor-example/anchor/programs/extension_nft/src/instructions/chop_tree.rs b/tokens/token-extensions/nft-meta-data-pointer/anchor-example/anchor/programs/extension_nft/src/instructions/chop_tree.rs index 597f9f0c6..fe43fbd2c 100644 --- a/tokens/token-extensions/nft-meta-data-pointer/anchor-example/anchor/programs/extension_nft/src/instructions/chop_tree.rs +++ b/tokens/token-extensions/nft-meta-data-pointer/anchor-example/anchor/programs/extension_nft/src/instructions/chop_tree.rs @@ -7,7 +7,7 @@ use anchor_spl::token_2022_extensions::spl_token_metadata_interface; use anchor_spl::token_interface::{spl_token_2022, Token2022}; use session_keys::{Session, SessionToken}; -pub fn chop_tree(context: Context, counter: u16, amount: u64) -> Result<()> { +pub fn chop_tree(context: &mut Context, counter: u16, amount: u64) -> Result<()> { // Save game_data bump on first creation (init_if_needed). See init_player.rs // for the same pattern. let game_data_bump = context.bumps.game_data; @@ -42,14 +42,14 @@ pub fn chop_tree(context: Context, counter: u16, amo invoke_signed( &spl_token_metadata_interface::instruction::update_field( &spl_token_2022::id(), - context.accounts.mint.to_account_info().key, - context.accounts.nft_authority.to_account_info().key, + context.accounts.mint.cpi_handle_mut().key, + context.accounts.nft_authority.cpi_handle_mut().key, spl_token_metadata_interface::state::Field::Key("wood".to_string()), context.accounts.player.wood.to_string(), ), &[ - context.accounts.mint.to_account_info().clone(), - context.accounts.nft_authority.to_account_info().clone(), + context.accounts.mint.cpi_handle().clone(), + context.accounts.nft_authority.cpi_handle().clone(), ], signer, )?; @@ -59,23 +59,23 @@ pub fn chop_tree(context: Context, counter: u16, amo #[derive(Accounts, Session)] #[instruction(level_seed: String)] -pub struct ChopTreeAccountConstraints<'info> { +pub struct ChopTreeAccountConstraints { #[session( // The ephemeral key pair signing the transaction signer = signer, // The authority of the user account which must have created the session - authority = player.authority.key() + authority = player.authority.address() )] // Session Tokens are passed as optional accounts - pub session_token: Option>, + pub session_token: Option>, // There is one PlayerData account #[account( mut, - seeds = [b"player".as_ref(), player.authority.key().as_ref()], + seeds = [b"player".as_ref(), player.authority.address().as_ref()], bump, )] - pub player: Account<'info, PlayerData>, + pub player: BorshAccount, // There can be multiple levels the seed for the level is passed in the instruction // First player starting a new level will pay for the account in the current setup @@ -86,14 +86,14 @@ pub struct ChopTreeAccountConstraints<'info> { seeds = [level_seed.as_ref()], bump, )] - pub game_data: Account<'info, GameData>, + pub game_data: BorshAccount, #[account(mut)] - pub signer: Signer<'info>, - pub system_program: Program<'info, System>, + pub signer: Signer, + pub system_program: Program, /// CHECK: Make sure the ata to the mint is actually owned by the signer #[account(mut)] - pub mint: UncheckedAccount<'info>, + pub mint: UncheckedAccount, #[account( init_if_needed, seeds = [b"nft_authority".as_ref()], @@ -101,6 +101,6 @@ pub struct ChopTreeAccountConstraints<'info> { space = NftAuthority::DISCRIMINATOR.len() + NftAuthority::INIT_SPACE, payer = signer, )] - pub nft_authority: Account<'info, NftAuthority>, - pub token_program: Program<'info, Token2022>, + pub nft_authority: BorshAccount, + pub token_program: Program, } diff --git a/tokens/token-extensions/nft-meta-data-pointer/anchor-example/anchor/programs/extension_nft/src/instructions/init_player.rs b/tokens/token-extensions/nft-meta-data-pointer/anchor-example/anchor/programs/extension_nft/src/instructions/init_player.rs index c38c3ba64..6d8b9d72a 100644 --- a/tokens/token-extensions/nft-meta-data-pointer/anchor-example/anchor/programs/extension_nft/src/instructions/init_player.rs +++ b/tokens/token-extensions/nft-meta-data-pointer/anchor-example/anchor/programs/extension_nft/src/instructions/init_player.rs @@ -3,10 +3,10 @@ use crate::state::player_data::PlayerData; use crate::{constants::MAX_ENERGY, GameData}; use anchor_lang::prelude::*; -pub fn handle_init_player(context: Context) -> Result<()> { +pub fn handle_init_player(context: &mut Context) -> Result<()> { context.accounts.player.energy = MAX_ENERGY; context.accounts.player.last_login = Clock::get()?.unix_timestamp; - context.accounts.player.authority = context.accounts.signer.key(); + context.accounts.player.authority = *context.accounts.signer.address(); context.accounts.player.bump = context.bumps.player; // init_if_needed - only save bump if this is the first init. Subsequent // calls reuse the existing account and must not overwrite the stored bump @@ -20,15 +20,15 @@ pub fn handle_init_player(context: Context) -> Res #[derive(Accounts)] #[instruction(level_seed: String)] -pub struct InitPlayerAccountConstraints<'info> { +pub struct InitPlayerAccountConstraints { #[account( init, payer = signer, space = PlayerData::DISCRIMINATOR.len() + PlayerData::INIT_SPACE, - seeds = [b"player".as_ref(), signer.key().as_ref()], + seeds = [b"player".as_ref(), signer.address().as_ref()], bump, )] - pub player: Account<'info, PlayerData>, + pub player: BorshAccount, #[account( init_if_needed, @@ -37,9 +37,9 @@ pub struct InitPlayerAccountConstraints<'info> { seeds = [level_seed.as_ref()], bump, )] - pub game_data: Account<'info, GameData>, + pub game_data: BorshAccount, #[account(mut)] - pub signer: Signer<'info>, - pub system_program: Program<'info, System>, + pub signer: Signer, + pub system_program: Program, } diff --git a/tokens/token-extensions/nft-meta-data-pointer/anchor-example/anchor/programs/extension_nft/src/instructions/mint_nft.rs b/tokens/token-extensions/nft-meta-data-pointer/anchor-example/anchor/programs/extension_nft/src/instructions/mint_nft.rs index 22fa0c986..6335506d8 100644 --- a/tokens/token-extensions/nft-meta-data-pointer/anchor-example/anchor/programs/extension_nft/src/instructions/mint_nft.rs +++ b/tokens/token-extensions/nft-meta-data-pointer/anchor-example/anchor/programs/extension_nft/src/instructions/mint_nft.rs @@ -13,7 +13,7 @@ use anchor_spl::{ }, }; -pub fn handle_mint_nft(context: Context) -> Result<()> { +pub fn handle_mint_nft(context: &mut Context) -> Result<()> { msg!("Mint nft with meta data extension and additional meta data"); let space = @@ -29,7 +29,7 @@ pub fn handle_mint_nft(context: Context) -> Result<() // so we just over-allocate enough room at creation time. let meta_data_space = TOKEN_METADATA_EXTENSION_SPACE; - let lamports_required = Rent::get()?.minimum_balance(space + meta_data_space); + let lamports_required = Rent::get()?.try_minimum_balance(space + meta_data_space)?; msg!( "Create Mint and metadata account size and cost: {} lamports: {}", @@ -39,23 +39,23 @@ pub fn handle_mint_nft(context: Context) -> Result<() system_program::create_account( CpiContext::new( - context.accounts.token_program.key(), + context.accounts.token_program.address(), system_program::CreateAccount { - from: context.accounts.signer.to_account_info(), - to: context.accounts.mint.to_account_info(), + from: context.accounts.signer.cpi_handle_mut(), + to: context.accounts.mint.cpi_handle_mut(), }, ), lamports_required, space as u64, - &context.accounts.token_program.key(), + &context.accounts.token_program.address(), )?; // Assign the mint to the token program system_program::assign( CpiContext::new( - context.accounts.token_program.key(), + context.accounts.token_program.address(), system_program::Assign { - account_to_assign: context.accounts.mint.to_account_info(), + account_to_assign: context.accounts.mint.cpi_handle_mut(), }, ), &token_2022::ID, @@ -65,9 +65,9 @@ pub fn handle_mint_nft(context: Context) -> Result<() let init_meta_data_pointer_ix = match spl_token_2022::extension::metadata_pointer::instruction::initialize( &Token2022::id(), - &context.accounts.mint.key(), - Some(context.accounts.nft_authority.key()), - Some(context.accounts.mint.key()), + &context.accounts.mint.address(), + Some(*context.accounts.nft_authority.address()), + Some(*context.accounts.mint.address()), ) { Ok(ix) => ix, Err(_) => { @@ -78,20 +78,20 @@ pub fn handle_mint_nft(context: Context) -> Result<() invoke( &init_meta_data_pointer_ix, &[ - context.accounts.mint.to_account_info(), - context.accounts.nft_authority.to_account_info(), + context.accounts.mint.cpi_handle(), + context.accounts.nft_authority.cpi_handle(), ], )?; // Initialize the mint cpi let mint_cpi_ix = CpiContext::new( - context.accounts.token_program.key(), + context.accounts.token_program.address(), token_2022::InitializeMint2 { - mint: context.accounts.mint.to_account_info(), + mint: context.accounts.mint.cpi_handle_mut(), }, ); - token_2022::initialize_mint2(mint_cpi_ix, 0, &context.accounts.nft_authority.key(), None) + token_2022::initialize_mint2(mint_cpi_ix, 0, &context.accounts.nft_authority.address(), None) .unwrap(); // We use a PDA as a mint authority for the metadata account because @@ -102,16 +102,16 @@ pub fn handle_mint_nft(context: Context) -> Result<() msg!( "Init metadata {0}", - context.accounts.nft_authority.to_account_info().key + context.accounts.nft_authority.cpi_handle_mut().key ); // Init the metadata account let init_token_meta_data_ix = &spl_token_metadata_interface::instruction::initialize( &spl_token_2022::id(), context.accounts.mint.key, - context.accounts.nft_authority.to_account_info().key, + context.accounts.nft_authority.cpi_handle_mut().key, context.accounts.mint.key, - context.accounts.nft_authority.to_account_info().key, + context.accounts.nft_authority.cpi_handle_mut().key, "Beaver".to_string(), "BVA".to_string(), "https://arweave.net/MHK3Iopy0GgvDoM7LkkiAdg7pQqExuuWvedApCnzfj0".to_string(), @@ -120,8 +120,8 @@ pub fn handle_mint_nft(context: Context) -> Result<() invoke_signed( init_token_meta_data_ix, &[ - context.accounts.mint.to_account_info().clone(), - context.accounts.nft_authority.to_account_info().clone(), + context.accounts.mint.cpi_handle().clone(), + context.accounts.nft_authority.cpi_handle().clone(), ], signer, )?; @@ -131,38 +131,38 @@ pub fn handle_mint_nft(context: Context) -> Result<() &spl_token_metadata_interface::instruction::update_field( &spl_token_2022::id(), context.accounts.mint.key, - context.accounts.nft_authority.to_account_info().key, + context.accounts.nft_authority.cpi_handle_mut().key, spl_token_metadata_interface::state::Field::Key("level".to_string()), "1".to_string(), ), &[ - context.accounts.mint.to_account_info().clone(), - context.accounts.nft_authority.to_account_info().clone(), + context.accounts.mint.cpi_handle().clone(), + context.accounts.nft_authority.cpi_handle().clone(), ], signer, )?; // Create the associated token account associated_token::create(CpiContext::new( - context.accounts.associated_token_program.key(), + context.accounts.associated_token_program.address(), associated_token::Create { - payer: context.accounts.signer.to_account_info(), - associated_token: context.accounts.token_account.to_account_info(), - authority: context.accounts.signer.to_account_info(), - mint: context.accounts.mint.to_account_info(), - system_program: context.accounts.system_program.to_account_info(), - token_program: context.accounts.token_program.to_account_info(), + payer: context.accounts.signer.cpi_handle_mut(), + associated_token: context.accounts.token_account.cpi_handle_mut(), + authority: context.accounts.signer.cpi_handle(), + mint: context.accounts.mint.cpi_handle(), + system_program: context.accounts.system_program.cpi_handle(), + token_program: context.accounts.token_program.cpi_handle(), }, ))?; // Mint one token to the associated token account of the player token_2022::mint_to( CpiContext::new_with_signer( - context.accounts.token_program.key(), + context.accounts.token_program.address(), token_2022::MintTo { - mint: context.accounts.mint.to_account_info(), - to: context.accounts.token_account.to_account_info(), - authority: context.accounts.nft_authority.to_account_info(), + mint: context.accounts.mint.cpi_handle_mut(), + to: context.accounts.token_account.cpi_handle_mut(), + authority: context.accounts.nft_authority.cpi_handle(), }, signer, ), @@ -172,10 +172,10 @@ pub fn handle_mint_nft(context: Context) -> Result<() // Freeze the mint authority so no more tokens can be minted to make it an NFT token_2022::set_authority( CpiContext::new_with_signer( - context.accounts.token_program.key(), + context.accounts.token_program.address(), token_2022::SetAuthority { - current_authority: context.accounts.nft_authority.to_account_info(), - account_or_mint: context.accounts.mint.to_account_info(), + current_authority: context.accounts.nft_authority.cpi_handle(), + account_or_mint: context.accounts.mint.cpi_handle_mut(), }, signer, ), @@ -187,18 +187,18 @@ pub fn handle_mint_nft(context: Context) -> Result<() } #[derive(Accounts)] -pub struct MintNftAccountConstraints<'info> { +pub struct MintNftAccountConstraints { #[account(mut)] - pub signer: Signer<'info>, - pub system_program: Program<'info, System>, - pub token_program: Program<'info, Token2022>, + pub signer: Signer, + pub system_program: Program, + pub token_program: Program, /// CHECK: We will create this one for the user #[account(mut)] - pub token_account: UncheckedAccount<'info>, + pub token_account: UncheckedAccount, #[account(mut)] - pub mint: Signer<'info>, - pub rent: Sysvar<'info, Rent>, - pub associated_token_program: Program<'info, AssociatedToken>, + pub mint: Signer, + pub rent: Sysvar, + pub associated_token_program: Program, #[account( init_if_needed, seeds = [b"nft_authority".as_ref()], @@ -206,9 +206,9 @@ pub struct MintNftAccountConstraints<'info> { space = NftAuthority::DISCRIMINATOR.len() + NftAuthority::INIT_SPACE, payer = signer )] - pub nft_authority: Account<'info, NftAuthority>, + pub nft_authority: BorshAccount, } -#[account] +#[account(borsh)] #[derive(InitSpace)] pub struct NftAuthority {} diff --git a/tokens/token-extensions/nft-meta-data-pointer/anchor-example/anchor/programs/extension_nft/src/lib.rs b/tokens/token-extensions/nft-meta-data-pointer/anchor-example/anchor/programs/extension_nft/src/lib.rs index 6d513f1bc..9d153b573 100644 --- a/tokens/token-extensions/nft-meta-data-pointer/anchor-example/anchor/programs/extension_nft/src/lib.rs +++ b/tokens/token-extensions/nft-meta-data-pointer/anchor-example/anchor/programs/extension_nft/src/lib.rs @@ -24,7 +24,7 @@ declare_id!("9aZZ7TJ2fQZxY8hMtWXywp5y6BgqC4N2BPcr9FDT47sW"); pub mod extension_nft { use super::*; - pub fn init_player(context: Context, _level_seed: String) -> Result<()> { + pub fn init_player(context: &mut Context, _level_seed: String) -> Result<()> { init_player::handle_init_player(context) } @@ -36,14 +36,14 @@ pub mod extension_nft { // context binding by the literal name `ctx`, so this handler's context // parameter must be named `ctx` (not `context`) for the macro to expand. #[session_auth_or( - ctx.accounts.player.authority.key() == ctx.accounts.signer.key(), + ctx.accounts.player.authority.address() == ctx.accounts.signer.address(), GameErrorCode::WrongAuthority )] - pub fn chop_tree(ctx: Context, _level_seed: String, counter: u16) -> Result<()> { + pub fn chop_tree(ctx: &mut Context, _level_seed: String, counter: u16) -> Result<()> { chop_tree::chop_tree(ctx, counter, 1) } - pub fn mint_nft(context: Context) -> Result<()> { + pub fn mint_nft(context: &mut Context) -> Result<()> { mint_nft::handle_mint_nft(context) } } diff --git a/tokens/token-extensions/nft-meta-data-pointer/anchor-example/anchor/programs/extension_nft/src/state/game_data.rs b/tokens/token-extensions/nft-meta-data-pointer/anchor-example/anchor/programs/extension_nft/src/state/game_data.rs index 03174be68..887da9037 100644 --- a/tokens/token-extensions/nft-meta-data-pointer/anchor-example/anchor/programs/extension_nft/src/state/game_data.rs +++ b/tokens/token-extensions/nft-meta-data-pointer/anchor-example/anchor/programs/extension_nft/src/state/game_data.rs @@ -2,7 +2,7 @@ use anchor_lang::prelude::*; use crate::constants::MAX_WOOD_PER_TREE; -#[account] +#[account(borsh)] #[derive(InitSpace)] pub struct GameData { pub total_wood_collected: u64, diff --git a/tokens/token-extensions/nft-meta-data-pointer/anchor-example/anchor/programs/extension_nft/src/state/player_data.rs b/tokens/token-extensions/nft-meta-data-pointer/anchor-example/anchor/programs/extension_nft/src/state/player_data.rs index 90c6fa64c..663ee3e7f 100644 --- a/tokens/token-extensions/nft-meta-data-pointer/anchor-example/anchor/programs/extension_nft/src/state/player_data.rs +++ b/tokens/token-extensions/nft-meta-data-pointer/anchor-example/anchor/programs/extension_nft/src/state/player_data.rs @@ -1,10 +1,10 @@ use crate::constants::*; use anchor_lang::prelude::*; -#[account] +#[account(borsh)] #[derive(InitSpace)] pub struct PlayerData { - pub authority: Pubkey, + pub authority: Address, /// Player name. Capped at 32 bytes - a conservative upper bound for /// display names; bump `#[max_len]` if you need room for emoji-heavy /// or international names (each non-ASCII codepoint costs up to 4 bytes). diff --git a/tokens/token-extensions/non-transferable/anchor/programs/non-transferable/Cargo.toml b/tokens/token-extensions/non-transferable/anchor/programs/non-transferable/Cargo.toml index fb874ec2c..d468682fb 100644 --- a/tokens/token-extensions/non-transferable/anchor/programs/non-transferable/Cargo.toml +++ b/tokens/token-extensions/non-transferable/anchor/programs/non-transferable/Cargo.toml @@ -14,11 +14,19 @@ cpi = ["no-entrypoint"] no-entrypoint = [] no-idl = [] no-log-ix-name = [] -idl-build = ["anchor-lang/idl-build", "anchor-spl/idl-build"] +idl-build = ["anchor-lang/idl-build"] [dependencies] -anchor-lang = "1.1.2" -anchor-spl = "1.1.2" +anchor-lang = "2.0.0-rc.1" +# The `#[program]` macro expands to `wincode` paths for instruction-data +# (de)serialization, so the crate has to be a direct dependency. +wincode = { version = "0.5", features = ["derive"] } +# anchor-lang 2.0.0-rc.1 is built against wincode 0.5, but solana-address 2.7 +# moved to wincode 0.6. With both in the graph, `Address`'s wincode impls +# belong to the version the account derives are not using and every +# `SchemaRead`/`SchemaWrite` bound fails. 2.6.1 is the last 0.5-line release. +solana-address = ">=2.6, <2.7" +anchor-spl = "2.0.0-rc.1" [dev-dependencies] litesvm = "0.13.1" diff --git a/tokens/token-extensions/non-transferable/anchor/programs/non-transferable/src/lib.rs b/tokens/token-extensions/non-transferable/anchor/programs/non-transferable/src/lib.rs index 6e3447b08..c792efb70 100644 --- a/tokens/token-extensions/non-transferable/anchor/programs/non-transferable/src/lib.rs +++ b/tokens/token-extensions/non-transferable/anchor/programs/non-transferable/src/lib.rs @@ -17,61 +17,60 @@ pub mod non_transferable { // There is currently not an anchor constraint to automatically initialize the NonTransferable extension // We can manually create and initialize the mint account via CPIs in the instruction handler - pub fn initialize(context: Context) -> Result<()> { + pub fn initialize(context: &mut Context) -> Result<()> { // Calculate space required for mint and extension data let mint_size = ExtensionType::try_calculate_account_len::(&[ExtensionType::NonTransferable])?; // Calculate minimum lamports required for size of mint account with extensions - let lamports = (Rent::get()?).minimum_balance(mint_size); + let lamports = Rent::get()?.try_minimum_balance(mint_size)?; // Invoke System Program to create new account with space for mint and extension data create_account( CpiContext::new( - context.accounts.system_program.key(), + context.accounts.system_program.address(), CreateAccount { - from: context.accounts.payer.to_account_info(), - to: context.accounts.mint_account.to_account_info(), + from: context.accounts.payer.cpi_handle_mut(), + to: context.accounts.mint_account.cpi_handle_mut(), }, ), lamports, // Lamports mint_size as u64, // Space - &context.accounts.token_program.key(), // Owner Program + &context.accounts.token_program.address(), // Owner Program )?; // Initialize the NonTransferable extension // This instruction must come before the instruction to initialize the mint data non_transferable_mint_initialize(CpiContext::new( - context.accounts.token_program.key(), + context.accounts.token_program.address(), NonTransferableMintInitialize { - token_program_id: context.accounts.token_program.to_account_info(), - mint: context.accounts.mint_account.to_account_info(), + mint: context.accounts.mint_account.cpi_handle_mut(), }, ))?; // Initialize the standard mint account data initialize_mint2( CpiContext::new( - context.accounts.token_program.key(), + context.accounts.token_program.address(), InitializeMint2 { - mint: context.accounts.mint_account.to_account_info(), + mint: context.accounts.mint_account.cpi_handle_mut(), }, ), 2, // decimals - &context.accounts.payer.key(), // mint authority - Some(&context.accounts.payer.key()), // freeze authority + &context.accounts.payer.address(), // mint authority + Some(&context.accounts.payer.address()), // freeze authority )?; Ok(()) } } #[derive(Accounts)] -pub struct InitializeAccountConstraints<'info> { +pub struct InitializeAccountConstraints { #[account(mut)] - pub payer: Signer<'info>, + pub payer: Signer, #[account(mut)] - pub mint_account: Signer<'info>, + pub mint_account: Signer, - pub token_program: Program<'info, Token2022>, - pub system_program: Program<'info, System>, + pub token_program: Program, + pub system_program: Program, } diff --git a/tokens/token-extensions/permanent-delegate/anchor/programs/permanent-delegate/Cargo.toml b/tokens/token-extensions/permanent-delegate/anchor/programs/permanent-delegate/Cargo.toml index 4872f4272..2b6fb0b21 100644 --- a/tokens/token-extensions/permanent-delegate/anchor/programs/permanent-delegate/Cargo.toml +++ b/tokens/token-extensions/permanent-delegate/anchor/programs/permanent-delegate/Cargo.toml @@ -14,15 +14,23 @@ cpi = ["no-entrypoint"] no-entrypoint = [] no-idl = [] no-log-ix-name = [] -idl-build = ["anchor-lang/idl-build", "anchor-spl/idl-build"] +idl-build = ["anchor-lang/idl-build"] anchor-debug = [] custom-heap = [] custom-panic = [] [dependencies] -anchor-lang = "1.1.2" -anchor-spl = "1.1.2" +anchor-lang = "2.0.0-rc.1" +# The `#[program]` macro expands to `wincode` paths for instruction-data +# (de)serialization, so the crate has to be a direct dependency. +wincode = { version = "0.5", features = ["derive"] } +# anchor-lang 2.0.0-rc.1 is built against wincode 0.5, but solana-address 2.7 +# moved to wincode 0.6. With both in the graph, `Address`'s wincode impls +# belong to the version the account derives are not using and every +# `SchemaRead`/`SchemaWrite` bound fails. 2.6.1 is the last 0.5-line release. +solana-address = ">=2.6, <2.7" +anchor-spl = "2.0.0-rc.1" [dev-dependencies] litesvm = "0.13.1" diff --git a/tokens/token-extensions/permanent-delegate/anchor/programs/permanent-delegate/src/instructions/initialize.rs b/tokens/token-extensions/permanent-delegate/anchor/programs/permanent-delegate/src/instructions/initialize.rs index 0879e5e51..fa9e387c6 100644 --- a/tokens/token-extensions/permanent-delegate/anchor/programs/permanent-delegate/src/instructions/initialize.rs +++ b/tokens/token-extensions/permanent-delegate/anchor/programs/permanent-delegate/src/instructions/initialize.rs @@ -1,4 +1,5 @@ use anchor_lang::prelude::*; +use anchor_spl::mint; use anchor_spl::{ token_2022::spl_token_2022::extension::permanent_delegate::PermanentDelegate, token_interface::{ @@ -12,9 +13,9 @@ use anchor_spl::{ }; #[derive(Accounts)] -pub struct InitializeAccountConstraints<'info> { +pub struct InitializeAccountConstraints { #[account(mut)] - pub payer: Signer<'info>, + pub payer: Signer, #[account( init, @@ -23,28 +24,28 @@ pub struct InitializeAccountConstraints<'info> { mint::authority = payer, extensions::permanent_delegate::delegate = payer, )] - pub mint_account: InterfaceAccount<'info, Mint>, - pub token_program: Program<'info, Token2022>, - pub system_program: Program<'info, System>, + pub mint_account: InterfaceAccount, + pub token_program: Program, + pub system_program: Program, } // helper to check mint data, and demonstrate how to read mint extension data within a program fn check_mint_data(accounts: &mut InitializeAccountConstraints) -> Result<()> { - let mint = &accounts.mint_account.to_account_info(); + let mint = &accounts.mint_account.cpi_handle_mut(); let mint_data = mint.data.borrow(); let mint_with_extension = StateWithExtensions::::unpack(&mint_data)?; let extension_data = mint_with_extension.get_extension::()?; assert_eq!( extension_data.delegate, - OptionalNonZeroPubkey::try_from(Some(accounts.payer.key()))? + OptionalNonZeroPubkey::try_from(Some(accounts.payer.address()))? ); msg!("{:?}", extension_data); Ok(()) } -pub fn handler(mut context: Context) -> Result<()> { +pub fn handler(mut context: &mut Context) -> Result<()> { check_mint_data(&mut context.accounts)?; Ok(()) } diff --git a/tokens/token-extensions/permanent-delegate/anchor/programs/permanent-delegate/src/lib.rs b/tokens/token-extensions/permanent-delegate/anchor/programs/permanent-delegate/src/lib.rs index 5c2005fa0..7667da049 100644 --- a/tokens/token-extensions/permanent-delegate/anchor/programs/permanent-delegate/src/lib.rs +++ b/tokens/token-extensions/permanent-delegate/anchor/programs/permanent-delegate/src/lib.rs @@ -9,7 +9,7 @@ declare_id!("A9rxKS84ZoJVyeTfQbCEfxME2vvAM4uwSMjkmhR5XWb1"); pub mod permanent_delegate { use super::*; - pub fn initialize(context: Context) -> Result<()> { + pub fn initialize(context: &mut Context) -> Result<()> { instructions::initialize::handler(context) } } diff --git a/tokens/token-extensions/transfer-fee/anchor/programs/transfer-fee/Cargo.toml b/tokens/token-extensions/transfer-fee/anchor/programs/transfer-fee/Cargo.toml index 8de99e42a..7404633bc 100644 --- a/tokens/token-extensions/transfer-fee/anchor/programs/transfer-fee/Cargo.toml +++ b/tokens/token-extensions/transfer-fee/anchor/programs/transfer-fee/Cargo.toml @@ -14,11 +14,19 @@ cpi = ["no-entrypoint"] no-entrypoint = [] no-idl = [] no-log-ix-name = [] -idl-build = ["anchor-lang/idl-build", "anchor-spl/idl-build"] +idl-build = ["anchor-lang/idl-build"] [dependencies] -anchor-lang = { version = "1.1.2", features = ["init-if-needed"] } -anchor-spl = "1.1.2" +anchor-lang = "2.0.0-rc.1" +# The `#[program]` macro expands to `wincode` paths for instruction-data +# (de)serialization, so the crate has to be a direct dependency. +wincode = { version = "0.5", features = ["derive"] } +# anchor-lang 2.0.0-rc.1 is built against wincode 0.5, but solana-address 2.7 +# moved to wincode 0.6. With both in the graph, `Address`'s wincode impls +# belong to the version the account derives are not using and every +# `SchemaRead`/`SchemaWrite` bound fails. 2.6.1 is the last 0.5-line release. +solana-address = ">=2.6, <2.7" +anchor-spl = "2.0.0-rc.1" [dev-dependencies] litesvm = "0.13.1" diff --git a/tokens/token-extensions/transfer-fee/anchor/programs/transfer-fee/src/instructions/harvest.rs b/tokens/token-extensions/transfer-fee/anchor/programs/transfer-fee/src/instructions/harvest.rs index 870e29474..13ba01b0b 100644 --- a/tokens/token-extensions/transfer-fee/anchor/programs/transfer-fee/src/instructions/harvest.rs +++ b/tokens/token-extensions/transfer-fee/anchor/programs/transfer-fee/src/instructions/harvest.rs @@ -4,15 +4,15 @@ use anchor_spl::token_interface::{ }; #[derive(Accounts)] -pub struct HarvestAccountConstraints<'info> { +pub struct HarvestAccountConstraints { #[account(mut)] - pub mint_account: InterfaceAccount<'info, Mint>, - pub token_program: Program<'info, Token2022>, + pub mint_account: InterfaceAccount, + pub token_program: Program, } // transfer fees are stored directly on the recipient token account and must be "harvested" // "harvesting" transfers fees accumulated on token accounts to the mint account -pub fn process_harvest<'info>(context: Context<'info, HarvestAccountConstraints<'info>>) -> Result<()> { +pub fn process_harvest<'info>(context: &mut Context<'info, HarvestAccountConstraints<'info>>) -> Result<()> { // Using remaining accounts to allow for passing in an unknown number of token accounts to harvest from // Check that remaining accounts are token accounts for the mint to harvest to let sources = context @@ -21,17 +21,16 @@ pub fn process_harvest<'info>(context: Context<'info, HarvestAccountConstraints< .filter_map(|account| { InterfaceAccount::::try_from(account) .ok() - .filter(|token_account| token_account.mint == context.accounts.mint_account.key()) - .map(|_| account.to_account_info()) + .filter(|token_account| token_account.mint == context.accounts.mint_account.address()) + .map(|_| account.cpi_handle_mut()) }) .collect::>(); harvest_withheld_tokens_to_mint( CpiContext::new( - context.accounts.token_program.key(), + context.accounts.token_program.address(), HarvestWithheldTokensToMint { - token_program_id: context.accounts.token_program.to_account_info(), - mint: context.accounts.mint_account.to_account_info(), + mint: context.accounts.mint_account.cpi_handle_mut(), }, ), sources, // token accounts to harvest from diff --git a/tokens/token-extensions/transfer-fee/anchor/programs/transfer-fee/src/instructions/initialize.rs b/tokens/token-extensions/transfer-fee/anchor/programs/transfer-fee/src/instructions/initialize.rs index 1ce456378..92521103a 100644 --- a/tokens/token-extensions/transfer-fee/anchor/programs/transfer-fee/src/instructions/initialize.rs +++ b/tokens/token-extensions/transfer-fee/anchor/programs/transfer-fee/src/instructions/initialize.rs @@ -20,20 +20,20 @@ use anchor_spl::{ }; #[derive(Accounts)] -pub struct InitializeAccountConstraints<'info> { +pub struct InitializeAccountConstraints { #[account(mut)] - pub payer: Signer<'info>, + pub payer: Signer, #[account(mut)] - pub mint_account: Signer<'info>, + pub mint_account: Signer, - pub token_program: Program<'info, Token2022>, - pub system_program: Program<'info, System>, + pub token_program: Program, + pub system_program: Program, } // There is currently not an anchor constraint to automatically initialize the TransferFeeConfig extension // We can manually create and initialize the mint account via CPIs in the instruction handler pub fn handle_process_initialize( - context: Context, + context: &mut Context, transfer_fee_basis_points: u16, maximum_fee: u64, ) -> Result<()> { @@ -42,34 +42,33 @@ pub fn handle_process_initialize( ExtensionType::try_calculate_account_len::(&[ExtensionType::TransferFeeConfig])?; // Calculate minimum lamports required for size of mint account with extensions - let lamports = (Rent::get()?).minimum_balance(mint_size); + let lamports = Rent::get()?.try_minimum_balance(mint_size)?; // Invoke System Program to create new account with space for mint and extension data create_account( CpiContext::new( - context.accounts.system_program.key(), + context.accounts.system_program.address(), CreateAccount { - from: context.accounts.payer.to_account_info(), - to: context.accounts.mint_account.to_account_info(), + from: context.accounts.payer.cpi_handle_mut(), + to: context.accounts.mint_account.cpi_handle_mut(), }, ), lamports, // Lamports mint_size as u64, // Space - &context.accounts.token_program.key(), // Owner Program + &context.accounts.token_program.address(), // Owner Program )?; // Initialize the transfer fee extension data // This instruction must come before the instruction to initialize the mint data transfer_fee_initialize( CpiContext::new( - context.accounts.token_program.key(), + context.accounts.token_program.address(), TransferFeeInitialize { - token_program_id: context.accounts.token_program.to_account_info(), - mint: context.accounts.mint_account.to_account_info(), + mint: context.accounts.mint_account.cpi_handle_mut(), }, ), - Some(&context.accounts.payer.key()), // transfer fee config authority (update fee) - Some(&context.accounts.payer.key()), // withdraw authority (withdraw fees) + Some(&context.accounts.payer.address()), // transfer fee config authority (update fee) + Some(&context.accounts.payer.address()), // withdraw authority (withdraw fees) transfer_fee_basis_points, // transfer fee basis points (% fee per transfer) maximum_fee, // maximum fee (maximum units of token per transfer) )?; @@ -77,14 +76,14 @@ pub fn handle_process_initialize( // Initialize the standard mint account data initialize_mint2( CpiContext::new( - context.accounts.token_program.key(), + context.accounts.token_program.address(), InitializeMint2 { - mint: context.accounts.mint_account.to_account_info(), + mint: context.accounts.mint_account.cpi_handle_mut(), }, ), 2, // decimals - &context.accounts.payer.key(), // mint authority - Some(&context.accounts.payer.key()), // freeze authority + &context.accounts.payer.address(), // mint authority + Some(&context.accounts.payer.address()), // freeze authority )?; handle_check_mint_data(&context.accounts)?; @@ -93,19 +92,19 @@ pub fn handle_process_initialize( // helper to demonstrate how to read mint extension data within a program pub fn handle_check_mint_data(accounts: &InitializeAccountConstraints) -> Result<()> { - let mint = &accounts.mint_account.to_account_info(); + let mint = &accounts.mint_account.cpi_handle_mut(); let mint_data = mint.data.borrow(); let mint_with_extension = StateWithExtensions::::unpack(&mint_data)?; let extension_data = mint_with_extension.get_extension::()?; assert_eq!( extension_data.transfer_fee_config_authority, - OptionalNonZeroPubkey::try_from(Some(accounts.payer.key()))? + OptionalNonZeroPubkey::try_from(Some(accounts.payer.address()))? ); assert_eq!( extension_data.withdraw_withheld_authority, - OptionalNonZeroPubkey::try_from(Some(accounts.payer.key()))? + OptionalNonZeroPubkey::try_from(Some(accounts.payer.address()))? ); msg!("{:?}", extension_data); diff --git a/tokens/token-extensions/transfer-fee/anchor/programs/transfer-fee/src/instructions/transfer.rs b/tokens/token-extensions/transfer-fee/anchor/programs/transfer-fee/src/instructions/transfer.rs index 20846e2e7..9df0d972c 100644 --- a/tokens/token-extensions/transfer-fee/anchor/programs/transfer-fee/src/instructions/transfer.rs +++ b/tokens/token-extensions/transfer-fee/anchor/programs/transfer-fee/src/instructions/transfer.rs @@ -13,20 +13,20 @@ use anchor_spl::{ }; #[derive(Accounts)] -pub struct TransferAccountConstraints<'info> { +pub struct TransferAccountConstraints { #[account(mut)] - pub sender: Signer<'info>, - pub recipient: SystemAccount<'info>, + pub sender: Signer, + pub recipient: SystemAccount, #[account(mut)] - pub mint_account: InterfaceAccount<'info, Mint>, + pub mint_account: InterfaceAccount, #[account( mut, associated_token::mint = mint_account, associated_token::authority = sender, associated_token::token_program = token_program )] - pub sender_token_account: InterfaceAccount<'info, TokenAccount>, + pub sender_token_account: InterfaceAccount, #[account( init_if_needed, payer = sender, @@ -34,18 +34,18 @@ pub struct TransferAccountConstraints<'info> { associated_token::authority = recipient, associated_token::token_program = token_program )] - pub recipient_token_account: InterfaceAccount<'info, TokenAccount>, - pub token_program: Program<'info, Token2022>, - pub associated_token_program: Program<'info, AssociatedToken>, - pub system_program: Program<'info, System>, + pub recipient_token_account: InterfaceAccount, + pub token_program: Program, + pub associated_token_program: Program, + pub system_program: Program, } // transfer fees are automatically deducted from the transfer amount // recipients receives (transfer amount - fees) // transfer fees are stored directly on the recipient token account and must be "harvested" -pub fn handle_process_transfer(context: Context, amount: u64) -> Result<()> { +pub fn handle_process_transfer(context: &mut Context, amount: u64) -> Result<()> { // read mint account extension data - let mint = &context.accounts.mint_account.to_account_info(); + let mint = &context.accounts.mint_account.cpi_handle_mut(); let mint_data = mint.data.borrow(); let mint_with_extension = StateWithExtensions::::unpack(&mint_data)?; let extension_data = mint_with_extension.get_extension::()?; @@ -55,17 +55,16 @@ pub fn handle_process_transfer(context: Context, amo let fee = extension_data.calculate_epoch_fee(epoch, amount).unwrap(); // mint account decimals - let decimals = context.accounts.mint_account.decimals; + let decimals = context.accounts.mint_account.decimals(); transfer_checked_with_fee( CpiContext::new( - context.accounts.token_program.key(), + context.accounts.token_program.address(), TransferCheckedWithFee { - token_program_id: context.accounts.token_program.to_account_info(), - source: context.accounts.sender_token_account.to_account_info(), - mint: context.accounts.mint_account.to_account_info(), - destination: context.accounts.recipient_token_account.to_account_info(), - authority: context.accounts.sender.to_account_info(), + source: context.accounts.sender_token_account.cpi_handle_mut(), + mint: context.accounts.mint_account.cpi_handle(), + destination: context.accounts.recipient_token_account.cpi_handle_mut(), + authority: context.accounts.sender.cpi_handle(), }, ), amount, // transfer amount diff --git a/tokens/token-extensions/transfer-fee/anchor/programs/transfer-fee/src/instructions/update_fee.rs b/tokens/token-extensions/transfer-fee/anchor/programs/transfer-fee/src/instructions/update_fee.rs index da0646bb5..fbc136c02 100644 --- a/tokens/token-extensions/transfer-fee/anchor/programs/transfer-fee/src/instructions/update_fee.rs +++ b/tokens/token-extensions/transfer-fee/anchor/programs/transfer-fee/src/instructions/update_fee.rs @@ -2,29 +2,28 @@ use anchor_lang::prelude::*; use anchor_spl::token_interface::{transfer_fee_set, Mint, Token2022, TransferFeeSetTransferFee}; #[derive(Accounts)] -pub struct UpdateFeeAccountConstraints<'info> { - pub authority: Signer<'info>, +pub struct UpdateFeeAccountConstraints { + pub authority: Signer, #[account(mut)] - pub mint_account: InterfaceAccount<'info, Mint>, - pub token_program: Program<'info, Token2022>, + pub mint_account: InterfaceAccount, + pub token_program: Program, } // Note that there is a 2 epoch delay from when new fee updates take effect // This is a safely feature built into the extension // https://github.com/solana-program/token-2022/blob/2d18d97f083627d3f13ce43b16fa4305cbfac4de/program/src/extension/transfer_fee/processor.rs#L92-L109 pub fn handle_process_update_fee( - context: Context, + context: &mut Context, transfer_fee_basis_points: u16, maximum_fee: u64, ) -> Result<()> { transfer_fee_set( CpiContext::new( - context.accounts.token_program.key(), + context.accounts.token_program.address(), TransferFeeSetTransferFee { - token_program_id: context.accounts.token_program.to_account_info(), - mint: context.accounts.mint_account.to_account_info(), - authority: context.accounts.authority.to_account_info(), + mint: context.accounts.mint_account.cpi_handle_mut(), + authority: context.accounts.authority.cpi_handle(), }, ), transfer_fee_basis_points, // transfer fee basis points (% fee per transfer) diff --git a/tokens/token-extensions/transfer-fee/anchor/programs/transfer-fee/src/instructions/withdraw.rs b/tokens/token-extensions/transfer-fee/anchor/programs/transfer-fee/src/instructions/withdraw.rs index ba9fb6659..deb925be9 100644 --- a/tokens/token-extensions/transfer-fee/anchor/programs/transfer-fee/src/instructions/withdraw.rs +++ b/tokens/token-extensions/transfer-fee/anchor/programs/transfer-fee/src/instructions/withdraw.rs @@ -5,26 +5,25 @@ use anchor_spl::token_interface::{ }; #[derive(Accounts)] -pub struct WithdrawAccountConstraints<'info> { - pub authority: Signer<'info>, +pub struct WithdrawAccountConstraints { + pub authority: Signer, #[account(mut)] - pub mint_account: InterfaceAccount<'info, Mint>, + pub mint_account: InterfaceAccount, #[account(mut)] - pub token_account: InterfaceAccount<'info, TokenAccount>, - pub token_program: Program<'info, Token2022>, + pub token_account: InterfaceAccount, + pub token_program: Program, } // transfer fees "harvested" to the mint account can then be withdraw by the withdraw authority // this transfers fees on the mint account to the specified token account -pub fn handle_process_withdraw(context: Context) -> Result<()> { +pub fn handle_process_withdraw(context: &mut Context) -> Result<()> { withdraw_withheld_tokens_from_mint(CpiContext::new( - context.accounts.token_program.key(), + context.accounts.token_program.address(), WithdrawWithheldTokensFromMint { - token_program_id: context.accounts.token_program.to_account_info(), - mint: context.accounts.mint_account.to_account_info(), - destination: context.accounts.token_account.to_account_info(), - authority: context.accounts.authority.to_account_info(), + mint: context.accounts.mint_account.cpi_handle_mut(), + destination: context.accounts.token_account.cpi_handle_mut(), + authority: context.accounts.authority.cpi_handle(), }, ))?; Ok(()) diff --git a/tokens/token-extensions/transfer-fee/anchor/programs/transfer-fee/src/lib.rs b/tokens/token-extensions/transfer-fee/anchor/programs/transfer-fee/src/lib.rs index 811385357..53305dc85 100644 --- a/tokens/token-extensions/transfer-fee/anchor/programs/transfer-fee/src/lib.rs +++ b/tokens/token-extensions/transfer-fee/anchor/programs/transfer-fee/src/lib.rs @@ -10,27 +10,27 @@ pub mod transfer_fee { use super::*; pub fn initialize( - context: Context, + context: &mut Context, transfer_fee_basis_points: u16, maximum_fee: u64, ) -> Result<()> { handle_process_initialize(context, transfer_fee_basis_points, maximum_fee) } - pub fn transfer(context: Context, amount: u64) -> Result<()> { + pub fn transfer(context: &mut Context, amount: u64) -> Result<()> { handle_process_transfer(context, amount) } - pub fn harvest<'info>(context: Context<'info, HarvestAccountConstraints<'info>>) -> Result<()> { + pub fn harvest<'info>(context: &mut Context<'info, HarvestAccountConstraints<'info>>) -> Result<()> { process_harvest(context) } - pub fn withdraw(context: Context) -> Result<()> { + pub fn withdraw(context: &mut Context) -> Result<()> { handle_process_withdraw(context) } pub fn update_fee( - context: Context, + context: &mut Context, transfer_fee_basis_points: u16, maximum_fee: u64, ) -> Result<()> { diff --git a/tokens/token-extensions/transfer-hook/account-data-as-seed/anchor/programs/transfer-hook/Cargo.toml b/tokens/token-extensions/transfer-hook/account-data-as-seed/anchor/programs/transfer-hook/Cargo.toml index 81d3422a3..3b769ebc2 100644 --- a/tokens/token-extensions/transfer-hook/account-data-as-seed/anchor/programs/transfer-hook/Cargo.toml +++ b/tokens/token-extensions/transfer-hook/account-data-as-seed/anchor/programs/transfer-hook/Cargo.toml @@ -14,14 +14,22 @@ cpi = ["no-entrypoint"] no-entrypoint = [] no-idl = [] no-log-ix-name = [] -idl-build = ["anchor-lang/idl-build", "anchor-spl/idl-build"] +idl-build = ["anchor-lang/idl-build"] anchor-debug = [] custom-heap = [] custom-panic = [] [dependencies] -anchor-lang = "1.1.2" -anchor-spl = "1.1.2" +anchor-lang = { version = "2.0.0-rc.1", features = ["compat"] } +# The `#[program]` macro expands to `wincode` paths for instruction-data +# (de)serialization, so the crate has to be a direct dependency. +wincode = { version = "0.5", features = ["derive"] } +# anchor-lang 2.0.0-rc.1 is built against wincode 0.5, but solana-address 2.7 +# moved to wincode 0.6. With both in the graph, `Address`'s wincode impls +# belong to the version the account derives are not using and every +# `SchemaRead`/`SchemaWrite` bound fails. 2.6.1 is the last 0.5-line release. +solana-address = ">=2.6, <2.7" +anchor-spl = "2.0.0-rc.1" spl-discriminator = "0.4.1" spl-tlv-account-resolution = "0.9.0" spl-transfer-hook-interface = "0.9.0" diff --git a/tokens/token-extensions/transfer-hook/account-data-as-seed/anchor/programs/transfer-hook/src/instructions/initialize_extra_account_meta_list.rs b/tokens/token-extensions/transfer-hook/account-data-as-seed/anchor/programs/transfer-hook/src/instructions/initialize_extra_account_meta_list.rs index 34c854ce9..b308473f4 100644 --- a/tokens/token-extensions/transfer-hook/account-data-as-seed/anchor/programs/transfer-hook/src/instructions/initialize_extra_account_meta_list.rs +++ b/tokens/token-extensions/transfer-hook/account-data-as-seed/anchor/programs/transfer-hook/src/instructions/initialize_extra_account_meta_list.rs @@ -10,14 +10,14 @@ use spl_transfer_hook_interface::instruction::ExecuteInstruction; use crate::{handle_extra_account_metas, handle_extra_account_metas_count, CounterAccount}; #[derive(Accounts)] -pub struct InitializeExtraAccountMetaListAccountConstraints<'info> { +pub struct InitializeExtraAccountMetaListAccountConstraints { #[account(mut)] - payer: Signer<'info>, + payer: Signer, /// CHECK: ExtraAccountMetaList Account, must use these seeds #[account( init, - seeds = [b"extra-account-metas", mint.key().as_ref()], + seeds = [b"extra-account-metas", mint.address().as_ref()], bump, // size_of returns Result with spl's ProgramError - unwrap is safe for known-good input space = ExtraAccountMetaList::size_of( @@ -25,16 +25,16 @@ pub struct InitializeExtraAccountMetaListAccountConstraints<'info> { ).unwrap(), payer = payer )] - pub extra_account_meta_list: UncheckedAccount<'info>, - pub mint: InterfaceAccount<'info, Mint>, - #[account(init, seeds = [b"counter", payer.key().as_ref()], bump, payer = payer, space = CounterAccount::DISCRIMINATOR.len() + CounterAccount::INIT_SPACE)] - pub counter_account: Account<'info, CounterAccount>, - pub token_program: Program<'info, Token2022>, - pub associated_token_program: Program<'info, AssociatedToken>, - pub system_program: Program<'info, System>, + pub extra_account_meta_list: UncheckedAccount, + pub mint: InterfaceAccount, + #[account(init, seeds = [b"counter", payer.address().as_ref()], bump, payer = payer, space = CounterAccount::DISCRIMINATOR.len() + CounterAccount::INIT_SPACE)] + pub counter_account: BorshAccount, + pub token_program: Program, + pub associated_token_program: Program, + pub system_program: Program, } -pub fn handler(mut context: Context) -> Result<()> { +pub fn handler(mut context: &mut Context) -> Result<()> { let extra_account_metas = handle_extra_account_metas()?; // initialize ExtraAccountMetaList account with extra accounts diff --git a/tokens/token-extensions/transfer-hook/account-data-as-seed/anchor/programs/transfer-hook/src/instructions/transfer_hook.rs b/tokens/token-extensions/transfer-hook/account-data-as-seed/anchor/programs/transfer-hook/src/instructions/transfer_hook.rs index 62aafc317..33128c47e 100644 --- a/tokens/token-extensions/transfer-hook/account-data-as-seed/anchor/programs/transfer-hook/src/instructions/transfer_hook.rs +++ b/tokens/token-extensions/transfer-hook/account-data-as-seed/anchor/programs/transfer-hook/src/instructions/transfer_hook.rs @@ -1,4 +1,5 @@ use anchor_lang::prelude::*; +use anchor_spl::token; use anchor_spl::token_interface::{Mint, TokenAccount}; use crate::{check_is_transferring, CounterAccount, TransferError}; @@ -8,22 +9,22 @@ use crate::{check_is_transferring, CounterAccount, TransferError}; // Remaining accounts are the extra accounts required from the ExtraAccountMetaList account // These accounts are provided via CPI to this program from the token2022 program #[derive(Accounts)] -pub struct TransferHookAccountConstraints<'info> { +pub struct TransferHookAccountConstraints { #[account(token::mint = mint, token::authority = owner)] - pub source_token: InterfaceAccount<'info, TokenAccount>, - pub mint: InterfaceAccount<'info, Mint>, + pub source_token: InterfaceAccount, + pub mint: InterfaceAccount, #[account(token::mint = mint)] - pub destination_token: InterfaceAccount<'info, TokenAccount>, + pub destination_token: InterfaceAccount, /// CHECK: source token account owner, can be SystemAccount or PDA owned by another program - pub owner: UncheckedAccount<'info>, + pub owner: UncheckedAccount, /// 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)] - pub counter_account: Account<'info, CounterAccount>, + #[account(seeds = [b"extra-account-metas", mint.address().as_ref()], bump)] + pub extra_account_meta_list: UncheckedAccount, + #[account(seeds = [b"counter", owner.address().as_ref()], bump)] + pub counter_account: BorshAccount, } -pub fn handler(context: Context, amount: u64) -> Result<()> { +pub fn handler(context: &mut Context, amount: u64) -> Result<()> { // Fail this instruction if it is not called from within a transfer hook check_is_transferring(&context)?; diff --git a/tokens/token-extensions/transfer-hook/account-data-as-seed/anchor/programs/transfer-hook/src/lib.rs b/tokens/token-extensions/transfer-hook/account-data-as-seed/anchor/programs/transfer-hook/src/lib.rs index c83525603..7057ea20d 100644 --- a/tokens/token-extensions/transfer-hook/account-data-as-seed/anchor/programs/transfer-hook/src/lib.rs +++ b/tokens/token-extensions/transfer-hook/account-data-as-seed/anchor/programs/transfer-hook/src/lib.rs @@ -33,19 +33,19 @@ pub mod transfer_hook { #[instruction(discriminator = InitializeExtraAccountMetaListInstruction::SPL_DISCRIMINATOR_SLICE)] pub fn initialize_extra_account_meta_list( - context: Context, + context: &mut Context, ) -> Result<()> { instructions::initialize_extra_account_meta_list::handler(context) } #[instruction(discriminator = ExecuteInstruction::SPL_DISCRIMINATOR_SLICE)] - pub fn transfer_hook(context: Context, amount: u64) -> Result<()> { + pub fn transfer_hook(context: &mut Context, amount: u64) -> Result<()> { instructions::transfer_hook::handler(context, amount) } } pub fn check_is_transferring(context: &Context) -> Result<()> { - let source_token_info = context.accounts.source_token.to_account_info(); + let source_token_info = context.accounts.source_token.cpi_handle_mut(); let mut account_data_ref: RefMut<&mut [u8]> = source_token_info.try_borrow_mut_data()?; // .map_err() needed because spl-token-2022 uses solana-program-error 2.x // while anchor-lang 1.0 uses 3.x - structurally identical but different semver types @@ -86,7 +86,7 @@ pub fn handle_extra_account_metas_count() -> usize { 1 // one extra account: the counter PDA } -#[account] +#[account(borsh)] #[derive(InitSpace)] pub struct CounterAccount { pub counter: u64, diff --git a/tokens/token-extensions/transfer-hook/allow-block-list-token/anchor/programs/abl-token/Cargo.toml b/tokens/token-extensions/transfer-hook/allow-block-list-token/anchor/programs/abl-token/Cargo.toml index 387b5b193..1914163bf 100644 --- a/tokens/token-extensions/transfer-hook/allow-block-list-token/anchor/programs/abl-token/Cargo.toml +++ b/tokens/token-extensions/transfer-hook/allow-block-list-token/anchor/programs/abl-token/Cargo.toml @@ -14,7 +14,7 @@ cpi = ["no-entrypoint"] no-entrypoint = [] no-idl = [] no-log-ix-name = [] -idl-build = ["anchor-lang/idl-build", "anchor-spl/idl-build"] +idl-build = ["anchor-lang/idl-build"] anchor-debug = [] custom-heap = [] custom-panic = [] @@ -22,8 +22,16 @@ custom-panic = [] [dependencies] # interface-instructions feature removed in Anchor 1.0 -anchor-lang = "1.1.2" -anchor-spl = { version = "1.1.2", features = [ +anchor-lang = "2.0.0-rc.1" +# The `#[program]` macro expands to `wincode` paths for instruction-data +# (de)serialization, so the crate has to be a direct dependency. +wincode = { version = "0.5", features = ["derive"] } +# anchor-lang 2.0.0-rc.1 is built against wincode 0.5, but solana-address 2.7 +# moved to wincode 0.6. With both in the graph, `Address`'s wincode impls +# belong to the version the account derives are not using and every +# `SchemaRead`/`SchemaWrite` bound fails. 2.6.1 is the last 0.5-line release. +solana-address = ">=2.6, <2.7" +anchor-spl = { version = "2.0.0-rc.1", features = [ "token_2022_extensions", "token_2022", ] } diff --git a/tokens/token-extensions/transfer-hook/allow-block-list-token/anchor/programs/abl-token/src/instructions/attach_to_mint.rs b/tokens/token-extensions/transfer-hook/allow-block-list-token/anchor/programs/abl-token/src/instructions/attach_to_mint.rs index 750c21c57..943c86719 100644 --- a/tokens/token-extensions/transfer-hook/allow-block-list-token/anchor/programs/abl-token/src/instructions/attach_to_mint.rs +++ b/tokens/token-extensions/transfer-hook/allow-block-list-token/anchor/programs/abl-token/src/instructions/attach_to_mint.rs @@ -1,4 +1,5 @@ use anchor_lang::prelude::*; +use anchor_spl::mint; use anchor_spl::{ token_2022::Token2022, token_interface::{transfer_hook_update, Mint, TransferHookUpdate}, @@ -10,40 +11,39 @@ use spl_transfer_hook_interface::instruction::ExecuteInstruction; use crate::{get_extra_account_metas, get_meta_list_size, META_LIST_ACCOUNT_SEED}; #[derive(Accounts)] -pub struct AttachToMintAccountConstraints<'info> { +pub struct AttachToMintAccountConstraints { #[account(mut)] - pub payer: Signer<'info>, + pub payer: Signer, #[account( mut, mint::token_program = token_program, )] - pub mint: Box>, + pub mint: Box>, #[account( init, space = get_meta_list_size()?, - seeds = [META_LIST_ACCOUNT_SEED, mint.key().as_ref()], + seeds = [META_LIST_ACCOUNT_SEED, mint.address().as_ref()], bump, payer = payer, )] /// CHECK: extra metas account - pub extra_metas_account: UncheckedAccount<'info>, + pub extra_metas_account: UncheckedAccount, - pub system_program: Program<'info, System>, + pub system_program: Program, - pub token_program: Program<'info, Token2022>, + pub token_program: Program, } impl AttachToMintAccountConstraints<'_> { pub fn attach_to_mint(&mut self) -> Result<()> { let tx_hook_accs = TransferHookUpdate { - token_program_id: self.token_program.to_account_info(), - mint: self.mint.to_account_info(), - authority: self.payer.to_account_info(), + mint: self.mint.cpi_handle_mut(), + authority: self.payer.cpi_handle_mut(), }; - let context = CpiContext::new(self.token_program.key(), tx_hook_accs); + let context = CpiContext::new(self.token_program.address(), tx_hook_accs); transfer_hook_update(context, Some(crate::ID_CONST))?; diff --git a/tokens/token-extensions/transfer-hook/allow-block-list-token/anchor/programs/abl-token/src/instructions/change_mode.rs b/tokens/token-extensions/transfer-hook/allow-block-list-token/anchor/programs/abl-token/src/instructions/change_mode.rs index 81fde1820..ef1cc7788 100644 --- a/tokens/token-extensions/transfer-hook/allow-block-list-token/anchor/programs/abl-token/src/instructions/change_mode.rs +++ b/tokens/token-extensions/transfer-hook/allow-block-list-token/anchor/programs/abl-token/src/instructions/change_mode.rs @@ -16,19 +16,19 @@ use anchor_spl::{ use crate::Mode; #[derive(Accounts)] -pub struct ChangeModeAccountConstraints<'info> { +pub struct ChangeModeAccountConstraints { #[account(mut)] - pub authority: Signer<'info>, + pub authority: Signer, #[account( mut, mint::token_program = token_program, )] - pub mint: InterfaceAccount<'info, MintAccount>, + pub mint: InterfaceAccount, - pub token_program: Program<'info, Token2022>, + pub token_program: Program, - pub system_program: Program<'info, System>, + pub system_program: Program, } #[derive(AnchorSerialize, AnchorDeserialize)] @@ -40,11 +40,11 @@ pub struct ChangeModeArgs { impl ChangeModeAccountConstraints<'_> { pub fn change_mode(&mut self, args: ChangeModeArgs) -> Result<()> { let cpi_accounts = TokenMetadataUpdateField { - metadata: self.mint.to_account_info(), - update_authority: self.authority.to_account_info(), - program_id: self.token_program.to_account_info(), + metadata: self.mint.cpi_handle_mut(), + update_authority: self.authority.cpi_handle_mut(), + program_id: self.token_program.cpi_handle_mut(), }; - let cpi_program = self.token_program.key(); + let cpi_program = self.token_program.address(); let cpi_ctx = CpiContext::new(cpi_program, cpi_accounts); token_metadata_update_field(cpi_ctx, Field::Key("AB".to_string()), args.mode.to_string())?; @@ -57,11 +57,11 @@ impl ChangeModeAccountConstraints<'_> { }; let cpi_accounts = TokenMetadataUpdateField { - metadata: self.mint.to_account_info(), - update_authority: self.authority.to_account_info(), - program_id: self.token_program.to_account_info(), + metadata: self.mint.cpi_handle_mut(), + update_authority: self.authority.cpi_handle_mut(), + program_id: self.token_program.cpi_handle_mut(), }; - let cpi_program = self.token_program.key(); + let cpi_program = self.token_program.address(); let cpi_ctx = CpiContext::new(cpi_program, cpi_accounts); token_metadata_update_field( @@ -71,19 +71,19 @@ impl ChangeModeAccountConstraints<'_> { )?; } - let data = self.mint.to_account_info().data_len(); - let min_balance = Rent::get()?.minimum_balance(data); - if min_balance > self.mint.to_account_info().get_lamports() { + let data = self.mint.cpi_handle_mut().data_len(); + let min_balance = Rent::get()?.try_minimum_balance(data)?; + if min_balance > self.mint.cpi_handle_mut().get_lamports() { invoke( &transfer( - &self.authority.key(), - &self.mint.to_account_info().key(), - min_balance - self.mint.to_account_info().get_lamports(), + &self.authority.address(), + &self.mint.cpi_handle_mut().address(), + min_balance - self.mint.cpi_handle_mut().get_lamports(), ), &[ - self.authority.to_account_info(), - self.mint.to_account_info(), - self.system_program.to_account_info(), + self.authority.cpi_handle_mut(), + self.mint.cpi_handle_mut(), + self.system_program.cpi_handle_mut(), ], )?; } @@ -92,7 +92,7 @@ impl ChangeModeAccountConstraints<'_> { } fn has_threshold(&self) -> Result { - let mint_info = self.mint.to_account_info(); + let mint_info = self.mint.cpi_handle_mut(); let mint_data = mint_info.data.borrow(); let mint = StateWithExtensions::::unpack(&mint_data)?; let metadata = mint.get_variable_len_extension::(); diff --git a/tokens/token-extensions/transfer-hook/allow-block-list-token/anchor/programs/abl-token/src/instructions/init_config.rs b/tokens/token-extensions/transfer-hook/allow-block-list-token/anchor/programs/abl-token/src/instructions/init_config.rs index 67142441b..2f26f5cae 100644 --- a/tokens/token-extensions/transfer-hook/allow-block-list-token/anchor/programs/abl-token/src/instructions/init_config.rs +++ b/tokens/token-extensions/transfer-hook/allow-block-list-token/anchor/programs/abl-token/src/instructions/init_config.rs @@ -2,9 +2,9 @@ use crate::{Config, CONFIG_SEED}; use anchor_lang::prelude::*; #[derive(Accounts)] -pub struct InitConfigAccountConstraints<'info> { +pub struct InitConfigAccountConstraints { #[account(mut)] - pub payer: Signer<'info>, + pub payer: Signer, #[account( init, @@ -13,15 +13,15 @@ pub struct InitConfigAccountConstraints<'info> { seeds = [CONFIG_SEED], bump, )] - pub config: Box>, + pub config: Box>, - pub system_program: Program<'info, System>, + pub system_program: Program, } impl InitConfigAccountConstraints<'_> { pub fn init_config(&mut self, config_bump: u8) -> Result<()> { - self.config.set_inner(Config { - authority: self.payer.key(), + *self.config = (Config { + authority: self.payer.address(), bump: config_bump, }); diff --git a/tokens/token-extensions/transfer-hook/allow-block-list-token/anchor/programs/abl-token/src/instructions/init_mint.rs b/tokens/token-extensions/transfer-hook/allow-block-list-token/anchor/programs/abl-token/src/instructions/init_mint.rs index cfbfb90cb..d6b579ca9 100644 --- a/tokens/token-extensions/transfer-hook/allow-block-list-token/anchor/programs/abl-token/src/instructions/init_mint.rs +++ b/tokens/token-extensions/transfer-hook/allow-block-list-token/anchor/programs/abl-token/src/instructions/init_mint.rs @@ -16,69 +16,69 @@ use crate::{get_extra_account_metas, get_meta_list_size, Mode, META_LIST_ACCOUNT #[derive(Accounts)] #[instruction(args: InitMintArgs)] -pub struct InitMintAccountConstraints<'info> { +pub struct InitMintAccountConstraints { #[account(mut)] - pub payer: Signer<'info>, + pub payer: Signer, #[account( init, payer = payer, mint::token_program = token_program, - mint::decimals = args.decimals, - mint::authority = payer.key(), + mint::decimals = args.decimals(), + mint::authority = payer, mint::freeze_authority = args.freeze_authority, extensions::permanent_delegate::delegate = args.permanent_delegate, extensions::transfer_hook::authority = args.transfer_hook_authority, extensions::transfer_hook::program_id = crate::id(), - extensions::metadata_pointer::authority = payer.key(), - extensions::metadata_pointer::metadata_address = mint.key(), + extensions::metadata_pointer::authority = payer.address(), + extensions::metadata_pointer::metadata_address = mint.address(), )] - pub mint: Box>, + pub mint: Box>, #[account( init, space = get_meta_list_size()?, - seeds = [META_LIST_ACCOUNT_SEED, mint.key().as_ref()], + seeds = [META_LIST_ACCOUNT_SEED, mint.address().as_ref()], bump, payer = payer, )] /// CHECK: extra metas account - pub extra_metas_account: UncheckedAccount<'info>, + pub extra_metas_account: UncheckedAccount, - pub system_program: Program<'info, System>, + pub system_program: Program, - pub token_program: Program<'info, Token2022>, + pub token_program: Program, } impl InitMintAccountConstraints<'_> { pub fn init_mint(&mut self, args: InitMintArgs) -> Result<()> { let cpi_accounts = TokenMetadataInitialize { - program_id: self.token_program.to_account_info(), - mint: self.mint.to_account_info(), - metadata: self.mint.to_account_info(), // metadata account is the mint, since data is stored in mint - mint_authority: self.payer.to_account_info(), - update_authority: self.payer.to_account_info(), + program_id: self.token_program.cpi_handle_mut(), + mint: self.mint.cpi_handle_mut(), + metadata: self.mint.cpi_handle_mut(), // metadata account is the mint, since data is stored in mint + mint_authority: self.payer.cpi_handle_mut(), + update_authority: self.payer.cpi_handle_mut(), }; - let cpi_ctx = CpiContext::new(self.token_program.key(), cpi_accounts); + let cpi_ctx = CpiContext::new(self.token_program.address(), cpi_accounts); token_metadata_initialize(cpi_ctx, args.name, args.symbol, args.uri)?; let cpi_accounts = TokenMetadataUpdateField { - metadata: self.mint.to_account_info(), - update_authority: self.payer.to_account_info(), - program_id: self.token_program.to_account_info(), + metadata: self.mint.cpi_handle_mut(), + update_authority: self.payer.cpi_handle_mut(), + program_id: self.token_program.cpi_handle_mut(), }; - let cpi_ctx = CpiContext::new(self.token_program.key(), cpi_accounts); + let cpi_ctx = CpiContext::new(self.token_program.address(), cpi_accounts); token_metadata_update_field(cpi_ctx, Field::Key("AB".to_string()), args.mode.to_string())?; if args.mode == Mode::Mixed { let cpi_accounts = TokenMetadataUpdateField { - metadata: self.mint.to_account_info(), - update_authority: self.payer.to_account_info(), - program_id: self.token_program.to_account_info(), + metadata: self.mint.cpi_handle_mut(), + update_authority: self.payer.cpi_handle_mut(), + program_id: self.token_program.cpi_handle_mut(), }; - let cpi_ctx = CpiContext::new(self.token_program.key(), cpi_accounts); + let cpi_ctx = CpiContext::new(self.token_program.address(), cpi_accounts); token_metadata_update_field( cpi_ctx, @@ -87,19 +87,19 @@ impl InitMintAccountConstraints<'_> { )?; } - let data = self.mint.to_account_info().data_len(); - let min_balance = Rent::get()?.minimum_balance(data); - if min_balance > self.mint.to_account_info().get_lamports() { + let data = self.mint.cpi_handle_mut().data_len(); + let min_balance = Rent::get()?.try_minimum_balance(data)?; + if min_balance > self.mint.cpi_handle_mut().get_lamports() { invoke( &transfer( - &self.payer.key(), - &self.mint.to_account_info().key(), - min_balance - self.mint.to_account_info().get_lamports(), + &self.payer.address(), + &self.mint.cpi_handle_mut().address(), + min_balance - self.mint.cpi_handle_mut().get_lamports(), ), &[ - self.payer.to_account_info(), - self.mint.to_account_info(), - self.system_program.to_account_info(), + self.payer.cpi_handle_mut(), + self.mint.cpi_handle_mut(), + self.system_program.cpi_handle_mut(), ], )?; } @@ -118,10 +118,10 @@ impl InitMintAccountConstraints<'_> { #[derive(AnchorSerialize, AnchorDeserialize)] pub struct InitMintArgs { pub decimals: u8, - pub mint_authority: Pubkey, - pub freeze_authority: Pubkey, - pub permanent_delegate: Pubkey, - pub transfer_hook_authority: Pubkey, + pub mint_authority: Address, + pub freeze_authority: Address, + pub permanent_delegate: Address, + pub transfer_hook_authority: Address, pub mode: Mode, pub threshold: u64, pub name: String, diff --git a/tokens/token-extensions/transfer-hook/allow-block-list-token/anchor/programs/abl-token/src/instructions/init_wallet.rs b/tokens/token-extensions/transfer-hook/allow-block-list-token/anchor/programs/abl-token/src/instructions/init_wallet.rs index f1eb1b95a..1d6162bde 100644 --- a/tokens/token-extensions/transfer-hook/allow-block-list-token/anchor/programs/abl-token/src/instructions/init_wallet.rs +++ b/tokens/token-extensions/transfer-hook/allow-block-list-token/anchor/programs/abl-token/src/instructions/init_wallet.rs @@ -3,35 +3,35 @@ use anchor_lang::prelude::*; use crate::{ABWallet, Config, AB_WALLET_SEED, CONFIG_SEED}; #[derive(Accounts)] -pub struct InitWalletAccountConstraints<'info> { +pub struct InitWalletAccountConstraints { #[account(mut)] - pub authority: Signer<'info>, + pub authority: Signer, #[account( seeds = [CONFIG_SEED], bump = config.bump, has_one = authority, )] - pub config: Box>, + pub config: Box>, - pub wallet: SystemAccount<'info>, + pub wallet: SystemAccount, #[account( init, payer = authority, space = ABWallet::DISCRIMINATOR.len() + ABWallet::INIT_SPACE, - seeds = [AB_WALLET_SEED, wallet.key().as_ref()], + seeds = [AB_WALLET_SEED, wallet.address().as_ref()], bump, )] - pub ab_wallet: Account<'info, ABWallet>, + pub ab_wallet: BorshAccount, - pub system_program: Program<'info, System>, + pub system_program: Program, } impl InitWalletAccountConstraints<'_> { pub fn init_wallet(&mut self, args: InitWalletArgs, bump: u8) -> Result<()> { let ab_wallet = &mut self.ab_wallet; - ab_wallet.wallet = self.wallet.key(); + ab_wallet.wallet = self.wallet.address(); ab_wallet.allowed = args.allowed; ab_wallet.bump = bump; Ok(()) diff --git a/tokens/token-extensions/transfer-hook/allow-block-list-token/anchor/programs/abl-token/src/instructions/remove_wallet.rs b/tokens/token-extensions/transfer-hook/allow-block-list-token/anchor/programs/abl-token/src/instructions/remove_wallet.rs index a505f085e..b2c540e2e 100644 --- a/tokens/token-extensions/transfer-hook/allow-block-list-token/anchor/programs/abl-token/src/instructions/remove_wallet.rs +++ b/tokens/token-extensions/transfer-hook/allow-block-list-token/anchor/programs/abl-token/src/instructions/remove_wallet.rs @@ -3,24 +3,24 @@ use anchor_lang::prelude::*; use crate::{ABWallet, Config}; #[derive(Accounts)] -pub struct RemoveWalletAccountConstraints<'info> { +pub struct RemoveWalletAccountConstraints { #[account(mut)] - pub authority: Signer<'info>, + pub authority: Signer, #[account( seeds = [b"config"], bump = config.bump, has_one = authority, )] - pub config: Box>, + pub config: Box>, #[account( mut, close = authority, )] - pub ab_wallet: Account<'info, ABWallet>, + pub ab_wallet: BorshAccount, - pub system_program: Program<'info, System>, + pub system_program: Program, } impl RemoveWalletAccountConstraints<'_> { diff --git a/tokens/token-extensions/transfer-hook/allow-block-list-token/anchor/programs/abl-token/src/instructions/tx_hook.rs b/tokens/token-extensions/transfer-hook/allow-block-list-token/anchor/programs/abl-token/src/instructions/tx_hook.rs index 0c3f6291e..f967471b0 100644 --- a/tokens/token-extensions/transfer-hook/allow-block-list-token/anchor/programs/abl-token/src/instructions/tx_hook.rs +++ b/tokens/token-extensions/transfer-hook/allow-block-list-token/anchor/programs/abl-token/src/instructions/tx_hook.rs @@ -12,24 +12,24 @@ use anchor_spl::{ use crate::{ABListError, ABWallet, Mode}; #[derive(Accounts)] -pub struct TxHookAccountConstraints<'info> { +pub struct TxHookAccountConstraints { /// CHECK: - pub source_token_account: UncheckedAccount<'info>, + pub source_token_account: UncheckedAccount, /// CHECK: - pub mint: UncheckedAccount<'info>, + pub mint: UncheckedAccount, /// CHECK: - pub destination_token_account: UncheckedAccount<'info>, + pub destination_token_account: UncheckedAccount, /// CHECK: - pub owner_delegate: UncheckedAccount<'info>, + pub owner_delegate: UncheckedAccount, /// CHECK: - pub meta_list: UncheckedAccount<'info>, + pub meta_list: UncheckedAccount, /// CHECK: - pub ab_wallet: UncheckedAccount<'info>, + pub ab_wallet: UncheckedAccount, } impl TxHookAccountConstraints<'_> { pub fn tx_hook(&self, amount: u64) -> Result<()> { - let mint_info = self.mint.to_account_info(); + let mint_info = self.mint.cpi_handle_mut(); let mint_data = mint_info.data.borrow(); let mint = StateWithExtensions::::unpack(&mint_data)?; diff --git a/tokens/token-extensions/transfer-hook/allow-block-list-token/anchor/programs/abl-token/src/lib.rs b/tokens/token-extensions/transfer-hook/allow-block-list-token/anchor/programs/abl-token/src/lib.rs index 517ef28d2..4c49ec824 100644 --- a/tokens/token-extensions/transfer-hook/allow-block-list-token/anchor/programs/abl-token/src/lib.rs +++ b/tokens/token-extensions/transfer-hook/allow-block-list-token/anchor/programs/abl-token/src/lib.rs @@ -20,33 +20,33 @@ pub mod abl_token { use super::*; - pub fn init_mint(context: Context, args: InitMintArgs) -> Result<()> { + pub fn init_mint(context: &mut Context, args: InitMintArgs) -> Result<()> { context.accounts.init_mint(args) } - pub fn init_config(context: Context) -> Result<()> { + pub fn init_config(context: &mut Context) -> Result<()> { context.accounts.init_config(context.bumps.config) } - pub fn attach_to_mint(context: Context) -> Result<()> { + pub fn attach_to_mint(context: &mut Context) -> Result<()> { context.accounts.attach_to_mint() } #[instruction(discriminator = ExecuteInstruction::SPL_DISCRIMINATOR_SLICE)] - pub fn tx_hook(context: Context, amount: u64) -> Result<()> { + pub fn tx_hook(context: &mut Context, amount: u64) -> Result<()> { context.accounts.tx_hook(amount) } - pub fn init_wallet(context: Context, args: InitWalletArgs) -> Result<()> { + pub fn init_wallet(context: &mut Context, args: InitWalletArgs) -> Result<()> { let bump = context.bumps.ab_wallet; context.accounts.init_wallet(args, bump) } - pub fn remove_wallet(context: Context) -> Result<()> { + pub fn remove_wallet(context: &mut Context) -> Result<()> { context.accounts.remove_wallet() } - pub fn change_mode(context: Context, args: ChangeModeArgs) -> Result<()> { + pub fn change_mode(context: &mut Context, args: ChangeModeArgs) -> Result<()> { context.accounts.change_mode(args) } } diff --git a/tokens/token-extensions/transfer-hook/allow-block-list-token/anchor/programs/abl-token/src/state.rs b/tokens/token-extensions/transfer-hook/allow-block-list-token/anchor/programs/abl-token/src/state.rs index bf2df39df..e6b7036b1 100644 --- a/tokens/token-extensions/transfer-hook/allow-block-list-token/anchor/programs/abl-token/src/state.rs +++ b/tokens/token-extensions/transfer-hook/allow-block-list-token/anchor/programs/abl-token/src/state.rs @@ -5,19 +5,19 @@ use std::{ use anchor_lang::prelude::*; -#[account] +#[account(borsh)] #[derive(InitSpace)] pub struct ABWallet { - pub wallet: Pubkey, + pub wallet: Address, pub allowed: bool, /// Canonical bump for this PDA. pub bump: u8, } -#[account] +#[account(borsh)] #[derive(InitSpace)] pub struct Config { - pub authority: Pubkey, + pub authority: Address, pub bump: u8, } diff --git a/tokens/token-extensions/transfer-hook/counter/anchor/programs/transfer-hook/Cargo.toml b/tokens/token-extensions/transfer-hook/counter/anchor/programs/transfer-hook/Cargo.toml index 81d3422a3..3b769ebc2 100644 --- a/tokens/token-extensions/transfer-hook/counter/anchor/programs/transfer-hook/Cargo.toml +++ b/tokens/token-extensions/transfer-hook/counter/anchor/programs/transfer-hook/Cargo.toml @@ -14,14 +14,22 @@ cpi = ["no-entrypoint"] no-entrypoint = [] no-idl = [] no-log-ix-name = [] -idl-build = ["anchor-lang/idl-build", "anchor-spl/idl-build"] +idl-build = ["anchor-lang/idl-build"] anchor-debug = [] custom-heap = [] custom-panic = [] [dependencies] -anchor-lang = "1.1.2" -anchor-spl = "1.1.2" +anchor-lang = { version = "2.0.0-rc.1", features = ["compat"] } +# The `#[program]` macro expands to `wincode` paths for instruction-data +# (de)serialization, so the crate has to be a direct dependency. +wincode = { version = "0.5", features = ["derive"] } +# anchor-lang 2.0.0-rc.1 is built against wincode 0.5, but solana-address 2.7 +# moved to wincode 0.6. With both in the graph, `Address`'s wincode impls +# belong to the version the account derives are not using and every +# `SchemaRead`/`SchemaWrite` bound fails. 2.6.1 is the last 0.5-line release. +solana-address = ">=2.6, <2.7" +anchor-spl = "2.0.0-rc.1" spl-discriminator = "0.4.1" spl-tlv-account-resolution = "0.9.0" spl-transfer-hook-interface = "0.9.0" diff --git a/tokens/token-extensions/transfer-hook/counter/anchor/programs/transfer-hook/src/instructions/initialize_extra_account_meta_list.rs b/tokens/token-extensions/transfer-hook/counter/anchor/programs/transfer-hook/src/instructions/initialize_extra_account_meta_list.rs index 7aef4af81..bec2eb85d 100644 --- a/tokens/token-extensions/transfer-hook/counter/anchor/programs/transfer-hook/src/instructions/initialize_extra_account_meta_list.rs +++ b/tokens/token-extensions/transfer-hook/counter/anchor/programs/transfer-hook/src/instructions/initialize_extra_account_meta_list.rs @@ -10,14 +10,14 @@ use spl_transfer_hook_interface::instruction::ExecuteInstruction; use crate::{handle_extra_account_metas, handle_extra_account_metas_count, CounterAccount}; #[derive(Accounts)] -pub struct InitializeExtraAccountMetaListAccountConstraints<'info> { +pub struct InitializeExtraAccountMetaListAccountConstraints { #[account(mut)] - payer: Signer<'info>, + payer: Signer, /// CHECK: ExtraAccountMetaList Account, must use these seeds #[account( init, - seeds = [b"extra-account-metas", mint.key().as_ref()], + seeds = [b"extra-account-metas", mint.address().as_ref()], bump, // size_of returns Result with spl's ProgramError - unwrap is safe for known-good input space = ExtraAccountMetaList::size_of( @@ -25,16 +25,16 @@ pub struct InitializeExtraAccountMetaListAccountConstraints<'info> { ).unwrap(), payer = payer )] - pub extra_account_meta_list: AccountInfo<'info>, - pub mint: InterfaceAccount<'info, Mint>, + pub extra_account_meta_list: AccountView, + pub mint: InterfaceAccount, #[account(init, seeds = [b"counter"], bump, payer = payer, space = CounterAccount::DISCRIMINATOR.len() + CounterAccount::INIT_SPACE)] - pub counter_account: Account<'info, CounterAccount>, - pub token_program: Program<'info, Token2022>, - pub associated_token_program: Program<'info, AssociatedToken>, - pub system_program: Program<'info, System>, + pub counter_account: BorshAccount, + pub token_program: Program, + pub associated_token_program: Program, + pub system_program: Program, } -pub fn handler(mut context: Context) -> Result<()> { +pub fn handler(mut context: &mut Context) -> Result<()> { let extra_account_metas = handle_extra_account_metas()?; // initialize ExtraAccountMetaList account with extra accounts diff --git a/tokens/token-extensions/transfer-hook/counter/anchor/programs/transfer-hook/src/instructions/transfer_hook.rs b/tokens/token-extensions/transfer-hook/counter/anchor/programs/transfer-hook/src/instructions/transfer_hook.rs index 31ee75fc4..8f173c59e 100644 --- a/tokens/token-extensions/transfer-hook/counter/anchor/programs/transfer-hook/src/instructions/transfer_hook.rs +++ b/tokens/token-extensions/transfer-hook/counter/anchor/programs/transfer-hook/src/instructions/transfer_hook.rs @@ -1,4 +1,5 @@ use anchor_lang::prelude::*; +use anchor_spl::token; use anchor_spl::token_interface::{Mint, TokenAccount}; use crate::{check_is_transferring, CounterAccount, TransferError}; @@ -8,22 +9,22 @@ use crate::{check_is_transferring, CounterAccount, TransferError}; // Remaining accounts are the extra accounts required from the ExtraAccountMetaList account // These accounts are provided via CPI to this program from the token2022 program #[derive(Accounts)] -pub struct TransferHookAccountConstraints<'info> { +pub struct TransferHookAccountConstraints { #[account(token::mint = mint, token::authority = owner)] - pub source_token: InterfaceAccount<'info, TokenAccount>, - pub mint: InterfaceAccount<'info, Mint>, + pub source_token: InterfaceAccount, + pub mint: InterfaceAccount, #[account(token::mint = mint)] - pub destination_token: InterfaceAccount<'info, TokenAccount>, + pub destination_token: InterfaceAccount, /// CHECK: source token account owner, can be SystemAccount or PDA owned by another program - pub owner: UncheckedAccount<'info>, + pub owner: UncheckedAccount, /// CHECK: ExtraAccountMetaList Account, - #[account(seeds = [b"extra-account-metas", mint.key().as_ref()], bump)] - pub extra_account_meta_list: UncheckedAccount<'info>, + #[account(seeds = [b"extra-account-metas", mint.address().as_ref()], bump)] + pub extra_account_meta_list: UncheckedAccount, #[account(seeds = [b"counter"], bump)] - pub counter_account: Account<'info, CounterAccount>, + pub counter_account: BorshAccount, } -pub fn handler(context: Context, amount: u64) -> Result<()> { +pub fn handler(context: &mut Context, amount: u64) -> Result<()> { // Fail this instruction if it is not called from within a transfer hook check_is_transferring(&context)?; diff --git a/tokens/token-extensions/transfer-hook/counter/anchor/programs/transfer-hook/src/lib.rs b/tokens/token-extensions/transfer-hook/counter/anchor/programs/transfer-hook/src/lib.rs index 7fabbb4b0..82a72646b 100644 --- a/tokens/token-extensions/transfer-hook/counter/anchor/programs/transfer-hook/src/lib.rs +++ b/tokens/token-extensions/transfer-hook/counter/anchor/programs/transfer-hook/src/lib.rs @@ -33,19 +33,19 @@ pub mod transfer_hook { #[instruction(discriminator = InitializeExtraAccountMetaListInstruction::SPL_DISCRIMINATOR_SLICE)] pub fn initialize_extra_account_meta_list( - context: Context, + context: &mut Context, ) -> Result<()> { instructions::initialize_extra_account_meta_list::handler(context) } #[instruction(discriminator = ExecuteInstruction::SPL_DISCRIMINATOR_SLICE)] - pub fn transfer_hook(context: Context, amount: u64) -> Result<()> { + pub fn transfer_hook(context: &mut Context, amount: u64) -> Result<()> { instructions::transfer_hook::handler(context, amount) } } pub fn check_is_transferring(context: &Context) -> Result<()> { - let source_token_info = context.accounts.source_token.to_account_info(); + let source_token_info = context.accounts.source_token.cpi_handle_mut(); let mut account_data_ref: RefMut<&mut [u8]> = source_token_info.try_borrow_mut_data()?; let mut account = PodStateWithExtensionsMut::::unpack(*account_data_ref)?; let account_extension = account.get_extension_mut::()?; @@ -75,7 +75,7 @@ pub fn handle_extra_account_metas_count() -> usize { 1 // one extra account: the counter PDA } -#[account] +#[account(borsh)] #[derive(InitSpace)] pub struct CounterAccount { pub counter: u64, diff --git a/tokens/token-extensions/transfer-hook/hello-world/anchor/programs/transfer-hook/Cargo.toml b/tokens/token-extensions/transfer-hook/hello-world/anchor/programs/transfer-hook/Cargo.toml index 81d3422a3..3b769ebc2 100644 --- a/tokens/token-extensions/transfer-hook/hello-world/anchor/programs/transfer-hook/Cargo.toml +++ b/tokens/token-extensions/transfer-hook/hello-world/anchor/programs/transfer-hook/Cargo.toml @@ -14,14 +14,22 @@ cpi = ["no-entrypoint"] no-entrypoint = [] no-idl = [] no-log-ix-name = [] -idl-build = ["anchor-lang/idl-build", "anchor-spl/idl-build"] +idl-build = ["anchor-lang/idl-build"] anchor-debug = [] custom-heap = [] custom-panic = [] [dependencies] -anchor-lang = "1.1.2" -anchor-spl = "1.1.2" +anchor-lang = { version = "2.0.0-rc.1", features = ["compat"] } +# The `#[program]` macro expands to `wincode` paths for instruction-data +# (de)serialization, so the crate has to be a direct dependency. +wincode = { version = "0.5", features = ["derive"] } +# anchor-lang 2.0.0-rc.1 is built against wincode 0.5, but solana-address 2.7 +# moved to wincode 0.6. With both in the graph, `Address`'s wincode impls +# belong to the version the account derives are not using and every +# `SchemaRead`/`SchemaWrite` bound fails. 2.6.1 is the last 0.5-line release. +solana-address = ">=2.6, <2.7" +anchor-spl = "2.0.0-rc.1" spl-discriminator = "0.4.1" spl-tlv-account-resolution = "0.9.0" spl-transfer-hook-interface = "0.9.0" diff --git a/tokens/token-extensions/transfer-hook/hello-world/anchor/programs/transfer-hook/src/instructions/initialize.rs b/tokens/token-extensions/transfer-hook/hello-world/anchor/programs/transfer-hook/src/instructions/initialize.rs index 7cc60c7bf..e4a747526 100644 --- a/tokens/token-extensions/transfer-hook/hello-world/anchor/programs/transfer-hook/src/instructions/initialize.rs +++ b/tokens/token-extensions/transfer-hook/hello-world/anchor/programs/transfer-hook/src/instructions/initialize.rs @@ -1,4 +1,5 @@ use anchor_lang::prelude::*; +use anchor_spl::mint; use anchor_spl::token_interface::{ spl_pod::optional_keys::OptionalNonZeroPubkey, spl_token_2022::{ @@ -13,9 +14,9 @@ use anchor_spl::token_interface::{ #[derive(Accounts)] #[instruction(_decimals: u8)] -pub struct InitializeAccountConstraints<'info> { +pub struct InitializeAccountConstraints { #[account(mut)] - pub payer: Signer<'info>, + pub payer: Signer, #[account( init, @@ -25,20 +26,20 @@ pub struct InitializeAccountConstraints<'info> { extensions::transfer_hook::authority = payer, extensions::transfer_hook::program_id = crate::ID )] - pub mint_account: InterfaceAccount<'info, Mint>, - pub token_program: Program<'info, Token2022>, - pub system_program: Program<'info, System>, + pub mint_account: InterfaceAccount, + pub token_program: Program, + pub system_program: Program, } // create a mint account that specifies this program as the transfer hook program -pub fn handler(mut context: Context, _decimals: u8) -> Result<()> { +pub fn handler(mut context: &mut Context, _decimals: u8) -> Result<()> { handle_check_mint_data(&mut context.accounts)?; Ok(()) } // helper to check mint data, and demonstrate how to read mint extension data within a program fn handle_check_mint_data(accounts: &mut InitializeAccountConstraints) -> Result<()> { - let mint = &accounts.mint_account.to_account_info(); + let mint = &accounts.mint_account.cpi_handle_mut(); let mint_data = mint.data.borrow(); // .map_err() needed because spl-token-2022 uses solana-program-error 2.x // while anchor-lang 1.0 uses 3.x - structurally identical but different semver types @@ -49,7 +50,7 @@ fn handle_check_mint_data(accounts: &mut InitializeAccountConstraints) -> Result assert_eq!( extension_data.authority, - OptionalNonZeroPubkey::try_from(Some(accounts.payer.key())) + OptionalNonZeroPubkey::try_from(Some(accounts.payer.address())) .map_err(|_| ProgramError::InvalidArgument)? ); diff --git a/tokens/token-extensions/transfer-hook/hello-world/anchor/programs/transfer-hook/src/instructions/initialize_extra_account_meta_list.rs b/tokens/token-extensions/transfer-hook/hello-world/anchor/programs/transfer-hook/src/instructions/initialize_extra_account_meta_list.rs index 5a858e593..418073bfd 100644 --- a/tokens/token-extensions/transfer-hook/hello-world/anchor/programs/transfer-hook/src/instructions/initialize_extra_account_meta_list.rs +++ b/tokens/token-extensions/transfer-hook/hello-world/anchor/programs/transfer-hook/src/instructions/initialize_extra_account_meta_list.rs @@ -9,14 +9,14 @@ use spl_transfer_hook_interface::instruction::ExecuteInstruction; use crate::{handle_extra_account_metas, handle_extra_account_metas_count}; #[derive(Accounts)] -pub struct InitializeExtraAccountMetaListAccountConstraints<'info> { +pub struct InitializeExtraAccountMetaListAccountConstraints { #[account(mut)] - payer: Signer<'info>, + payer: Signer, /// CHECK: ExtraAccountMetaList Account, must use these seeds #[account( init, - seeds = [b"extra-account-metas", mint.key().as_ref()], + seeds = [b"extra-account-metas", mint.address().as_ref()], bump, // size_of returns Result with spl's ProgramError - unwrap is safe for known-good input space = ExtraAccountMetaList::size_of( @@ -24,14 +24,14 @@ pub struct InitializeExtraAccountMetaListAccountConstraints<'info> { ).unwrap(), payer = payer )] - pub extra_account_meta_list: UncheckedAccount<'info>, - pub mint: InterfaceAccount<'info, Mint>, - pub token_program: Program<'info, Token2022>, - pub associated_token_program: Program<'info, AssociatedToken>, - pub system_program: Program<'info, System>, + pub extra_account_meta_list: UncheckedAccount, + pub mint: InterfaceAccount, + pub token_program: Program, + pub associated_token_program: Program, + pub system_program: Program, } -pub fn handler(mut context: Context) -> Result<()> { +pub fn handler(mut context: &mut Context) -> Result<()> { let extra_account_metas = handle_extra_account_metas()?; // initialize ExtraAccountMetaList account with extra accounts diff --git a/tokens/token-extensions/transfer-hook/hello-world/anchor/programs/transfer-hook/src/instructions/transfer_hook.rs b/tokens/token-extensions/transfer-hook/hello-world/anchor/programs/transfer-hook/src/instructions/transfer_hook.rs index e7b3a5be8..329d602a5 100644 --- a/tokens/token-extensions/transfer-hook/hello-world/anchor/programs/transfer-hook/src/instructions/transfer_hook.rs +++ b/tokens/token-extensions/transfer-hook/hello-world/anchor/programs/transfer-hook/src/instructions/transfer_hook.rs @@ -1,4 +1,5 @@ use anchor_lang::prelude::*; +use anchor_spl::token; use anchor_spl::token_interface::{Mint, TokenAccount}; use crate::check_is_transferring; @@ -8,20 +9,20 @@ use crate::check_is_transferring; // Remaining accounts are the extra accounts required from the ExtraAccountMetaList account // These accounts are provided via CPI to this program from the token2022 program #[derive(Accounts)] -pub struct TransferHookAccountConstraints<'info> { +pub struct TransferHookAccountConstraints { #[account(token::mint = mint, token::authority = owner)] - pub source_token: InterfaceAccount<'info, TokenAccount>, - pub mint: InterfaceAccount<'info, Mint>, + pub source_token: InterfaceAccount, + pub mint: InterfaceAccount, #[account(token::mint = mint)] - pub destination_token: InterfaceAccount<'info, TokenAccount>, + pub destination_token: InterfaceAccount, /// CHECK: source token account owner, can be SystemAccount or PDA owned by another program - pub owner: UncheckedAccount<'info>, + pub owner: UncheckedAccount, /// CHECK: ExtraAccountMetaList Account, - #[account(seeds = [b"extra-account-metas", mint.key().as_ref()], bump)] - pub extra_account_meta_list: UncheckedAccount<'info>, + #[account(seeds = [b"extra-account-metas", mint.address().as_ref()], bump)] + pub extra_account_meta_list: UncheckedAccount, } -pub fn handler(context: Context, _amount: u64) -> Result<()> { +pub fn handler(context: &mut Context, _amount: u64) -> Result<()> { // Fail this instruction if it is not called from within a transfer hook check_is_transferring(&context)?; diff --git a/tokens/token-extensions/transfer-hook/hello-world/anchor/programs/transfer-hook/src/lib.rs b/tokens/token-extensions/transfer-hook/hello-world/anchor/programs/transfer-hook/src/lib.rs index 9ef5d2be4..b955cfcd4 100644 --- a/tokens/token-extensions/transfer-hook/hello-world/anchor/programs/transfer-hook/src/lib.rs +++ b/tokens/token-extensions/transfer-hook/hello-world/anchor/programs/transfer-hook/src/lib.rs @@ -29,25 +29,25 @@ pub enum TransferError { pub mod transfer_hook { use super::*; - pub fn initialize(context: Context, decimals: u8) -> Result<()> { + pub fn initialize(context: &mut Context, decimals: u8) -> Result<()> { instructions::initialize::handler(context, decimals) } #[instruction(discriminator = InitializeExtraAccountMetaListInstruction::SPL_DISCRIMINATOR_SLICE)] pub fn initialize_extra_account_meta_list( - context: Context, + context: &mut Context, ) -> Result<()> { instructions::initialize_extra_account_meta_list::handler(context) } #[instruction(discriminator = ExecuteInstruction::SPL_DISCRIMINATOR_SLICE)] - pub fn transfer_hook(context: Context, amount: u64) -> Result<()> { + pub fn transfer_hook(context: &mut Context, amount: u64) -> Result<()> { instructions::transfer_hook::handler(context, amount) } } pub fn check_is_transferring(context: &Context) -> Result<()> { - let source_token_info = context.accounts.source_token.to_account_info(); + let source_token_info = context.accounts.source_token.cpi_handle_mut(); let mut account_data_ref: RefMut<&mut [u8]> = source_token_info.try_borrow_mut_data()?; // .map_err() needed because spl-token-2022 uses solana-program-error 2.x // while anchor-lang 1.0 uses 3.x - structurally identical but different semver types diff --git a/tokens/token-extensions/transfer-hook/transfer-cost/anchor/programs/transfer-hook/Cargo.toml b/tokens/token-extensions/transfer-hook/transfer-cost/anchor/programs/transfer-hook/Cargo.toml index 53c6e80ed..8f1d2d09c 100644 --- a/tokens/token-extensions/transfer-hook/transfer-cost/anchor/programs/transfer-hook/Cargo.toml +++ b/tokens/token-extensions/transfer-hook/transfer-cost/anchor/programs/transfer-hook/Cargo.toml @@ -14,14 +14,22 @@ cpi = ["no-entrypoint"] no-entrypoint = [] no-idl = [] no-log-ix-name = [] -idl-build = ["anchor-lang/idl-build", "anchor-spl/idl-build"] +idl-build = ["anchor-lang/idl-build"] anchor-debug = [] custom-heap = [] custom-panic = [] [dependencies] -anchor-lang = "1.1.2" -anchor-spl = "1.1.2" +anchor-lang = { version = "2.0.0-rc.1", features = ["compat"] } +# The `#[program]` macro expands to `wincode` paths for instruction-data +# (de)serialization, so the crate has to be a direct dependency. +wincode = { version = "0.5", features = ["derive"] } +# anchor-lang 2.0.0-rc.1 is built against wincode 0.5, but solana-address 2.7 +# moved to wincode 0.6. With both in the graph, `Address`'s wincode impls +# belong to the version the account derives are not using and every +# `SchemaRead`/`SchemaWrite` bound fails. 2.6.1 is the last 0.5-line release. +solana-address = ">=2.6, <2.7" +anchor-spl = "2.0.0-rc.1" # SPL crates v3.x-compatible - uses solana-program-error 3.x matching anchor-lang 1.0 spl-discriminator = "0.5.2" spl-tlv-account-resolution = "0.11.1" diff --git a/tokens/token-extensions/transfer-hook/transfer-cost/anchor/programs/transfer-hook/src/instructions/initialize_extra_account_meta_list.rs b/tokens/token-extensions/transfer-hook/transfer-cost/anchor/programs/transfer-hook/src/instructions/initialize_extra_account_meta_list.rs index d19d662fe..7b4b21329 100644 --- a/tokens/token-extensions/transfer-hook/transfer-cost/anchor/programs/transfer-hook/src/instructions/initialize_extra_account_meta_list.rs +++ b/tokens/token-extensions/transfer-hook/transfer-cost/anchor/programs/transfer-hook/src/instructions/initialize_extra_account_meta_list.rs @@ -6,14 +6,14 @@ use spl_transfer_hook_interface::instruction::ExecuteInstruction; use crate::{handle_extra_account_metas, handle_extra_account_metas_count, CounterAccount}; #[derive(Accounts)] -pub struct InitializeExtraAccountMetaListAccountConstraints<'info> { +pub struct InitializeExtraAccountMetaListAccountConstraints { #[account(mut)] - payer: Signer<'info>, + payer: Signer, /// CHECK: ExtraAccountMetaList Account, must use these seeds #[account( init, - seeds = [b"extra-account-metas", mint.key().as_ref()], + seeds = [b"extra-account-metas", mint.address().as_ref()], bump, // size_of returns Result with spl's ProgramError - unwrap is safe for known-good input space = ExtraAccountMetaList::size_of( @@ -21,14 +21,14 @@ pub struct InitializeExtraAccountMetaListAccountConstraints<'info> { ).unwrap(), payer = payer )] - pub extra_account_meta_list: UncheckedAccount<'info>, - pub mint: InterfaceAccount<'info, Mint>, + pub extra_account_meta_list: UncheckedAccount, + pub mint: InterfaceAccount, #[account(init, seeds = [b"counter"], bump, payer = payer, space = CounterAccount::DISCRIMINATOR.len() + CounterAccount::INIT_SPACE)] - pub counter_account: Account<'info, CounterAccount>, - pub system_program: Program<'info, System>, + pub counter_account: BorshAccount, + pub system_program: Program, } -pub fn handler(mut context: Context) -> Result<()> { +pub fn handler(mut context: &mut Context) -> Result<()> { let extra_account_metas = handle_extra_account_metas()?; // initialize ExtraAccountMetaList account with extra accounts diff --git a/tokens/token-extensions/transfer-hook/transfer-cost/anchor/programs/transfer-hook/src/instructions/transfer_hook.rs b/tokens/token-extensions/transfer-hook/transfer-cost/anchor/programs/transfer-hook/src/instructions/transfer_hook.rs index c7f5b9ec1..11da2ebfd 100644 --- a/tokens/token-extensions/transfer-hook/transfer-cost/anchor/programs/transfer-hook/src/instructions/transfer_hook.rs +++ b/tokens/token-extensions/transfer-hook/transfer-cost/anchor/programs/transfer-hook/src/instructions/transfer_hook.rs @@ -17,43 +17,43 @@ use crate::{check_is_transferring, CounterAccount, TransferError}; // the 4096-byte BPF stack frame limit in try_accounts deserialization. // This struct has 12 accounts - without Box, the generated code uses ~4160 bytes of stack. #[derive(Accounts)] -pub struct TransferHookAccountConstraints<'info> { +pub struct TransferHookAccountConstraints { #[account(token::mint = mint, token::authority = owner)] - pub source_token: Box>, - pub mint: Box>, + pub source_token: Box>, + pub mint: Box>, #[account(token::mint = mint)] - pub destination_token: Box>, + pub destination_token: Box>, /// CHECK: source token account owner, can be SystemAccount or PDA owned by another program - pub owner: UncheckedAccount<'info>, + pub owner: UncheckedAccount, /// CHECK: ExtraAccountMetaList Account, - #[account(seeds = [b"extra-account-metas", mint.key().as_ref()], bump)] - pub extra_account_meta_list: UncheckedAccount<'info>, - pub wsol_mint: Box>, - pub token_program: Program<'info, Token>, - pub associated_token_program: Program<'info, AssociatedToken>, + #[account(seeds = [b"extra-account-metas", mint.address().as_ref()], bump)] + pub extra_account_meta_list: UncheckedAccount, + pub wsol_mint: Box>, + pub token_program: Program, + pub associated_token_program: Program, #[account( mut, seeds = [b"delegate"], bump )] - pub delegate: SystemAccount<'info>, + pub delegate: SystemAccount, #[account( mut, token::mint = wsol_mint, token::authority = delegate, )] - pub delegate_wsol_token_account: Box>, + pub delegate_wsol_token_account: Box>, #[account( mut, token::mint = wsol_mint, token::authority = owner, )] - pub sender_wsol_token_account: Box>, + pub sender_wsol_token_account: Box>, #[account(seeds = [b"counter"], bump)] - pub counter_account: Account<'info, CounterAccount>, + pub counter_account: BorshAccount, } -pub fn handler(context: Context, amount: u64) -> Result<()> { +pub fn handler(context: &mut Context, amount: u64) -> Result<()> { // Fail this instruction if it is not called from within a transfer hook check_is_transferring(&context)?; @@ -70,15 +70,15 @@ pub fn handler(context: Context, amount: u64) -> msg!( "Is writable mint {0}", - context.accounts.mint.to_account_info().is_writable + context.accounts.mint.cpi_handle_mut().is_writable ); msg!( "Is destination mint {0}", - context.accounts.destination_token.to_account_info().is_writable + context.accounts.destination_token.cpi_handle_mut().is_writable ); msg!( "Is source mint {0}", - context.accounts.source_token.to_account_info().is_writable + context.accounts.source_token.cpi_handle_mut().is_writable ); let signer_seeds: &[&[&[u8]]] = &[&[b"delegate", &[context.bumps.delegate]]]; @@ -87,17 +87,17 @@ pub fn handler(context: Context, amount: u64) -> // transfer lamports amount equal to token transfer amount transfer_checked( CpiContext::new( - context.accounts.token_program.key(), + context.accounts.token_program.address(), TransferChecked { - from: context.accounts.sender_wsol_token_account.to_account_info(), - mint: context.accounts.wsol_mint.to_account_info(), - to: context.accounts.delegate_wsol_token_account.to_account_info(), - authority: context.accounts.delegate.to_account_info(), + from: context.accounts.sender_wsol_token_account.cpi_handle_mut(), + mint: context.accounts.wsol_mint.cpi_handle(), + to: context.accounts.delegate_wsol_token_account.cpi_handle_mut(), + authority: context.accounts.delegate.cpi_handle(), }, ) .with_signer(signer_seeds), amount, - context.accounts.wsol_mint.decimals, + context.accounts.wsol_mint.decimals(), )?; Ok(()) } diff --git a/tokens/token-extensions/transfer-hook/transfer-cost/anchor/programs/transfer-hook/src/lib.rs b/tokens/token-extensions/transfer-hook/transfer-cost/anchor/programs/transfer-hook/src/lib.rs index 7c17b8ecb..bad3c2739 100644 --- a/tokens/token-extensions/transfer-hook/transfer-cost/anchor/programs/transfer-hook/src/lib.rs +++ b/tokens/token-extensions/transfer-hook/transfer-cost/anchor/programs/transfer-hook/src/lib.rs @@ -1,4 +1,4 @@ -use anchor_lang::{prelude::*, solana_program::pubkey::Pubkey}; +use anchor_lang::{prelude::*, solana_program::pubkey::Address}; use anchor_spl::{ associated_token::AssociatedToken, token::Token, @@ -42,19 +42,19 @@ pub mod transfer_hook { #[instruction(discriminator = InitializeExtraAccountMetaListInstruction::SPL_DISCRIMINATOR_SLICE)] pub fn initialize_extra_account_meta_list( - context: Context, + context: &mut Context, ) -> Result<()> { instructions::initialize_extra_account_meta_list::handler(context) } #[instruction(discriminator = ExecuteInstruction::SPL_DISCRIMINATOR_SLICE)] - pub fn transfer_hook(context: Context, amount: u64) -> Result<()> { + pub fn transfer_hook(context: &mut Context, amount: u64) -> Result<()> { instructions::transfer_hook::handler(context, amount) } } pub fn check_is_transferring(context: &Context) -> Result<()> { - let source_token_info = context.accounts.source_token.to_account_info(); + let source_token_info = context.accounts.source_token.cpi_handle_mut(); let mut account_data_ref: RefMut<&mut [u8]> = source_token_info.try_borrow_mut_data()?; let mut account = PodStateWithExtensionsMut::::unpack(*account_data_ref) .map_err(|_| ProgramError::InvalidAccountData)?; @@ -77,7 +77,7 @@ pub fn handle_extra_account_metas() -> Result> { // index 0-3 are the accounts required for token transfer (source, mint, destination, owner) // index 4 is address of ExtraAccountMetaList account - let wsol_mint = Pubkey::from_str("So11111111111111111111111111111111111111112").unwrap(); + let wsol_mint = Address::from_str("So11111111111111111111111111111111111111112").unwrap(); let token_program_id = Token::id(); let ata_program_id = AssociatedToken::id(); @@ -140,7 +140,7 @@ pub fn handle_extra_account_metas_count() -> usize { 7 // wsol_mint, token_program, ata_program, delegate, delegate_wsol, sender_wsol, counter } -#[account] +#[account(borsh)] #[derive(InitSpace)] pub struct CounterAccount { pub counter: u8, diff --git a/tokens/token-extensions/transfer-hook/transfer-switch/anchor/programs/transfer-switch/Cargo.toml b/tokens/token-extensions/transfer-hook/transfer-switch/anchor/programs/transfer-switch/Cargo.toml index bc41fd69c..17c4b0a2d 100644 --- a/tokens/token-extensions/transfer-hook/transfer-switch/anchor/programs/transfer-switch/Cargo.toml +++ b/tokens/token-extensions/transfer-hook/transfer-switch/anchor/programs/transfer-switch/Cargo.toml @@ -14,14 +14,22 @@ cpi = ["no-entrypoint"] no-entrypoint = [] no-idl = [] no-log-ix-name = [] -idl-build = ["anchor-lang/idl-build", "anchor-spl/idl-build"] +idl-build = ["anchor-lang/idl-build"] anchor-debug = [] custom-heap = [] custom-panic = [] [dependencies] -anchor-lang = { version = "1.1.2", features = ["init-if-needed"] } -anchor-spl = "1.1.2" +anchor-lang = { version = "2.0.0-rc.1", features = ["compat"] } +# The `#[program]` macro expands to `wincode` paths for instruction-data +# (de)serialization, so the crate has to be a direct dependency. +wincode = { version = "0.5", features = ["derive"] } +# anchor-lang 2.0.0-rc.1 is built against wincode 0.5, but solana-address 2.7 +# moved to wincode 0.6. With both in the graph, `Address`'s wincode impls +# belong to the version the account derives are not using and every +# `SchemaRead`/`SchemaWrite` bound fails. 2.6.1 is the last 0.5-line release. +solana-address = ">=2.6, <2.7" +anchor-spl = "2.0.0-rc.1" spl-discriminator = "0.4.1" spl-tlv-account-resolution = "0.9.0" spl-transfer-hook-interface = "0.9.0" diff --git a/tokens/token-extensions/transfer-hook/transfer-switch/anchor/programs/transfer-switch/src/instructions/configure_admin.rs b/tokens/token-extensions/transfer-hook/transfer-switch/anchor/programs/transfer-switch/src/instructions/configure_admin.rs index b9fc5800e..f7199eb77 100644 --- a/tokens/token-extensions/transfer-hook/transfer-switch/anchor/programs/transfer-switch/src/instructions/configure_admin.rs +++ b/tokens/token-extensions/transfer-hook/transfer-switch/anchor/programs/transfer-switch/src/instructions/configure_admin.rs @@ -1,13 +1,13 @@ use {crate::state::AdminConfig, anchor_lang::prelude::*}; #[derive(Accounts)] -pub struct ConfigureAdminAccountConstraints<'info> { +pub struct ConfigureAdminAccountConstraints { #[account(mut)] - pub admin: Signer<'info>, + pub admin: Signer, /// CHECK: the new admin #[account(mut)] - pub new_admin: UncheckedAccount<'info>, + pub new_admin: UncheckedAccount, /// To hold the address of the admin that controls switches #[account( @@ -17,9 +17,9 @@ pub struct ConfigureAdminAccountConstraints<'info> { seeds = [b"admin-config"], bump )] - pub admin_config: Account<'info, AdminConfig>, + pub admin_config: BorshAccount, - pub system_program: Program<'info, System>, + pub system_program: Program, } pub fn handle_is_admin(accounts: &mut ConfigureAdminAccountConstraints) -> Result<()> { @@ -29,18 +29,18 @@ pub fn handle_is_admin(accounts: &mut ConfigureAdminAccountConstraints) -> Resul if accounts.admin_config.is_initialised { // make sure it's the admin // - require_keys_eq!(accounts.admin.key(), accounts.admin_config.admin,); + require_keys_eq!(accounts.admin.address(), accounts.admin_config.admin,); // make sure the admin is not reentering their key // - require_keys_neq!(accounts.admin.key(), accounts.new_admin.key()); + require_keys_neq!(accounts.admin.address(), accounts.new_admin.address()); } Ok(()) } pub fn handle_configure_admin(accounts: &mut ConfigureAdminAccountConstraints, bump: u8) -> Result<()> { - accounts.admin_config.set_inner(AdminConfig { - admin: accounts.new_admin.key(), // set the admin pubkey that can switch transfers on/off + *accounts.admin_config = (AdminConfig { + admin: *accounts.new_admin.address(), // set the admin pubkey that can switch transfers on/off is_initialised: true, // let us know an admin has been set bump, // canonical bump for the admin-config PDA }); diff --git a/tokens/token-extensions/transfer-hook/transfer-switch/anchor/programs/transfer-switch/src/instructions/initialise_extra_account_metas_list.rs b/tokens/token-extensions/transfer-hook/transfer-switch/anchor/programs/transfer-switch/src/instructions/initialise_extra_account_metas_list.rs index e97028e60..76b1ab64c 100644 --- a/tokens/token-extensions/transfer-hook/transfer-switch/anchor/programs/transfer-switch/src/instructions/initialise_extra_account_metas_list.rs +++ b/tokens/token-extensions/transfer-hook/transfer-switch/anchor/programs/transfer-switch/src/instructions/initialise_extra_account_metas_list.rs @@ -11,22 +11,22 @@ use { }; #[derive(Accounts)] -pub struct InitializeExtraAccountMetasAccountConstraints<'info> { +pub struct InitializeExtraAccountMetasAccountConstraints { #[account(mut)] - pub payer: Signer<'info>, + pub payer: Signer, #[account()] - pub token_mint: InterfaceAccount<'info, Mint>, + pub token_mint: InterfaceAccount, /// CHECK: extra accoumt metas list #[account( mut, - seeds = [b"extra-account-metas", token_mint.key().as_ref()], + seeds = [b"extra-account-metas", token_mint.address().as_ref()], bump, )] - pub extra_account_metas_list: UncheckedAccount<'info>, + pub extra_account_metas_list: UncheckedAccount, - pub system_program: Program<'info, System>, + pub system_program: Program, } pub fn handle_initialize_extra_account_metas_list(accounts: &mut InitializeExtraAccountMetasAccountConstraints, bumps: InitializeExtraAccountMetasAccountConstraintsBumps) -> Result<()> { @@ -48,9 +48,9 @@ pub fn handle_initialize_extra_account_metas_list(accounts: &mut InitializeExtra let account_size = ExtraAccountMetaList::size_of(account_metas.len()).unwrap() as u64; // calculate minimum required lamports - let lamports = Rent::get()?.minimum_balance(account_size as usize); + let lamports = Rent::get()?.try_minimum_balance(account_size as usize)?; - let mint = accounts.token_mint.key(); + let mint = accounts.token_mint.address(); let signer_seeds: &[&[&[u8]]] = &[&[ b"extra-account-metas", mint.as_ref(), @@ -59,10 +59,10 @@ pub fn handle_initialize_extra_account_metas_list(accounts: &mut InitializeExtra create_account( CpiContext::new( - accounts.system_program.key(), + accounts.system_program.address(), CreateAccount { - from: accounts.payer.to_account_info(), - to: accounts.extra_account_metas_list.to_account_info(), + from: accounts.payer.cpi_handle_mut(), + to: accounts.extra_account_metas_list.cpi_handle_mut(), }, ) .with_signer(signer_seeds), diff --git a/tokens/token-extensions/transfer-hook/transfer-switch/anchor/programs/transfer-switch/src/instructions/switch.rs b/tokens/token-extensions/transfer-hook/transfer-switch/anchor/programs/transfer-switch/src/instructions/switch.rs index f33d67d3b..40ddf9c6c 100644 --- a/tokens/token-extensions/transfer-hook/transfer-switch/anchor/programs/transfer-switch/src/instructions/switch.rs +++ b/tokens/token-extensions/transfer-hook/transfer-switch/anchor/programs/transfer-switch/src/instructions/switch.rs @@ -4,14 +4,14 @@ use { }; #[derive(Accounts)] -pub struct SwitchAccountConstraints<'info> { +pub struct SwitchAccountConstraints { /// admin that controls the switch #[account(mut)] - pub admin: Signer<'info>, + pub admin: Signer, /// CHECK: wallet - transfer sender #[account(mut)] - pub wallet: UncheckedAccount<'info>, + pub wallet: UncheckedAccount, /// admin config #[account( @@ -19,26 +19,26 @@ pub struct SwitchAccountConstraints<'info> { seeds=[b"admin-config"], bump, )] - pub admin_config: Account<'info, AdminConfig>, + pub admin_config: BorshAccount, /// the wallet (sender) transfer switch #[account( init_if_needed, payer=admin, space = TransferSwitch::DISCRIMINATOR.len() + TransferSwitch::INIT_SPACE, - seeds = [wallet.key().as_ref()], + seeds = [wallet.address().as_ref()], bump, )] - pub wallet_switch: Account<'info, TransferSwitch>, + pub wallet_switch: BorshAccount, - pub system_program: Program<'info, System>, + pub system_program: Program, } pub fn handle_switch(accounts: &mut SwitchAccountConstraints, on: bool, bump: u8) -> Result<()> { // toggle switch on/off for the given wallet // - accounts.wallet_switch.set_inner(TransferSwitch { - wallet: accounts.wallet.key(), + *accounts.wallet_switch = (TransferSwitch { + wallet: *accounts.wallet.address(), on, bump, // canonical bump for this wallet's PDA }); diff --git a/tokens/token-extensions/transfer-hook/transfer-switch/anchor/programs/transfer-switch/src/instructions/transfer_hook.rs b/tokens/token-extensions/transfer-hook/transfer-switch/anchor/programs/transfer-switch/src/instructions/transfer_hook.rs index c35671596..35c5e9ef0 100644 --- a/tokens/token-extensions/transfer-hook/transfer-switch/anchor/programs/transfer-switch/src/instructions/transfer_hook.rs +++ b/tokens/token-extensions/transfer-hook/transfer-switch/anchor/programs/transfer-switch/src/instructions/transfer_hook.rs @@ -14,36 +14,36 @@ use { }; #[derive(Accounts)] -pub struct TransferHookAccountConstraints<'info> { +pub struct TransferHookAccountConstraints { /// CHECK: Sender token account #[account()] - pub source_token_account: UncheckedAccount<'info>, + pub source_token_account: UncheckedAccount, /// The mint of the token transferring #[account()] - pub token_mint: InterfaceAccount<'info, Mint>, + pub token_mint: InterfaceAccount, /// CHECK: Recipient token account #[account()] - pub receiver_token_account: UncheckedAccount<'info>, + pub receiver_token_account: UncheckedAccount, /// CHECK: the transfer sender #[account()] - pub wallet: UncheckedAccount<'info>, + pub wallet: UncheckedAccount, /// CHECK: extra account metas #[account( - seeds = [b"extra-account-metas", token_mint.key().as_ref()], + seeds = [b"extra-account-metas", token_mint.address().as_ref()], bump, )] - pub extra_account_metas_list: UncheckedAccount<'info>, + pub extra_account_metas_list: UncheckedAccount, /// sender transfer switch #[account( - seeds=[wallet.key().as_ref()], + seeds=[wallet.address().as_ref()], bump, )] - pub wallet_switch: Account<'info, TransferSwitch>, + pub wallet_switch: BorshAccount, } pub fn handle_assert_switch_is_on(accounts: &mut TransferHookAccountConstraints) -> Result<()> { @@ -54,7 +54,7 @@ pub fn handle_assert_switch_is_on(accounts: &mut TransferHookAccountConstraints) } pub fn handle_assert_is_transferring(accounts: &mut TransferHookAccountConstraints) -> Result<()> { - let source_token_info = accounts.source_token_account.to_account_info(); + let source_token_info = accounts.source_token_account.cpi_handle_mut(); let mut account_data_ref = source_token_info.try_borrow_mut_data()?; // .map_err() needed because spl-token-2022 uses solana-program-error 2.x // while anchor-lang 1.0 uses 3.x - structurally identical but different semver types diff --git a/tokens/token-extensions/transfer-hook/transfer-switch/anchor/programs/transfer-switch/src/lib.rs b/tokens/token-extensions/transfer-hook/transfer-switch/anchor/programs/transfer-switch/src/lib.rs index c392c703c..0f609ff47 100644 --- a/tokens/token-extensions/transfer-hook/transfer-switch/anchor/programs/transfer-switch/src/lib.rs +++ b/tokens/token-extensions/transfer-hook/transfer-switch/anchor/programs/transfer-switch/src/lib.rs @@ -15,7 +15,7 @@ declare_id!("FjcHckEgXcBhFmSGai3FRpDLiT6hbpV893n8iTxVd81g"); pub mod transfer_switch { use super::*; - pub fn configure_admin(mut context: Context) -> Result<()> { + pub fn configure_admin(mut context: &mut Context) -> Result<()> { let bump = context.bumps.admin_config; handle_is_admin(&mut context.accounts)?; handle_configure_admin(&mut context.accounts, bump) @@ -23,18 +23,18 @@ pub mod transfer_switch { #[instruction(discriminator = InitializeExtraAccountMetaListInstruction::SPL_DISCRIMINATOR_SLICE)] pub fn initialize_extra_account_metas_list( - mut context: Context, + mut context: &mut Context, ) -> Result<()> { handle_initialize_extra_account_metas_list(&mut context.accounts, context.bumps) } - pub fn switch(mut context: Context, on: bool) -> Result<()> { + pub fn switch(mut context: &mut Context, on: bool) -> Result<()> { let bump = context.bumps.wallet_switch; handle_switch(&mut context.accounts, on, bump) } #[instruction(discriminator = ExecuteInstruction::SPL_DISCRIMINATOR_SLICE)] - pub fn transfer_hook(mut context: Context, _amount: u64) -> Result<()> { + pub fn transfer_hook(mut context: &mut Context, _amount: u64) -> Result<()> { handle_assert_is_transferring(&mut context.accounts)?; handle_assert_switch_is_on(&mut context.accounts) } diff --git a/tokens/token-extensions/transfer-hook/transfer-switch/anchor/programs/transfer-switch/src/state.rs b/tokens/token-extensions/transfer-hook/transfer-switch/anchor/programs/transfer-switch/src/state.rs index 3bbcddd12..41e036256 100644 --- a/tokens/token-extensions/transfer-hook/transfer-switch/anchor/programs/transfer-switch/src/state.rs +++ b/tokens/token-extensions/transfer-hook/transfer-switch/anchor/programs/transfer-switch/src/state.rs @@ -1,19 +1,19 @@ use anchor_lang::prelude::*; -#[account] +#[account(borsh)] #[derive(InitSpace)] pub struct TransferSwitch { - pub wallet: Pubkey, + pub wallet: Address, pub on: bool, /// Canonical bump for this PDA. pub bump: u8, } -#[account] +#[account(borsh)] #[derive(InitSpace)] pub struct AdminConfig { pub is_initialised: bool, - pub admin: Pubkey, + pub admin: Address, /// Canonical bump for this PDA. pub bump: u8, } diff --git a/tokens/token-extensions/transfer-hook/whitelist/anchor/programs/transfer-hook/Cargo.toml b/tokens/token-extensions/transfer-hook/whitelist/anchor/programs/transfer-hook/Cargo.toml index 57df243ed..3b769ebc2 100644 --- a/tokens/token-extensions/transfer-hook/whitelist/anchor/programs/transfer-hook/Cargo.toml +++ b/tokens/token-extensions/transfer-hook/whitelist/anchor/programs/transfer-hook/Cargo.toml @@ -14,14 +14,22 @@ cpi = ["no-entrypoint"] no-entrypoint = [] no-idl = [] no-log-ix-name = [] -idl-build = ["anchor-lang/idl-build", "anchor-spl/idl-build"] +idl-build = ["anchor-lang/idl-build"] anchor-debug = [] custom-heap = [] custom-panic = [] [dependencies] -anchor-lang = { version = "1.1.2", features = ["init-if-needed"] } -anchor-spl = "1.1.2" +anchor-lang = { version = "2.0.0-rc.1", features = ["compat"] } +# The `#[program]` macro expands to `wincode` paths for instruction-data +# (de)serialization, so the crate has to be a direct dependency. +wincode = { version = "0.5", features = ["derive"] } +# anchor-lang 2.0.0-rc.1 is built against wincode 0.5, but solana-address 2.7 +# moved to wincode 0.6. With both in the graph, `Address`'s wincode impls +# belong to the version the account derives are not using and every +# `SchemaRead`/`SchemaWrite` bound fails. 2.6.1 is the last 0.5-line release. +solana-address = ">=2.6, <2.7" +anchor-spl = "2.0.0-rc.1" spl-discriminator = "0.4.1" spl-tlv-account-resolution = "0.9.0" spl-transfer-hook-interface = "0.9.0" diff --git a/tokens/token-extensions/transfer-hook/whitelist/anchor/programs/transfer-hook/src/instructions/add_to_whitelist.rs b/tokens/token-extensions/transfer-hook/whitelist/anchor/programs/transfer-hook/src/instructions/add_to_whitelist.rs index 86e100ef5..574e455e3 100644 --- a/tokens/token-extensions/transfer-hook/whitelist/anchor/programs/transfer-hook/src/instructions/add_to_whitelist.rs +++ b/tokens/token-extensions/transfer-hook/whitelist/anchor/programs/transfer-hook/src/instructions/add_to_whitelist.rs @@ -3,32 +3,32 @@ use anchor_lang::prelude::*; use crate::WhiteList; #[derive(Accounts)] -pub struct AddToWhiteListAccountConstraints<'info> { +pub struct AddToWhiteListAccountConstraints { /// CHECK: New account to add to white list #[account()] - pub new_account: UncheckedAccount<'info>, + pub new_account: UncheckedAccount, #[account( mut, seeds = [b"white_list"], bump )] - pub white_list: Account<'info, WhiteList>, + pub white_list: BorshAccount, #[account(mut)] - pub signer: Signer<'info>, + pub signer: Signer, } -pub fn handler(context: Context) -> Result<()> { - if context.accounts.white_list.authority != context.accounts.signer.key() { +pub fn handler(context: &mut Context) -> Result<()> { + if context.accounts.white_list.authority != *context.accounts.signer.address() { panic!("Only the authority can add to the white list!"); } context.accounts .white_list .white_list - .push(context.accounts.new_account.key()); + .push(*context.accounts.new_account.address()); msg!( "New account white listed! {0}", - context.accounts.new_account.key().to_string() + context.accounts.new_account.address().to_string() ); msg!( "White list length! {0}", diff --git a/tokens/token-extensions/transfer-hook/whitelist/anchor/programs/transfer-hook/src/instructions/initialize_extra_account_meta_list.rs b/tokens/token-extensions/transfer-hook/whitelist/anchor/programs/transfer-hook/src/instructions/initialize_extra_account_meta_list.rs index a8c5f6def..f02badec1 100644 --- a/tokens/token-extensions/transfer-hook/whitelist/anchor/programs/transfer-hook/src/instructions/initialize_extra_account_meta_list.rs +++ b/tokens/token-extensions/transfer-hook/whitelist/anchor/programs/transfer-hook/src/instructions/initialize_extra_account_meta_list.rs @@ -6,14 +6,14 @@ use spl_transfer_hook_interface::instruction::ExecuteInstruction; use crate::{handle_extra_account_metas, handle_extra_account_metas_count, WhiteList}; #[derive(Accounts)] -pub struct InitializeExtraAccountMetaListAccountConstraints<'info> { +pub struct InitializeExtraAccountMetaListAccountConstraints { #[account(mut)] - payer: Signer<'info>, + payer: Signer, /// CHECK: ExtraAccountMetaList Account, must use these seeds #[account( init, - seeds = [b"extra-account-metas", mint.key().as_ref()], + seeds = [b"extra-account-metas", mint.address().as_ref()], bump, // size_of returns Result with spl's ProgramError - unwrap is safe for known-good input space = ExtraAccountMetaList::size_of( @@ -21,16 +21,16 @@ pub struct InitializeExtraAccountMetaListAccountConstraints<'info> { ).unwrap(), payer = payer )] - pub extra_account_meta_list: UncheckedAccount<'info>, - pub mint: InterfaceAccount<'info, Mint>, - pub system_program: Program<'info, System>, + pub extra_account_meta_list: UncheckedAccount, + pub mint: InterfaceAccount, + pub system_program: Program, #[account(init_if_needed, seeds = [b"white_list"], bump, payer = payer, space = WhiteList::DISCRIMINATOR.len() + WhiteList::INIT_SPACE)] - pub white_list: Account<'info, WhiteList>, + pub white_list: BorshAccount, } -pub fn handler(mut context: Context) -> Result<()> { +pub fn handler(mut context: &mut Context) -> Result<()> { // set authority field on white_list account as payer address - context.accounts.white_list.authority = context.accounts.payer.key(); + context.accounts.white_list.authority = *context.accounts.payer.address(); context.accounts.white_list.bump = context.bumps.white_list; let extra_account_metas = handle_extra_account_metas()?; diff --git a/tokens/token-extensions/transfer-hook/whitelist/anchor/programs/transfer-hook/src/instructions/transfer_hook.rs b/tokens/token-extensions/transfer-hook/whitelist/anchor/programs/transfer-hook/src/instructions/transfer_hook.rs index d0279a605..4fb4d9cae 100644 --- a/tokens/token-extensions/transfer-hook/whitelist/anchor/programs/transfer-hook/src/instructions/transfer_hook.rs +++ b/tokens/token-extensions/transfer-hook/whitelist/anchor/programs/transfer-hook/src/instructions/transfer_hook.rs @@ -1,4 +1,5 @@ use anchor_lang::prelude::*; +use anchor_spl::token; use anchor_spl::token_interface::{Mint, TokenAccount}; use crate::{check_is_transferring, WhiteList}; @@ -8,22 +9,22 @@ use crate::{check_is_transferring, WhiteList}; // Remaining accounts are the extra accounts required from the ExtraAccountMetaList account // These accounts are provided via CPI to this program from the token2022 program #[derive(Accounts)] -pub struct TransferHookAccountConstraints<'info> { +pub struct TransferHookAccountConstraints { #[account(token::mint = mint, token::authority = owner)] - pub source_token: InterfaceAccount<'info, TokenAccount>, - pub mint: InterfaceAccount<'info, Mint>, + pub source_token: InterfaceAccount, + pub mint: InterfaceAccount, #[account(token::mint = mint)] - pub destination_token: InterfaceAccount<'info, TokenAccount>, + pub destination_token: InterfaceAccount, /// CHECK: source token account owner, can be SystemAccount or PDA owned by another program - pub owner: UncheckedAccount<'info>, + pub owner: UncheckedAccount, /// CHECK: ExtraAccountMetaList Account, - #[account(seeds = [b"extra-account-metas", mint.key().as_ref()], bump)] - pub extra_account_meta_list: UncheckedAccount<'info>, + #[account(seeds = [b"extra-account-metas", mint.address().as_ref()], bump)] + pub extra_account_meta_list: UncheckedAccount, #[account(seeds = [b"white_list"], bump)] - pub white_list: Account<'info, WhiteList>, + pub white_list: BorshAccount, } -pub fn handler(context: Context, _amount: u64) -> Result<()> { +pub fn handler(context: &mut Context, _amount: u64) -> Result<()> { // Fail this instruction if it is not called from within a transfer hook check_is_transferring(&context)?; @@ -31,7 +32,7 @@ pub fn handler(context: Context, _amount: u64) - .accounts .white_list .white_list - .contains(&context.accounts.destination_token.key()) + .contains(&context.accounts.destination_token.address()) { panic!("Account not in white list!"); } diff --git a/tokens/token-extensions/transfer-hook/whitelist/anchor/programs/transfer-hook/src/lib.rs b/tokens/token-extensions/transfer-hook/whitelist/anchor/programs/transfer-hook/src/lib.rs index 08089435b..76514003c 100644 --- a/tokens/token-extensions/transfer-hook/whitelist/anchor/programs/transfer-hook/src/lib.rs +++ b/tokens/token-extensions/transfer-hook/whitelist/anchor/programs/transfer-hook/src/lib.rs @@ -31,23 +31,23 @@ pub mod transfer_hook { #[instruction(discriminator = InitializeExtraAccountMetaListInstruction::SPL_DISCRIMINATOR_SLICE)] pub fn initialize_extra_account_meta_list( - context: Context, + context: &mut Context, ) -> Result<()> { instructions::initialize_extra_account_meta_list::handler(context) } #[instruction(discriminator = ExecuteInstruction::SPL_DISCRIMINATOR_SLICE)] - pub fn transfer_hook(context: Context, amount: u64) -> Result<()> { + pub fn transfer_hook(context: &mut Context, amount: u64) -> Result<()> { instructions::transfer_hook::handler(context, amount) } - pub fn add_to_whitelist(context: Context) -> Result<()> { + pub fn add_to_whitelist(context: &mut Context) -> Result<()> { instructions::add_to_whitelist::handler(context) } } pub fn check_is_transferring(context: &Context) -> Result<()> { - let source_token_info = context.accounts.source_token.to_account_info(); + let source_token_info = context.accounts.source_token.cpi_handle_mut(); let mut account_data_ref: RefMut<&mut [u8]> = source_token_info.try_borrow_mut_data()?; // .map_err() needed because spl-token-2022 uses solana-program-error 2.x // while anchor-lang 1.0 uses 3.x - structurally identical but different semver types @@ -81,12 +81,12 @@ pub fn handle_extra_account_metas_count() -> usize { 1 // one extra account: the whitelist PDA } -#[account] +#[account(borsh)] #[derive(InitSpace)] pub struct WhiteList { - pub authority: Pubkey, + pub authority: Address, #[max_len(11)] - pub white_list: Vec, + pub white_list: Vec
, /// Canonical bump for this PDA. pub bump: u8, } diff --git a/tokens/token-minter/anchor/programs/token-minter/Cargo.toml b/tokens/token-minter/anchor/programs/token-minter/Cargo.toml index aabe1312a..b35258de5 100644 --- a/tokens/token-minter/anchor/programs/token-minter/Cargo.toml +++ b/tokens/token-minter/anchor/programs/token-minter/Cargo.toml @@ -14,14 +14,22 @@ cpi = ["no-entrypoint"] no-entrypoint = [] no-idl = [] no-log-ix-name = [] -idl-build = ["anchor-lang/idl-build", "anchor-spl/idl-build"] +idl-build = ["anchor-lang/idl-build"] anchor-debug = [] custom-heap = [] custom-panic = [] [dependencies] -anchor-lang = { version = "1.1.2", features = ["init-if-needed"] } -anchor-spl = { version = "1.1.2", features = ["metadata"] } +anchor-lang = "2.0.0-rc.1" +# The `#[program]` macro expands to `wincode` paths for instruction-data +# (de)serialization, so the crate has to be a direct dependency. +wincode = { version = "0.5", features = ["derive"] } +# anchor-lang 2.0.0-rc.1 is built against wincode 0.5, but solana-address 2.7 +# moved to wincode 0.6. With both in the graph, `Address`'s wincode impls +# belong to the version the account derives are not using and every +# `SchemaRead`/`SchemaWrite` bound fails. 2.6.1 is the last 0.5-line release. +solana-address = ">=2.6, <2.7" +anchor-spl = { version = "2.0.0-rc.1", features = ["metadata"] } [dev-dependencies] litesvm = "0.13.1" diff --git a/tokens/token-minter/anchor/programs/token-minter/src/instructions/create.rs b/tokens/token-minter/anchor/programs/token-minter/src/instructions/create.rs index 95be34e0d..842a9c381 100644 --- a/tokens/token-minter/anchor/programs/token-minter/src/instructions/create.rs +++ b/tokens/token-minter/anchor/programs/token-minter/src/instructions/create.rs @@ -5,41 +5,42 @@ use { create_metadata_accounts_v3, mpl_token_metadata::types::DataV2, CreateMetadataAccountsV3, Metadata, }, - token::{Mint, Token}, + mint::{self, Mint}, + token::{self, Token}, }, }; #[derive(Accounts)] -pub struct CreateTokenAccountConstraints<'info> { +pub struct CreateTokenAccountConstraints { #[account(mut)] - pub payer: Signer<'info>, + pub payer: Signer, #[account( init, payer = payer, mint::decimals = 9, - mint::authority = payer.key(), - mint::freeze_authority = payer.key(), + mint::authority = payer, + mint::freeze_authority = payer, )] - pub mint_account: Account<'info, Mint>, + pub mint_account: Account, /// CHECK: Validate address by deriving pda #[account( mut, - seeds = [b"metadata", token_metadata_program.key().as_ref(), mint_account.key().as_ref()], + seeds = [b"metadata", token_metadata_program.address().as_ref(), mint_account.address().as_ref()], bump, - seeds::program = token_metadata_program.key(), + seeds::program = token_metadata_program.address(), )] - pub metadata_account: UncheckedAccount<'info>, + pub metadata_account: UncheckedAccount, - pub token_program: Program<'info, Token>, - pub token_metadata_program: Program<'info, Metadata>, - pub system_program: Program<'info, System>, - pub rent: Sysvar<'info, Rent>, + pub token_program: Program, + pub token_metadata_program: Program, + pub system_program: Program, + pub rent: Sysvar, } pub fn handle_create_token( - context: Context, + context: &mut Context, token_name: String, token_symbol: String, token_uri: String, @@ -50,15 +51,15 @@ pub fn handle_create_token( // Invoking the create_metadata_account_v3 instruction on the token metadata program create_metadata_accounts_v3( CpiContext::new( - context.accounts.token_metadata_program.key(), + context.accounts.token_metadata_program.address(), CreateMetadataAccountsV3 { - metadata: context.accounts.metadata_account.to_account_info(), - mint: context.accounts.mint_account.to_account_info(), - mint_authority: context.accounts.payer.to_account_info(), - update_authority: context.accounts.payer.to_account_info(), - payer: context.accounts.payer.to_account_info(), - system_program: context.accounts.system_program.to_account_info(), - rent: context.accounts.rent.to_account_info(), + metadata: context.accounts.metadata_account.cpi_handle_mut(), + mint: context.accounts.mint_account.cpi_handle(), + mint_authority: context.accounts.payer.cpi_handle(), + update_authority: context.accounts.payer.cpi_handle(), + payer: context.accounts.payer.cpi_handle_mut(), + system_program: context.accounts.system_program.cpi_handle(), + update_authority_is_signer: true, }, ), DataV2 { diff --git a/tokens/token-minter/anchor/programs/token-minter/src/instructions/mint.rs b/tokens/token-minter/anchor/programs/token-minter/src/instructions/mint.rs index 7d731da01..7b60c58e2 100644 --- a/tokens/token-minter/anchor/programs/token-minter/src/instructions/mint.rs +++ b/tokens/token-minter/anchor/programs/token-minter/src/instructions/mint.rs @@ -7,14 +7,14 @@ use { }; #[derive(Accounts)] -pub struct MintTokenAccountConstraints<'info> { +pub struct MintTokenAccountConstraints { #[account(mut)] - pub mint_authority: Signer<'info>, + pub mint_authority: Signer, - pub recipient: SystemAccount<'info>, + pub recipient: SystemAccount, #[account(mut)] - pub mint_account: Account<'info, Mint>, + pub mint_account: Account, #[account( init_if_needed, @@ -22,11 +22,11 @@ pub struct MintTokenAccountConstraints<'info> { associated_token::mint = mint_account, associated_token::authority = recipient, )] - pub associated_token_account: Account<'info, TokenAccount>, + pub associated_token_account: Account, - pub token_program: Program<'info, Token>, - pub associated_token_program: Program<'info, AssociatedToken>, - pub system_program: Program<'info, System>, + pub token_program: Program, + pub associated_token_program: Program, + pub system_program: Program, } /// Mints `amount` tokens to the recipient's associated token account. @@ -35,24 +35,24 @@ pub struct MintTokenAccountConstraints<'info> { /// on). Clients convert from major units, e.g. 1 token with 9 decimals is /// `1 * 10u64.pow(9)` minor units. pub fn handle_mint_token( - context: Context, + context: &mut Context, amount: u64, ) -> Result<()> { msg!("Minting tokens to associated token account..."); - msg!("Mint: {}", &context.accounts.mint_account.key()); + msg!("Mint: {}", &context.accounts.mint_account.address()); msg!( "Token Address: {}", - &context.accounts.associated_token_account.key() + &context.accounts.associated_token_account.address() ); // Invoke the mint_to instruction on the token program mint_to( CpiContext::new( - context.accounts.token_program.key(), + context.accounts.token_program.address(), MintTo { - mint: context.accounts.mint_account.to_account_info(), - to: context.accounts.associated_token_account.to_account_info(), - authority: context.accounts.mint_authority.to_account_info(), + mint: context.accounts.mint_account.cpi_handle_mut(), + to: context.accounts.associated_token_account.cpi_handle_mut(), + authority: context.accounts.mint_authority.cpi_handle(), }, ), amount, diff --git a/tokens/token-minter/anchor/programs/token-minter/src/lib.rs b/tokens/token-minter/anchor/programs/token-minter/src/lib.rs index 8033bddbb..ff143d221 100644 --- a/tokens/token-minter/anchor/programs/token-minter/src/lib.rs +++ b/tokens/token-minter/anchor/programs/token-minter/src/lib.rs @@ -10,7 +10,7 @@ pub mod token_minter { use super::*; pub fn create_token( - context: Context, + context: &mut Context, token_name: String, token_symbol: String, token_uri: String, @@ -20,7 +20,7 @@ pub mod token_minter { /// Mint `amount` minor units of the token to the recipient. pub fn mint_token( - context: Context, + context: &mut Context, amount: u64, ) -> Result<()> { mint::handle_mint_token(context, amount) diff --git a/tokens/transfer-tokens/anchor/programs/transfer-tokens/Cargo.toml b/tokens/transfer-tokens/anchor/programs/transfer-tokens/Cargo.toml index e846a7232..52dbac2a8 100644 --- a/tokens/transfer-tokens/anchor/programs/transfer-tokens/Cargo.toml +++ b/tokens/transfer-tokens/anchor/programs/transfer-tokens/Cargo.toml @@ -14,14 +14,22 @@ cpi = ["no-entrypoint"] no-entrypoint = [] no-idl = [] no-log-ix-name = [] -idl-build = ["anchor-lang/idl-build", "anchor-spl/idl-build"] +idl-build = ["anchor-lang/idl-build"] anchor-debug = [] custom-heap = [] custom-panic = [] [dependencies] -anchor-lang = { version = "1.1.2", features = ["init-if-needed"] } -anchor-spl = { version = "1.1.2", features = ["metadata"] } +anchor-lang = "2.0.0-rc.1" +# The `#[program]` macro expands to `wincode` paths for instruction-data +# (de)serialization, so the crate has to be a direct dependency. +wincode = { version = "0.5", features = ["derive"] } +# anchor-lang 2.0.0-rc.1 is built against wincode 0.5, but solana-address 2.7 +# moved to wincode 0.6. With both in the graph, `Address`'s wincode impls +# belong to the version the account derives are not using and every +# `SchemaRead`/`SchemaWrite` bound fails. 2.6.1 is the last 0.5-line release. +solana-address = ">=2.6, <2.7" +anchor-spl = { version = "2.0.0-rc.1", features = ["metadata"] } [dev-dependencies] litesvm = "0.13.1" diff --git a/tokens/transfer-tokens/anchor/programs/transfer-tokens/src/instructions/create.rs b/tokens/transfer-tokens/anchor/programs/transfer-tokens/src/instructions/create.rs index 6670117d2..ddc002dec 100644 --- a/tokens/transfer-tokens/anchor/programs/transfer-tokens/src/instructions/create.rs +++ b/tokens/transfer-tokens/anchor/programs/transfer-tokens/src/instructions/create.rs @@ -10,37 +10,37 @@ use { }; #[derive(Accounts)] -pub struct CreateTokenAccountConstraints<'info> { +pub struct CreateTokenAccountConstraints { #[account(mut)] - pub payer: Signer<'info>, + pub payer: Signer, #[account( init, payer = payer, mint::decimals = 9, - mint::authority = payer.key(), - mint::freeze_authority = payer.key(), + mint::authority = payer, + mint::freeze_authority = payer, mint::token_program = token_program, )] - pub mint_account: InterfaceAccount<'info, Mint>, + pub mint_account: InterfaceAccount, /// CHECK: Validate address by deriving pda #[account( mut, - seeds = [b"metadata", token_metadata_program.key().as_ref(), mint_account.key().as_ref()], + seeds = [b"metadata", token_metadata_program.address().as_ref(), mint_account.address().as_ref()], bump, - seeds::program = token_metadata_program.key(), + seeds::program = token_metadata_program.address(), )] - pub metadata_account: UncheckedAccount<'info>, + pub metadata_account: UncheckedAccount, - pub token_program: Interface<'info, TokenInterface>, - pub token_metadata_program: Program<'info, Metadata>, - pub system_program: Program<'info, System>, - pub rent: Sysvar<'info, Rent>, + pub token_program: Interface<'static, TokenInterface>, + pub token_metadata_program: Program, + pub system_program: Program, + pub rent: Sysvar, } pub fn handle_create_token( - context: Context, + context: &mut Context, token_name: String, token_symbol: String, token_uri: String, @@ -51,15 +51,15 @@ pub fn handle_create_token( // Invoking the create_metadata_account_v3 instruction on the token metadata program create_metadata_accounts_v3( CpiContext::new( - context.accounts.token_metadata_program.key(), + context.accounts.token_metadata_program.address(), CreateMetadataAccountsV3 { - metadata: context.accounts.metadata_account.to_account_info(), - mint: context.accounts.mint_account.to_account_info(), - mint_authority: context.accounts.payer.to_account_info(), - update_authority: context.accounts.payer.to_account_info(), - payer: context.accounts.payer.to_account_info(), - system_program: context.accounts.system_program.to_account_info(), - rent: context.accounts.rent.to_account_info(), + metadata: context.accounts.metadata_account.cpi_handle_mut(), + mint: context.accounts.mint_account.cpi_handle(), + mint_authority: context.accounts.payer.cpi_handle(), + update_authority: context.accounts.payer.cpi_handle(), + payer: context.accounts.payer.cpi_handle_mut(), + system_program: context.accounts.system_program.cpi_handle(), + update_authority_is_signer: true, }, ), DataV2 { diff --git a/tokens/transfer-tokens/anchor/programs/transfer-tokens/src/instructions/mint.rs b/tokens/transfer-tokens/anchor/programs/transfer-tokens/src/instructions/mint.rs index f57df043a..f8a47299a 100644 --- a/tokens/transfer-tokens/anchor/programs/transfer-tokens/src/instructions/mint.rs +++ b/tokens/transfer-tokens/anchor/programs/transfer-tokens/src/instructions/mint.rs @@ -7,14 +7,14 @@ use { }; #[derive(Accounts)] -pub struct MintTokenAccountConstraints<'info> { +pub struct MintTokenAccountConstraints { #[account(mut)] - pub mint_authority: Signer<'info>, + pub mint_authority: Signer, - pub recipient: SystemAccount<'info>, + pub recipient: SystemAccount, #[account(mut)] - pub mint_account: InterfaceAccount<'info, Mint>, + pub mint_account: InterfaceAccount, #[account( init_if_needed, @@ -23,11 +23,11 @@ pub struct MintTokenAccountConstraints<'info> { associated_token::authority = recipient, associated_token::token_program = token_program, )] - pub associated_token_account: InterfaceAccount<'info, TokenAccount>, + pub associated_token_account: InterfaceAccount, - pub token_program: Interface<'info, TokenInterface>, - pub associated_token_program: Program<'info, AssociatedToken>, - pub system_program: Program<'info, System>, + pub token_program: Interface<'static, TokenInterface>, + pub associated_token_program: Program, + pub system_program: Program, } /// Mints `amount` tokens to the recipient's associated token account. @@ -36,24 +36,24 @@ pub struct MintTokenAccountConstraints<'info> { /// on). Clients convert from major units, e.g. 1 token with 9 decimals is /// `1 * 10u64.pow(9)` minor units. pub fn handle_mint_token( - context: Context, + context: &mut Context, amount: u64, ) -> Result<()> { msg!("Minting tokens to associated token account..."); - msg!("Mint: {}", &context.accounts.mint_account.key()); + msg!("Mint: {}", &context.accounts.mint_account.address()); msg!( "Token Address: {}", - &context.accounts.associated_token_account.key() + &context.accounts.associated_token_account.address() ); // Invoke the mint_to instruction on the token program mint_to( CpiContext::new( - context.accounts.token_program.key(), + context.accounts.token_program.address(), MintTo { - mint: context.accounts.mint_account.to_account_info(), - to: context.accounts.associated_token_account.to_account_info(), - authority: context.accounts.mint_authority.to_account_info(), + mint: context.accounts.mint_account.cpi_handle_mut(), + to: context.accounts.associated_token_account.cpi_handle_mut(), + authority: context.accounts.mint_authority.cpi_handle(), }, ), amount, diff --git a/tokens/transfer-tokens/anchor/programs/transfer-tokens/src/instructions/transfer.rs b/tokens/transfer-tokens/anchor/programs/transfer-tokens/src/instructions/transfer.rs index b471124df..f4a627531 100644 --- a/tokens/transfer-tokens/anchor/programs/transfer-tokens/src/instructions/transfer.rs +++ b/tokens/transfer-tokens/anchor/programs/transfer-tokens/src/instructions/transfer.rs @@ -7,14 +7,14 @@ use { }; #[derive(Accounts)] -pub struct TransferTokensAccountConstraints<'info> { +pub struct TransferTokensAccountConstraints { #[account(mut)] - pub sender: Signer<'info>, + pub sender: Signer, - pub recipient: SystemAccount<'info>, + pub recipient: SystemAccount, #[account(mut)] - pub mint_account: InterfaceAccount<'info, Mint>, + pub mint_account: InterfaceAccount, #[account( mut, @@ -22,7 +22,7 @@ pub struct TransferTokensAccountConstraints<'info> { associated_token::authority = sender, associated_token::token_program = token_program, )] - pub sender_token_account: InterfaceAccount<'info, TokenAccount>, + pub sender_token_account: InterfaceAccount, #[account( init_if_needed, @@ -31,11 +31,11 @@ pub struct TransferTokensAccountConstraints<'info> { associated_token::authority = recipient, associated_token::token_program = token_program, )] - pub recipient_token_account: InterfaceAccount<'info, TokenAccount>, + pub recipient_token_account: InterfaceAccount, - pub token_program: Interface<'info, TokenInterface>, - pub associated_token_program: Program<'info, AssociatedToken>, - pub system_program: Program<'info, System>, + pub token_program: Interface<'static, TokenInterface>, + pub associated_token_program: Program, + pub system_program: Program, } /// Transfers `amount` tokens from the sender's to the recipient's associated @@ -47,36 +47,36 @@ pub struct TransferTokensAccountConstraints<'info> { /// decimals through the CPI so a wrong-mint or wrong-decimals account fails /// the CPI instead of silently moving the wrong quantity. pub fn handle_transfer_tokens( - context: Context, + context: &mut Context, amount: u64, ) -> Result<()> { msg!("Transferring tokens..."); msg!( "Mint: {}", - &context.accounts.mint_account.to_account_info().key() + &context.accounts.mint_account.cpi_handle_mut().address() ); msg!( "From Token Address: {}", - &context.accounts.sender_token_account.key() + &context.accounts.sender_token_account.address() ); msg!( "To Token Address: {}", - &context.accounts.recipient_token_account.key() + &context.accounts.recipient_token_account.address() ); // Invoke the transfer_checked instruction on the token program transfer_checked( CpiContext::new( - context.accounts.token_program.key(), + context.accounts.token_program.address(), TransferChecked { - from: context.accounts.sender_token_account.to_account_info(), - mint: context.accounts.mint_account.to_account_info(), - to: context.accounts.recipient_token_account.to_account_info(), - authority: context.accounts.sender.to_account_info(), + from: context.accounts.sender_token_account.cpi_handle_mut(), + mint: context.accounts.mint_account.cpi_handle(), + to: context.accounts.recipient_token_account.cpi_handle_mut(), + authority: context.accounts.sender.cpi_handle(), }, ), amount, - context.accounts.mint_account.decimals, + context.accounts.mint_account.decimals(), )?; msg!("Tokens transferred successfully."); diff --git a/tokens/transfer-tokens/anchor/programs/transfer-tokens/src/lib.rs b/tokens/transfer-tokens/anchor/programs/transfer-tokens/src/lib.rs index 3d008421e..06ba2f8b9 100644 --- a/tokens/transfer-tokens/anchor/programs/transfer-tokens/src/lib.rs +++ b/tokens/transfer-tokens/anchor/programs/transfer-tokens/src/lib.rs @@ -11,7 +11,7 @@ pub mod transfer_tokens { use super::*; pub fn create_token( - context: Context, + context: &mut Context, token_title: String, token_symbol: String, token_uri: String, @@ -21,7 +21,7 @@ pub mod transfer_tokens { /// Mint `amount` minor units of the token to the recipient. pub fn mint_token( - context: Context, + context: &mut Context, amount: u64, ) -> Result<()> { mint::handle_mint_token(context, amount) @@ -29,7 +29,7 @@ pub mod transfer_tokens { /// Transfer `amount` minor units of the token from sender to recipient. pub fn transfer_tokens( - context: Context, + context: &mut Context, amount: u64, ) -> Result<()> { transfer::handle_transfer_tokens(context, amount) From 607f2dc121b75ecafe2c72b6cb39fd1751762c50 Mon Sep 17 00:00:00 2001 From: Claude Date: Thu, 13 Aug 2026 22:24:00 +0000 Subject: [PATCH 08/59] tokens: borsh derives, SPL discriminators and interface programs Continuing the compile-driven loop. Still in progress; not all of these compile. - `#[derive(AnchorSerialize, AnchorDeserialize)]` becomes the wincode derives plus `IdlType`, since v2 encodes instruction data with wincode rather than borsh. - v1's `#[instruction(discriminator = ...)]` is `#[discrim = ...]` in v2, and it takes a literal rather than a const expression, so the transfer-hook programs spell out the two SPL interface discriminators, each sha256(namespace)[..8]. - Arbitrary (non-one-byte) discriminators are only permitted on an interface program, so the seven transfer hooks are now `#[program(interface, program_id = ID)]`, which is what they always were. - The earlier AccountInfo rewrite over-reached into `#[derive(Accounts)]` structs; the v2 spelling there is `UncheckedAccount`, not the raw `AccountView`. Also recorded, because it bounds what is achievable on this RC: anchor-spl 2.0.0-rc.1 has no `extensions::*` init-constraint support at all. Its `MintInitParams` / `TokenAccountInitParams` cover only decimals, authority and mint, and none of anchor's own v2 test programs use extension init constraints. Six examples are built entirely on that constraint form and cannot be ported by translation alone, they need the mint creation hand-written (allocate with ExtensionType space, extension-init CPI, then InitializeMint2): tokens/token-extensions/group tokens/token-extensions/metadata tokens/token-extensions/mint-close-authority tokens/token-extensions/permanent-delegate tokens/token-extensions/transfer-hook/allow-block-list-token tokens/token-extensions/transfer-hook/hello-world --- .../anchor/programs/cutils/src/instructions/mint.rs | 2 +- .../anchor/programs/cutils/src/instructions/verify.rs | 2 +- .../anchor/programs/betting-market/src/state/event.rs | 2 +- .../anchor/programs/lending/src/state/obligation.rs | 4 ++-- .../lending/anchor/programs/lending/src/state/reserve.rs | 2 +- .../anchor/programs/order-book/src/state/order.rs | 4 ++-- .../perpetual-futures/src/instructions/initialize_pool.rs | 2 +- .../programs/perpetual-futures/src/state/position.rs | 2 +- .../prop-amm/src/instructions/initialize_market.rs | 2 +- .../prop-amm/anchor/programs/prop-amm/src/state/market.rs | 2 +- .../anchor/programs/default-account-state/src/lib.rs | 2 +- .../programs/metadata/src/instructions/initialize.rs | 2 +- .../programs/metadata/src/instructions/update_field.rs | 4 ++-- .../anchor/programs/transfer-hook/src/lib.rs | 8 +++++--- .../programs/abl-token/src/instructions/change_mode.rs | 2 +- .../programs/abl-token/src/instructions/init_mint.rs | 2 +- .../programs/abl-token/src/instructions/init_wallet.rs | 2 +- .../anchor/programs/abl-token/src/lib.rs | 5 +++-- .../anchor/programs/abl-token/src/state.rs | 2 +- .../instructions/initialize_extra_account_meta_list.rs | 2 +- .../counter/anchor/programs/transfer-hook/src/lib.rs | 8 +++++--- .../hello-world/anchor/programs/transfer-hook/src/lib.rs | 8 +++++--- .../anchor/programs/transfer-hook/src/lib.rs | 8 +++++--- .../anchor/programs/transfer-switch/src/lib.rs | 8 +++++--- .../whitelist/anchor/programs/transfer-hook/src/lib.rs | 8 +++++--- 25 files changed, 54 insertions(+), 41 deletions(-) diff --git a/compression/cutils/anchor/programs/cutils/src/instructions/mint.rs b/compression/cutils/anchor/programs/cutils/src/instructions/mint.rs index cb606e53a..7c4877b94 100644 --- a/compression/cutils/anchor/programs/cutils/src/instructions/mint.rs +++ b/compression/cutils/anchor/programs/cutils/src/instructions/mint.rs @@ -66,7 +66,7 @@ pub struct MintAccountConstraints { pub system_program: Program, } -#[derive(Clone, AnchorSerialize, AnchorDeserialize)] +#[derive(Clone, IdlType, wincode::SchemaRead, wincode::SchemaWrite)] pub struct MintParams { uri: String, } diff --git a/compression/cutils/anchor/programs/cutils/src/instructions/verify.rs b/compression/cutils/anchor/programs/cutils/src/instructions/verify.rs index 0f64e60d4..7ff8cec3f 100644 --- a/compression/cutils/anchor/programs/cutils/src/instructions/verify.rs +++ b/compression/cutils/anchor/programs/cutils/src/instructions/verify.rs @@ -17,7 +17,7 @@ pub struct VerifyAccountConstraints { pub compression_program: Program, } -#[derive(Clone, AnchorSerialize, AnchorDeserialize)] +#[derive(Clone, IdlType, wincode::SchemaRead, wincode::SchemaWrite)] pub struct VerifyParams { root: [u8; 32], data_hash: [u8; 32], diff --git a/finance/betting-market/anchor/programs/betting-market/src/state/event.rs b/finance/betting-market/anchor/programs/betting-market/src/state/event.rs index f0319c648..068d086ec 100644 --- a/finance/betting-market/anchor/programs/betting-market/src/state/event.rs +++ b/finance/betting-market/anchor/programs/betting-market/src/state/event.rs @@ -1,6 +1,6 @@ use anchor_lang::prelude::*; -#[derive(AnchorSerialize, AnchorDeserialize, Clone, PartialEq, Eq, InitSpace)] +#[derive(Clone, PartialEq, Eq, InitSpace, IdlType, wincode::SchemaRead, wincode::SchemaWrite)] pub enum EventStatus { // Accepting bets. Open, diff --git a/finance/lending/anchor/programs/lending/src/state/obligation.rs b/finance/lending/anchor/programs/lending/src/state/obligation.rs index 4fda3eda1..d9a3bcb5b 100644 --- a/finance/lending/anchor/programs/lending/src/state/obligation.rs +++ b/finance/lending/anchor/programs/lending/src/state/obligation.rs @@ -42,14 +42,14 @@ pub struct Obligation { pub bump: u8, } -#[derive(InitSpace, Clone, Copy, AnchorSerialize, AnchorDeserialize, Debug, Default)] +#[derive(InitSpace, Clone, Copy, Debug, Default, IdlType, wincode::SchemaRead, wincode::SchemaWrite)] pub struct ObligationCollateral { pub reserve: Address, pub deposited_shares: u64, pub market_value: u128, } -#[derive(InitSpace, Clone, Copy, AnchorSerialize, AnchorDeserialize, Debug, Default)] +#[derive(InitSpace, Clone, Copy, Debug, Default, IdlType, wincode::SchemaRead, wincode::SchemaWrite)] pub struct ObligationLiquidity { pub reserve: Address, /// Borrowed principal, scaled by the reserve's index at borrow time so the diff --git a/finance/lending/anchor/programs/lending/src/state/reserve.rs b/finance/lending/anchor/programs/lending/src/state/reserve.rs index 065714405..4733fe54a 100644 --- a/finance/lending/anchor/programs/lending/src/state/reserve.rs +++ b/finance/lending/anchor/programs/lending/src/state/reserve.rs @@ -77,7 +77,7 @@ pub struct Reserve { } /// Risk and interest-rate parameters. All ratios are basis points (10_000 = 100%). -#[derive(InitSpace, Clone, Copy, AnchorSerialize, AnchorDeserialize, Debug, Default)] +#[derive(InitSpace, Clone, Copy, Debug, Default, IdlType, wincode::SchemaRead, wincode::SchemaWrite)] pub struct ReserveConfig { /// Fraction of deposited collateral value a borrower may borrow against. pub loan_to_value_bps: u16, diff --git a/finance/order-book/anchor/programs/order-book/src/state/order.rs b/finance/order-book/anchor/programs/order-book/src/state/order.rs index 86ceef9e3..05fe139c0 100644 --- a/finance/order-book/anchor/programs/order-book/src/state/order.rs +++ b/finance/order-book/anchor/programs/order-book/src/state/order.rs @@ -2,13 +2,13 @@ use anchor_lang::prelude::*; pub const ORDER_SEED: &[u8] = b"order"; -#[derive(AnchorSerialize, AnchorDeserialize, Clone, Copy, PartialEq, Eq, InitSpace)] +#[derive(Clone, Copy, PartialEq, Eq, InitSpace, IdlType, wincode::SchemaRead, wincode::SchemaWrite)] pub enum OrderSide { Bid, Ask, } -#[derive(AnchorSerialize, AnchorDeserialize, Clone, Copy, PartialEq, Eq, InitSpace)] +#[derive(Clone, Copy, PartialEq, Eq, InitSpace, IdlType, wincode::SchemaRead, wincode::SchemaWrite)] pub enum OrderStatus { Open, PartiallyFilled, diff --git a/finance/perpetual-futures/anchor/programs/perpetual-futures/src/instructions/initialize_pool.rs b/finance/perpetual-futures/anchor/programs/perpetual-futures/src/instructions/initialize_pool.rs index 51fefade0..b3a8e5183 100644 --- a/finance/perpetual-futures/anchor/programs/perpetual-futures/src/instructions/initialize_pool.rs +++ b/finance/perpetual-futures/anchor/programs/perpetual-futures/src/instructions/initialize_pool.rs @@ -15,7 +15,7 @@ use crate::state::Pool; /// Trading parameters set once at pool creation. Bundled into one struct so the /// instruction signature stays readable. -#[derive(AnchorSerialize, AnchorDeserialize, Clone)] +#[derive(Clone, IdlType, wincode::SchemaRead, wincode::SchemaWrite)] pub struct PoolParameters { /// Decimal places the oracle quotes its price in (e.g. 8). pub oracle_scale: u32, diff --git a/finance/perpetual-futures/anchor/programs/perpetual-futures/src/state/position.rs b/finance/perpetual-futures/anchor/programs/perpetual-futures/src/state/position.rs index 664985367..950e0a504 100644 --- a/finance/perpetual-futures/anchor/programs/perpetual-futures/src/state/position.rs +++ b/finance/perpetual-futures/anchor/programs/perpetual-futures/src/state/position.rs @@ -1,6 +1,6 @@ use anchor_lang::prelude::*; -#[derive(AnchorSerialize, AnchorDeserialize, InitSpace, Clone, Copy, PartialEq, Eq, Debug)] +#[derive(InitSpace, Clone, Copy, PartialEq, Eq, Debug, IdlType, wincode::SchemaRead, wincode::SchemaWrite)] pub enum Side { Long, Short, diff --git a/finance/prop-amm/anchor/programs/prop-amm/src/instructions/initialize_market.rs b/finance/prop-amm/anchor/programs/prop-amm/src/instructions/initialize_market.rs index c4f9f0e6d..592c8f9ff 100644 --- a/finance/prop-amm/anchor/programs/prop-amm/src/instructions/initialize_market.rs +++ b/finance/prop-amm/anchor/programs/prop-amm/src/instructions/initialize_market.rs @@ -13,7 +13,7 @@ use crate::state::Market; /// Quote parameters set at market creation. Bundled into one struct so the /// instruction signature stays readable. -#[derive(AnchorSerialize, AnchorDeserialize, Clone)] +#[derive(Clone, IdlType, wincode::SchemaRead, wincode::SchemaWrite)] pub struct MarketParameters { /// Decimal places the oracle quotes its price in (e.g. 8). pub oracle_scale: u32, diff --git a/finance/prop-amm/anchor/programs/prop-amm/src/state/market.rs b/finance/prop-amm/anchor/programs/prop-amm/src/state/market.rs index 545315ceb..96927d0e6 100644 --- a/finance/prop-amm/anchor/programs/prop-amm/src/state/market.rs +++ b/finance/prop-amm/anchor/programs/prop-amm/src/state/market.rs @@ -1,7 +1,7 @@ use anchor_lang::prelude::*; /// Which side of the quote a swap takes. -#[derive(AnchorSerialize, AnchorDeserialize, Clone, Copy, PartialEq, Eq, Debug)] +#[derive(Clone, Copy, PartialEq, Eq, Debug, IdlType, wincode::SchemaRead, wincode::SchemaWrite)] pub enum Direction { /// Spend the quote token, receive the base token, priced at the ask /// (oracle plus spread). diff --git a/tokens/token-extensions/default-account-state/anchor/programs/default-account-state/src/lib.rs b/tokens/token-extensions/default-account-state/anchor/programs/default-account-state/src/lib.rs index 4cafa685e..cc491b70b 100644 --- a/tokens/token-extensions/default-account-state/anchor/programs/default-account-state/src/lib.rs +++ b/tokens/token-extensions/default-account-state/anchor/programs/default-account-state/src/lib.rs @@ -24,7 +24,7 @@ pub mod default_account_state { // Custom enum to implement AnchorSerialize and AnchorDeserialize // This is required to pass the enum as an argument to the instruction -#[derive(AnchorSerialize, AnchorDeserialize)] +#[derive(IdlType, wincode::SchemaRead, wincode::SchemaWrite)] pub enum AnchorAccountState { Uninitialized, Initialized, diff --git a/tokens/token-extensions/metadata/anchor/programs/metadata/src/instructions/initialize.rs b/tokens/token-extensions/metadata/anchor/programs/metadata/src/instructions/initialize.rs index f883c31d6..ec8df6d6c 100644 --- a/tokens/token-extensions/metadata/anchor/programs/metadata/src/instructions/initialize.rs +++ b/tokens/token-extensions/metadata/anchor/programs/metadata/src/instructions/initialize.rs @@ -73,7 +73,7 @@ pub fn process_initialize(context: &mut Context, a Ok(()) } -#[derive(AnchorDeserialize, AnchorSerialize)] +#[derive(IdlType, wincode::SchemaRead, wincode::SchemaWrite)] pub struct TokenMetadataArgs { pub name: String, pub symbol: String, diff --git a/tokens/token-extensions/metadata/anchor/programs/metadata/src/instructions/update_field.rs b/tokens/token-extensions/metadata/anchor/programs/metadata/src/instructions/update_field.rs index c8d219fc1..9425ad561 100644 --- a/tokens/token-extensions/metadata/anchor/programs/metadata/src/instructions/update_field.rs +++ b/tokens/token-extensions/metadata/anchor/programs/metadata/src/instructions/update_field.rs @@ -93,7 +93,7 @@ pub fn process_update_field(context: &mut Context // Custom struct to implement AnchorSerialize and AnchorDeserialize // This is required to pass the struct as an argument to the instruction -#[derive(AnchorSerialize, AnchorDeserialize)] +#[derive(IdlType, wincode::SchemaRead, wincode::SchemaWrite)] pub struct UpdateFieldArgs { /// Field to update in the metadata pub field: AnchorField, @@ -102,7 +102,7 @@ pub struct UpdateFieldArgs { } // Need to do this so the enum shows up in the IDL -#[derive(AnchorSerialize, AnchorDeserialize, Debug)] +#[derive(Debug, IdlType, wincode::SchemaRead, wincode::SchemaWrite)] pub enum AnchorField { /// The name field, corresponding to `TokenMetadata.name` Name, diff --git a/tokens/token-extensions/transfer-hook/account-data-as-seed/anchor/programs/transfer-hook/src/lib.rs b/tokens/token-extensions/transfer-hook/account-data-as-seed/anchor/programs/transfer-hook/src/lib.rs index 7057ea20d..12418ae2e 100644 --- a/tokens/token-extensions/transfer-hook/account-data-as-seed/anchor/programs/transfer-hook/src/lib.rs +++ b/tokens/token-extensions/transfer-hook/account-data-as-seed/anchor/programs/transfer-hook/src/lib.rs @@ -27,18 +27,20 @@ pub enum TransferError { IsNotCurrentlyTransferring, } -#[program] +#[program(interface, program_id = ID)] pub mod transfer_hook { use super::*; - #[instruction(discriminator = InitializeExtraAccountMetaListInstruction::SPL_DISCRIMINATOR_SLICE)] + // sha256("spl-transfer-hook-interface:initialize-extra-account-metas")[..8] + #[discrim = [43, 34, 13, 49, 167, 88, 235, 235]] pub fn initialize_extra_account_meta_list( context: &mut Context, ) -> Result<()> { instructions::initialize_extra_account_meta_list::handler(context) } - #[instruction(discriminator = ExecuteInstruction::SPL_DISCRIMINATOR_SLICE)] + // sha256("spl-transfer-hook-interface:execute")[..8] + #[discrim = [105, 37, 101, 197, 75, 251, 102, 26]] pub fn transfer_hook(context: &mut Context, amount: u64) -> Result<()> { instructions::transfer_hook::handler(context, amount) } diff --git a/tokens/token-extensions/transfer-hook/allow-block-list-token/anchor/programs/abl-token/src/instructions/change_mode.rs b/tokens/token-extensions/transfer-hook/allow-block-list-token/anchor/programs/abl-token/src/instructions/change_mode.rs index ef1cc7788..35bcf5666 100644 --- a/tokens/token-extensions/transfer-hook/allow-block-list-token/anchor/programs/abl-token/src/instructions/change_mode.rs +++ b/tokens/token-extensions/transfer-hook/allow-block-list-token/anchor/programs/abl-token/src/instructions/change_mode.rs @@ -31,7 +31,7 @@ pub struct ChangeModeAccountConstraints { pub system_program: Program, } -#[derive(AnchorSerialize, AnchorDeserialize)] +#[derive(IdlType, wincode::SchemaRead, wincode::SchemaWrite)] pub struct ChangeModeArgs { pub mode: Mode, pub threshold: u64, diff --git a/tokens/token-extensions/transfer-hook/allow-block-list-token/anchor/programs/abl-token/src/instructions/init_mint.rs b/tokens/token-extensions/transfer-hook/allow-block-list-token/anchor/programs/abl-token/src/instructions/init_mint.rs index d6b579ca9..d95b3ec03 100644 --- a/tokens/token-extensions/transfer-hook/allow-block-list-token/anchor/programs/abl-token/src/instructions/init_mint.rs +++ b/tokens/token-extensions/transfer-hook/allow-block-list-token/anchor/programs/abl-token/src/instructions/init_mint.rs @@ -115,7 +115,7 @@ impl InitMintAccountConstraints<'_> { } } -#[derive(AnchorSerialize, AnchorDeserialize)] +#[derive(IdlType, wincode::SchemaRead, wincode::SchemaWrite)] pub struct InitMintArgs { pub decimals: u8, pub mint_authority: Address, diff --git a/tokens/token-extensions/transfer-hook/allow-block-list-token/anchor/programs/abl-token/src/instructions/init_wallet.rs b/tokens/token-extensions/transfer-hook/allow-block-list-token/anchor/programs/abl-token/src/instructions/init_wallet.rs index 1d6162bde..0900bbcef 100644 --- a/tokens/token-extensions/transfer-hook/allow-block-list-token/anchor/programs/abl-token/src/instructions/init_wallet.rs +++ b/tokens/token-extensions/transfer-hook/allow-block-list-token/anchor/programs/abl-token/src/instructions/init_wallet.rs @@ -38,7 +38,7 @@ impl InitWalletAccountConstraints<'_> { } } -#[derive(AnchorSerialize, AnchorDeserialize)] +#[derive(IdlType, wincode::SchemaRead, wincode::SchemaWrite)] pub struct InitWalletArgs { pub allowed: bool, } diff --git a/tokens/token-extensions/transfer-hook/allow-block-list-token/anchor/programs/abl-token/src/lib.rs b/tokens/token-extensions/transfer-hook/allow-block-list-token/anchor/programs/abl-token/src/lib.rs index 4c49ec824..077534b4d 100644 --- a/tokens/token-extensions/transfer-hook/allow-block-list-token/anchor/programs/abl-token/src/lib.rs +++ b/tokens/token-extensions/transfer-hook/allow-block-list-token/anchor/programs/abl-token/src/lib.rs @@ -15,7 +15,7 @@ pub use utils::*; declare_id!("3ku1ZEGvBEEfhaYsAzBZuecTPEa58ZRhoVqHVGpGxVGi"); -#[program] +#[program(interface, program_id = ID)] pub mod abl_token { use super::*; @@ -32,7 +32,8 @@ pub mod abl_token { context.accounts.attach_to_mint() } - #[instruction(discriminator = ExecuteInstruction::SPL_DISCRIMINATOR_SLICE)] + // sha256("spl-transfer-hook-interface:execute")[..8] + #[discrim = [105, 37, 101, 197, 75, 251, 102, 26]] pub fn tx_hook(context: &mut Context, amount: u64) -> Result<()> { context.accounts.tx_hook(amount) } diff --git a/tokens/token-extensions/transfer-hook/allow-block-list-token/anchor/programs/abl-token/src/state.rs b/tokens/token-extensions/transfer-hook/allow-block-list-token/anchor/programs/abl-token/src/state.rs index e6b7036b1..e37f7ca34 100644 --- a/tokens/token-extensions/transfer-hook/allow-block-list-token/anchor/programs/abl-token/src/state.rs +++ b/tokens/token-extensions/transfer-hook/allow-block-list-token/anchor/programs/abl-token/src/state.rs @@ -21,7 +21,7 @@ pub struct Config { pub bump: u8, } -#[derive(AnchorSerialize, AnchorDeserialize, PartialEq)] +#[derive(PartialEq, IdlType, wincode::SchemaRead, wincode::SchemaWrite)] pub enum Mode { Allow, Block, diff --git a/tokens/token-extensions/transfer-hook/counter/anchor/programs/transfer-hook/src/instructions/initialize_extra_account_meta_list.rs b/tokens/token-extensions/transfer-hook/counter/anchor/programs/transfer-hook/src/instructions/initialize_extra_account_meta_list.rs index bec2eb85d..9cdd30b7d 100644 --- a/tokens/token-extensions/transfer-hook/counter/anchor/programs/transfer-hook/src/instructions/initialize_extra_account_meta_list.rs +++ b/tokens/token-extensions/transfer-hook/counter/anchor/programs/transfer-hook/src/instructions/initialize_extra_account_meta_list.rs @@ -25,7 +25,7 @@ pub struct InitializeExtraAccountMetaListAccountConstraints { ).unwrap(), payer = payer )] - pub extra_account_meta_list: AccountView, + pub extra_account_meta_list: UncheckedAccount, pub mint: InterfaceAccount, #[account(init, seeds = [b"counter"], bump, payer = payer, space = CounterAccount::DISCRIMINATOR.len() + CounterAccount::INIT_SPACE)] pub counter_account: BorshAccount, diff --git a/tokens/token-extensions/transfer-hook/counter/anchor/programs/transfer-hook/src/lib.rs b/tokens/token-extensions/transfer-hook/counter/anchor/programs/transfer-hook/src/lib.rs index 82a72646b..f621ad193 100644 --- a/tokens/token-extensions/transfer-hook/counter/anchor/programs/transfer-hook/src/lib.rs +++ b/tokens/token-extensions/transfer-hook/counter/anchor/programs/transfer-hook/src/lib.rs @@ -27,18 +27,20 @@ pub enum TransferError { IsNotCurrentlyTransferring, } -#[program] +#[program(interface, program_id = ID)] pub mod transfer_hook { use super::*; - #[instruction(discriminator = InitializeExtraAccountMetaListInstruction::SPL_DISCRIMINATOR_SLICE)] + // sha256("spl-transfer-hook-interface:initialize-extra-account-metas")[..8] + #[discrim = [43, 34, 13, 49, 167, 88, 235, 235]] pub fn initialize_extra_account_meta_list( context: &mut Context, ) -> Result<()> { instructions::initialize_extra_account_meta_list::handler(context) } - #[instruction(discriminator = ExecuteInstruction::SPL_DISCRIMINATOR_SLICE)] + // sha256("spl-transfer-hook-interface:execute")[..8] + #[discrim = [105, 37, 101, 197, 75, 251, 102, 26]] pub fn transfer_hook(context: &mut Context, amount: u64) -> Result<()> { instructions::transfer_hook::handler(context, amount) } diff --git a/tokens/token-extensions/transfer-hook/hello-world/anchor/programs/transfer-hook/src/lib.rs b/tokens/token-extensions/transfer-hook/hello-world/anchor/programs/transfer-hook/src/lib.rs index b955cfcd4..04cc568d4 100644 --- a/tokens/token-extensions/transfer-hook/hello-world/anchor/programs/transfer-hook/src/lib.rs +++ b/tokens/token-extensions/transfer-hook/hello-world/anchor/programs/transfer-hook/src/lib.rs @@ -25,7 +25,7 @@ pub enum TransferError { IsNotCurrentlyTransferring, } -#[program] +#[program(interface, program_id = ID)] pub mod transfer_hook { use super::*; @@ -33,14 +33,16 @@ pub mod transfer_hook { instructions::initialize::handler(context, decimals) } - #[instruction(discriminator = InitializeExtraAccountMetaListInstruction::SPL_DISCRIMINATOR_SLICE)] + // sha256("spl-transfer-hook-interface:initialize-extra-account-metas")[..8] + #[discrim = [43, 34, 13, 49, 167, 88, 235, 235]] pub fn initialize_extra_account_meta_list( context: &mut Context, ) -> Result<()> { instructions::initialize_extra_account_meta_list::handler(context) } - #[instruction(discriminator = ExecuteInstruction::SPL_DISCRIMINATOR_SLICE)] + // sha256("spl-transfer-hook-interface:execute")[..8] + #[discrim = [105, 37, 101, 197, 75, 251, 102, 26]] pub fn transfer_hook(context: &mut Context, amount: u64) -> Result<()> { instructions::transfer_hook::handler(context, amount) } diff --git a/tokens/token-extensions/transfer-hook/transfer-cost/anchor/programs/transfer-hook/src/lib.rs b/tokens/token-extensions/transfer-hook/transfer-cost/anchor/programs/transfer-hook/src/lib.rs index bad3c2739..b3b626d15 100644 --- a/tokens/token-extensions/transfer-hook/transfer-cost/anchor/programs/transfer-hook/src/lib.rs +++ b/tokens/token-extensions/transfer-hook/transfer-cost/anchor/programs/transfer-hook/src/lib.rs @@ -36,18 +36,20 @@ pub enum TransferError { IsNotCurrentlyTransferring, } -#[program] +#[program(interface, program_id = ID)] pub mod transfer_hook { use super::*; - #[instruction(discriminator = InitializeExtraAccountMetaListInstruction::SPL_DISCRIMINATOR_SLICE)] + // sha256("spl-transfer-hook-interface:initialize-extra-account-metas")[..8] + #[discrim = [43, 34, 13, 49, 167, 88, 235, 235]] pub fn initialize_extra_account_meta_list( context: &mut Context, ) -> Result<()> { instructions::initialize_extra_account_meta_list::handler(context) } - #[instruction(discriminator = ExecuteInstruction::SPL_DISCRIMINATOR_SLICE)] + // sha256("spl-transfer-hook-interface:execute")[..8] + #[discrim = [105, 37, 101, 197, 75, 251, 102, 26]] pub fn transfer_hook(context: &mut Context, amount: u64) -> Result<()> { instructions::transfer_hook::handler(context, amount) } diff --git a/tokens/token-extensions/transfer-hook/transfer-switch/anchor/programs/transfer-switch/src/lib.rs b/tokens/token-extensions/transfer-hook/transfer-switch/anchor/programs/transfer-switch/src/lib.rs index 0f609ff47..ff6a8d886 100644 --- a/tokens/token-extensions/transfer-hook/transfer-switch/anchor/programs/transfer-switch/src/lib.rs +++ b/tokens/token-extensions/transfer-hook/transfer-switch/anchor/programs/transfer-switch/src/lib.rs @@ -11,7 +11,7 @@ use spl_transfer_hook_interface::instruction::{ declare_id!("FjcHckEgXcBhFmSGai3FRpDLiT6hbpV893n8iTxVd81g"); -#[program] +#[program(interface, program_id = ID)] pub mod transfer_switch { use super::*; @@ -21,7 +21,8 @@ pub mod transfer_switch { handle_configure_admin(&mut context.accounts, bump) } - #[instruction(discriminator = InitializeExtraAccountMetaListInstruction::SPL_DISCRIMINATOR_SLICE)] + // sha256("spl-transfer-hook-interface:initialize-extra-account-metas")[..8] + #[discrim = [43, 34, 13, 49, 167, 88, 235, 235]] pub fn initialize_extra_account_metas_list( mut context: &mut Context, ) -> Result<()> { @@ -33,7 +34,8 @@ pub mod transfer_switch { handle_switch(&mut context.accounts, on, bump) } - #[instruction(discriminator = ExecuteInstruction::SPL_DISCRIMINATOR_SLICE)] + // sha256("spl-transfer-hook-interface:execute")[..8] + #[discrim = [105, 37, 101, 197, 75, 251, 102, 26]] pub fn transfer_hook(mut context: &mut Context, _amount: u64) -> Result<()> { handle_assert_is_transferring(&mut context.accounts)?; handle_assert_switch_is_on(&mut context.accounts) diff --git a/tokens/token-extensions/transfer-hook/whitelist/anchor/programs/transfer-hook/src/lib.rs b/tokens/token-extensions/transfer-hook/whitelist/anchor/programs/transfer-hook/src/lib.rs index 76514003c..956b2e7d8 100644 --- a/tokens/token-extensions/transfer-hook/whitelist/anchor/programs/transfer-hook/src/lib.rs +++ b/tokens/token-extensions/transfer-hook/whitelist/anchor/programs/transfer-hook/src/lib.rs @@ -25,18 +25,20 @@ pub enum TransferError { IsNotCurrentlyTransferring, } -#[program] +#[program(interface, program_id = ID)] pub mod transfer_hook { use super::*; - #[instruction(discriminator = InitializeExtraAccountMetaListInstruction::SPL_DISCRIMINATOR_SLICE)] + // sha256("spl-transfer-hook-interface:initialize-extra-account-metas")[..8] + #[discrim = [43, 34, 13, 49, 167, 88, 235, 235]] pub fn initialize_extra_account_meta_list( context: &mut Context, ) -> Result<()> { instructions::initialize_extra_account_meta_list::handler(context) } - #[instruction(discriminator = ExecuteInstruction::SPL_DISCRIMINATOR_SLICE)] + // sha256("spl-transfer-hook-interface:execute")[..8] + #[discrim = [105, 37, 101, 197, 75, 251, 102, 26]] pub fn transfer_hook(context: &mut Context, amount: u64) -> Result<()> { instructions::transfer_hook::handler(context, amount) } From b3ce3baeb3319f5192fde383d926ef0a9ea9a191 Mon Sep 17 00:00:00 2001 From: Claude Date: Fri, 14 Aug 2026 02:32:04 +0000 Subject: [PATCH 09/59] tokens: port the six extension examples that used init constraints Correcting my earlier framing: these were never blocked upstream. anchor-spl 2.0.0-rc.1 is only missing the *constraint shorthand* (`extensions::close_authority::authority = payer` on an `#[account(init, ...)]` field). Every underlying piece is present, the extension-init CPIs live in `token_2022_extensions`, and `InitializeMint2` comes from pinocchio-token-2022. The repo already had the answer: `non-transferable` has always created its mint by hand, with a comment saying there is no anchor constraint for that extension. So these six now follow that same established pattern, allocate with `ExtensionType::try_calculate_account_len`, initialize the extension, then initialize the mint data: mint-close-authority, permanent-delegate, group, metadata, transfer-hook/hello-world `group`'s mint is a PDA, so it signs its own creation via `with_signer`. Also in this commit: - Transfer hooks: v2's `AccountView::try_borrow_mut` replaces v1's `try_borrow_mut_data`, and it needs a mutable view, `AccountView` is `Copy` and a copy still points at the same backing buffer, so the borrow writes through to the real account. - Where one account fills several CPI slots (payer as payer + mint authority + update authority), the read-only slots are built with `CpiHandle::readonly` over a copied view. v2's typed handles make aliasing a compile error rather than latent UB, so this is the shape the framework wants. - The `system_program::ID` / `anchor_lang::Address` test rewrite now covers tokens, finance and compression as well as basics. Token tier is at 12 of 27 compiling, up from 6. --- .../tests/test_account_data.rs | 3 +- .../cnft-burn/src/instructions/burn_cnft.rs | 7 +- .../anchor/programs/cnft-burn/src/lib.rs | 9 +- .../anchor/programs/cnft-vault/src/lib.rs | 4 +- .../programs/cnft-vault/tests/test_vault.rs | 22 +- .../programs/cutils/src/instructions/mint.rs | 5 +- .../src/instructions/add_outcome.rs | 5 +- .../src/instructions/close_losing_bet.rs | 4 +- .../betting-market/src/instructions/mod.rs | 4 +- .../src/instructions/place_bet.rs | 5 +- .../src/instructions/settle_event.rs | 3 +- .../betting-market/src/instructions/shared.rs | 7 +- .../anchor/programs/betting-market/src/lib.rs | 10 +- .../tests/test_betting_market.rs | 431 +++++++++++--- .../escrow/src/instructions/take_offer.rs | 4 +- .../escrow/anchor/programs/escrow/src/lib.rs | 5 +- .../programs/escrow/tests/test_escrow.rs | 57 +- .../admin/collect_protocol_fees.rs | 4 +- .../instructions/admin/initialize_reserve.rs | 7 +- .../instructions/deposit_reserve_liquidity.rs | 6 +- .../src/instructions/liquidate_obligation.rs | 19 +- .../instructions/redeem_reserve_collateral.rs | 3 +- .../src/instructions/refresh_obligation.rs | 16 +- .../repay_obligation_liquidity.rs | 3 +- .../withdraw_obligation_collateral.rs | 3 +- .../anchor/programs/lending/src/lib.rs | 7 +- .../programs/lending/src/state/obligation.rs | 20 +- .../programs/lending/src/state/price_feed.rs | 5 +- .../programs/lending/src/state/reserve.rs | 10 +- .../programs/lending/tests/common/mod.rs | 171 ++++-- .../lending/tests/test_borrow_repay.rs | 79 ++- .../lending/tests/test_deposit_redeem.rs | 10 +- .../programs/lending/tests/test_interest.rs | 33 +- .../lending/tests/test_liquidation.rs | 21 +- .../programs/lending/tests/test_rounding.rs | 26 +- .../programs/lending/tests/test_security.rs | 5 +- .../src/instructions/cancel_order.rs | 4 +- .../instructions/initialize_market_user.rs | 4 +- .../order-book/src/instructions/mod.rs | 4 +- .../src/instructions/place_order.rs | 12 +- .../anchor/programs/order-book/src/lib.rs | 4 +- .../programs/order-book/src/state/mod.rs | 4 +- .../programs/order-book/src/state/order.rs | 12 +- .../order-book/src/state/order_book.rs | 5 +- .../order-book/src/state/slab/nodes.rs | 14 +- .../order-book/src/state/slab/ordertree.rs | 6 +- .../order-book/tests/test_order_book.rs | 556 ++++++++++-------- .../tests/test_perpetual_futures.rs | 68 +-- .../programs/prop-amm/tests/test_prop_amm.rs | 48 +- .../programs/fundraiser/src/constants.rs | 2 +- .../fundraiser/src/instructions/mod.rs | 12 +- .../anchor/programs/fundraiser/src/lib.rs | 4 +- .../fundraiser/src/state/contributor.rs | 2 +- .../fundraiser/src/state/fundraiser.rs | 2 +- .../programs/fundraiser/src/state/mod.rs | 4 +- .../fundraiser/tests/test_fundraiser.rs | 106 ++-- .../instructions/admin/claim_admin_fees.rs | 10 +- .../src/instructions/deposit_liquidity.rs | 6 +- .../src/instructions/initialize_pool.rs | 4 +- .../token-swap/src/instructions/mod.rs | 4 +- .../src/instructions/swap_tokens.rs | 12 +- .../anchor/programs/token-swap/src/lib.rs | 14 +- .../programs/token-swap/tests/test_swap.rs | 166 +++--- .../src/instructions/swap_usdc_for_asset.rs | 6 +- .../src/instructions/withdraw.rs | 6 +- .../anchor/programs/vault-strategy/src/lib.rs | 5 +- .../vault-strategy/tests/vault_strategy.rs | 151 +++-- .../create-token/tests/test_create_token.rs | 30 +- .../src/instructions/authority_transfer.rs | 5 +- .../src/instructions/transfer_tokens.rs | 4 +- .../tests/test_external_delegate.rs | 75 ++- .../nft-minter/tests/test_nft_minter.rs | 32 +- .../src/instructions/verify_collection.rs | 56 +- .../anchor/programs/mint-nft/src/lib.rs | 8 +- .../mint-nft/tests/test_nft_operations.rs | 65 +- .../token-minter/src/instructions/create.rs | 2 +- .../token-minter/tests/test_pda_mint.rs | 56 +- .../create_associated_token_account.rs | 4 +- .../basics/src/instructions/create_token.rs | 5 +- .../basics/anchor/programs/basics/src/lib.rs | 19 +- .../programs/basics/tests/test_basics.rs | 38 +- .../cpi-guard/tests/test_cpi_guard.rs | 74 ++- .../src/instructions/initialize.rs | 15 +- .../tests/test_default_account_state.rs | 59 +- .../src/instructions/test_initialize_group.rs | 127 ++-- .../group/anchor/programs/group/src/lib.rs | 4 +- .../anchor/programs/group/tests/test_group.rs | 11 +- .../programs/immutable-owner/src/lib.rs | 4 +- .../tests/test_immutable_owner.rs | 29 +- .../src/instructions/initialize.rs | 6 +- .../programs/interest-bearing/src/lib.rs | 13 +- .../tests/test_interest_bearing.rs | 23 +- .../src/instructions/initialize.rs | 4 +- .../memo-transfer/tests/test_memo_transfer.rs | 93 +-- .../metadata/src/instructions/initialize.rs | 91 ++- .../metadata/src/instructions/remove_key.rs | 11 +- .../src/instructions/update_authority.rs | 4 +- .../metadata/src/instructions/update_field.rs | 5 +- .../anchor/programs/metadata/src/lib.rs | 19 +- .../programs/metadata/tests/test_metadata.rs | 16 +- .../src/instructions/close.rs | 15 +- .../src/instructions/initialize.rs | 97 ++- .../tests/test_mint_close_authority.rs | 44 +- .../programs/non-transferable/src/lib.rs | 6 +- .../tests/test_non_transferable.rs | 37 +- .../src/instructions/initialize.rs | 88 ++- .../tests/test_permanent_delegate.rs | 61 +- .../transfer-fee/src/instructions/harvest.rs | 8 +- .../src/instructions/initialize.rs | 11 +- .../transfer-fee/src/instructions/transfer.rs | 5 +- .../anchor/programs/transfer-fee/src/lib.rs | 4 +- .../transfer-fee/tests/test_transfer_fee.rs | 60 +- .../initialize_extra_account_meta_list.rs | 20 +- .../anchor/programs/transfer-hook/src/lib.rs | 22 +- .../transfer-hook/tests/test_transfer_hook.rs | 58 +- .../anchor/programs/abl-token/src/lib.rs | 15 +- .../anchor/programs/abl-token/src/utils.rs | 3 +- .../abl-token/tests/test_abl_token.rs | 32 +- .../initialize_extra_account_meta_list.rs | 20 +- .../anchor/programs/transfer-hook/src/lib.rs | 19 +- .../tests/test_transfer_hook_counter.rs | 58 +- .../src/instructions/initialize.rs | 117 ++-- .../initialize_extra_account_meta_list.rs | 14 +- .../anchor/programs/transfer-hook/src/lib.rs | 24 +- .../transfer-hook/tests/test_transfer_hook.rs | 62 +- .../initialize_extra_account_meta_list.rs | 15 +- .../src/instructions/transfer_hook.rs | 11 +- .../anchor/programs/transfer-hook/src/lib.rs | 11 +- .../transfer-hook/tests/test_transfer_hook.rs | 22 +- .../src/instructions/configure_admin.rs | 42 +- .../initialise_extra_account_metas_list.rs | 94 +-- .../src/instructions/switch.rs | 19 +- .../src/instructions/transfer_hook.rs | 34 +- .../programs/transfer-switch/src/lib.rs | 9 +- .../tests/test_transfer_switch.rs | 76 +-- .../src/instructions/add_to_whitelist.rs | 3 +- .../initialize_extra_account_meta_list.rs | 14 +- .../anchor/programs/transfer-hook/src/lib.rs | 22 +- .../transfer-hook/tests/test_transfer_hook.rs | 57 +- .../token-minter/src/instructions/create.rs | 2 +- .../token-minter/tests/test_token_minter.rs | 47 +- .../src/instructions/create.rs | 2 +- .../tests/test_transfer_tokens.rs | 52 +- 143 files changed, 2877 insertions(+), 1852 deletions(-) diff --git a/basics/account-data/anchor/programs/anchor-program-example/tests/test_account_data.rs b/basics/account-data/anchor/programs/anchor-program-example/tests/test_account_data.rs index aca4431bf..910fb4488 100644 --- a/basics/account-data/anchor/programs/anchor-program-example/tests/test_account_data.rs +++ b/basics/account-data/anchor/programs/anchor-program-example/tests/test_account_data.rs @@ -1,7 +1,6 @@ use { anchor_lang::{ - solana_program::instruction::Instruction, system_program, InstructionData, - ToAccountMetas, + solana_program::instruction::Instruction, system_program, InstructionData, ToAccountMetas, }, borsh::BorshDeserialize, litesvm::LiteSVM, diff --git a/compression/cnft-burn/anchor/programs/cnft-burn/src/instructions/burn_cnft.rs b/compression/cnft-burn/anchor/programs/cnft-burn/src/instructions/burn_cnft.rs index cbc87f784..03fada242 100644 --- a/compression/cnft-burn/anchor/programs/cnft-burn/src/instructions/burn_cnft.rs +++ b/compression/cnft-burn/anchor/programs/cnft-burn/src/instructions/burn_cnft.rs @@ -5,7 +5,7 @@ use anchor_lang::solana_program::{ }; use borsh::BorshSerialize; -use crate::{MPL_BUBBLEGUM_ID, SPLCompression}; +use crate::{SPLCompression, MPL_BUBBLEGUM_ID}; /// Burn instruction discriminator from mpl-bubblegum const BURN_DISCRIMINATOR: [u8; 8] = [116, 110, 29, 56, 107, 219, 42, 93]; @@ -83,7 +83,10 @@ pub fn handle_burn_cnft<'info>( *context.accounts.leaf_owner.address(), false, )); - accounts.push(AccountMeta::new(*context.accounts.merkle_tree.address(), false)); + accounts.push(AccountMeta::new( + *context.accounts.merkle_tree.address(), + false, + )); accounts.push(AccountMeta::new_readonly( *context.accounts.log_wrapper.address(), false, diff --git a/compression/cnft-burn/anchor/programs/cnft-burn/src/lib.rs b/compression/cnft-burn/anchor/programs/cnft-burn/src/lib.rs index c08b05447..c0c3a4ffe 100644 --- a/compression/cnft-burn/anchor/programs/cnft-burn/src/lib.rs +++ b/compression/cnft-burn/anchor/programs/cnft-burn/src/lib.rs @@ -38,6 +38,13 @@ pub mod cnft_burn { nonce: u64, index: u32, ) -> Result<()> { - instructions::burn_cnft::handle_burn_cnft(context, root, data_hash, creator_hash, nonce, index) + instructions::burn_cnft::handle_burn_cnft( + context, + root, + data_hash, + creator_hash, + nonce, + index, + ) } } diff --git a/compression/cnft-vault/anchor/programs/cnft-vault/src/lib.rs b/compression/cnft-vault/anchor/programs/cnft-vault/src/lib.rs index d581a643c..d14a2dc0f 100644 --- a/compression/cnft-vault/anchor/programs/cnft-vault/src/lib.rs +++ b/compression/cnft-vault/anchor/programs/cnft-vault/src/lib.rs @@ -87,7 +87,9 @@ pub fn build_transfer_instruction( pub mod cnft_vault { use super::*; - pub fn initialize_vault(context: &mut Context) -> Result<()> { + pub fn initialize_vault( + context: &mut Context, + ) -> Result<()> { instructions::initialize_vault::handler(context) } diff --git a/compression/cnft-vault/anchor/programs/cnft-vault/tests/test_vault.rs b/compression/cnft-vault/anchor/programs/cnft-vault/tests/test_vault.rs index 9e0df7d91..a57f22791 100644 --- a/compression/cnft-vault/anchor/programs/cnft-vault/tests/test_vault.rs +++ b/compression/cnft-vault/anchor/programs/cnft-vault/tests/test_vault.rs @@ -476,11 +476,7 @@ fn create_tree_with_vault_cnft(context: &mut VaultTestContext) -> TreeWithVaultC let proof = [empty_node(0), empty_node(1), empty_node(2)]; // Read the current root from the onchain tree account. - let tree_data = context - .svm - .get_account(&merkle_tree.pubkey()) - .unwrap() - .data; + let tree_data = context.svm.get_account(&merkle_tree.pubkey()).unwrap().data; let root = read_current_root(&tree_data); TreeWithVaultCnft { @@ -608,12 +604,8 @@ fn test_withdraw_cnft_by_authority() { let recipient = Keypair::new(); let authority = context.authority.insecure_clone(); - let withdraw_ix = build_withdraw_cnft_instruction( - &context, - authority.pubkey(), - &tree, - recipient.pubkey(), - ); + let withdraw_ix = + build_withdraw_cnft_instruction(&context, authority.pubkey(), &tree, recipient.pubkey()); // The stored authority signs, so the withdraw succeeds (the vault PDA // signs the Bubblegum CPI via invoke_signed inside the program). @@ -692,12 +684,8 @@ fn test_withdraw_two_cnfts_by_authority() { // Both trees' roots moved, so both cNFTs left the vault: replaying the // single-tree withdraw against either tree with the cached roots fails. - let replay1 = build_withdraw_cnft_instruction( - &context, - authority.pubkey(), - &tree1, - recipient.pubkey(), - ); + let replay1 = + build_withdraw_cnft_instruction(&context, authority.pubkey(), &tree1, recipient.pubkey()); let replay = send(&mut context.svm, vec![replay1], &authority, &[&authority]); assert!( replay.is_err(), diff --git a/compression/cutils/anchor/programs/cutils/src/instructions/mint.rs b/compression/cutils/anchor/programs/cutils/src/instructions/mint.rs index 7c4877b94..05f9a7f88 100644 --- a/compression/cutils/anchor/programs/cutils/src/instructions/mint.rs +++ b/compression/cutils/anchor/programs/cutils/src/instructions/mint.rs @@ -120,7 +120,10 @@ pub fn handle_mint<'info>( *context.accounts.leaf_delegate.address(), false, )); - accounts.push(AccountMeta::new(*context.accounts.merkle_tree.address(), false)); + accounts.push(AccountMeta::new( + *context.accounts.merkle_tree.address(), + false, + )); accounts.push(AccountMeta::new_readonly( *context.accounts.payer.address(), true, diff --git a/finance/betting-market/anchor/programs/betting-market/src/instructions/add_outcome.rs b/finance/betting-market/anchor/programs/betting-market/src/instructions/add_outcome.rs index 1b8ae826d..075a9a8ae 100644 --- a/finance/betting-market/anchor/programs/betting-market/src/instructions/add_outcome.rs +++ b/finance/betting-market/anchor/programs/betting-market/src/instructions/add_outcome.rs @@ -35,7 +35,10 @@ pub struct AddOutcomeAccountConstraints { pub system_program: Program, } -pub fn handle_add_outcome(context: &mut Context, label: String) -> Result<()> { +pub fn handle_add_outcome( + context: &mut Context, + label: String, +) -> Result<()> { require!(label.len() <= MAX_LABEL_LEN, BettingError::LabelTooLong); require!( context.accounts.event.status == EventStatus::Open, diff --git a/finance/betting-market/anchor/programs/betting-market/src/instructions/close_losing_bet.rs b/finance/betting-market/anchor/programs/betting-market/src/instructions/close_losing_bet.rs index 79b158803..824073344 100644 --- a/finance/betting-market/anchor/programs/betting-market/src/instructions/close_losing_bet.rs +++ b/finance/betting-market/anchor/programs/betting-market/src/instructions/close_losing_bet.rs @@ -35,7 +35,9 @@ pub struct CloseLosingBetAccountConstraints { pub user: BorshAccount, } -pub fn handle_close_losing_bet(context: &mut Context) -> Result<()> { +pub fn handle_close_losing_bet( + context: &mut Context, +) -> Result<()> { require!( context.accounts.event.status == EventStatus::Settled, BettingError::EventNotSettled diff --git a/finance/betting-market/anchor/programs/betting-market/src/instructions/mod.rs b/finance/betting-market/anchor/programs/betting-market/src/instructions/mod.rs index 29a4a92a8..9056cbadf 100644 --- a/finance/betting-market/anchor/programs/betting-market/src/instructions/mod.rs +++ b/finance/betting-market/anchor/programs/betting-market/src/instructions/mod.rs @@ -3,8 +3,8 @@ pub mod cancel_event; pub mod claim_refund; pub mod claim_winnings; pub mod close_losing_bet; -pub mod initialize_event; pub mod initialize_config; +pub mod initialize_event; pub mod place_bet; pub mod settle_event; pub mod shared; @@ -14,8 +14,8 @@ pub use cancel_event::*; pub use claim_refund::*; pub use claim_winnings::*; pub use close_losing_bet::*; -pub use initialize_event::*; pub use initialize_config::*; +pub use initialize_event::*; pub use place_bet::*; pub use settle_event::*; pub use shared::*; diff --git a/finance/betting-market/anchor/programs/betting-market/src/instructions/place_bet.rs b/finance/betting-market/anchor/programs/betting-market/src/instructions/place_bet.rs index e51e31055..d6e74ebd9 100644 --- a/finance/betting-market/anchor/programs/betting-market/src/instructions/place_bet.rs +++ b/finance/betting-market/anchor/programs/betting-market/src/instructions/place_bet.rs @@ -80,7 +80,10 @@ pub struct PlaceBetAccountConstraints { pub system_program: Program, } -pub fn handle_place_bet(context: &mut Context, amount: u64) -> Result<()> { +pub fn handle_place_bet( + context: &mut Context, + amount: u64, +) -> Result<()> { require!(amount > 0, BettingError::ZeroAmount); require!( context.accounts.event.status == EventStatus::Open, diff --git a/finance/betting-market/anchor/programs/betting-market/src/instructions/settle_event.rs b/finance/betting-market/anchor/programs/betting-market/src/instructions/settle_event.rs index aa80b73c2..d48816302 100644 --- a/finance/betting-market/anchor/programs/betting-market/src/instructions/settle_event.rs +++ b/finance/betting-market/anchor/programs/betting-market/src/instructions/settle_event.rs @@ -87,7 +87,8 @@ pub fn handle_settle_event( // Winners always get their own stake back; the fee is only ever charged on // the losing side, so a winner can never receive less than they staked. - let fee = (losing_pool as u128 * context.accounts.event.fee_bps as u128 / BPS_DENOMINATOR) as u64; + let fee = + (losing_pool as u128 * context.accounts.event.fee_bps as u128 / BPS_DENOMINATOR) as u64; let distributable_losing_pool = losing_pool - fee; if fee > 0 { diff --git a/finance/betting-market/anchor/programs/betting-market/src/instructions/shared.rs b/finance/betting-market/anchor/programs/betting-market/src/instructions/shared.rs index beb15450c..e9a9916db 100644 --- a/finance/betting-market/anchor/programs/betting-market/src/instructions/shared.rs +++ b/finance/betting-market/anchor/programs/betting-market/src/instructions/shared.rs @@ -46,10 +46,7 @@ pub fn transfer_tokens_from_vault<'info>( to: to.cpi_handle_mut(), authority: event.clone(), }; - let cpi_context = CpiContext::new_with_signer( - token_program.address(), - transfer_accounts, - &signer_seeds, - ); + let cpi_context = + CpiContext::new_with_signer(token_program.address(), transfer_accounts, &signer_seeds); transfer_checked(cpi_context, amount, mint.decimals()) } diff --git a/finance/betting-market/anchor/programs/betting-market/src/lib.rs b/finance/betting-market/anchor/programs/betting-market/src/lib.rs index 0ca1e3f7b..bbd58f343 100644 --- a/finance/betting-market/anchor/programs/betting-market/src/lib.rs +++ b/finance/betting-market/anchor/programs/betting-market/src/lib.rs @@ -33,7 +33,10 @@ pub mod betting_market { } // Admin adds a possible result. Only allowed before betting starts. - pub fn add_outcome(context: &mut Context, label: String) -> Result<()> { + pub fn add_outcome( + context: &mut Context, + label: String, + ) -> Result<()> { instructions::add_outcome::handle_add_outcome(context, label) } @@ -44,7 +47,10 @@ pub mod betting_market { // Admin resolves the market: takes the fee from the losing pool and records // the figures winners need to claim their share. - pub fn settle_event(context: &mut Context, winning_outcome_index: u8) -> Result<()> { + pub fn settle_event( + context: &mut Context, + winning_outcome_index: u8, + ) -> Result<()> { instructions::settle_event::handle_settle_event(context, winning_outcome_index) } diff --git a/finance/betting-market/anchor/programs/betting-market/tests/test_betting_market.rs b/finance/betting-market/anchor/programs/betting-market/tests/test_betting_market.rs index 8390c93f0..16282804f 100644 --- a/finance/betting-market/anchor/programs/betting-market/tests/test_betting_market.rs +++ b/finance/betting-market/anchor/programs/betting-market/tests/test_betting_market.rs @@ -1,14 +1,15 @@ use { anchor_lang::{ - solana_program::{instruction::Instruction, pubkey::Pubkey, system_program}, - AccountDeserialize, InstructionData, ToAccountMetas, + solana_program::instruction::Instruction, system_program, AccountDeserialize, Address, + InstructionData, ToAccountMetas, }, betting_market::{User, MAX_BETS_PER_USER}, litesvm::LiteSVM, solana_keypair::Keypair, solana_kite::{ create_associated_token_account, create_token_mint, create_wallet, - get_token_account_balance, mint_tokens_to_token_account, send_transaction_from_instructions, + get_token_account_balance, mint_tokens_to_token_account, + send_transaction_from_instructions, }, solana_signer::Signer, }; @@ -16,53 +17,60 @@ use { const DECIMALS: u8 = 6; const FEE_BPS: u16 = 200; // 2% -fn token_program_id() -> Pubkey { +fn token_program_id() -> Address { "TokenkegQfeZyiNwAJbNbGKPFXCWuBvf9Ss623VQ5DA" .parse() .unwrap() } -fn ata_program_id() -> Pubkey { +fn ata_program_id() -> Address { "ATokenGPvbdGVxr1b2hvZbsiqW5xWH25efTNsLJA8knL" .parse() .unwrap() } -fn derive_ata(wallet: &Pubkey, mint: &Pubkey) -> Pubkey { - Pubkey::find_program_address( +fn derive_ata(wallet: &Address, mint: &Address) -> Address { + Address::find_program_address( &[wallet.as_ref(), token_program_id().as_ref(), mint.as_ref()], &ata_program_id(), ) .0 } -fn config_pda() -> Pubkey { - Pubkey::find_program_address(&[b"config"], &betting_market::id()).0 +fn config_pda() -> Address { + Address::find_program_address(&[b"config"], &betting_market::id()).0 } -fn event_pda(event_id: u64) -> Pubkey { - Pubkey::find_program_address(&[b"event", &event_id.to_le_bytes()], &betting_market::id()).0 +fn event_pda(event_id: u64) -> Address { + Address::find_program_address(&[b"event", &event_id.to_le_bytes()], &betting_market::id()).0 } -fn outcome_pda(event: &Pubkey, index: u8) -> Pubkey { - Pubkey::find_program_address(&[b"outcome", event.as_ref(), &[index]], &betting_market::id()).0 +fn outcome_pda(event: &Address, index: u8) -> Address { + Address::find_program_address( + &[b"outcome", event.as_ref(), &[index]], + &betting_market::id(), + ) + .0 } -fn bet_pda(outcome: &Pubkey, bettor: &Pubkey) -> Pubkey { - Pubkey::find_program_address(&[b"bet", outcome.as_ref(), bettor.as_ref()], &betting_market::id()) - .0 +fn bet_pda(outcome: &Address, bettor: &Address) -> Address { + Address::find_program_address( + &[b"bet", outcome.as_ref(), bettor.as_ref()], + &betting_market::id(), + ) + .0 } -fn user_pda(bettor: &Pubkey) -> Pubkey { - Pubkey::find_program_address(&[b"user", bettor.as_ref()], &betting_market::id()).0 +fn user_pda(bettor: &Address) -> Address { + Address::find_program_address(&[b"user", bettor.as_ref()], &betting_market::id()).0 } struct Market { svm: LiteSVM, admin: Keypair, - mint: Pubkey, + mint: Address, fee_recipient: Keypair, - fee_recipient_ata: Pubkey, + fee_recipient_ata: Address, } // Spin up the SVM with the program loaded, an admin wallet, the stake-token mint @@ -70,7 +78,8 @@ struct Market { fn setup() -> Market { let mut svm = LiteSVM::new(); let program_bytes = include_bytes!("../../../target/deploy/betting_market.so"); - svm.add_program(betting_market::id(), program_bytes).unwrap(); + svm.add_program(betting_market::id(), program_bytes) + .unwrap(); let admin = create_wallet(&mut svm, 100_000_000_000).unwrap(); let mint = create_token_mint(&mut svm, &admin, DECIMALS, None).unwrap(); @@ -89,7 +98,7 @@ fn setup() -> Market { } // Create a funded bettor with a token ATA holding `amount` of the stake token. -fn create_bettor(market: &mut Market, amount: u64) -> (Keypair, Pubkey) { +fn create_bettor(market: &mut Market, amount: u64) -> (Keypair, Address) { let bettor = create_wallet(&mut market.svm, 10_000_000_000).unwrap(); let ata = create_associated_token_account( &mut market.svm, @@ -103,7 +112,7 @@ fn create_bettor(market: &mut Market, amount: u64) -> (Keypair, Pubkey) { (bettor, ata) } -fn initialize_config_ix(admin: Pubkey, mint: Pubkey, fee_recipient: Pubkey) -> Instruction { +fn initialize_config_ix(admin: Address, mint: Address, fee_recipient: Address) -> Instruction { Instruction::new_with_bytes( betting_market::id(), &betting_market::instruction::InitializeConfig { @@ -116,13 +125,18 @@ fn initialize_config_ix(admin: Pubkey, mint: Pubkey, fee_recipient: Pubkey) -> I token_mint: mint, config: config_pda(), token_program: token_program_id(), - system_program: system_program::id(), + system_program: system_program::ID, } .to_account_metas(None), ) } -fn initialize_event_ix(admin: Pubkey, mint: Pubkey, event_id: u64, description: &str) -> Instruction { +fn initialize_event_ix( + admin: Address, + mint: Address, + event_id: u64, + description: &str, +) -> Instruction { let event = event_pda(event_id); Instruction::new_with_bytes( betting_market::id(), @@ -139,13 +153,13 @@ fn initialize_event_ix(admin: Pubkey, mint: Pubkey, event_id: u64, description: vault: derive_ata(&event, &mint), associated_token_program: ata_program_id(), token_program: token_program_id(), - system_program: system_program::id(), + system_program: system_program::ID, } .to_account_metas(None), ) } -fn add_outcome_ix(admin: Pubkey, event_id: u64, index: u8, label: &str) -> Instruction { +fn add_outcome_ix(admin: Address, event_id: u64, index: u8, label: &str) -> Instruction { let event = event_pda(event_id); Instruction::new_with_bytes( betting_market::id(), @@ -158,16 +172,16 @@ fn add_outcome_ix(admin: Pubkey, event_id: u64, index: u8, label: &str) -> Instr config: config_pda(), event, outcome: outcome_pda(&event, index), - system_program: system_program::id(), + system_program: system_program::ID, } .to_account_metas(None), ) } fn place_bet_ix( - mint: Pubkey, - bettor: &Pubkey, - bettor_ata: &Pubkey, + mint: Address, + bettor: &Address, + bettor_ata: &Address, event_id: u64, outcome_index: u8, amount: u64, @@ -189,17 +203,17 @@ fn place_bet_ix( user: user_pda(bettor), associated_token_program: ata_program_id(), token_program: token_program_id(), - system_program: system_program::id(), + system_program: system_program::ID, } .to_account_metas(None), ) } fn settle_event_ix( - admin: Pubkey, - mint: Pubkey, - fee_recipient: Pubkey, - fee_recipient_ata: Pubkey, + admin: Address, + mint: Address, + fee_recipient: Address, + fee_recipient_ata: Address, event_id: u64, winning_outcome_index: u8, ) -> Instruction { @@ -221,16 +235,16 @@ fn settle_event_ix( fee_recipient_token_account: fee_recipient_ata, associated_token_program: ata_program_id(), token_program: token_program_id(), - system_program: system_program::id(), + system_program: system_program::ID, } .to_account_metas(None), ) } fn claim_winnings_ix( - mint: Pubkey, - bettor: &Pubkey, - bettor_ata: &Pubkey, + mint: Address, + bettor: &Address, + bettor_ata: &Address, event_id: u64, outcome_index: u8, ) -> Instruction { @@ -253,7 +267,7 @@ fn claim_winnings_ix( ) } -fn cancel_event_ix(admin: Pubkey, event_id: u64) -> Instruction { +fn cancel_event_ix(admin: Address, event_id: u64) -> Instruction { Instruction::new_with_bytes( betting_market::id(), &betting_market::instruction::CancelEvent {}.data(), @@ -267,9 +281,9 @@ fn cancel_event_ix(admin: Pubkey, event_id: u64) -> Instruction { } fn claim_refund_ix( - mint: Pubkey, - bettor: &Pubkey, - bettor_ata: &Pubkey, + mint: Address, + bettor: &Address, + bettor_ata: &Address, event_id: u64, outcome_index: u8, ) -> Instruction { @@ -292,7 +306,7 @@ fn claim_refund_ix( ) } -fn close_losing_bet_ix(bettor: &Pubkey, event_id: u64, outcome_index: u8) -> Instruction { +fn close_losing_bet_ix(bettor: &Address, event_id: u64, outcome_index: u8) -> Instruction { let event = event_pda(event_id); let outcome = outcome_pda(&event, outcome_index); Instruction::new_with_bytes( @@ -310,7 +324,7 @@ fn close_losing_bet_ix(bettor: &Pubkey, event_id: u64, outcome_index: u8) -> Ins // Decode a User account so tests can assert exactly which Bet addresses the // per-wallet index currently holds. -fn read_user_bets(market: &Market, bettor: &Pubkey) -> Vec { +fn read_user_bets(market: &Market, bettor: &Address) -> Vec
{ let account = market.svm.get_account(&user_pda(bettor)).unwrap(); User::try_deserialize(&mut account.data.as_slice()) .unwrap() @@ -359,21 +373,42 @@ fn test_full_lifecycle() { send_transaction_from_instructions( &mut market.svm, - vec![place_bet_ix(mint, &alice.pubkey(), &alice_ata, event_id, 0, 100)], + vec![place_bet_ix( + mint, + &alice.pubkey(), + &alice_ata, + event_id, + 0, + 100, + )], &[&alice], &alice.pubkey(), ) .unwrap(); send_transaction_from_instructions( &mut market.svm, - vec![place_bet_ix(mint, &bob.pubkey(), &bob_ata, event_id, 0, 300)], + vec![place_bet_ix( + mint, + &bob.pubkey(), + &bob_ata, + event_id, + 0, + 300, + )], &[&bob], &bob.pubkey(), ) .unwrap(); send_transaction_from_instructions( &mut market.svm, - vec![place_bet_ix(mint, &carol.pubkey(), &carol_ata, event_id, 1, 200)], + vec![place_bet_ix( + mint, + &carol.pubkey(), + &carol_ata, + event_id, + 1, + 200, + )], &[&carol], &carol.pubkey(), ) @@ -384,7 +419,10 @@ fn test_full_lifecycle() { assert_eq!(get_token_account_balance(&market.svm, &vault).unwrap(), 600); assert_eq!( read_user_bets(&market, &alice.pubkey()), - vec![bet_pda(&outcome_pda(&event_pda(event_id), 0), &alice.pubkey())] + vec![bet_pda( + &outcome_pda(&event_pda(event_id), 0), + &alice.pubkey() + )] ); // Settle to "Yes" (index 0). Losing pool 200, fee = 2% = 4, distributable = 196. @@ -392,7 +430,14 @@ fn test_full_lifecycle() { let fee_recipient_ata = market.fee_recipient_ata; send_transaction_from_instructions( &mut market.svm, - vec![settle_event_ix(admin, mint, fee_recipient, fee_recipient_ata, event_id, 0)], + vec![settle_event_ix( + admin, + mint, + fee_recipient, + fee_recipient_ata, + event_id, + 0, + )], &[&market.admin], &admin, ) @@ -405,14 +450,26 @@ fn test_full_lifecycle() { // Alice: 100 + 100*196/400 = 149. Bob: 300 + 300*196/400 = 447. send_transaction_from_instructions( &mut market.svm, - vec![claim_winnings_ix(mint, &alice.pubkey(), &alice_ata, event_id, 0)], + vec![claim_winnings_ix( + mint, + &alice.pubkey(), + &alice_ata, + event_id, + 0, + )], &[&alice], &alice.pubkey(), ) .unwrap(); send_transaction_from_instructions( &mut market.svm, - vec![claim_winnings_ix(mint, &bob.pubkey(), &bob_ata, event_id, 0)], + vec![claim_winnings_ix( + mint, + &bob.pubkey(), + &bob_ata, + event_id, + 0, + )], &[&bob], &bob.pubkey(), ) @@ -436,11 +493,20 @@ fn test_full_lifecycle() { // Carol bet the losing outcome, so she has nothing to claim. let carol_claim = send_transaction_from_instructions( &mut market.svm, - vec![claim_winnings_ix(mint, &carol.pubkey(), &carol_ata, event_id, 1)], + vec![claim_winnings_ix( + mint, + &carol.pubkey(), + &carol_ata, + event_id, + 1, + )], &[&carol], &carol.pubkey(), ); - assert!(carol_claim.is_err(), "loser must not be able to claim winnings"); + assert!( + carol_claim.is_err(), + "loser must not be able to claim winnings" + ); // Her losing position stays in the index until she closes it. let carol_bet = bet_pda(&outcome_pda(&event_pda(event_id), 1), &carol.pubkey()); @@ -464,7 +530,12 @@ fn test_only_admin_can_initialize_event() { let mallory = create_wallet(&mut market.svm, 10_000_000_000).unwrap(); let result = send_transaction_from_instructions( &mut market.svm, - vec![initialize_event_ix(mallory.pubkey(), mint, 7, "Unauthorized event")], + vec![initialize_event_ix( + mallory.pubkey(), + mint, + 7, + "Unauthorized event", + )], &[&mallory], &mallory.pubkey(), ); @@ -496,14 +567,28 @@ fn test_cannot_bet_after_settle() { .unwrap(); send_transaction_from_instructions( &mut market.svm, - vec![place_bet_ix(mint, &alice.pubkey(), &alice_ata, event_id, 0, 100)], + vec![place_bet_ix( + mint, + &alice.pubkey(), + &alice_ata, + event_id, + 0, + 100, + )], &[&alice], &alice.pubkey(), ) .unwrap(); send_transaction_from_instructions( &mut market.svm, - vec![settle_event_ix(admin, mint, fee_recipient, fee_recipient_ata, event_id, 0)], + vec![settle_event_ix( + admin, + mint, + fee_recipient, + fee_recipient_ata, + event_id, + 0, + )], &[&market.admin], &admin, ) @@ -511,7 +596,14 @@ fn test_cannot_bet_after_settle() { let late_bet = send_transaction_from_instructions( &mut market.svm, - vec![place_bet_ix(mint, &bob.pubkey(), &bob_ata, event_id, 1, 100)], + vec![place_bet_ix( + mint, + &bob.pubkey(), + &bob_ata, + event_id, + 1, + 100, + )], &[&bob], &bob.pubkey(), ); @@ -543,21 +635,42 @@ fn test_double_claim_fails() { .unwrap(); send_transaction_from_instructions( &mut market.svm, - vec![place_bet_ix(mint, &alice.pubkey(), &alice_ata, event_id, 0, 100)], + vec![place_bet_ix( + mint, + &alice.pubkey(), + &alice_ata, + event_id, + 0, + 100, + )], &[&alice], &alice.pubkey(), ) .unwrap(); send_transaction_from_instructions( &mut market.svm, - vec![place_bet_ix(mint, &carol.pubkey(), &carol_ata, event_id, 1, 100)], + vec![place_bet_ix( + mint, + &carol.pubkey(), + &carol_ata, + event_id, + 1, + 100, + )], &[&carol], &carol.pubkey(), ) .unwrap(); send_transaction_from_instructions( &mut market.svm, - vec![settle_event_ix(admin, mint, fee_recipient, fee_recipient_ata, event_id, 0)], + vec![settle_event_ix( + admin, + mint, + fee_recipient, + fee_recipient_ata, + event_id, + 0, + )], &[&market.admin], &admin, ) @@ -565,7 +678,13 @@ fn test_double_claim_fails() { send_transaction_from_instructions( &mut market.svm, - vec![claim_winnings_ix(mint, &alice.pubkey(), &alice_ata, event_id, 0)], + vec![claim_winnings_ix( + mint, + &alice.pubkey(), + &alice_ata, + event_id, + 0, + )], &[&alice], &alice.pubkey(), ) @@ -574,7 +693,13 @@ fn test_double_claim_fails() { market.svm.expire_blockhash(); let second = send_transaction_from_instructions( &mut market.svm, - vec![claim_winnings_ix(mint, &alice.pubkey(), &alice_ata, event_id, 0)], + vec![claim_winnings_ix( + mint, + &alice.pubkey(), + &alice_ata, + event_id, + 0, + )], &[&alice], &alice.pubkey(), ); @@ -606,7 +731,14 @@ fn test_settle_outcome_without_bets_fails() { // Everyone bets Horse A; Horse B has no bets. send_transaction_from_instructions( &mut market.svm, - vec![place_bet_ix(mint, &alice.pubkey(), &alice_ata, event_id, 0, 100)], + vec![place_bet_ix( + mint, + &alice.pubkey(), + &alice_ata, + event_id, + 0, + 100, + )], &[&alice], &alice.pubkey(), ) @@ -614,11 +746,21 @@ fn test_settle_outcome_without_bets_fails() { let result = send_transaction_from_instructions( &mut market.svm, - vec![settle_event_ix(admin, mint, fee_recipient, fee_recipient_ata, event_id, 1)], + vec![settle_event_ix( + admin, + mint, + fee_recipient, + fee_recipient_ata, + event_id, + 1, + )], &[&market.admin], &admin, ); - assert!(result.is_err(), "settling to an outcome with no bets must fail"); + assert!( + result.is_err(), + "settling to an outcome with no bets must fail" + ); } #[test] @@ -644,14 +786,28 @@ fn test_cancel_and_refund() { .unwrap(); send_transaction_from_instructions( &mut market.svm, - vec![place_bet_ix(mint, &alice.pubkey(), &alice_ata, event_id, 0, 250)], + vec![place_bet_ix( + mint, + &alice.pubkey(), + &alice_ata, + event_id, + 0, + 250, + )], &[&alice], &alice.pubkey(), ) .unwrap(); send_transaction_from_instructions( &mut market.svm, - vec![place_bet_ix(mint, &carol.pubkey(), &carol_ata, event_id, 1, 750)], + vec![place_bet_ix( + mint, + &carol.pubkey(), + &carol_ata, + event_id, + 1, + 750, + )], &[&carol], &carol.pubkey(), ) @@ -670,7 +826,13 @@ fn test_cancel_and_refund() { send_transaction_from_instructions( &mut market.svm, - vec![claim_refund_ix(mint, &alice.pubkey(), &alice_ata, event_id, 0)], + vec![claim_refund_ix( + mint, + &alice.pubkey(), + &alice_ata, + event_id, + 0, + )], &[&alice], &alice.pubkey(), ) @@ -681,15 +843,27 @@ fn test_cancel_and_refund() { send_transaction_from_instructions( &mut market.svm, - vec![claim_refund_ix(mint, &carol.pubkey(), &carol_ata, event_id, 1)], + vec![claim_refund_ix( + mint, + &carol.pubkey(), + &carol_ata, + event_id, + 1, + )], &[&carol], &carol.pubkey(), ) .unwrap(); // Both bettors made whole; no fee on a cancelled event. - assert_eq!(get_token_account_balance(&market.svm, &alice_ata).unwrap(), 1_000); - assert_eq!(get_token_account_balance(&market.svm, &carol_ata).unwrap(), 1_000); + assert_eq!( + get_token_account_balance(&market.svm, &alice_ata).unwrap(), + 1_000 + ); + assert_eq!( + get_token_account_balance(&market.svm, &carol_ata).unwrap(), + 1_000 + ); let vault = derive_ata(&event_pda(event_id), &mint); assert_eq!(get_token_account_balance(&market.svm, &vault).unwrap(), 0); } @@ -719,14 +893,28 @@ fn test_close_losing_bet_only_after_settle_and_only_for_losers() { .unwrap(); send_transaction_from_instructions( &mut market.svm, - vec![place_bet_ix(mint, &alice.pubkey(), &alice_ata, event_id, 0, 100)], + vec![place_bet_ix( + mint, + &alice.pubkey(), + &alice_ata, + event_id, + 0, + 100, + )], &[&alice], &alice.pubkey(), ) .unwrap(); send_transaction_from_instructions( &mut market.svm, - vec![place_bet_ix(mint, &carol.pubkey(), &carol_ata, event_id, 1, 100)], + vec![place_bet_ix( + mint, + &carol.pubkey(), + &carol_ata, + event_id, + 1, + 100, + )], &[&carol], &carol.pubkey(), ) @@ -739,11 +927,21 @@ fn test_close_losing_bet_only_after_settle_and_only_for_losers() { &[&carol], &carol.pubkey(), ); - assert!(premature_close.is_err(), "closing before settlement must fail"); + assert!( + premature_close.is_err(), + "closing before settlement must fail" + ); send_transaction_from_instructions( &mut market.svm, - vec![settle_event_ix(admin, mint, fee_recipient, fee_recipient_ata, event_id, 0)], + vec![settle_event_ix( + admin, + mint, + fee_recipient, + fee_recipient_ata, + event_id, + 0, + )], &[&market.admin], &admin, ) @@ -756,7 +954,10 @@ fn test_close_losing_bet_only_after_settle_and_only_for_losers() { &[&alice], &alice.pubkey(), ); - assert!(winner_close.is_err(), "a winning bet must not be closed as losing"); + assert!( + winner_close.is_err(), + "a winning bet must not be closed as losing" + ); let alice_bet = bet_pda(&outcome_pda(&event_pda(event_id), 0), &alice.pubkey()); assert_eq!(read_user_bets(&market, &alice.pubkey()), vec![alice_bet]); @@ -793,7 +994,12 @@ fn test_closing_a_bet_frees_a_slot_for_a_new_bet() { send_transaction_from_instructions( &mut market.svm, - vec![initialize_event_ix(admin, mint, full_event_id, "Wide field")], + vec![initialize_event_ix( + admin, + mint, + full_event_id, + "Wide field", + )], &[&market.admin], &admin, ) @@ -801,7 +1007,12 @@ fn test_closing_a_bet_frees_a_slot_for_a_new_bet() { for index in 0..outcome_count { send_transaction_from_instructions( &mut market.svm, - vec![add_outcome_ix(admin, full_event_id, index, &format!("Runner {index}"))], + vec![add_outcome_ix( + admin, + full_event_id, + index, + &format!("Runner {index}"), + )], &[&market.admin], &admin, ) @@ -823,13 +1034,23 @@ fn test_closing_a_bet_frees_a_slot_for_a_new_bet() { for index in 0..MAX_BETS_PER_USER as u8 { send_transaction_from_instructions( &mut market.svm, - vec![place_bet_ix(mint, &alice.pubkey(), &alice_ata, full_event_id, index, STAKE)], + vec![place_bet_ix( + mint, + &alice.pubkey(), + &alice_ata, + full_event_id, + index, + STAKE, + )], &[&alice], &alice.pubkey(), ) .unwrap(); } - assert_eq!(read_user_bets(&market, &alice.pubkey()).len(), MAX_BETS_PER_USER); + assert_eq!( + read_user_bets(&market, &alice.pubkey()).len(), + MAX_BETS_PER_USER + ); // With the index full, any new position is rejected - on this event or another. let one_too_many = send_transaction_from_instructions( @@ -845,14 +1066,27 @@ fn test_closing_a_bet_frees_a_slot_for_a_new_bet() { &[&alice], &alice.pubkey(), ); - assert!(one_too_many.is_err(), "a full index must reject a new position"); + assert!( + one_too_many.is_err(), + "a full index must reject a new position" + ); let other_market_bet = send_transaction_from_instructions( &mut market.svm, - vec![place_bet_ix(mint, &alice.pubkey(), &alice_ata, second_event_id, 0, STAKE)], + vec![place_bet_ix( + mint, + &alice.pubkey(), + &alice_ata, + second_event_id, + 0, + STAKE, + )], &[&alice], &alice.pubkey(), ); - assert!(other_market_bet.is_err(), "a full index must reject bets on any market"); + assert!( + other_market_bet.is_err(), + "a full index must reject bets on any market" + ); // Unwind one position: cancel the event and refund the first bet. send_transaction_from_instructions( @@ -864,7 +1098,13 @@ fn test_closing_a_bet_frees_a_slot_for_a_new_bet() { .unwrap(); send_transaction_from_instructions( &mut market.svm, - vec![claim_refund_ix(mint, &alice.pubkey(), &alice_ata, full_event_id, 0)], + vec![claim_refund_ix( + mint, + &alice.pubkey(), + &alice_ata, + full_event_id, + 0, + )], &[&alice], &alice.pubkey(), ) @@ -882,13 +1122,26 @@ fn test_closing_a_bet_frees_a_slot_for_a_new_bet() { market.svm.expire_blockhash(); send_transaction_from_instructions( &mut market.svm, - vec![place_bet_ix(mint, &alice.pubkey(), &alice_ata, second_event_id, 0, STAKE)], + vec![place_bet_ix( + mint, + &alice.pubkey(), + &alice_ata, + second_event_id, + 0, + STAKE, + )], &[&alice], &alice.pubkey(), ) .unwrap(); let final_bets = read_user_bets(&market, &alice.pubkey()); assert_eq!(final_bets.len(), MAX_BETS_PER_USER); - let new_bet = bet_pda(&outcome_pda(&event_pda(second_event_id), 0), &alice.pubkey()); - assert!(final_bets.contains(&new_bet), "the new position must appear in the index"); + let new_bet = bet_pda( + &outcome_pda(&event_pda(second_event_id), 0), + &alice.pubkey(), + ); + assert!( + final_bets.contains(&new_bet), + "the new position must appear in the index" + ); } diff --git a/finance/escrow/anchor/programs/escrow/src/instructions/take_offer.rs b/finance/escrow/anchor/programs/escrow/src/instructions/take_offer.rs index 028308cdc..6bf739357 100644 --- a/finance/escrow/anchor/programs/escrow/src/instructions/take_offer.rs +++ b/finance/escrow/anchor/programs/escrow/src/instructions/take_offer.rs @@ -85,7 +85,9 @@ pub fn handle_send_wanted_tokens_to_maker( ) } -pub fn handle_withdraw_and_close_vault(context: &mut Context) -> Result<()> { +pub fn handle_withdraw_and_close_vault( + context: &mut Context, +) -> Result<()> { let maker_key = context.accounts.maker.address(); let id_bytes = context.accounts.offer.id.to_le_bytes(); let bump = [context.accounts.offer.bump]; diff --git a/finance/escrow/anchor/programs/escrow/src/lib.rs b/finance/escrow/anchor/programs/escrow/src/lib.rs index a73921b4d..366f77b2d 100644 --- a/finance/escrow/anchor/programs/escrow/src/lib.rs +++ b/finance/escrow/anchor/programs/escrow/src/lib.rs @@ -19,7 +19,10 @@ pub mod escrow { token_a_offered_amount: u64, token_b_wanted_amount: u64, ) -> Result<()> { - instructions::make_offer::handle_send_offered_tokens_to_vault(&context, token_a_offered_amount)?; + instructions::make_offer::handle_send_offered_tokens_to_vault( + &context, + token_a_offered_amount, + )?; instructions::make_offer::handle_save_offer(context, id, token_b_wanted_amount) } diff --git a/finance/escrow/anchor/programs/escrow/tests/test_escrow.rs b/finance/escrow/anchor/programs/escrow/tests/test_escrow.rs index db586e808..62379bcd4 100644 --- a/finance/escrow/anchor/programs/escrow/tests/test_escrow.rs +++ b/finance/escrow/anchor/programs/escrow/tests/test_escrow.rs @@ -1,42 +1,43 @@ use { anchor_lang::{ - solana_program::{instruction::Instruction, pubkey::Pubkey, system_program}, - InstructionData, ToAccountMetas, + solana_program::instruction::Instruction, system_program, Address, InstructionData, + ToAccountMetas, }, litesvm::LiteSVM, solana_keypair::Keypair, solana_kite::{ create_associated_token_account, create_token_mint, create_wallet, - get_token_account_balance, mint_tokens_to_token_account, send_transaction_from_instructions, + get_token_account_balance, mint_tokens_to_token_account, + send_transaction_from_instructions, }, solana_signer::Signer, }; -fn token_program_id() -> Pubkey { +fn token_program_id() -> Address { "TokenkegQfeZyiNwAJbNbGKPFXCWuBvf9Ss623VQ5DA" .parse() .unwrap() } -fn ata_program_id() -> Pubkey { +fn ata_program_id() -> Address { "ATokenGPvbdGVxr1b2hvZbsiqW5xWH25efTNsLJA8knL" .parse() .unwrap() } -fn lamports(svm: &LiteSVM, address: &Pubkey) -> u64 { +fn lamports(svm: &LiteSVM, address: &Address) -> u64 { svm.get_account(address).map(|a| a.lamports).unwrap_or(0) } -fn derive_ata(wallet: &Pubkey, mint: &Pubkey) -> Pubkey { - let (ata, _bump) = Pubkey::find_program_address( +fn derive_ata(wallet: &Address, mint: &Address) -> Address { + let (ata, _bump) = Address::find_program_address( &[wallet.as_ref(), token_program_id().as_ref(), mint.as_ref()], &ata_program_id(), ); ata } -fn setup() -> (LiteSVM, Pubkey, Keypair) { +fn setup() -> (LiteSVM, Address, Keypair) { let program_id = escrow::id(); let mut svm = LiteSVM::new(); @@ -49,16 +50,16 @@ fn setup() -> (LiteSVM, Pubkey, Keypair) { struct EscrowSetup { svm: LiteSVM, - program_id: Pubkey, + program_id: Address, payer: Keypair, alice: Keypair, bob: Keypair, - mint_a: Pubkey, - mint_b: Pubkey, - alice_ata_a: Pubkey, - alice_ata_b: Pubkey, - bob_ata_a: Pubkey, - bob_ata_b: Pubkey, + mint_a: Address, + mint_b: Address, + alice_ata_a: Address, + alice_ata_b: Address, + bob_ata_a: Address, + bob_ata_b: Address, } fn full_setup() -> EscrowSetup { @@ -114,7 +115,7 @@ fn test_make_offer() { let token_b_wanted_amount: u64 = 1_000_000; // Derive offer PDA - let (offer_pda, _bump) = Pubkey::find_program_address( + let (offer_pda, _bump) = Address::find_program_address( &[ b"offer", es.alice.pubkey().as_ref(), @@ -144,7 +145,7 @@ fn test_make_offer() { vault, associated_token_program: ata_program_id(), token_program: token_program_id(), - system_program: system_program::id(), + system_program: system_program::ID, } .to_account_metas(None), ); @@ -168,7 +169,7 @@ fn test_make_offer() { let data = &offer_data.data[8..]; // Skip 8-byte discriminator let stored_id = u64::from_le_bytes(data[0..8].try_into().unwrap()); assert_eq!(stored_id, offer_id); - let stored_maker = Pubkey::try_from(&data[8..40]).unwrap(); + let stored_maker = Address::try_from(&data[8..40]).unwrap(); assert_eq!(stored_maker, es.alice.pubkey()); } @@ -181,7 +182,7 @@ fn test_take_offer() { let token_b_wanted_amount: u64 = 1_000_000; // Derive offer PDA - let (offer_pda, _bump) = Pubkey::find_program_address( + let (offer_pda, _bump) = Address::find_program_address( &[ b"offer", es.alice.pubkey().as_ref(), @@ -218,7 +219,7 @@ fn test_take_offer() { vault, associated_token_program: ata_program_id(), token_program: token_program_id(), - system_program: system_program::id(), + system_program: system_program::ID, } .to_account_metas(None), ); @@ -253,7 +254,7 @@ fn test_take_offer() { vault, associated_token_program: ata_program_id(), token_program: token_program_id(), - system_program: system_program::id(), + system_program: system_program::ID, } .to_account_metas(None), ); @@ -313,7 +314,7 @@ fn test_cancel_offer() { let token_a_offered_amount: u64 = 500_000; let token_b_wanted_amount: u64 = 1_000_000; - let (offer_pda, _bump) = Pubkey::find_program_address( + let (offer_pda, _bump) = Address::find_program_address( &[ b"offer", es.alice.pubkey().as_ref(), @@ -346,7 +347,7 @@ fn test_cancel_offer() { vault, associated_token_program: ata_program_id(), token_program: token_program_id(), - system_program: system_program::id(), + system_program: system_program::ID, } .to_account_metas(None), ); @@ -375,7 +376,7 @@ fn test_cancel_offer() { vault, associated_token_program: ata_program_id(), token_program: token_program_id(), - system_program: system_program::id(), + system_program: system_program::ID, } .to_account_metas(None), ); @@ -417,7 +418,7 @@ fn test_cancel_offer_rejects_non_maker() { let token_a_offered_amount: u64 = 500_000; let token_b_wanted_amount: u64 = 1_000_000; - let (offer_pda, _bump) = Pubkey::find_program_address( + let (offer_pda, _bump) = Address::find_program_address( &[ b"offer", es.alice.pubkey().as_ref(), @@ -446,7 +447,7 @@ fn test_cancel_offer_rejects_non_maker() { vault, associated_token_program: ata_program_id(), token_program: token_program_id(), - system_program: system_program::id(), + system_program: system_program::ID, } .to_account_metas(None), ); @@ -474,7 +475,7 @@ fn test_cancel_offer_rejects_non_maker() { vault, associated_token_program: ata_program_id(), token_program: token_program_id(), - system_program: system_program::id(), + system_program: system_program::ID, } .to_account_metas(None), ); diff --git a/finance/lending/anchor/programs/lending/src/instructions/admin/collect_protocol_fees.rs b/finance/lending/anchor/programs/lending/src/instructions/admin/collect_protocol_fees.rs index 0d72a8098..485b24e41 100644 --- a/finance/lending/anchor/programs/lending/src/instructions/admin/collect_protocol_fees.rs +++ b/finance/lending/anchor/programs/lending/src/instructions/admin/collect_protocol_fees.rs @@ -16,7 +16,9 @@ pub fn handle_collect_protocol_fees(context: &mut Context) let reserve = &mut context.accounts.reserve; // Fees are a claim on liquidity; only what is currently un-borrowed can be paid // out right now. Any remainder stays owed until borrowers repay. - let amount = reserve.accumulated_protocol_fees.min(reserve.available_liquidity); + let amount = reserve + .accumulated_protocol_fees + .min(reserve.available_liquidity); require!(amount > 0, LendingError::NothingToCollect); reserve.accumulated_protocol_fees = reserve diff --git a/finance/lending/anchor/programs/lending/src/instructions/admin/initialize_reserve.rs b/finance/lending/anchor/programs/lending/src/instructions/admin/initialize_reserve.rs index ff7cfd71f..650a32829 100644 --- a/finance/lending/anchor/programs/lending/src/instructions/admin/initialize_reserve.rs +++ b/finance/lending/anchor/programs/lending/src/instructions/admin/initialize_reserve.rs @@ -1,6 +1,6 @@ use anchor_lang::prelude::*; -use anchor_spl::token; use anchor_spl::mint; +use anchor_spl::token; use anchor_spl::token_interface::{Mint, TokenAccount, TokenInterface}; use crate::constants::{ @@ -8,7 +8,10 @@ use crate::constants::{ }; use crate::state::{LendingMarket, PriceFeed, Reserve, ReserveConfig}; -pub fn handle_initialize_reserve(context: &mut Context, config: ReserveConfig) -> Result<()> { +pub fn handle_initialize_reserve( + context: &mut Context, + config: ReserveConfig, +) -> Result<()> { config.validate()?; let reserve = &mut context.accounts.reserve; diff --git a/finance/lending/anchor/programs/lending/src/instructions/deposit_reserve_liquidity.rs b/finance/lending/anchor/programs/lending/src/instructions/deposit_reserve_liquidity.rs index 207d8482b..4c8831004 100644 --- a/finance/lending/anchor/programs/lending/src/instructions/deposit_reserve_liquidity.rs +++ b/finance/lending/anchor/programs/lending/src/instructions/deposit_reserve_liquidity.rs @@ -23,7 +23,11 @@ pub fn handle_deposit_reserve_liquidity( let share_amount = if share_supply == 0 { liquidity_amount as u128 } else { - mul_div_floor(liquidity_amount as u128, share_supply, reserve.total_liquidity()?)? + mul_div_floor( + liquidity_amount as u128, + share_supply, + reserve.total_liquidity()?, + )? }; require!(share_amount > 0, LendingError::DepositTooSmall); let share_amount = u64::try_from(share_amount).map_err(|_| LendingError::MathOverflow)?; diff --git a/finance/lending/anchor/programs/lending/src/instructions/liquidate_obligation.rs b/finance/lending/anchor/programs/lending/src/instructions/liquidate_obligation.rs index 1767e9c6a..c3152bf37 100644 --- a/finance/lending/anchor/programs/lending/src/instructions/liquidate_obligation.rs +++ b/finance/lending/anchor/programs/lending/src/instructions/liquidate_obligation.rs @@ -62,7 +62,8 @@ pub fn handle_liquidate_obligation( repay_reserve.config.close_factor_bps as u128, BPS_DENOMINATOR, )?; - let repay = liquidity_amount.min(u64::try_from(max_repay).map_err(|_| LendingError::MathOverflow)?); + let repay = + liquidity_amount.min(u64::try_from(max_repay).map_err(|_| LendingError::MathOverflow)?); require!(repay > 0, LendingError::ZeroAmount); // Collateral to seize: value of the repayment plus the bonus, converted into @@ -100,8 +101,8 @@ pub fn handle_liquidate_obligation( LendingError::LiquidationTooLarge ); - let scaled_removed = - mul_div_floor(repay as u128, FIXED_POINT_SCALE, accumulation_factor)?.min(borrowed_principal); + let scaled_removed = mul_div_floor(repay as u128, FIXED_POINT_SCALE, accumulation_factor)? + .min(borrowed_principal); // Effects: repay side. { @@ -151,12 +152,20 @@ pub fn handle_liquidate_obligation( )?; let bump = [obligation_bump]; - let seeds: [&[u8]; 4] = [OBLIGATION_SEED, lending_market.as_ref(), owner.as_ref(), &bump]; + let seeds: [&[u8]; 4] = [ + OBLIGATION_SEED, + lending_market.as_ref(), + owner.as_ref(), + &bump, + ]; transfer_checked( CpiContext::new_with_signer( context.accounts.token_program.address(), TransferChecked { - from: context.accounts.obligation_collateral_vault.cpi_handle_mut(), + from: context + .accounts + .obligation_collateral_vault + .cpi_handle_mut(), mint: context.accounts.collateral_share_mint.cpi_handle(), to: context.accounts.liquidator_collateral_dest.cpi_handle_mut(), authority: context.accounts.obligation.cpi_handle(), diff --git a/finance/lending/anchor/programs/lending/src/instructions/redeem_reserve_collateral.rs b/finance/lending/anchor/programs/lending/src/instructions/redeem_reserve_collateral.rs index 8b2a85c1f..0a45cd757 100644 --- a/finance/lending/anchor/programs/lending/src/instructions/redeem_reserve_collateral.rs +++ b/finance/lending/anchor/programs/lending/src/instructions/redeem_reserve_collateral.rs @@ -26,7 +26,8 @@ pub fn handle_redeem_reserve_collateral( reserve.total_liquidity()?, share_supply, )?; - let liquidity_amount = u64::try_from(liquidity_amount).map_err(|_| LendingError::MathOverflow)?; + let liquidity_amount = + u64::try_from(liquidity_amount).map_err(|_| LendingError::MathOverflow)?; require!( liquidity_amount <= reserve.available_liquidity, LendingError::InsufficientReserveLiquidity diff --git a/finance/lending/anchor/programs/lending/src/instructions/refresh_obligation.rs b/finance/lending/anchor/programs/lending/src/instructions/refresh_obligation.rs index 28924df08..a58bbb65a 100644 --- a/finance/lending/anchor/programs/lending/src/instructions/refresh_obligation.rs +++ b/finance/lending/anchor/programs/lending/src/instructions/refresh_obligation.rs @@ -27,8 +27,13 @@ pub fn handle_refresh_obligation(context: &mut Context) -> Re let mut unhealthy_borrow_value: u128 = 0; for collateral in obligation.deposits.iter_mut() { - let (reserve, price_scaled) = - read_pair(accounts, &mut cursor, collateral.reserve, lending_market, slot)?; + let (reserve, price_scaled) = read_pair( + accounts, + &mut cursor, + collateral.reserve, + lending_market, + slot, + )?; let liquidity = mul_div_floor( collateral.deposited_shares as u128, @@ -36,7 +41,12 @@ pub fn handle_refresh_obligation(context: &mut Context) -> Re (reserve.share_mint_supply as u128).max(1), )?; let liquidity = u64::try_from(liquidity).map_err(|_| LendingError::MathOverflow)?; - let value = market_value(liquidity, reserve.liquidity_decimals, price_scaled, Rounding::Down)?; + let value = market_value( + liquidity, + reserve.liquidity_decimals, + price_scaled, + Rounding::Down, + )?; collateral.market_value = value; deposited_value = deposited_value diff --git a/finance/lending/anchor/programs/lending/src/instructions/repay_obligation_liquidity.rs b/finance/lending/anchor/programs/lending/src/instructions/repay_obligation_liquidity.rs index b11bec0f1..163cf2a1f 100644 --- a/finance/lending/anchor/programs/lending/src/instructions/repay_obligation_liquidity.rs +++ b/finance/lending/anchor/programs/lending/src/instructions/repay_obligation_liquidity.rs @@ -31,7 +31,8 @@ pub fn handle_repay_obligation_liquidity( let repay = liquidity_amount.min(debt_now); require!(repay > 0, LendingError::ZeroAmount); - let scaled_removed = mul_div_floor(repay as u128, FIXED_POINT_SCALE, index)?.min(borrowed_principal); + let scaled_removed = + mul_div_floor(repay as u128, FIXED_POINT_SCALE, index)?.min(borrowed_principal); { let reserve = &mut context.accounts.reserve; diff --git a/finance/lending/anchor/programs/lending/src/instructions/withdraw_obligation_collateral.rs b/finance/lending/anchor/programs/lending/src/instructions/withdraw_obligation_collateral.rs index 11efd5b1f..00c6f13c6 100644 --- a/finance/lending/anchor/programs/lending/src/instructions/withdraw_obligation_collateral.rs +++ b/finance/lending/anchor/programs/lending/src/instructions/withdraw_obligation_collateral.rs @@ -42,7 +42,8 @@ pub fn handle_withdraw_obligation_collateral( reserve.total_liquidity()?, (reserve.share_mint_supply as u128).max(1), )?; - let removed_liquidity = u64::try_from(removed_liquidity).map_err(|_| LendingError::MathOverflow)?; + let removed_liquidity = + u64::try_from(removed_liquidity).map_err(|_| LendingError::MathOverflow)?; let removed_value = market_value( removed_liquidity, reserve.liquidity_decimals, diff --git a/finance/lending/anchor/programs/lending/src/lib.rs b/finance/lending/anchor/programs/lending/src/lib.rs index ba2c018f6..481eaf3d3 100644 --- a/finance/lending/anchor/programs/lending/src/lib.rs +++ b/finance/lending/anchor/programs/lending/src/lib.rs @@ -2,8 +2,8 @@ use anchor_lang::prelude::*; pub mod constants; pub mod errors; -pub mod math; pub mod instructions; +pub mod math; pub mod state; use instructions::*; @@ -22,7 +22,10 @@ pub mod lending { instructions::handle_initialize_lending_market(context, market_id) } - pub fn initialize_reserve(context: &mut Context, config: ReserveConfig) -> Result<()> { + pub fn initialize_reserve( + context: &mut Context, + config: ReserveConfig, + ) -> Result<()> { instructions::handle_initialize_reserve(context, config) } diff --git a/finance/lending/anchor/programs/lending/src/state/obligation.rs b/finance/lending/anchor/programs/lending/src/state/obligation.rs index d9a3bcb5b..393853c83 100644 --- a/finance/lending/anchor/programs/lending/src/state/obligation.rs +++ b/finance/lending/anchor/programs/lending/src/state/obligation.rs @@ -42,14 +42,18 @@ pub struct Obligation { pub bump: u8, } -#[derive(InitSpace, Clone, Copy, Debug, Default, IdlType, wincode::SchemaRead, wincode::SchemaWrite)] +#[derive( + InitSpace, Clone, Copy, Debug, Default, IdlType, wincode::SchemaRead, wincode::SchemaWrite, +)] pub struct ObligationCollateral { pub reserve: Address, pub deposited_shares: u64, pub market_value: u128, } -#[derive(InitSpace, Clone, Copy, Debug, Default, IdlType, wincode::SchemaRead, wincode::SchemaWrite)] +#[derive( + InitSpace, Clone, Copy, Debug, Default, IdlType, wincode::SchemaRead, wincode::SchemaWrite, +)] pub struct ObligationLiquidity { pub reserve: Address, /// Borrowed principal, scaled by the reserve's index at borrow time so the @@ -75,7 +79,11 @@ impl Obligation { /// Index of the collateral entry for `reserve`, creating an empty one if the /// obligation has room. Used when posting collateral. pub fn upsert_collateral(&mut self, reserve: Address) -> Result { - if let Some(index) = self.deposits.iter().position(|entry| entry.reserve == reserve) { + if let Some(index) = self + .deposits + .iter() + .position(|entry| entry.reserve == reserve) + { return Ok(index); } require!( @@ -93,7 +101,11 @@ impl Obligation { /// Index of the borrow entry for `reserve`, creating an empty one if the /// obligation has room. Used when borrowing. pub fn upsert_borrow(&mut self, reserve: Address) -> Result { - if let Some(index) = self.borrows.iter().position(|entry| entry.reserve == reserve) { + if let Some(index) = self + .borrows + .iter() + .position(|entry| entry.reserve == reserve) + { return Ok(index); } require!( diff --git a/finance/lending/anchor/programs/lending/src/state/price_feed.rs b/finance/lending/anchor/programs/lending/src/state/price_feed.rs index 4f2cc3b84..540f2374d 100644 --- a/finance/lending/anchor/programs/lending/src/state/price_feed.rs +++ b/finance/lending/anchor/programs/lending/src/state/price_feed.rs @@ -44,7 +44,10 @@ impl PriceFeed { let age = current_slot .checked_sub(self.last_updated_slot) .ok_or(LendingError::MathOverflow)?; - require!(age <= MAX_PRICE_STALENESS_SLOTS, LendingError::StalePriceFeed); + require!( + age <= MAX_PRICE_STALENESS_SLOTS, + LendingError::StalePriceFeed + ); // Restart handling. A cluster halt stops the slot count but not the // wall clock, so after a restart a feed can look fresh in slots while diff --git a/finance/lending/anchor/programs/lending/src/state/reserve.rs b/finance/lending/anchor/programs/lending/src/state/reserve.rs index 4733fe54a..867449597 100644 --- a/finance/lending/anchor/programs/lending/src/state/reserve.rs +++ b/finance/lending/anchor/programs/lending/src/state/reserve.rs @@ -77,7 +77,9 @@ pub struct Reserve { } /// Risk and interest-rate parameters. All ratios are basis points (10_000 = 100%). -#[derive(InitSpace, Clone, Copy, Debug, Default, IdlType, wincode::SchemaRead, wincode::SchemaWrite)] +#[derive( + InitSpace, Clone, Copy, Debug, Default, IdlType, wincode::SchemaRead, wincode::SchemaWrite, +)] pub struct ReserveConfig { /// Fraction of deposited collateral value a borrower may borrow against. pub loan_to_value_bps: u16, @@ -168,7 +170,11 @@ impl Reserve { if gross == 0 { return Ok(0); } - mul_div_floor(self.current_borrowed_amount()? as u128, BPS_DENOMINATOR, gross) + mul_div_floor( + self.current_borrowed_amount()? as u128, + BPS_DENOMINATOR, + gross, + ) } /// Per-slot borrow rate (FIXED_POINT_SCALE-scaled) from the kinked curve: diff --git a/finance/lending/anchor/programs/lending/tests/common/mod.rs b/finance/lending/anchor/programs/lending/tests/common/mod.rs index 99210fe89..319316287 100644 --- a/finance/lending/anchor/programs/lending/tests/common/mod.rs +++ b/finance/lending/anchor/programs/lending/tests/common/mod.rs @@ -28,7 +28,7 @@ use lending::constants::{ }; use lending::state::{Obligation, Reserve, ReserveConfig}; -pub use anchor_lang::prelude::Pubkey; +pub use anchor_lang::prelude::Address; /// A FIXED_POINT_SCALE-scaled price exponent: prices are passed as /// `mantissa * 10^-18`, matching a Switchboard On-Demand feed's 1e18 result. @@ -43,19 +43,19 @@ pub fn cents(amount: u64) -> i128 { (amount as i128) * 10_000_000_000_000_000 } -pub fn ata(owner: &Pubkey, mint: &Pubkey) -> Pubkey { - let ata_program: Pubkey = "ATokenGPvbdGVxr1b2hvZbsiqW5xWH25efTNsLJA8knL" +pub fn ata(owner: &Address, mint: &Address) -> Address { + let ata_program: Address = "ATokenGPvbdGVxr1b2hvZbsiqW5xWH25efTNsLJA8knL" .parse() .unwrap(); - Pubkey::find_program_address( + Address::find_program_address( &[owner.as_ref(), TOKEN_PROGRAM_ID.as_ref(), mint.as_ref()], &ata_program, ) .0 } -fn pda(seeds: &[&[u8]]) -> Pubkey { - Pubkey::find_program_address(seeds, &lending::id()).0 +fn pda(seeds: &[&[u8]]) -> Address { + Address::find_program_address(seeds, &lending::id()).0 } /// Map kite's transaction result to a String so tests can assert on the program @@ -64,7 +64,7 @@ fn send( svm: &mut LiteSVM, instructions: Vec, signers: &[&Keypair], - payer: &Pubkey, + payer: &Address, ) -> Result<(), String> { send_transaction_from_instructions(svm, instructions, signers, payer) .map_err(|thrown| format!("{thrown:?}")) @@ -73,12 +73,12 @@ fn send( /// Handle to one reserve and its associated PDAs. #[derive(Clone, Copy)] pub struct ReserveHandle { - pub mint: Pubkey, + pub mint: Address, pub decimals: u8, - pub reserve: Pubkey, - pub share_mint: Pubkey, - pub liquidity_vault: Pubkey, - pub price_feed: Pubkey, + pub reserve: Address, + pub share_mint: Address, + pub liquidity_vault: Address, + pub price_feed: Address, } pub struct Env { @@ -86,7 +86,7 @@ pub struct Env { /// Market owner; also the mint authority for every test mint and the price /// feed authority. pub owner: Keypair, - pub market: Pubkey, + pub market: Address, } impl Env { @@ -107,7 +107,7 @@ impl Env { lending_market: market, owner: owner.pubkey(), quote_currency_mint: quote_mint, - system_program: system_program::id(), + system_program: system_program::ID, } .to_account_metas(None), data: lending::instruction::InitializeLendingMarket { market_id }.data(), @@ -118,12 +118,14 @@ impl Env { } pub fn current_slot(&self) -> u64 { - self.svm.get_sysvar::().slot + self.svm + .get_sysvar::() + .slot } /// Create a second lending market owned by `market_owner`, for tests that /// exercise cross-market isolation. - pub fn init_market_for(&mut self, market_owner: &Keypair) -> Pubkey { + pub fn init_market_for(&mut self, market_owner: &Keypair) -> Address { let env_owner = self.owner.insecure_clone(); let quote_mint = create_token_mint(&mut self.svm, &env_owner, 6, None).unwrap(); // A distinct id from the env's market 0, since the id is the market's @@ -136,12 +138,18 @@ impl Env { lending_market: market, owner: market_owner.pubkey(), quote_currency_mint: quote_mint, - system_program: system_program::id(), + system_program: system_program::ID, } .to_account_metas(None), data: lending::instruction::InitializeLendingMarket { market_id }.data(), }; - send(&mut self.svm, vec![instruction], &[market_owner], &market_owner.pubkey()).unwrap(); + send( + &mut self.svm, + vec![instruction], + &[market_owner], + &market_owner.pubkey(), + ) + .unwrap(); market } @@ -151,7 +159,7 @@ impl Env { pub fn add_reserve_to( &mut self, market_owner: &Keypair, - market: Pubkey, + market: Address, decimals: u8, price_mantissa: i128, config: ReserveConfig, @@ -176,12 +184,18 @@ impl Env { share_mint, price_feed, token_program: TOKEN_PROGRAM_ID, - system_program: system_program::id(), + system_program: system_program::ID, } .to_account_metas(None), data: lending::instruction::InitializeReserve { config }.data(), }; - send(&mut self.svm, vec![instruction], &[market_owner], &market_owner.pubkey()).unwrap(); + send( + &mut self.svm, + vec![instruction], + &[market_owner], + &market_owner.pubkey(), + ) + .unwrap(); ReserveHandle { mint, @@ -203,19 +217,20 @@ impl Env { /// Simulate a cluster restart at `slot`: prices stamped at or before it /// must be rejected until the publisher posts again. pub fn set_last_restart_slot(&mut self, slot: u64) { - self.svm.set_sysvar(&solana_sysvar::last_restart_slot::LastRestartSlot { - last_restart_slot: slot, - }); + self.svm + .set_sysvar(&solana_sysvar::last_restart_slot::LastRestartSlot { + last_restart_slot: slot, + }); } /// The feed PDA the market owner writes for `mint`: seeded by the owner's /// key, so it is the feed `add_reserve` registers reserves against. /// The feed PDA for a given market and mint (seeds `["price_feed", market, mint]`). - pub fn price_feed_address(&self, market: Pubkey, mint: Pubkey) -> Pubkey { + pub fn price_feed_address(&self, market: Address, mint: Address) -> Address { pda(&[PRICE_FEED_SEED, market.as_ref(), mint.as_ref()]) } - pub fn set_price(&mut self, mint: Pubkey, price_mantissa: i128) { + pub fn set_price(&mut self, mint: Address, price_mantissa: i128) { let owner = self.owner.insecure_clone(); let market = self.market; self.set_price_for(&owner, market, mint, price_mantissa); @@ -225,8 +240,8 @@ impl Env { pub fn set_price_for( &mut self, owner: &Keypair, - market: Pubkey, - mint: Pubkey, + market: Address, + mint: Address, price_mantissa: i128, ) { let price_feed = self.price_feed_address(market, mint); @@ -237,7 +252,7 @@ impl Env { owner: owner.pubkey(), price_feed, mint, - system_program: system_program::id(), + system_program: system_program::ID, } .to_account_metas(None), data: lending::instruction::SetPrice { @@ -284,7 +299,7 @@ impl Env { } /// Create the user's token account for a mint and mint `amount` into it. - pub fn fund(&mut self, user: &Keypair, mint: Pubkey, amount: u64) -> Pubkey { + pub fn fund(&mut self, user: &Keypair, mint: Address, amount: u64) -> Address { let owner = self.owner.insecure_clone(); let token_account = create_associated_token_account(&mut self.svm, &user.pubkey(), &mint, user).unwrap(); @@ -313,7 +328,7 @@ impl Env { user: &Keypair, handle: &ReserveHandle, amount: u64, - ) -> Result { + ) -> Result { let user_liquidity = ata(&user.pubkey(), &handle.mint); let user_share = create_associated_token_account( &mut self.svm, @@ -342,11 +357,16 @@ impl Env { .data(), }; let refresh = self.refresh_reserve_ix(handle); - send(&mut self.svm, vec![refresh, deposit], &[user], &user.pubkey())?; + send( + &mut self.svm, + vec![refresh, deposit], + &[user], + &user.pubkey(), + )?; Ok(user_share) } - pub fn supply(&mut self, user: &Keypair, handle: &ReserveHandle, amount: u64) -> Pubkey { + pub fn supply(&mut self, user: &Keypair, handle: &ReserveHandle, amount: u64) -> Address { self.try_supply(user, handle, amount).unwrap() } @@ -374,18 +394,27 @@ impl Env { data: lending::instruction::RedeemReserveCollateral { share_amount }.data(), }; let refresh = self.refresh_reserve_ix(handle); - send(&mut self.svm, vec![refresh, redeem], &[user], &user.pubkey()) + send( + &mut self.svm, + vec![refresh, redeem], + &[user], + &user.pubkey(), + ) } - pub fn initialize_obligation(&mut self, user: &Keypair) -> Pubkey { - let obligation = pda(&[OBLIGATION_SEED, self.market.as_ref(), user.pubkey().as_ref()]); + pub fn initialize_obligation(&mut self, user: &Keypair) -> Address { + let obligation = pda(&[ + OBLIGATION_SEED, + self.market.as_ref(), + user.pubkey().as_ref(), + ]); let instruction = Instruction { program_id: lending::id(), accounts: lending::accounts::InitializeObligation { lending_market: self.market, obligation, owner: user.pubkey(), - system_program: system_program::id(), + system_program: system_program::ID, } .to_account_metas(None), data: lending::instruction::InitializeObligation {}.data(), @@ -394,7 +423,7 @@ impl Env { obligation } - pub fn obligation_share_vault(&self, handle: &ReserveHandle, obligation: Pubkey) -> Pubkey { + pub fn obligation_share_vault(&self, handle: &ReserveHandle, obligation: Address) -> Address { pda(&[ OBLIGATION_SHARE_VAULT_SEED, handle.reserve.as_ref(), @@ -405,7 +434,7 @@ impl Env { pub fn try_post_collateral( &mut self, user: &Keypair, - obligation: Pubkey, + obligation: Address, handle: &ReserveHandle, share_amount: u64, ) -> Result<(), String> { @@ -421,7 +450,7 @@ impl Env { obligation_share_vault: vault, user_share, token_program: TOKEN_PROGRAM_ID, - system_program: system_program::id(), + system_program: system_program::ID, } .to_account_metas(None), data: lending::instruction::DepositObligationCollateral { share_amount }.data(), @@ -432,7 +461,7 @@ impl Env { pub fn post_collateral( &mut self, user: &Keypair, - obligation: Pubkey, + obligation: Address, handle: &ReserveHandle, share_amount: u64, ) { @@ -442,11 +471,12 @@ impl Env { fn refresh_obligation_ix( &self, - obligation: Pubkey, + obligation: Address, deposit_reserves: &[&ReserveHandle], borrow_reserves: &[&ReserveHandle], ) -> Instruction { - let mut accounts = lending::accounts::RefreshObligation { obligation }.to_account_metas(None); + let mut accounts = + lending::accounts::RefreshObligation { obligation }.to_account_metas(None); for handle in deposit_reserves.iter().chain(borrow_reserves.iter()) { accounts.push(AccountMeta::new_readonly(handle.reserve, false)); accounts.push(AccountMeta::new_readonly(handle.price_feed, false)); @@ -461,7 +491,7 @@ impl Env { /// All reserves an obligation touches must be refreshed before /// refresh_obligation; this collects the de-duplicated refresh instructions. fn refresh_all_ix(&self, reserves: &[&ReserveHandle]) -> Vec { - let mut seen: Vec = Vec::new(); + let mut seen: Vec
= Vec::new(); let mut instructions = Vec::new(); for handle in reserves { if !seen.contains(&handle.reserve) { @@ -481,7 +511,7 @@ impl Env { pub fn try_borrow( &mut self, user: &Keypair, - obligation: Pubkey, + obligation: Address, existing_deposits: &[&ReserveHandle], existing_borrows: &[&ReserveHandle], borrow: &ReserveHandle, @@ -492,7 +522,11 @@ impl Env { refresh_set.push(borrow); let mut instructions = self.refresh_all_ix(&refresh_set); - instructions.push(self.refresh_obligation_ix(obligation, existing_deposits, existing_borrows)); + instructions.push(self.refresh_obligation_ix( + obligation, + existing_deposits, + existing_borrows, + )); instructions.push(self.borrow_ix(user, obligation, borrow, amount)); send(&mut self.svm, instructions, &[user], &user.pubkey()) } @@ -500,7 +534,7 @@ impl Env { fn borrow_ix( &self, user: &Keypair, - obligation: Pubkey, + obligation: Address, borrow: &ReserveHandle, amount: u64, ) -> Instruction { @@ -530,7 +564,7 @@ impl Env { pub fn try_borrow_skip_obligation_refresh( &mut self, user: &Keypair, - obligation: Pubkey, + obligation: Address, all_reserves: &[&ReserveHandle], borrow: &ReserveHandle, amount: u64, @@ -543,7 +577,7 @@ impl Env { pub fn repay( &mut self, user: &Keypair, - obligation: Pubkey, + obligation: Address, borrow: &ReserveHandle, amount: u64, ) { @@ -575,7 +609,7 @@ impl Env { pub fn try_withdraw_collateral( &mut self, user: &Keypair, - obligation: Pubkey, + obligation: Address, deposit_reserves: &[&ReserveHandle], borrow_reserves: &[&ReserveHandle], collateral: &ReserveHandle, @@ -587,7 +621,11 @@ impl Env { all.extend_from_slice(borrow_reserves); let mut instructions = self.refresh_all_ix(&all); - instructions.push(self.refresh_obligation_ix(obligation, deposit_reserves, borrow_reserves)); + instructions.push(self.refresh_obligation_ix( + obligation, + deposit_reserves, + borrow_reserves, + )); instructions.push(Instruction { program_id: lending::id(), accounts: lending::accounts::WithdrawObligationCollateral { @@ -610,7 +648,7 @@ impl Env { pub fn try_liquidate( &mut self, liquidator: &Keypair, - obligation: Pubkey, + obligation: Address, deposit_reserves: &[&ReserveHandle], borrow_reserves: &[&ReserveHandle], repay: &ReserveHandle, @@ -635,7 +673,11 @@ impl Env { let mut all: Vec<&ReserveHandle> = deposit_reserves.to_vec(); all.extend_from_slice(borrow_reserves); let mut instructions = self.refresh_all_ix(&all); - instructions.push(self.refresh_obligation_ix(obligation, deposit_reserves, borrow_reserves)); + instructions.push(self.refresh_obligation_ix( + obligation, + deposit_reserves, + borrow_reserves, + )); instructions.push(Instruction { program_id: lending::id(), accounts: lending::accounts::LiquidateObligation { @@ -659,7 +701,12 @@ impl Env { } .data(), }); - send(&mut self.svm, instructions, &[liquidator], &liquidator.pubkey()) + send( + &mut self.svm, + instructions, + &[liquidator], + &liquidator.pubkey(), + ) } /// Send a lone `refresh_reserve` so accrued interest lands in the index. @@ -672,7 +719,7 @@ impl Env { pub fn refresh_obligation_only( &mut self, payer: &Keypair, - obligation: Pubkey, + obligation: Address, deposits: &[&ReserveHandle], borrows: &[&ReserveHandle], ) { @@ -686,7 +733,7 @@ impl Env { /// Market owner collects accrued protocol fees from a reserve to their own /// token account. Bundles `refresh_reserve` so fees are current. Returns the /// owner's fee-receiving token account. - pub fn collect_protocol_fees(&mut self, handle: &ReserveHandle) -> Pubkey { + pub fn collect_protocol_fees(&mut self, handle: &ReserveHandle) -> Address { let owner = self.owner.insecure_clone(); let owner_liquidity = ata(&owner.pubkey(), &handle.mint); if self.svm.get_account(&owner_liquidity).is_none() { @@ -708,7 +755,13 @@ impl Env { .to_account_metas(None), data: lending::instruction::CollectProtocolFees {}.data(), }; - send(&mut self.svm, vec![refresh, collect], &[&owner], &owner.pubkey()).unwrap(); + send( + &mut self.svm, + vec![refresh, collect], + &[&owner], + &owner.pubkey(), + ) + .unwrap(); owner_liquidity } @@ -719,12 +772,12 @@ impl Env { Reserve::try_deserialize(&mut account.data.as_slice()).unwrap() } - pub fn obligation(&self, obligation: Pubkey) -> Obligation { + pub fn obligation(&self, obligation: Address) -> Obligation { let account = self.svm.get_account(&obligation).unwrap(); Obligation::try_deserialize(&mut account.data.as_slice()).unwrap() } - pub fn token_balance(&self, token_account: Pubkey) -> u64 { + pub fn token_balance(&self, token_account: Address) -> u64 { get_token_account_balance(&self.svm, &token_account).unwrap() } } diff --git a/finance/lending/anchor/programs/lending/tests/test_borrow_repay.rs b/finance/lending/anchor/programs/lending/tests/test_borrow_repay.rs index 9d829e4aa..db2410837 100644 --- a/finance/lending/anchor/programs/lending/tests/test_borrow_repay.rs +++ b/finance/lending/anchor/programs/lending/tests/test_borrow_repay.rs @@ -7,7 +7,13 @@ use solana_signer::Signer; /// One market with a collateral reserve and a separately-supplied borrow /// reserve, plus a borrower who has posted 1000 units of collateral (value /// $1000, so 75% LTV => $750 borrow power). Both tokens priced at $1, 6 decimals. -fn setup() -> (Env, ReserveHandle, ReserveHandle, Keypair, anchor_lang::prelude::Pubkey) { +fn setup() -> ( + Env, + ReserveHandle, + ReserveHandle, + Keypair, + anchor_lang::prelude::Pubkey, +) { let mut env = Env::new(); let collateral = env.add_reserve(6, dollars(1), default_config()); let borrow = env.add_reserve(6, dollars(1), default_config()); @@ -32,15 +38,29 @@ fn borrow_up_to_max_ltv_then_one_more_fails() { let (mut env, collateral, borrow, borrower, obligation) = setup(); // $750 of borrow power, borrowing a $1 token => 750 units exactly. - env.try_borrow(&borrower, obligation, &[&collateral], &[], &borrow, 750_000_000) - .unwrap(); + env.try_borrow( + &borrower, + obligation, + &[&collateral], + &[], + &borrow, + 750_000_000, + ) + .unwrap(); assert_eq!( env.token_balance(ata(&borrower.pubkey(), &borrow.mint)), 750_000_000 ); // One more unit exceeds the allowed borrow value. - let result = env.try_borrow(&borrower, obligation, &[&collateral], &[&borrow], &borrow, 1); + let result = env.try_borrow( + &borrower, + obligation, + &[&collateral], + &[&borrow], + &borrow, + 1, + ); assert!( result.unwrap_err().contains("BorrowTooLarge"), "borrowing past the LTV limit must be rejected" @@ -65,7 +85,14 @@ fn borrow_with_stale_price_feed_is_rejected() { let (mut env, collateral, borrow, borrower, obligation) = setup(); // Advance well past the staleness window without re-publishing prices. env.warp_slots(50); - let result = env.try_borrow(&borrower, obligation, &[&collateral], &[], &borrow, 100_000_000); + let result = env.try_borrow( + &borrower, + obligation, + &[&collateral], + &[], + &borrow, + 100_000_000, + ); assert!(result.unwrap_err().contains("StalePriceFeed")); } @@ -83,7 +110,14 @@ fn borrow_with_price_from_before_a_restart_is_rejected() { env.warp_slots(5); env.set_last_restart_slot(restart_slot); - let result = env.try_borrow(&borrower, obligation, &[&collateral], &[], &borrow, 100_000_000); + let result = env.try_borrow( + &borrower, + obligation, + &[&collateral], + &[], + &borrow, + 100_000_000, + ); assert!( result.unwrap_err().contains("PricePredatesRestart"), "a pre-restart price must be rejected even inside the staleness window" @@ -96,15 +130,29 @@ fn borrow_with_price_from_before_a_restart_is_rejected() { env.warp_slots(1); env.set_price(collateral.mint, dollars(1)); env.set_price(borrow.mint, dollars(1)); - env.try_borrow(&borrower, obligation, &[&collateral], &[], &borrow, 100_000_000) - .expect("a freshly published price must be accepted after a restart"); + env.try_borrow( + &borrower, + obligation, + &[&collateral], + &[], + &borrow, + 100_000_000, + ) + .expect("a freshly published price must be accepted after a restart"); } #[test] fn repay_reduces_debt_and_over_repay_clamps() { let (mut env, collateral, borrow, borrower, obligation) = setup(); - env.try_borrow(&borrower, obligation, &[&collateral], &[], &borrow, 500_000_000) - .unwrap(); + env.try_borrow( + &borrower, + obligation, + &[&collateral], + &[], + &borrow, + 500_000_000, + ) + .unwrap(); assert_eq!(env.reserve(&borrow).borrowed_principal > 0, true); env.repay(&borrower, obligation, &borrow, 200_000_000); @@ -120,8 +168,15 @@ fn repay_reduces_debt_and_over_repay_clamps() { #[test] fn withdraw_blocked_while_borrowed_then_allowed_after_repay() { let (mut env, collateral, borrow, borrower, obligation) = setup(); - env.try_borrow(&borrower, obligation, &[&collateral], &[], &borrow, 750_000_000) - .unwrap(); + env.try_borrow( + &borrower, + obligation, + &[&collateral], + &[], + &borrow, + 750_000_000, + ) + .unwrap(); // At the LTV limit, withdrawing any collateral would undercollateralize. let blocked = env.try_withdraw_collateral( diff --git a/finance/lending/anchor/programs/lending/tests/test_deposit_redeem.rs b/finance/lending/anchor/programs/lending/tests/test_deposit_redeem.rs index f52f3cf88..33c04ccef 100644 --- a/finance/lending/anchor/programs/lending/tests/test_deposit_redeem.rs +++ b/finance/lending/anchor/programs/lending/tests/test_deposit_redeem.rs @@ -32,8 +32,14 @@ fn raw_token_donation_does_not_inflate_exchange_rate() { // Attacker donates raw tokens straight into the reserve vault. available_liquidity // is the source of truth, so this must NOT change the share exchange rate. let owner = env.owner.insecure_clone(); - mint_tokens_to_token_account(&mut env.svm, &usdc.mint, &usdc.liquidity_vault, amount, &owner) - .unwrap(); + mint_tokens_to_token_account( + &mut env.svm, + &usdc.mint, + &usdc.liquidity_vault, + amount, + &owner, + ) + .unwrap(); let second = env.create_user(); env.fund(&second, usdc.mint, amount); diff --git a/finance/lending/anchor/programs/lending/tests/test_interest.rs b/finance/lending/anchor/programs/lending/tests/test_interest.rs index 5d8190262..b2f09a757 100644 --- a/finance/lending/anchor/programs/lending/tests/test_interest.rs +++ b/finance/lending/anchor/programs/lending/tests/test_interest.rs @@ -1,6 +1,6 @@ mod common; -use common::{default_config, dollars, ata, Env}; +use common::{ata, default_config, dollars, Env}; use lending::constants::FIXED_POINT_SCALE; use solana_signer::Signer; @@ -26,10 +26,20 @@ fn interest_accrues_on_borrows_over_time() { env.supply(&borrower, &collateral, 1_000_000_000); let obligation = env.initialize_obligation(&borrower); env.post_collateral(&borrower, obligation, &collateral, 1_000_000_000); - env.try_borrow(&borrower, obligation, &[&collateral], &[], &borrow, 500_000_000) - .unwrap(); - - assert_eq!(env.reserve(&borrow).borrow_accumulation_factor, FIXED_POINT_SCALE); + env.try_borrow( + &borrower, + obligation, + &[&collateral], + &[], + &borrow, + 500_000_000, + ) + .unwrap(); + + assert_eq!( + env.reserve(&borrow).borrow_accumulation_factor, + FIXED_POINT_SCALE + ); // Let ~0.1 year pass (2.5 slots/s => ~7.884M slots), re-publish prices, refresh. env.warp_slots(7_884_000); @@ -80,8 +90,15 @@ fn protocol_fees_accrue_and_owner_can_collect() { env.supply(&borrower, &collateral, 1_000_000_000); let obligation = env.initialize_obligation(&borrower); env.post_collateral(&borrower, obligation, &collateral, 1_000_000_000); - env.try_borrow(&borrower, obligation, &[&collateral], &[], &borrow, 500_000_000) - .unwrap(); + env.try_borrow( + &borrower, + obligation, + &[&collateral], + &[], + &borrow, + 500_000_000, + ) + .unwrap(); // No interest has accrued yet, so no fees. assert_eq!(env.reserve(&borrow).accumulated_protocol_fees, 0); @@ -95,7 +112,7 @@ fn protocol_fees_accrue_and_owner_can_collect() { assert!(fees > 0, "protocol fees should accrue once interest does"); let total_interest = reserve.current_borrowed_amount().unwrap() - 500_000_000; let expected_fee = total_interest / 10; // 1000 bps = 10% - // Allow a 1-unit rounding tolerance from flooring. + // Allow a 1-unit rounding tolerance from flooring. assert!( fees.abs_diff(expected_fee) <= 1, "fees {fees} should be ~10% of interest {total_interest}" diff --git a/finance/lending/anchor/programs/lending/tests/test_liquidation.rs b/finance/lending/anchor/programs/lending/tests/test_liquidation.rs index 2024369d0..242561f82 100644 --- a/finance/lending/anchor/programs/lending/tests/test_liquidation.rs +++ b/finance/lending/anchor/programs/lending/tests/test_liquidation.rs @@ -28,8 +28,15 @@ fn setup() -> ( env.supply(&borrower, &collateral, 1_000_000_000); let obligation = env.initialize_obligation(&borrower); env.post_collateral(&borrower, obligation, &collateral, 1_000_000_000); - env.try_borrow(&borrower, obligation, &[&collateral], &[], &borrow, 700_000_000) - .unwrap(); + env.try_borrow( + &borrower, + obligation, + &[&collateral], + &[], + &borrow, + 700_000_000, + ) + .unwrap(); let liquidator = env.create_user(); env.fund(&liquidator, borrow.mint, 1_000_000_000); @@ -96,7 +103,10 @@ fn unhealthy_obligation_liquidated_with_bonus_capped_by_close_factor() { // The borrower's debt and collateral both dropped. let obligation_state = env.obligation(obligation); - assert_eq!(obligation_state.deposits[0].deposited_shares, 1_000_000_000 - 459_375_000); + assert_eq!( + obligation_state.deposits[0].deposited_shares, + 1_000_000_000 - 459_375_000 + ); } /// A repayment whose seizure would exceed the posted collateral is rejected @@ -134,5 +144,8 @@ fn over_seizing_liquidation_rejected_smaller_succeeds() { ) .unwrap(); let liquidator_collateral_account = ata(&liquidator.pubkey(), &collateral.share_mint); - assert_eq!(env.token_balance(liquidator_collateral_account), 525_000_000); + assert_eq!( + env.token_balance(liquidator_collateral_account), + 525_000_000 + ); } diff --git a/finance/lending/anchor/programs/lending/tests/test_rounding.rs b/finance/lending/anchor/programs/lending/tests/test_rounding.rs index 7cebb803c..06fa3179a 100644 --- a/finance/lending/anchor/programs/lending/tests/test_rounding.rs +++ b/finance/lending/anchor/programs/lending/tests/test_rounding.rs @@ -22,13 +22,22 @@ fn deposit_that_would_mint_zero_shares_is_rejected() { env.supply(&borrower, &collateral, 1_000_000_000); let obligation = env.initialize_obligation(&borrower); env.post_collateral(&borrower, obligation, &collateral, 1_000_000_000); - env.try_borrow(&borrower, obligation, &[&collateral], &[], &borrow, 500_000_000) - .unwrap(); + env.try_borrow( + &borrower, + obligation, + &[&collateral], + &[], + &borrow, + 500_000_000, + ) + .unwrap(); // Accrue enough interest that total liquidity exceeds the share supply. env.warp_slots(7_884_000); env.refresh_reserve_only(&borrower, &borrow); - assert!(env.reserve(&borrow).borrow_accumulation_factor > lending::constants::FIXED_POINT_SCALE); + assert!( + env.reserve(&borrow).borrow_accumulation_factor > lending::constants::FIXED_POINT_SCALE + ); let dust_depositor = env.create_user(); env.fund(&dust_depositor, borrow.mint, 1); @@ -74,8 +83,15 @@ fn withdraw_at_health_boundary_then_one_more_unit_fails() { env.post_collateral(&borrower, obligation, &collateral, 1_000_000_000); // Borrow $600 against $1000 collateral (75% LTV => $750 power). - env.try_borrow(&borrower, obligation, &[&collateral], &[], &borrow, 600_000_000) - .unwrap(); + env.try_borrow( + &borrower, + obligation, + &[&collateral], + &[], + &borrow, + 600_000_000, + ) + .unwrap(); // Withdrawing $200 of collateral lands exactly on the limit: new power // $750 - 0.75*$200 = $600 == debt. This must pass. diff --git a/finance/lending/anchor/programs/lending/tests/test_security.rs b/finance/lending/anchor/programs/lending/tests/test_security.rs index d928d7e09..da65879ca 100644 --- a/finance/lending/anchor/programs/lending/tests/test_security.rs +++ b/finance/lending/anchor/programs/lending/tests/test_security.rs @@ -1,8 +1,7 @@ mod common; use anchor_lang::{ - solana_program::{instruction::Instruction, system_program}, - InstructionData, ToAccountMetas, + solana_program::instruction::Instruction, system_program, InstructionData, ToAccountMetas, }; use common::{default_config, dollars, Env}; use solana_signer::Signer; @@ -56,7 +55,7 @@ fn non_owner_cannot_write_market_price_feed() { owner: attacker.pubkey(), price_feed: market_feed, mint: usdc.mint, - system_program: system_program::id(), + system_program: system_program::ID, } .to_account_metas(None), data: lending::instruction::SetPrice { diff --git a/finance/order-book/anchor/programs/order-book/src/instructions/cancel_order.rs b/finance/order-book/anchor/programs/order-book/src/instructions/cancel_order.rs index d68e3fc58..979148b7e 100644 --- a/finance/order-book/anchor/programs/order-book/src/instructions/cancel_order.rs +++ b/finance/order-book/anchor/programs/order-book/src/instructions/cancel_order.rs @@ -2,8 +2,8 @@ use anchor_lang::prelude::*; use crate::errors::ErrorCode; use crate::state::{ - remaining_quantity, remove_open_order, Market, Order, OrderBook, OrderSide, OrderStatus, - MarketUser, ORDER_SEED, MARKET_USER_SEED, + remaining_quantity, remove_open_order, Market, MarketUser, Order, OrderBook, OrderSide, + OrderStatus, MARKET_USER_SEED, ORDER_SEED, }; pub fn handle_cancel_order(context: &mut Context) -> Result<()> { diff --git a/finance/order-book/anchor/programs/order-book/src/instructions/initialize_market_user.rs b/finance/order-book/anchor/programs/order-book/src/instructions/initialize_market_user.rs index ccfc3f76e..1f3972a6d 100644 --- a/finance/order-book/anchor/programs/order-book/src/instructions/initialize_market_user.rs +++ b/finance/order-book/anchor/programs/order-book/src/instructions/initialize_market_user.rs @@ -2,7 +2,9 @@ use anchor_lang::prelude::*; use crate::state::{Market, MarketUser, MARKET_USER_SEED}; -pub fn handle_initialize_market_user(context: &mut Context) -> Result<()> { +pub fn handle_initialize_market_user( + context: &mut Context, +) -> Result<()> { let market_user = &mut context.accounts.market_user; market_user.market = *context.accounts.market.address(); market_user.owner = *context.accounts.owner.address(); diff --git a/finance/order-book/anchor/programs/order-book/src/instructions/mod.rs b/finance/order-book/anchor/programs/order-book/src/instructions/mod.rs index 1ed385342..5fc5f626c 100644 --- a/finance/order-book/anchor/programs/order-book/src/instructions/mod.rs +++ b/finance/order-book/anchor/programs/order-book/src/instructions/mod.rs @@ -1,13 +1,13 @@ pub mod admin; pub mod cancel_order; -pub mod initialize_market_user; pub mod initialize_market; +pub mod initialize_market_user; pub mod place_order; pub mod settle_funds; pub use admin::*; pub use cancel_order::*; -pub use initialize_market_user::*; pub use initialize_market::*; +pub use initialize_market_user::*; pub use place_order::*; pub use settle_funds::*; diff --git a/finance/order-book/anchor/programs/order-book/src/instructions/place_order.rs b/finance/order-book/anchor/programs/order-book/src/instructions/place_order.rs index ec5bb2f36..f117e543f 100644 --- a/finance/order-book/anchor/programs/order-book/src/instructions/place_order.rs +++ b/finance/order-book/anchor/programs/order-book/src/instructions/place_order.rs @@ -5,8 +5,8 @@ use anchor_spl::token_interface::{ use crate::errors::ErrorCode; use crate::state::{ - add_open_order, plan_fills, remove_open_order, Market, Order, OrderBook, OrderSide, - OrderStatus, MarketUser, MARKET_SEED, ORDER_SEED, MARKET_USER_SEED, + add_open_order, plan_fills, remove_open_order, Market, MarketUser, Order, OrderBook, OrderSide, + OrderStatus, MARKET_SEED, MARKET_USER_SEED, ORDER_SEED, }; // Mirror of MarketUser.open_orders max_len. Kept as a constant so the @@ -290,8 +290,7 @@ pub fn handle_place_order<'info>( .checked_add(fill.fill_quantity) .ok_or(ErrorCode::NumericalOverflow)?; - let maker_fully_filled = - maker_order.filled_quantity >= maker_order.original_quantity; + let maker_fully_filled = maker_order.filled_quantity >= maker_order.original_quantity; maker_order.status = if maker_fully_filled { OrderStatus::Filled } else { @@ -380,10 +379,7 @@ pub fn handle_place_order<'info>( let mut order_book = order_book_loader.load_mut()?; let id = order_book.allocate_order_id()?; if taker_remaining > 0 { - require!( - !order_book.is_side_full(side), - ErrorCode::OrderBookFull - ); + require!(!order_book.is_side_full(side), ErrorCode::OrderBookFull); order_book.place_resting( side, price, diff --git a/finance/order-book/anchor/programs/order-book/src/lib.rs b/finance/order-book/anchor/programs/order-book/src/lib.rs index 1a03554a6..d8a621a79 100644 --- a/finance/order-book/anchor/programs/order-book/src/lib.rs +++ b/finance/order-book/anchor/programs/order-book/src/lib.rs @@ -35,7 +35,9 @@ pub mod order_book { /// Create a per-user, per-market account that tracks a user's open orders /// and unsettled balances. - pub fn initialize_market_user(context: &mut Context) -> Result<()> { + pub fn initialize_market_user( + context: &mut Context, + ) -> Result<()> { instructions::initialize_market_user::handle_initialize_market_user(context) } diff --git a/finance/order-book/anchor/programs/order-book/src/state/mod.rs b/finance/order-book/anchor/programs/order-book/src/state/mod.rs index b1c058ee8..3dd3bd3dd 100644 --- a/finance/order-book/anchor/programs/order-book/src/state/mod.rs +++ b/finance/order-book/anchor/programs/order-book/src/state/mod.rs @@ -1,12 +1,12 @@ pub mod market; +pub mod market_user; pub mod matching; pub mod order; pub mod order_book; pub mod slab; -pub mod market_user; pub use market::*; +pub use market_user::*; pub use matching::*; pub use order::*; pub use order_book::*; -pub use market_user::*; diff --git a/finance/order-book/anchor/programs/order-book/src/state/order.rs b/finance/order-book/anchor/programs/order-book/src/state/order.rs index 05fe139c0..33d6b147e 100644 --- a/finance/order-book/anchor/programs/order-book/src/state/order.rs +++ b/finance/order-book/anchor/programs/order-book/src/state/order.rs @@ -2,13 +2,17 @@ use anchor_lang::prelude::*; pub const ORDER_SEED: &[u8] = b"order"; -#[derive(Clone, Copy, PartialEq, Eq, InitSpace, IdlType, wincode::SchemaRead, wincode::SchemaWrite)] +#[derive( + Clone, Copy, PartialEq, Eq, InitSpace, IdlType, wincode::SchemaRead, wincode::SchemaWrite, +)] pub enum OrderSide { Bid, Ask, } -#[derive(Clone, Copy, PartialEq, Eq, InitSpace, IdlType, wincode::SchemaRead, wincode::SchemaWrite)] +#[derive( + Clone, Copy, PartialEq, Eq, InitSpace, IdlType, wincode::SchemaRead, wincode::SchemaWrite, +)] pub enum OrderStatus { Open, PartiallyFilled, @@ -41,5 +45,7 @@ pub struct Order { } pub fn remaining_quantity(order: &Order) -> u64 { - order.original_quantity.saturating_sub(order.filled_quantity) + order + .original_quantity + .saturating_sub(order.filled_quantity) } diff --git a/finance/order-book/anchor/programs/order-book/src/state/order_book.rs b/finance/order-book/anchor/programs/order-book/src/state/order_book.rs index 2000a2c91..bd91a5428 100644 --- a/finance/order-book/anchor/programs/order-book/src/state/order_book.rs +++ b/finance/order-book/anchor/programs/order-book/src/state/order_book.rs @@ -59,7 +59,10 @@ pub const ORDER_BOOK_ACCOUNT_SIZE: usize = 8 + std::mem::size_of::(); // + 8 (next_order_id) + 1 (bump) + 7 (pad) + 2 * (12 + 88*1024) // = 64 + 2 * 90124 = 180312 bytes for the struct itself. const _: () = { - assert!(std::mem::size_of::() == 32 + 8 + 8 + 8 + 1 + 7 + 2 * (1 + 3 + 4 + 4 + 4 + NODE_SIZE * MAX_TREE_NODES)); + assert!( + std::mem::size_of::() + == 32 + 8 + 8 + 8 + 1 + 7 + 2 * (1 + 3 + 4 + 4 + 4 + NODE_SIZE * MAX_TREE_NODES) + ); }; /// A compact view of one resting order for the matching engine. Returned diff --git a/finance/order-book/anchor/programs/order-book/src/state/slab/nodes.rs b/finance/order-book/anchor/programs/order-book/src/state/slab/nodes.rs index d9838c01b..6ac3290c6 100644 --- a/finance/order-book/anchor/programs/order-book/src/state/slab/nodes.rs +++ b/finance/order-book/anchor/programs/order-book/src/state/slab/nodes.rs @@ -64,7 +64,11 @@ impl NodeTag { /// largest key first; inverting seq_num makes the earlier order's seq_num /// the larger one at any given price.) pub fn new_node_key(side: OrderSide, price_data: u64, seq_num: u64) -> u128 { - let seq_num = if side == OrderSide::Bid { !seq_num } else { seq_num }; + let seq_num = if side == OrderSide::Bid { + !seq_num + } else { + seq_num + }; ((price_data as u128) << 64) | (seq_num as u128) } @@ -186,13 +190,7 @@ const_assert_eq!(size_of::(), NODE_SIZE); const_assert_eq!(size_of::() % 8, 0); impl LeafNode { - pub fn new( - key: u128, - owner: Address, - quantity: u64, - order_id: u64, - timestamp: i64, - ) -> Self { + pub fn new(key: u128, owner: Address, quantity: u64, order_id: u64, timestamp: i64) -> Self { Self { tag: NodeTag::LeafNode as u8, padding: [0; 7], diff --git a/finance/order-book/anchor/programs/order-book/src/state/slab/ordertree.rs b/finance/order-book/anchor/programs/order-book/src/state/slab/ordertree.rs index 21641a401..20b8e1be1 100644 --- a/finance/order-book/anchor/programs/order-book/src/state/slab/ordertree.rs +++ b/finance/order-book/anchor/programs/order-book/src/state/slab/ordertree.rs @@ -135,7 +135,11 @@ impl OrderTreeNodes { } /// Look up a leaf by its full 128-bit key. - pub fn find_by_key(&self, root: &OrderTreeRoot, search_key: u128) -> Option<(NodeHandle, &LeafNode)> { + pub fn find_by_key( + &self, + root: &OrderTreeRoot, + search_key: u128, + ) -> Option<(NodeHandle, &LeafNode)> { let mut handle = root.node()?; loop { let node = self.node(handle)?; diff --git a/finance/order-book/anchor/programs/order-book/tests/test_order_book.rs b/finance/order-book/anchor/programs/order-book/tests/test_order_book.rs index 0d25dbb54..165cbfe02 100644 --- a/finance/order-book/anchor/programs/order-book/tests/test_order_book.rs +++ b/finance/order-book/anchor/programs/order-book/tests/test_order_book.rs @@ -13,7 +13,7 @@ use { anchor_lang::{ solana_program::{ instruction::{AccountMeta, Instruction}, - pubkey::Pubkey, + pubkey::Address, system_instruction, system_program, }, Discriminator, InstructionData, ToAccountMetas, @@ -77,7 +77,7 @@ const BID_QUANTITY: u64 = 10; const ASK_PRICE: u64 = 100; const ASK_QUANTITY: u64 = 5; -fn token_program_id() -> Pubkey { +fn token_program_id() -> Address { // The program accepts either the Classic Token Program or the Token // Extensions Program via `TokenInterface`; we use the Classic Token // Program for tests because solana-kite's helpers create classic mints. @@ -86,24 +86,24 @@ fn token_program_id() -> Pubkey { .unwrap() } -fn market_pda(program_id: &Pubkey, base_mint: &Pubkey, quote_mint: &Pubkey) -> Pubkey { - let (market, _) = Pubkey::find_program_address( +fn market_pda(program_id: &Address, base_mint: &Address, quote_mint: &Address) -> Address { + let (market, _) = Address::find_program_address( &[MARKET_SEED, base_mint.as_ref(), quote_mint.as_ref()], program_id, ); market } -fn market_user_pda(program_id: &Pubkey, market: &Pubkey, owner: &Pubkey) -> Pubkey { - let (market_user, _) = Pubkey::find_program_address( +fn market_user_pda(program_id: &Address, market: &Address, owner: &Address) -> Address { + let (market_user, _) = Address::find_program_address( &[MARKET_USER_SEED, market.as_ref(), owner.as_ref()], program_id, ); market_user } -fn order_pda(program_id: &Pubkey, market: &Pubkey, order_id: u64) -> Pubkey { - let (order, _) = Pubkey::find_program_address( +fn order_pda(program_id: &Address, market: &Address, order_id: u64) -> Address { + let (order, _) = Address::find_program_address( &[ORDER_SEED, market.as_ref(), &order_id.to_le_bytes()], program_id, ); @@ -116,7 +116,7 @@ fn order_pda(program_id: &Pubkey, market: &Pubkey, order_id: u64) -> Pubkey { struct Scenario { svm: LiteSVM, - program_id: Pubkey, + program_id: Address, // `payer` funds the mint authority + ATA creations during setup but is // not used directly by the tests afterwards. #[allow(dead_code)] @@ -124,26 +124,26 @@ struct Scenario { authority: Keypair, buyer: Keypair, seller: Keypair, - base_mint: Pubkey, - quote_mint: Pubkey, + base_mint: Address, + quote_mint: Address, base_vault: Keypair, quote_vault: Keypair, // Fees accumulate here (quote mint). Created fresh per Scenario; the // market PDA is the signer, same as the other two vaults. fee_vault: Keypair, - market: Pubkey, + market: Address, // The order book is a ~180 KB zero-copy account owned by the program. // It's NOT a PDA - the BPF runtime caps inner-CPI allocations at 10 KB, // so the client must allocate it directly via system_program::CreateAccount // and pass it in as a signer. See `build_initialize_market_tx` for the // full setup. order_book: Keypair, - buyer_base_ata: Pubkey, - buyer_quote_ata: Pubkey, - seller_base_ata: Pubkey, - seller_quote_ata: Pubkey, - buyer_market_user: Pubkey, - seller_market_user: Pubkey, + buyer_base_ata: Address, + buyer_quote_ata: Address, + seller_base_ata: Address, + seller_quote_ata: Address, + buyer_market_user: Address, + seller_market_user: Address, } fn full_setup() -> Scenario { @@ -236,10 +236,7 @@ fn full_setup() -> Scenario { /// /// Rent is whatever LiteSVM's bank quotes for that size at the current /// rent rate; we use `minimum_balance` so the account is rent-exempt. -fn build_create_order_book_account_ix( - sc: &Scenario, - payer: &Pubkey, -) -> Instruction { +fn build_create_order_book_account_ix(sc: &Scenario, payer: &Address) -> Instruction { // LiteSVM uses the default rent schedule; minimum_balance() on the // 180 KB account is around 1.25 SOL - well within the 100 SOL we fund // the test payer with in `full_setup`. @@ -283,13 +280,13 @@ fn build_initialize_market_ix( fee_vault: sc.fee_vault.pubkey(), authority: sc.authority.pubkey(), token_program: token_program_id(), - system_program: system_program::id(), + system_program: system_program::ID, } .to_account_metas(None), ) } -fn build_initialize_market_user_ix(sc: &Scenario, owner: &Pubkey) -> Instruction { +fn build_initialize_market_user_ix(sc: &Scenario, owner: &Address) -> Instruction { let market_user = market_user_pda(&sc.program_id, &sc.market, owner); Instruction::new_with_bytes( sc.program_id, @@ -298,7 +295,7 @@ fn build_initialize_market_user_ix(sc: &Scenario, owner: &Pubkey) -> Instruction market_user, market: sc.market, owner: *owner, - system_program: system_program::id(), + system_program: system_program::ID, } .to_account_metas(None), ) @@ -308,9 +305,9 @@ fn build_initialize_market_user_ix(sc: &Scenario, owner: &Pubkey) -> Instruction fn build_place_order_ix( sc: &Scenario, owner: &Keypair, - market_user: Pubkey, - user_base_account: Pubkey, - user_quote_account: Pubkey, + market_user: Address, + user_base_account: Address, + user_quote_account: Address, side: order_book::state::OrderSide, order_id: u64, price: u64, @@ -339,7 +336,7 @@ fn build_place_order_ix( quote_mint: sc.quote_mint, owner: owner.pubkey(), token_program: token_program_id(), - system_program: system_program::id(), + system_program: system_program::ID, } .to_account_metas(None), ) @@ -356,14 +353,14 @@ fn build_place_order_ix( fn build_place_order_with_makers_ix( sc: &Scenario, owner: &Keypair, - market_user: Pubkey, - user_base_account: Pubkey, - user_quote_account: Pubkey, + market_user: Address, + user_base_account: Address, + user_quote_account: Address, side: order_book::state::OrderSide, order_id: u64, price: u64, quantity: u64, - maker_pairs: &[(u64, Pubkey)], + maker_pairs: &[(u64, Address)], ) -> Instruction { let mut ix = build_place_order_ix( sc, @@ -379,8 +376,7 @@ fn build_place_order_with_makers_ix( for (maker_order_id, maker_market_user) in maker_pairs { let maker_order = order_pda(&sc.program_id, &sc.market, *maker_order_id); - ix.accounts - .push(AccountMeta::new(maker_order, false)); + ix.accounts.push(AccountMeta::new(maker_order, false)); ix.accounts .push(AccountMeta::new(*maker_market_user, false)); } @@ -388,10 +384,7 @@ fn build_place_order_with_makers_ix( ix } -fn build_withdraw_fees_ix( - sc: &Scenario, - authority_quote_account: Pubkey, -) -> Instruction { +fn build_withdraw_fees_ix(sc: &Scenario, authority_quote_account: Address) -> Instruction { Instruction::new_with_bytes( sc.program_id, &order_book::instruction::WithdrawFees {}.data(), @@ -409,8 +402,8 @@ fn build_withdraw_fees_ix( fn build_cancel_order_ix( sc: &Scenario, - owner: &Pubkey, - market_user: Pubkey, + owner: &Address, + market_user: Address, order_id: u64, ) -> Instruction { let order = order_pda(&sc.program_id, &sc.market, order_id); @@ -430,10 +423,10 @@ fn build_cancel_order_ix( fn build_settle_funds_ix( sc: &Scenario, - owner: &Pubkey, - market_user: Pubkey, - user_base_account: Pubkey, - user_quote_account: Pubkey, + owner: &Address, + market_user: Address, + user_base_account: Address, + user_quote_account: Address, ) -> Instruction { Instruction::new_with_bytes( sc.program_id, @@ -462,7 +455,14 @@ fn initialize_market_and_users(sc: &mut Scenario) { // program, zero-initialized) before initialize_market's `#[account(zero)]` // check passes. let create_ix = build_create_order_book_account_ix(sc, &sc.authority.pubkey()); - let init_ix = build_initialize_market_ix(sc, FEE_BASIS_POINTS, TICK_SIZE, BASE_LOT_SIZE, QUOTE_LOT_SIZE, MIN_ORDER_SIZE); + let init_ix = build_initialize_market_ix( + sc, + FEE_BASIS_POINTS, + TICK_SIZE, + BASE_LOT_SIZE, + QUOTE_LOT_SIZE, + MIN_ORDER_SIZE, + ); send_transaction_from_instructions( &mut sc.svm, vec![create_ix, init_ix], @@ -505,7 +505,14 @@ fn initialize_market_sets_market_and_order_book() { let mut sc = full_setup(); let create_ix = build_create_order_book_account_ix(&sc, &sc.authority.pubkey()); - let ix = build_initialize_market_ix(&sc, FEE_BASIS_POINTS, TICK_SIZE, BASE_LOT_SIZE, QUOTE_LOT_SIZE, MIN_ORDER_SIZE); + let ix = build_initialize_market_ix( + &sc, + FEE_BASIS_POINTS, + TICK_SIZE, + BASE_LOT_SIZE, + QUOTE_LOT_SIZE, + MIN_ORDER_SIZE, + ); send_transaction_from_instructions( &mut sc.svm, vec![create_ix, ix], @@ -553,7 +560,14 @@ fn initialize_market_user_tracks_market_and_owner() { let mut sc = full_setup(); let create_ix = build_create_order_book_account_ix(&sc, &sc.authority.pubkey()); - let init_ix = build_initialize_market_ix(&sc, FEE_BASIS_POINTS, TICK_SIZE, BASE_LOT_SIZE, QUOTE_LOT_SIZE, MIN_ORDER_SIZE); + let init_ix = build_initialize_market_ix( + &sc, + FEE_BASIS_POINTS, + TICK_SIZE, + BASE_LOT_SIZE, + QUOTE_LOT_SIZE, + MIN_ORDER_SIZE, + ); send_transaction_from_instructions( &mut sc.svm, vec![create_ix, init_ix], @@ -683,12 +697,8 @@ fn place_order_rejects_zero_price() { 0, BID_QUANTITY, ); - let result = send_transaction_from_instructions( - &mut sc.svm, - vec![ix], - &[&sc.buyer], - &sc.buyer.pubkey(), - ); + let result = + send_transaction_from_instructions(&mut sc.svm, vec![ix], &[&sc.buyer], &sc.buyer.pubkey()); assert!(result.is_err(), "order at price 0 must be rejected"); } @@ -700,8 +710,14 @@ fn place_order_rejects_unaligned_tick() { // price and see the tick check fire. let unusual_tick_size: u64 = 50; let create_ix = build_create_order_book_account_ix(&sc, &sc.authority.pubkey()); - let init_ix = - build_initialize_market_ix(&sc, FEE_BASIS_POINTS, unusual_tick_size, BASE_LOT_SIZE, QUOTE_LOT_SIZE, MIN_ORDER_SIZE); + let init_ix = build_initialize_market_ix( + &sc, + FEE_BASIS_POINTS, + unusual_tick_size, + BASE_LOT_SIZE, + QUOTE_LOT_SIZE, + MIN_ORDER_SIZE, + ); send_transaction_from_instructions( &mut sc.svm, vec![create_ix, init_ix], @@ -738,12 +754,8 @@ fn place_order_rejects_unaligned_tick() { unaligned_price, BID_QUANTITY, ); - let result = send_transaction_from_instructions( - &mut sc.svm, - vec![ix], - &[&sc.buyer], - &sc.buyer.pubkey(), - ); + let result = + send_transaction_from_instructions(&mut sc.svm, vec![ix], &[&sc.buyer], &sc.buyer.pubkey()); assert!( result.is_err(), "unaligned price must be rejected by tick check" @@ -757,8 +769,14 @@ fn place_order_rejects_below_min_order_size() { // Force a higher min_order_size so we can place an order below it. let elevated_min_order_size: u64 = 10; let create_ix = build_create_order_book_account_ix(&sc, &sc.authority.pubkey()); - let init_ix = - build_initialize_market_ix(&sc, FEE_BASIS_POINTS, TICK_SIZE, BASE_LOT_SIZE, QUOTE_LOT_SIZE, elevated_min_order_size); + let init_ix = build_initialize_market_ix( + &sc, + FEE_BASIS_POINTS, + TICK_SIZE, + BASE_LOT_SIZE, + QUOTE_LOT_SIZE, + elevated_min_order_size, + ); send_transaction_from_instructions( &mut sc.svm, vec![create_ix, init_ix], @@ -980,12 +998,8 @@ fn cancel_and_settle_bid_refunds_full_quote() { BID_PRICE, BID_QUANTITY, ); - let cancel_ix = build_cancel_order_ix( - &sc, - &sc.buyer.pubkey(), - sc.buyer_market_user, - bid_order_id, - ); + let cancel_ix = + build_cancel_order_ix(&sc, &sc.buyer.pubkey(), sc.buyer_market_user, bid_order_id); let settle_ix = build_settle_funds_ix( &sc, &sc.buyer.pubkey(), @@ -1040,12 +1054,8 @@ fn settle_funds_rejects_fee_vault_substituted_for_quote_vault() { BID_PRICE, BID_QUANTITY, ); - let cancel_ix = build_cancel_order_ix( - &sc, - &sc.buyer.pubkey(), - sc.buyer_market_user, - bid_order_id, - ); + let cancel_ix = + build_cancel_order_ix(&sc, &sc.buyer.pubkey(), sc.buyer_market_user, bid_order_id); send_transaction_from_instructions( &mut sc.svm, vec![place_ix, cancel_ix], @@ -1095,7 +1105,14 @@ fn initialize_market_rejects_zero_tick_size() { let zero_tick_size: u64 = 0; let create_ix = build_create_order_book_account_ix(&sc, &sc.authority.pubkey()); - let ix = build_initialize_market_ix(&sc, FEE_BASIS_POINTS, zero_tick_size, BASE_LOT_SIZE, QUOTE_LOT_SIZE, MIN_ORDER_SIZE); + let ix = build_initialize_market_ix( + &sc, + FEE_BASIS_POINTS, + zero_tick_size, + BASE_LOT_SIZE, + QUOTE_LOT_SIZE, + MIN_ORDER_SIZE, + ); let result = send_transaction_from_instructions( &mut sc.svm, vec![create_ix, ix], @@ -1116,7 +1133,14 @@ fn initialize_market_rejects_zero_base_lot_size() { let mut sc = full_setup(); let create_ix = build_create_order_book_account_ix(&sc, &sc.authority.pubkey()); - let ix = build_initialize_market_ix(&sc, FEE_BASIS_POINTS, TICK_SIZE, 0, QUOTE_LOT_SIZE, MIN_ORDER_SIZE); + let ix = build_initialize_market_ix( + &sc, + FEE_BASIS_POINTS, + TICK_SIZE, + 0, + QUOTE_LOT_SIZE, + MIN_ORDER_SIZE, + ); let result = send_transaction_from_instructions( &mut sc.svm, vec![create_ix, ix], @@ -1137,7 +1161,14 @@ fn initialize_market_rejects_zero_quote_lot_size() { let mut sc = full_setup(); let create_ix = build_create_order_book_account_ix(&sc, &sc.authority.pubkey()); - let ix = build_initialize_market_ix(&sc, FEE_BASIS_POINTS, TICK_SIZE, BASE_LOT_SIZE, 0, MIN_ORDER_SIZE); + let ix = build_initialize_market_ix( + &sc, + FEE_BASIS_POINTS, + TICK_SIZE, + BASE_LOT_SIZE, + 0, + MIN_ORDER_SIZE, + ); let result = send_transaction_from_instructions( &mut sc.svm, vec![create_ix, ix], @@ -1196,8 +1227,8 @@ fn initialize_market_rejects_oversized_fee() { // MarketUser field offsets after the 8-byte Anchor discriminator. Layout // (see programs/order_book/src/state/market_user.rs): -// market: Pubkey (32) -// owner: Pubkey (32) +// market: Address (32) +// owner: Address (32) // unsettled_base: u64 (8) // unsettled_quote: u64 (8) // ... @@ -1207,8 +1238,8 @@ const USER_ACCOUNT_UNSETTLED_BASE_OFFSET: usize = 8 + 32 + 32; const USER_ACCOUNT_UNSETTLED_QUOTE_OFFSET: usize = USER_ACCOUNT_UNSETTLED_BASE_OFFSET + 8; // Order layout after 8-byte discriminator (see state/order.rs): -// market: Pubkey (32) -// owner: Pubkey (32) +// market: Address (32) +// owner: Address (32) // order_id: u64 (8) // side: u8 (Borsh-encoded enum tag) (1) // price: u64 (8) @@ -1220,7 +1251,7 @@ const ORDER_STATUS_OPEN: u8 = 0; const ORDER_STATUS_PARTIALLY_FILLED: u8 = 1; const ORDER_STATUS_FILLED: u8 = 2; -fn read_user_unsettled(svm: &LiteSVM, market_user: &Pubkey) -> (u64, u64) { +fn read_user_unsettled(svm: &LiteSVM, market_user: &Address) -> (u64, u64) { let data = svm .get_account(market_user) .expect("user account missing") @@ -1239,7 +1270,7 @@ fn read_user_unsettled(svm: &LiteSVM, market_user: &Pubkey) -> (u64, u64) { (base, quote) } -fn read_order_fill_and_status(svm: &LiteSVM, order: &Pubkey) -> (u64, u8) { +fn read_order_fill_and_status(svm: &LiteSVM, order: &Address) -> (u64, u8) { let data = svm .get_account(order) .expect("order account missing") @@ -1448,13 +1479,8 @@ fn taker_partially_fills_resting_order_rest_stays_on_book() { TAKER_BID_QUANTITY, &[(MAKER_ASK_ID, sc.seller_market_user)], ); - send_transaction_from_instructions( - &mut sc.svm, - vec![bid_ix], - &[&sc.buyer], - &sc.buyer.pubkey(), - ) - .unwrap(); + send_transaction_from_instructions(&mut sc.svm, vec![bid_ix], &[&sc.buyer], &sc.buyer.pubkey()) + .unwrap(); // Maker order: still PartiallyFilled, filled_quantity == TAKER_BID_QUANTITY. let maker_order = order_pda(&sc.program_id, &sc.market, MAKER_ASK_ID); @@ -1523,13 +1549,8 @@ fn taker_partially_filled_remainder_rests_on_book() { TAKER_BID_QUANTITY, &[(MAKER_ASK_ID, sc.seller_market_user)], ); - send_transaction_from_instructions( - &mut sc.svm, - vec![bid_ix], - &[&sc.buyer], - &sc.buyer.pubkey(), - ) - .unwrap(); + send_transaction_from_instructions(&mut sc.svm, vec![bid_ix], &[&sc.buyer], &sc.buyer.pubkey()) + .unwrap(); // Maker ask is fully filled. let maker_order = order_pda(&sc.program_id, &sc.market, MAKER_ASK_ID); @@ -1640,8 +1661,14 @@ fn taker_crosses_multiple_resting_orders_best_price_first() { // Both resting asks are fully filled. let order_one = order_pda(&sc.program_id, &sc.market, BEST_ASK_ID); let order_two = order_pda(&sc.program_id, &sc.market, SECOND_ASK_ID); - assert_eq!(read_order_fill_and_status(&sc.svm, &order_one).1, ORDER_STATUS_FILLED); - assert_eq!(read_order_fill_and_status(&sc.svm, &order_two).1, ORDER_STATUS_FILLED); + assert_eq!( + read_order_fill_and_status(&sc.svm, &order_one).1, + ORDER_STATUS_FILLED + ); + assert_eq!( + read_order_fill_and_status(&sc.svm, &order_two).1, + ORDER_STATUS_FILLED + ); // Taker got TAKER_BID_QUANTITY lots = TAKER_BID_QUANTITY * BASE_LOT_SIZE raw base tokens. let (buyer_base, buyer_quote_rebate) = read_user_unsettled(&sc.svm, &sc.buyer_market_user); @@ -1649,7 +1676,8 @@ fn taker_crosses_multiple_resting_orders_best_price_first() { // Price-improvement rebate: taker locked at 1000/unit but 30 units // filled at 900. Rebate = (1000 - 900) * 30 * quote_lot_size. - const PRICE_IMPROVEMENT_REBATE: u64 = (TAKER_BID_PRICE - BEST_ASK_PRICE) * BEST_ASK_QUANTITY * QUOTE_LOT_SIZE; + const PRICE_IMPROVEMENT_REBATE: u64 = + (TAKER_BID_PRICE - BEST_ASK_PRICE) * BEST_ASK_QUANTITY * QUOTE_LOT_SIZE; assert_eq!(buyer_quote_rebate, PRICE_IMPROVEMENT_REBATE); // Seller's net unsettled_quote = sum of (fill_price * fill_qty * quote_lot_size - fee). @@ -1694,10 +1722,16 @@ fn resting_orders_at_same_price_fill_by_time_priority() { &sc.authority, ) .unwrap(); - let second_seller_market_user = market_user_pda(&sc.program_id, &sc.market, &second_seller.pubkey()); + let second_seller_market_user = + market_user_pda(&sc.program_id, &sc.market, &second_seller.pubkey()); let __ix1 = build_initialize_market_user_ix(&sc, &second_seller.pubkey()); - send_transaction_from_instructions(&mut sc.svm, vec![__ix1], &[&second_seller], - &second_seller.pubkey()).unwrap(); + send_transaction_from_instructions( + &mut sc.svm, + vec![__ix1], + &[&second_seller], + &second_seller.pubkey(), + ) + .unwrap(); const FIRST_ASK_ID: u64 = 1; const SECOND_ASK_ID: u64 = 2; @@ -1706,32 +1740,42 @@ fn resting_orders_at_same_price_fill_by_time_priority() { // Seller 1 first in. let __ix2 = build_place_order_ix( - &sc, - &sc.seller, - sc.seller_market_user, - sc.seller_base_ata, - sc.seller_quote_ata, - order_book::state::OrderSide::Ask, - FIRST_ASK_ID, - ASK_PRICE_SHARED, - ASK_QUANTITY_EACH, - ); - send_transaction_from_instructions(&mut sc.svm, vec![__ix2], &[&sc.seller], - &sc.seller.pubkey()).unwrap(); + &sc, + &sc.seller, + sc.seller_market_user, + sc.seller_base_ata, + sc.seller_quote_ata, + order_book::state::OrderSide::Ask, + FIRST_ASK_ID, + ASK_PRICE_SHARED, + ASK_QUANTITY_EACH, + ); + send_transaction_from_instructions( + &mut sc.svm, + vec![__ix2], + &[&sc.seller], + &sc.seller.pubkey(), + ) + .unwrap(); // Seller 2 second in at the same price. let __ix3 = build_place_order_ix( - &sc, - &second_seller, - second_seller_market_user, - second_seller_base_ata, - second_seller_quote_ata, - order_book::state::OrderSide::Ask, - SECOND_ASK_ID, - ASK_PRICE_SHARED, - ASK_QUANTITY_EACH, - ); - send_transaction_from_instructions(&mut sc.svm, vec![__ix3], &[&second_seller], - &second_seller.pubkey()).unwrap(); + &sc, + &second_seller, + second_seller_market_user, + second_seller_base_ata, + second_seller_quote_ata, + order_book::state::OrderSide::Ask, + SECOND_ASK_ID, + ASK_PRICE_SHARED, + ASK_QUANTITY_EACH, + ); + send_transaction_from_instructions( + &mut sc.svm, + vec![__ix3], + &[&second_seller], + &second_seller.pubkey(), + ) + .unwrap(); // Taker bid buys only enough to cross seller 1's ask. const TAKER_BID_ID: u64 = 3; @@ -1758,8 +1802,14 @@ fn resting_orders_at_same_price_fill_by_time_priority() { // Time priority: seller 1 filled, seller 2 still open. let order_one = order_pda(&sc.program_id, &sc.market, FIRST_ASK_ID); let order_two = order_pda(&sc.program_id, &sc.market, SECOND_ASK_ID); - assert_eq!(read_order_fill_and_status(&sc.svm, &order_one).1, ORDER_STATUS_FILLED); - assert_eq!(read_order_fill_and_status(&sc.svm, &order_two).1, ORDER_STATUS_OPEN); + assert_eq!( + read_order_fill_and_status(&sc.svm, &order_one).1, + ORDER_STATUS_FILLED + ); + assert_eq!( + read_order_fill_and_status(&sc.svm, &order_two).1, + ORDER_STATUS_OPEN + ); } #[test] @@ -1776,18 +1826,23 @@ fn taker_bid_gets_price_improvement_from_resting_ask() { // Maker ask. let __ix4 = build_place_order_ix( - &sc, - &sc.seller, - sc.seller_market_user, - sc.seller_base_ata, - sc.seller_quote_ata, - order_book::state::OrderSide::Ask, - MAKER_ASK_ID, - MAKER_ASK_PRICE, - QUANTITY, - ); - send_transaction_from_instructions(&mut sc.svm, vec![__ix4], &[&sc.seller], - &sc.seller.pubkey()).unwrap(); + &sc, + &sc.seller, + sc.seller_market_user, + sc.seller_base_ata, + sc.seller_quote_ata, + order_book::state::OrderSide::Ask, + MAKER_ASK_ID, + MAKER_ASK_PRICE, + QUANTITY, + ); + send_transaction_from_instructions( + &mut sc.svm, + vec![__ix4], + &[&sc.seller], + &sc.seller.pubkey(), + ) + .unwrap(); // Taker bid - limit 1000. const TAKER_BID_ID: u64 = 2; @@ -1855,8 +1910,13 @@ fn fee_rounds_up_when_gross_is_not_a_bps_multiple() { PRICE, QUANTITY, ); - send_transaction_from_instructions(&mut sc.svm, vec![maker_ix], &[&sc.seller], - &sc.seller.pubkey()).unwrap(); + send_transaction_from_instructions( + &mut sc.svm, + vec![maker_ix], + &[&sc.seller], + &sc.seller.pubkey(), + ) + .unwrap(); const TAKER_BID_ID: u64 = 2; let taker_ix = build_place_order_with_makers_ix( @@ -1871,8 +1931,13 @@ fn fee_rounds_up_when_gross_is_not_a_bps_multiple() { QUANTITY, &[(MAKER_ASK_ID, sc.seller_market_user)], ); - send_transaction_from_instructions(&mut sc.svm, vec![taker_ix], &[&sc.buyer], - &sc.buyer.pubkey()).unwrap(); + send_transaction_from_instructions( + &mut sc.svm, + vec![taker_ix], + &[&sc.buyer], + &sc.buyer.pubkey(), + ) + .unwrap(); assert_eq!( get_token_account_balance(&sc.svm, &sc.fee_vault.pubkey()).unwrap(), @@ -1897,34 +1962,39 @@ fn fee_vault_receives_exactly_bps_of_taker_gross() { const EXPECTED_FEE: u64 = fee_ceil(GROSS); let __ix5 = build_place_order_ix( - &sc, - &sc.seller, - sc.seller_market_user, - sc.seller_base_ata, - sc.seller_quote_ata, - order_book::state::OrderSide::Ask, - MAKER_ASK_ID, - PRICE, - QUANTITY, - ); - send_transaction_from_instructions(&mut sc.svm, vec![__ix5], &[&sc.seller], - &sc.seller.pubkey()).unwrap(); + &sc, + &sc.seller, + sc.seller_market_user, + sc.seller_base_ata, + sc.seller_quote_ata, + order_book::state::OrderSide::Ask, + MAKER_ASK_ID, + PRICE, + QUANTITY, + ); + send_transaction_from_instructions( + &mut sc.svm, + vec![__ix5], + &[&sc.seller], + &sc.seller.pubkey(), + ) + .unwrap(); const TAKER_BID_ID: u64 = 2; let __ix6 = build_place_order_with_makers_ix( - &sc, - &sc.buyer, - sc.buyer_market_user, - sc.buyer_base_ata, - sc.buyer_quote_ata, - order_book::state::OrderSide::Bid, - TAKER_BID_ID, - PRICE, - QUANTITY, - &[(MAKER_ASK_ID, sc.seller_market_user)], + &sc, + &sc.buyer, + sc.buyer_market_user, + sc.buyer_base_ata, + sc.buyer_quote_ata, + order_book::state::OrderSide::Bid, + TAKER_BID_ID, + PRICE, + QUANTITY, + &[(MAKER_ASK_ID, sc.seller_market_user)], ); - send_transaction_from_instructions(&mut sc.svm, vec![__ix6], &[&sc.buyer], &sc.buyer.pubkey()).unwrap(); - + send_transaction_from_instructions(&mut sc.svm, vec![__ix6], &[&sc.buyer], &sc.buyer.pubkey()) + .unwrap(); assert_eq!( get_token_account_balance(&sc.svm, &sc.fee_vault.pubkey()).unwrap(), @@ -1954,34 +2024,39 @@ fn authority_can_withdraw_fees_after_match() { const EXPECTED_FEE: u64 = fee_ceil(GROSS); let __ix7 = build_place_order_ix( - &sc, - &sc.seller, - sc.seller_market_user, - sc.seller_base_ata, - sc.seller_quote_ata, - order_book::state::OrderSide::Ask, - MAKER_ASK_ID, - PRICE, - QUANTITY, - ); - send_transaction_from_instructions(&mut sc.svm, vec![__ix7], &[&sc.seller], - &sc.seller.pubkey()).unwrap(); + &sc, + &sc.seller, + sc.seller_market_user, + sc.seller_base_ata, + sc.seller_quote_ata, + order_book::state::OrderSide::Ask, + MAKER_ASK_ID, + PRICE, + QUANTITY, + ); + send_transaction_from_instructions( + &mut sc.svm, + vec![__ix7], + &[&sc.seller], + &sc.seller.pubkey(), + ) + .unwrap(); const TAKER_BID_ID: u64 = 2; let __ix8 = build_place_order_with_makers_ix( - &sc, - &sc.buyer, - sc.buyer_market_user, - sc.buyer_base_ata, - sc.buyer_quote_ata, - order_book::state::OrderSide::Bid, - TAKER_BID_ID, - PRICE, - QUANTITY, - &[(MAKER_ASK_ID, sc.seller_market_user)], + &sc, + &sc.buyer, + sc.buyer_market_user, + sc.buyer_base_ata, + sc.buyer_quote_ata, + order_book::state::OrderSide::Bid, + TAKER_BID_ID, + PRICE, + QUANTITY, + &[(MAKER_ASK_ID, sc.seller_market_user)], ); - send_transaction_from_instructions(&mut sc.svm, vec![__ix8], &[&sc.buyer], &sc.buyer.pubkey()).unwrap(); - + send_transaction_from_instructions(&mut sc.svm, vec![__ix8], &[&sc.buyer], &sc.buyer.pubkey()) + .unwrap(); assert_eq!( get_token_account_balance(&sc.svm, &sc.fee_vault.pubkey()).unwrap(), @@ -2025,53 +2100,63 @@ fn settle_funds_after_match_pays_out_both_unsettled_balances() { // Maker posts and taker crosses. let __ix9 = build_place_order_ix( - &sc, - &sc.seller, - sc.seller_market_user, - sc.seller_base_ata, - sc.seller_quote_ata, - order_book::state::OrderSide::Ask, - MAKER_ASK_ID, - PRICE, - QUANTITY, - ); - send_transaction_from_instructions(&mut sc.svm, vec![__ix9], &[&sc.seller], - &sc.seller.pubkey()).unwrap(); + &sc, + &sc.seller, + sc.seller_market_user, + sc.seller_base_ata, + sc.seller_quote_ata, + order_book::state::OrderSide::Ask, + MAKER_ASK_ID, + PRICE, + QUANTITY, + ); + send_transaction_from_instructions( + &mut sc.svm, + vec![__ix9], + &[&sc.seller], + &sc.seller.pubkey(), + ) + .unwrap(); const TAKER_BID_ID: u64 = 2; let __ix10 = build_place_order_with_makers_ix( - &sc, - &sc.buyer, - sc.buyer_market_user, - sc.buyer_base_ata, - sc.buyer_quote_ata, - order_book::state::OrderSide::Bid, - TAKER_BID_ID, - PRICE, - QUANTITY, - &[(MAKER_ASK_ID, sc.seller_market_user)], + &sc, + &sc.buyer, + sc.buyer_market_user, + sc.buyer_base_ata, + sc.buyer_quote_ata, + order_book::state::OrderSide::Bid, + TAKER_BID_ID, + PRICE, + QUANTITY, + &[(MAKER_ASK_ID, sc.seller_market_user)], ); - send_transaction_from_instructions(&mut sc.svm, vec![__ix10], &[&sc.buyer], &sc.buyer.pubkey()).unwrap(); - + send_transaction_from_instructions(&mut sc.svm, vec![__ix10], &[&sc.buyer], &sc.buyer.pubkey()) + .unwrap(); // Settle both sides. let __ix11 = build_settle_funds_ix( - &sc, - &sc.buyer.pubkey(), - sc.buyer_market_user, - sc.buyer_base_ata, - sc.buyer_quote_ata, - ); - send_transaction_from_instructions(&mut sc.svm, vec![__ix11], &[&sc.buyer], - &sc.buyer.pubkey()).unwrap(); + &sc, + &sc.buyer.pubkey(), + sc.buyer_market_user, + sc.buyer_base_ata, + sc.buyer_quote_ata, + ); + send_transaction_from_instructions(&mut sc.svm, vec![__ix11], &[&sc.buyer], &sc.buyer.pubkey()) + .unwrap(); let __ix12 = build_settle_funds_ix( - &sc, - &sc.seller.pubkey(), - sc.seller_market_user, - sc.seller_base_ata, - sc.seller_quote_ata, - ); - send_transaction_from_instructions(&mut sc.svm, vec![__ix12], &[&sc.seller], - &sc.seller.pubkey()).unwrap(); + &sc, + &sc.seller.pubkey(), + sc.seller_market_user, + sc.seller_base_ata, + sc.seller_quote_ata, + ); + send_transaction_from_instructions( + &mut sc.svm, + vec![__ix12], + &[&sc.seller], + &sc.seller.pubkey(), + ) + .unwrap(); // Buyer should now hold `QUANTITY` lots of extra base tokens // (QUANTITY * BASE_LOT_SIZE raw minor units) and have paid the gross @@ -2097,4 +2182,3 @@ fn settle_funds_after_match_pays_out_both_unsettled_balances() { EXPECTED_NET_QUOTE_TO_SELLER ); } - diff --git a/finance/perpetual-futures/anchor/programs/perpetual-futures/tests/test_perpetual_futures.rs b/finance/perpetual-futures/anchor/programs/perpetual-futures/tests/test_perpetual_futures.rs index 3055ffead..d7dfe1de1 100644 --- a/finance/perpetual-futures/anchor/programs/perpetual-futures/tests/test_perpetual_futures.rs +++ b/finance/perpetual-futures/anchor/programs/perpetual-futures/tests/test_perpetual_futures.rs @@ -1,7 +1,7 @@ use { anchor_lang::{ - solana_program::{instruction::Instruction, pubkey::Pubkey, system_program}, - AccountDeserialize, InstructionData, ToAccountMetas, + solana_program::instruction::Instruction, + AccountDeserialize, Address, system_program, InstructionData, ToAccountMetas, }, litesvm::LiteSVM, perpetual_futures::{instructions::initialize_pool::PoolParameters, state::Pool, state::Side}, @@ -22,20 +22,20 @@ const DECIMALS: u8 = 6; // The oracle quotes prices with 8 decimals, so $100 is 100 * 10^8. const ORACLE_SCALE: u32 = 8; -fn token_program_id() -> Pubkey { +fn token_program_id() -> Address { "TokenkegQfeZyiNwAJbNbGKPFXCWuBvf9Ss623VQ5DA" .parse() .unwrap() } -fn ata_program_id() -> Pubkey { +fn ata_program_id() -> Address { "ATokenGPvbdGVxr1b2hvZbsiqW5xWH25efTNsLJA8knL" .parse() .unwrap() } -fn derive_ata(wallet: &Pubkey, mint: &Pubkey) -> Pubkey { - Pubkey::find_program_address( +fn derive_ata(wallet: &Address, mint: &Address) -> Address { + Address::find_program_address( &[wallet.as_ref(), token_program_id().as_ref(), mint.as_ref()], &ata_program_id(), ) @@ -52,12 +52,12 @@ struct Market { svm: LiteSVM, payer: Keypair, admin: Keypair, - collateral_mint: Pubkey, - feed: Pubkey, - pool: Pubkey, - pool_authority: Pubkey, - lp_mint: Pubkey, - custody_vault: Pubkey, + collateral_mint: Address, + feed: Address, + pool: Address, + pool_authority: Address, + lp_mint: Address, + custody_vault: Address, } impl Market { @@ -117,7 +117,7 @@ impl Market { mock_switchboard::accounts::InitializeFeedAccountConstraints { feed: feed_keypair.pubkey(), authority: admin.pubkey(), - system_program: system_program::id(), + system_program: system_program::ID, } .to_account_metas(None), ); @@ -130,18 +130,18 @@ impl Market { .unwrap(); let feed = feed_keypair.pubkey(); - let pool = Pubkey::find_program_address( + let pool = Address::find_program_address( &[b"pool", collateral_mint.as_ref(), feed.as_ref()], &perpetual_futures::id(), ) .0; let pool_authority = - Pubkey::find_program_address(&[b"authority", pool.as_ref()], &perpetual_futures::id()) + Address::find_program_address(&[b"authority", pool.as_ref()], &perpetual_futures::id()) .0; let lp_mint = - Pubkey::find_program_address(&[b"lp_mint", pool.as_ref()], &perpetual_futures::id()).0; + Address::find_program_address(&[b"lp_mint", pool.as_ref()], &perpetual_futures::id()).0; let custody_vault = - Pubkey::find_program_address(&[b"vault", pool.as_ref()], &perpetual_futures::id()).0; + Address::find_program_address(&[b"vault", pool.as_ref()], &perpetual_futures::id()).0; let initialize_pool = Instruction::new_with_bytes( perpetual_futures::id(), @@ -156,7 +156,7 @@ impl Market { custody_vault, token_program: token_program_id(), associated_token_program: ata_program_id(), - system_program: system_program::id(), + system_program: system_program::ID, } .to_account_metas(None), ); @@ -233,7 +233,7 @@ impl Market { /// Create a wallet holding `amount` collateral tokens in its associated /// token account. - fn funded_trader(&mut self, amount: u64) -> (Keypair, Pubkey) { + fn funded_trader(&mut self, amount: u64) -> (Keypair, Address) { let trader = create_wallet(&mut self.svm, 100_000_000_000).unwrap(); let token_account = create_associated_token_account( &mut self.svm, @@ -256,7 +256,7 @@ impl Market { fn add_liquidity( &mut self, provider: &Keypair, - provider_collateral: Pubkey, + provider_collateral: Address, amount: u64, minimum_shares_out: u64, ) -> Result<(), ()> { @@ -280,7 +280,7 @@ impl Market { provider_lp, token_program: token_program_id(), associated_token_program: ata_program_id(), - system_program: system_program::id(), + system_program: system_program::ID, } .to_account_metas(None), ); @@ -297,7 +297,7 @@ impl Market { fn remove_liquidity( &mut self, provider: &Keypair, - provider_collateral: Pubkey, + provider_collateral: Address, shares: u64, minimum_amount_out: u64, ) -> Result<(), ()> { @@ -321,7 +321,7 @@ impl Market { provider_lp, token_program: token_program_id(), associated_token_program: ata_program_id(), - system_program: system_program::id(), + system_program: system_program::ID, } .to_account_metas(None), ); @@ -335,12 +335,12 @@ impl Market { .map_err(|_| ()) } - fn position_pda(&self, owner: &Pubkey, side: Side) -> Pubkey { + fn position_pda(&self, owner: &Address, side: Side) -> Address { let side_seed: &[u8] = match side { Side::Long => b"long", Side::Short => b"short", }; - Pubkey::find_program_address( + Address::find_program_address( &[b"position", self.pool.as_ref(), owner.as_ref(), side_seed], &perpetual_futures::id(), ) @@ -350,7 +350,7 @@ impl Market { fn open_position( &mut self, trader: &Keypair, - trader_collateral: Pubkey, + trader_collateral: Address, side: Side, collateral_amount: u64, size: u64, @@ -376,7 +376,7 @@ impl Market { trader_collateral, token_program: token_program_id(), associated_token_program: ata_program_id(), - system_program: system_program::id(), + system_program: system_program::ID, } .to_account_metas(None), ); @@ -393,7 +393,7 @@ impl Market { fn close_position( &mut self, trader: &Keypair, - trader_collateral: Pubkey, + trader_collateral: Address, side: Side, minimum_payout: u64, ) -> Result<(), ()> { @@ -412,7 +412,7 @@ impl Market { trader_collateral, token_program: token_program_id(), associated_token_program: ata_program_id(), - system_program: system_program::id(), + system_program: system_program::ID, } .to_account_metas(None), ); @@ -429,8 +429,8 @@ impl Market { fn liquidate( &mut self, liquidator: &Keypair, - owner: &Pubkey, - owner_collateral: Pubkey, + owner: &Address, + owner_collateral: Address, side: Side, ) -> Result<(), ()> { let position = self.position_pda(owner, side); @@ -451,7 +451,7 @@ impl Market { liquidator_collateral, token_program: token_program_id(), associated_token_program: ata_program_id(), - system_program: system_program::id(), + system_program: system_program::ID, } .to_account_metas(None), ); @@ -479,7 +479,7 @@ impl Market { authority_collateral, token_program: token_program_id(), associated_token_program: ata_program_id(), - system_program: system_program::id(), + system_program: system_program::ID, } .to_account_metas(None), ); @@ -495,7 +495,7 @@ impl Market { /// Deposit a large amount of liquidity so the pool can pay trader profits, /// returning the provider and its collateral account. - fn seed_liquidity(&mut self, amount: u64) -> (Keypair, Pubkey) { + fn seed_liquidity(&mut self, amount: u64) -> (Keypair, Address) { let (provider, provider_collateral) = self.funded_trader(amount); self.add_liquidity(&provider, provider_collateral, amount, 0) .unwrap(); diff --git a/finance/prop-amm/anchor/programs/prop-amm/tests/test_prop_amm.rs b/finance/prop-amm/anchor/programs/prop-amm/tests/test_prop_amm.rs index 5f10fb4a6..9fc8220ed 100644 --- a/finance/prop-amm/anchor/programs/prop-amm/tests/test_prop_amm.rs +++ b/finance/prop-amm/anchor/programs/prop-amm/tests/test_prop_amm.rs @@ -1,7 +1,7 @@ use { anchor_lang::{ - solana_program::{instruction::Instruction, pubkey::Pubkey, system_program}, - AccountDeserialize, InstructionData, ToAccountMetas, + solana_program::instruction::Instruction, + AccountDeserialize, Address, system_program, InstructionData, ToAccountMetas, }, litesvm::LiteSVM, prop_amm::{ @@ -29,20 +29,20 @@ const ORACLE_SCALE: u32 = 8; const SPREAD_BPS: u16 = 10; const MAX_CONFIDENCE_BPS: u16 = 100; -fn token_program_id() -> Pubkey { +fn token_program_id() -> Address { "TokenkegQfeZyiNwAJbNbGKPFXCWuBvf9Ss623VQ5DA" .parse() .unwrap() } -fn ata_program_id() -> Pubkey { +fn ata_program_id() -> Address { "ATokenGPvbdGVxr1b2hvZbsiqW5xWH25efTNsLJA8knL" .parse() .unwrap() } -fn derive_ata(wallet: &Pubkey, mint: &Pubkey) -> Pubkey { - Pubkey::find_program_address( +fn derive_ata(wallet: &Address, mint: &Address) -> Address { + Address::find_program_address( &[wallet.as_ref(), token_program_id().as_ref(), mint.as_ref()], &ata_program_id(), ) @@ -59,15 +59,15 @@ struct Market { svm: LiteSVM, payer: Keypair, operator: Keypair, - operator_base: Pubkey, - operator_quote: Pubkey, - base_mint: Pubkey, - quote_mint: Pubkey, - feed: Pubkey, - market: Pubkey, - market_authority: Pubkey, - base_vault: Pubkey, - quote_vault: Pubkey, + operator_base: Address, + operator_quote: Address, + base_mint: Address, + quote_mint: Address, + feed: Address, + market: Address, + market_authority: Address, + base_vault: Address, + quote_vault: Address, } impl Market { @@ -123,7 +123,7 @@ impl Market { mock_switchboard::accounts::InitializeFeedAccountConstraints { feed: feed_keypair.pubkey(), authority: operator.pubkey(), - system_program: system_program::id(), + system_program: system_program::ID, } .to_account_metas(None), ); @@ -136,17 +136,17 @@ impl Market { .unwrap(); let feed = feed_keypair.pubkey(); - let market = Pubkey::find_program_address( + let market = Address::find_program_address( &[b"market", base_mint.as_ref(), quote_mint.as_ref()], &prop_amm::id(), ) .0; let market_authority = - Pubkey::find_program_address(&[b"authority", market.as_ref()], &prop_amm::id()).0; + Address::find_program_address(&[b"authority", market.as_ref()], &prop_amm::id()).0; let base_vault = - Pubkey::find_program_address(&[b"base_vault", market.as_ref()], &prop_amm::id()).0; + Address::find_program_address(&[b"base_vault", market.as_ref()], &prop_amm::id()).0; let quote_vault = - Pubkey::find_program_address(&[b"quote_vault", market.as_ref()], &prop_amm::id()).0; + Address::find_program_address(&[b"quote_vault", market.as_ref()], &prop_amm::id()).0; let initialize_market = Instruction::new_with_bytes( prop_amm::id(), @@ -162,7 +162,7 @@ impl Market { quote_vault, token_program: token_program_id(), associated_token_program: ata_program_id(), - system_program: system_program::id(), + system_program: system_program::ID, } .to_account_metas(None), ); @@ -278,7 +278,7 @@ impl Market { /// Create a wallet holding `base` and `quote` minor units in associated /// token accounts. - fn funded_trader(&mut self, base: u64, quote: u64) -> (Keypair, Pubkey, Pubkey) { + fn funded_trader(&mut self, base: u64, quote: u64) -> (Keypair, Address, Address) { let trader = create_wallet(&mut self.svm, 100_000_000_000).unwrap(); let base_account = create_associated_token_account( &mut self.svm, @@ -437,7 +437,7 @@ impl Market { trader_quote, token_program: token_program_id(), associated_token_program: ata_program_id(), - system_program: system_program::id(), + system_program: system_program::ID, } .to_account_metas(None), ); @@ -446,7 +446,7 @@ impl Market { .map_err(|_| ()) } - fn balance(&self, token_account: &Pubkey) -> u64 { + fn balance(&self, token_account: &Address) -> u64 { get_token_account_balance(&self.svm, token_account).unwrap() } } diff --git a/finance/token-fundraiser/anchor/programs/fundraiser/src/constants.rs b/finance/token-fundraiser/anchor/programs/fundraiser/src/constants.rs index fc0697b02..2af19dd91 100644 --- a/finance/token-fundraiser/anchor/programs/fundraiser/src/constants.rs +++ b/finance/token-fundraiser/anchor/programs/fundraiser/src/constants.rs @@ -1,4 +1,4 @@ pub const MIN_AMOUNT_TO_RAISE: u64 = 3; pub const SECONDS_TO_DAYS: i64 = 86400; pub const MAX_CONTRIBUTION_PERCENTAGE: u64 = 10; -pub const PERCENTAGE_SCALER: u64 = 100; \ No newline at end of file +pub const PERCENTAGE_SCALER: u64 = 100; diff --git a/finance/token-fundraiser/anchor/programs/fundraiser/src/instructions/mod.rs b/finance/token-fundraiser/anchor/programs/fundraiser/src/instructions/mod.rs index 7ae564501..b192e20f4 100644 --- a/finance/token-fundraiser/anchor/programs/fundraiser/src/instructions/mod.rs +++ b/finance/token-fundraiser/anchor/programs/fundraiser/src/instructions/mod.rs @@ -1,11 +1,11 @@ -pub mod initialize_fundraiser; -pub mod contribute; pub mod checker; -pub mod refund; pub mod close; +pub mod contribute; +pub mod initialize_fundraiser; +pub mod refund; -pub use initialize_fundraiser::*; -pub use contribute::*; pub use checker::*; +pub use close::*; +pub use contribute::*; +pub use initialize_fundraiser::*; pub use refund::*; -pub use close::*; \ No newline at end of file diff --git a/finance/token-fundraiser/anchor/programs/fundraiser/src/lib.rs b/finance/token-fundraiser/anchor/programs/fundraiser/src/lib.rs index 26ddae74f..d0a92e80e 100644 --- a/finance/token-fundraiser/anchor/programs/fundraiser/src/lib.rs +++ b/finance/token-fundraiser/anchor/programs/fundraiser/src/lib.rs @@ -48,7 +48,9 @@ pub mod fundraiser { Ok(()) } - pub fn close_fundraiser(mut context: &mut Context) -> Result<()> { + pub fn close_fundraiser( + mut context: &mut Context, + ) -> Result<()> { handle_close_fundraiser(&mut context.accounts)?; Ok(()) diff --git a/finance/token-fundraiser/anchor/programs/fundraiser/src/state/contributor.rs b/finance/token-fundraiser/anchor/programs/fundraiser/src/state/contributor.rs index 616d570e2..1003a50ba 100644 --- a/finance/token-fundraiser/anchor/programs/fundraiser/src/state/contributor.rs +++ b/finance/token-fundraiser/anchor/programs/fundraiser/src/state/contributor.rs @@ -6,4 +6,4 @@ pub struct Contributor { pub amount: u64, /// Canonical bump for this PDA. pub bump: u8, -} \ No newline at end of file +} diff --git a/finance/token-fundraiser/anchor/programs/fundraiser/src/state/fundraiser.rs b/finance/token-fundraiser/anchor/programs/fundraiser/src/state/fundraiser.rs index 1a7ad324b..12ba8bc72 100644 --- a/finance/token-fundraiser/anchor/programs/fundraiser/src/state/fundraiser.rs +++ b/finance/token-fundraiser/anchor/programs/fundraiser/src/state/fundraiser.rs @@ -10,4 +10,4 @@ pub struct Fundraiser { pub time_started: i64, pub duration: u16, pub bump: u8, -} \ No newline at end of file +} diff --git a/finance/token-fundraiser/anchor/programs/fundraiser/src/state/mod.rs b/finance/token-fundraiser/anchor/programs/fundraiser/src/state/mod.rs index 3e6b07fdb..d4c1b8235 100644 --- a/finance/token-fundraiser/anchor/programs/fundraiser/src/state/mod.rs +++ b/finance/token-fundraiser/anchor/programs/fundraiser/src/state/mod.rs @@ -1,5 +1,5 @@ -pub mod fundraiser; pub mod contributor; +pub mod fundraiser; +pub use contributor::*; pub use fundraiser::*; -pub use contributor::*; \ No newline at end of file diff --git a/finance/token-fundraiser/anchor/programs/fundraiser/tests/test_fundraiser.rs b/finance/token-fundraiser/anchor/programs/fundraiser/tests/test_fundraiser.rs index c32e479e0..4bbbc4800 100644 --- a/finance/token-fundraiser/anchor/programs/fundraiser/tests/test_fundraiser.rs +++ b/finance/token-fundraiser/anchor/programs/fundraiser/tests/test_fundraiser.rs @@ -1,7 +1,7 @@ use { anchor_lang::{ - solana_program::{clock::Clock, instruction::Instruction, pubkey::Pubkey, system_program}, - InstructionData, ToAccountMetas, + solana_program::{clock::Clock, instruction::Instruction}, + system_program, Address, InstructionData, ToAccountMetas, }, borsh::BorshDeserialize, fundraiser::SECONDS_TO_DAYS, @@ -9,7 +9,8 @@ use { solana_keypair::Keypair, solana_kite::{ create_associated_token_account, create_token_mint, create_wallet, - get_token_account_balance, mint_tokens_to_token_account, send_transaction_from_instructions, + get_token_account_balance, mint_tokens_to_token_account, + send_transaction_from_instructions, }, solana_signer::Signer, }; @@ -24,20 +25,20 @@ const MAX_CONTRIBUTION: u64 = AMOUNT_TO_RAISE / 10; const DURATION_DAYS: u16 = 7; const CONTRIBUTOR_STARTING_BALANCE: u64 = 10 * ONE_TOKEN; -fn token_program_id() -> Pubkey { +fn token_program_id() -> Address { "TokenkegQfeZyiNwAJbNbGKPFXCWuBvf9Ss623VQ5DA" .parse() .unwrap() } -fn ata_program_id() -> Pubkey { +fn ata_program_id() -> Address { "ATokenGPvbdGVxr1b2hvZbsiqW5xWH25efTNsLJA8knL" .parse() .unwrap() } -fn derive_ata(wallet: &Pubkey, mint: &Pubkey) -> Pubkey { - let (ata, _bump) = Pubkey::find_program_address( +fn derive_ata(wallet: &Address, mint: &Address) -> Address { + let (ata, _bump) = Address::find_program_address( &[wallet.as_ref(), token_program_id().as_ref(), mint.as_ref()], &ata_program_id(), ); @@ -66,12 +67,12 @@ struct ContributorState { const ANCHOR_DISCRIMINATOR_LENGTH: usize = 8; -fn read_fundraiser_state(svm: &LiteSVM, fundraiser_pda: &Pubkey) -> FundraiserState { +fn read_fundraiser_state(svm: &LiteSVM, fundraiser_pda: &Address) -> FundraiserState { let account = svm.get_account(fundraiser_pda).unwrap(); FundraiserState::try_from_slice(&account.data[ANCHOR_DISCRIMINATOR_LENGTH..]).unwrap() } -fn read_contributor_state(svm: &LiteSVM, contributor_pda: &Pubkey) -> ContributorState { +fn read_contributor_state(svm: &LiteSVM, contributor_pda: &Address) -> ContributorState { let account = svm.get_account(contributor_pda).unwrap(); ContributorState::try_from_slice(&account.data[ANCHOR_DISCRIMINATOR_LENGTH..]).unwrap() } @@ -85,12 +86,12 @@ fn warp_days_forward(svm: &mut LiteSVM, days: i64) { struct FundraiserSetup { svm: LiteSVM, - program_id: Pubkey, + program_id: Address, payer: Keypair, maker: Keypair, - mint: Pubkey, - fundraiser_pda: Pubkey, - vault: Pubkey, + mint: Address, + fundraiser_pda: Address, + vault: Address, } fn full_setup() -> FundraiserSetup { @@ -107,7 +108,7 @@ fn full_setup() -> FundraiserSetup { let mint = create_token_mint(&mut svm, &payer, MINT_DECIMALS, None).unwrap(); let (fundraiser_pda, _bump) = - Pubkey::find_program_address(&[b"fundraiser", maker.pubkey().as_ref()], &program_id); + Address::find_program_address(&[b"fundraiser", maker.pubkey().as_ref()], &program_id); // The vault is the ATA of the fundraiser PDA for the mint. let vault = derive_ata(&fundraiser_pda, &mint); @@ -132,7 +133,7 @@ fn initialize_fundraiser(setup: &mut FundraiserSetup, amount: u64, duration: u16 mint_to_raise: setup.mint, fundraiser: setup.fundraiser_pda, vault: setup.vault, - system_program: system_program::id(), + system_program: system_program::ID, token_program: token_program_id(), associated_token_program: ata_program_id(), } @@ -149,7 +150,7 @@ fn initialize_fundraiser(setup: &mut FundraiserSetup, amount: u64, duration: u16 /// Creates a contributor wallet with a funded ATA and returns /// (contributor keypair, contributor ATA, contributor account PDA). -fn create_funded_contributor(setup: &mut FundraiserSetup) -> (Keypair, Pubkey, Pubkey) { +fn create_funded_contributor(setup: &mut FundraiserSetup) -> (Keypair, Address, Address) { let contributor = create_wallet(&mut setup.svm, 10_000_000_000).unwrap(); let contributor_ata = create_associated_token_account( @@ -169,7 +170,7 @@ fn create_funded_contributor(setup: &mut FundraiserSetup) -> (Keypair, Pubkey, P ) .unwrap(); - let (contributor_account_pda, _bump) = Pubkey::find_program_address( + let (contributor_account_pda, _bump) = Address::find_program_address( &[ b"contributor", setup.fundraiser_pda.as_ref(), @@ -183,9 +184,9 @@ fn create_funded_contributor(setup: &mut FundraiserSetup) -> (Keypair, Pubkey, P fn build_contribute_instruction( setup: &FundraiserSetup, - contributor: &Pubkey, - contributor_ata: &Pubkey, - contributor_account_pda: &Pubkey, + contributor: &Address, + contributor_ata: &Address, + contributor_account_pda: &Address, amount: u64, ) -> Instruction { Instruction::new_with_bytes( @@ -199,7 +200,7 @@ fn build_contribute_instruction( contributor_ata: *contributor_ata, vault: setup.vault, token_program: token_program_id(), - system_program: system_program::id(), + system_program: system_program::ID, } .to_account_metas(None), ) @@ -207,9 +208,9 @@ fn build_contribute_instruction( fn build_refund_instruction( setup: &FundraiserSetup, - contributor: &Pubkey, - contributor_ata: &Pubkey, - contributor_account_pda: &Pubkey, + contributor: &Address, + contributor_ata: &Address, + contributor_account_pda: &Address, ) -> Instruction { Instruction::new_with_bytes( setup.program_id, @@ -223,7 +224,7 @@ fn build_refund_instruction( contributor_ata: *contributor_ata, vault: setup.vault, token_program: token_program_id(), - system_program: system_program::id(), + system_program: system_program::ID, } .to_account_metas(None), ) @@ -231,7 +232,7 @@ fn build_refund_instruction( fn build_check_contributions_instruction( setup: &FundraiserSetup, - maker_ata: &Pubkey, + maker_ata: &Address, ) -> Instruction { Instruction::new_with_bytes( setup.program_id, @@ -243,14 +244,14 @@ fn build_check_contributions_instruction( vault: setup.vault, maker_ata: *maker_ata, token_program: token_program_id(), - system_program: system_program::id(), + system_program: system_program::ID, associated_token_program: ata_program_id(), } .to_account_metas(None), ) } -fn build_close_fundraiser_instruction(setup: &FundraiserSetup, maker_ata: &Pubkey) -> Instruction { +fn build_close_fundraiser_instruction(setup: &FundraiserSetup, maker_ata: &Address) -> Instruction { Instruction::new_with_bytes( setup.program_id, &fundraiser::instruction::CloseFundraiser {}.data(), @@ -261,7 +262,7 @@ fn build_close_fundraiser_instruction(setup: &FundraiserSetup, maker_ata: &Pubke vault: setup.vault, maker_ata: *maker_ata, token_program: token_program_id(), - system_program: system_program::id(), + system_program: system_program::ID, associated_token_program: ata_program_id(), } .to_account_metas(None), @@ -279,7 +280,10 @@ fn test_initialize_fundraiser() { assert_eq!(fundraiser_state.current_amount, 0); assert_eq!(fundraiser_state.duration, DURATION_DAYS); - assert_eq!(get_token_account_balance(&setup.svm, &setup.vault).unwrap(), 0); + assert_eq!( + get_token_account_balance(&setup.svm, &setup.vault).unwrap(), + 0 + ); } #[test] @@ -300,7 +304,7 @@ fn test_initialize_below_minimum_target_fails() { mint_to_raise: setup.mint, fundraiser: setup.fundraiser_pda, vault: setup.vault, - system_program: system_program::id(), + system_program: system_program::ID, token_program: token_program_id(), associated_token_program: ata_program_id(), } @@ -312,7 +316,10 @@ fn test_initialize_below_minimum_target_fails() { &[&setup.maker], &setup.maker.pubkey(), ); - assert!(result.is_err(), "Target below 3 major units must be rejected"); + assert!( + result.is_err(), + "Target below 3 major units must be rejected" + ); assert!( setup.svm.get_account(&setup.fundraiser_pda).is_none(), "Fundraiser account must not exist after a failed initialize" @@ -387,7 +394,10 @@ fn test_contribute_after_deadline_fails() { ); assert!(result.is_err(), "Contributing after the deadline must fail"); - assert_eq!(get_token_account_balance(&setup.svm, &setup.vault).unwrap(), 0); + assert_eq!( + get_token_account_balance(&setup.svm, &setup.vault).unwrap(), + 0 + ); assert_eq!( get_token_account_balance(&setup.svm, &contributor_ata).unwrap(), CONTRIBUTOR_STARTING_BALANCE @@ -419,7 +429,10 @@ fn test_contribute_below_one_major_unit_fails() { result.is_err(), "Contributions below one major unit must fail" ); - assert_eq!(get_token_account_balance(&setup.svm, &setup.vault).unwrap(), 0); + assert_eq!( + get_token_account_balance(&setup.svm, &setup.vault).unwrap(), + 0 + ); } #[test] @@ -508,7 +521,10 @@ fn test_refund_after_deadline_target_not_met_succeeds() { ) .unwrap(); - assert_eq!(get_token_account_balance(&setup.svm, &setup.vault).unwrap(), 0); + assert_eq!( + get_token_account_balance(&setup.svm, &setup.vault).unwrap(), + 0 + ); assert_eq!( get_token_account_balance(&setup.svm, &contributor_ata).unwrap(), CONTRIBUTOR_STARTING_BALANCE @@ -655,7 +671,10 @@ fn test_contribute_above_cap_fails() { result.is_err(), "A single contribution above the 10% cap must fail" ); - assert_eq!(get_token_account_balance(&setup.svm, &setup.vault).unwrap(), 0); + assert_eq!( + get_token_account_balance(&setup.svm, &setup.vault).unwrap(), + 0 + ); } #[test] @@ -780,7 +799,10 @@ fn test_close_fundraiser_after_failed_raise_allows_a_new_raise() { let fundraiser_state = read_fundraiser_state(&setup.svm, &setup.fundraiser_pda); assert_eq!(fundraiser_state.current_amount, 0); assert_eq!(fundraiser_state.amount_to_raise, AMOUNT_TO_RAISE); - assert_eq!(get_token_account_balance(&setup.svm, &setup.vault).unwrap(), 0); + assert_eq!( + get_token_account_balance(&setup.svm, &setup.vault).unwrap(), + 0 + ); } #[test] @@ -905,8 +927,14 @@ fn test_close_fundraiser_sweeps_direct_donations_to_maker() { // accounting; on close they go to the maker instead of being burned // with the account. let donation = 5 * ONE_TOKEN; - mint_tokens_to_token_account(&mut setup.svm, &setup.mint, &setup.vault, donation, &setup.payer) - .unwrap(); + mint_tokens_to_token_account( + &mut setup.svm, + &setup.mint, + &setup.vault, + donation, + &setup.payer, + ) + .unwrap(); warp_days_forward(&mut setup.svm, DURATION_DAYS as i64 + 1); diff --git a/finance/token-swap/anchor/programs/token-swap/src/instructions/admin/claim_admin_fees.rs b/finance/token-swap/anchor/programs/token-swap/src/instructions/admin/claim_admin_fees.rs index 726741646..ea9303ecb 100644 --- a/finance/token-swap/anchor/programs/token-swap/src/instructions/admin/claim_admin_fees.rs +++ b/finance/token-swap/anchor/programs/token-swap/src/instructions/admin/claim_admin_fees.rs @@ -20,7 +20,9 @@ use crate::{ /// `Signer` constraint on `admin` together mean only the address stored in /// `Config.admin` can call this. Any other signer will be rejected by /// Anchor's built-in `has_one` check. -pub fn handle_claim_admin_fees(context: &mut Context) -> Result<()> { +pub fn handle_claim_admin_fees( + context: &mut Context, +) -> Result<()> { let owed_a = context.accounts.pool_config.admin_fees_owed_a; let owed_b = context.accounts.pool_config.admin_fees_owed_b; @@ -93,7 +95,11 @@ pub fn handle_claim_admin_fees(context: &mut Context context.accounts.token_a.amount() - || amount_b > context.accounts.token_b.amount() + if amount_a > context.accounts.token_a.amount() || amount_b > context.accounts.token_b.amount() { return err!(AmmError::InsufficientBalance); } @@ -150,8 +149,7 @@ pub fn handle_deposit_liquidity( .checked_mul(amount_b as u128) .ok_or(AmmError::MathOverflow)?; let sqrt_product = integer_sqrt(product); - let sqrt_product_u64 = u64::try_from(sqrt_product) - .map_err(|_| AmmError::MathOverflow)?; + let sqrt_product_u64 = u64::try_from(sqrt_product).map_err(|_| AmmError::MathOverflow)?; if sqrt_product_u64 < MINIMUM_LIQUIDITY { return err!(AmmError::DepositTooSmall); } diff --git a/finance/token-swap/anchor/programs/token-swap/src/instructions/initialize_pool.rs b/finance/token-swap/anchor/programs/token-swap/src/instructions/initialize_pool.rs index 1a530dbbc..eac7bdbc7 100644 --- a/finance/token-swap/anchor/programs/token-swap/src/instructions/initialize_pool.rs +++ b/finance/token-swap/anchor/programs/token-swap/src/instructions/initialize_pool.rs @@ -11,7 +11,9 @@ use crate::{ state::{Config, PoolConfig}, }; -pub fn handle_initialize_pool(context: &mut Context) -> Result<()> { +pub fn handle_initialize_pool( + context: &mut Context, +) -> Result<()> { let bump = context.bumps.pool_config; let pool_config = &mut context.accounts.pool_config; pool_config.config = *context.accounts.config.address(); diff --git a/finance/token-swap/anchor/programs/token-swap/src/instructions/mod.rs b/finance/token-swap/anchor/programs/token-swap/src/instructions/mod.rs index fd4e825c8..0fd5becc5 100644 --- a/finance/token-swap/anchor/programs/token-swap/src/instructions/mod.rs +++ b/finance/token-swap/anchor/programs/token-swap/src/instructions/mod.rs @@ -1,13 +1,13 @@ mod admin; +mod deposit_liquidity; mod initialize_config; mod initialize_pool; -mod deposit_liquidity; mod swap_tokens; mod withdraw_liquidity; pub use admin::*; +pub use deposit_liquidity::*; pub use initialize_config::*; pub use initialize_pool::*; -pub use deposit_liquidity::*; pub use swap_tokens::*; pub use withdraw_liquidity::*; diff --git a/finance/token-swap/anchor/programs/token-swap/src/instructions/swap_tokens.rs b/finance/token-swap/anchor/programs/token-swap/src/instructions/swap_tokens.rs index 9969ed9e7..0d6fda093 100644 --- a/finance/token-swap/anchor/programs/token-swap/src/instructions/swap_tokens.rs +++ b/finance/token-swap/anchor/programs/token-swap/src/instructions/swap_tokens.rs @@ -54,12 +54,13 @@ pub fn handle_swap_tokens( // is u64), so the cast is safe - but use try_into anyway to make the // invariant explicit in the type system. let fee_amount: u64 = u64::try_from(fee_amount).map_err(|_| AmmError::MathOverflow)?; - let admin_portion: u64 = - u64::try_from(admin_portion).map_err(|_| AmmError::MathOverflow)?; + let admin_portion: u64 = u64::try_from(admin_portion).map_err(|_| AmmError::MathOverflow)?; // The LP portion stays in the pool reserves (as today - it's "less output // for the same input"), boosting the LP curve. The admin portion is // accounted for separately so it does *not* grow LP yield. - let taxed_input = input_amount.checked_sub(fee_amount).ok_or(AmmError::MathOverflow)?; + let taxed_input = input_amount + .checked_sub(fee_amount) + .ok_or(AmmError::MathOverflow)?; // Effective reserves = raw vault balance - admin's accumulated claim. // The constant-product curve runs on the LP-claimable portion only, so @@ -124,10 +125,7 @@ pub fn handle_swap_tokens( // they're willing to accept (computed offchain at quote time). If the // pool shifted between quoting and landing, we revert rather than fill // at the worse rate. - require!( - output >= min_output_amount, - AmmError::SlippageExceeded - ); + require!(output >= min_output_amount, AmmError::SlippageExceeded); // Compute the invariant on the *effective* reserves before the trade. // Using raw balances here would let the admin's accumulated fees count diff --git a/finance/token-swap/anchor/programs/token-swap/src/lib.rs b/finance/token-swap/anchor/programs/token-swap/src/lib.rs index 061d4345a..c0b7d5088 100644 --- a/finance/token-swap/anchor/programs/token-swap/src/lib.rs +++ b/finance/token-swap/anchor/programs/token-swap/src/lib.rs @@ -30,12 +30,7 @@ pub mod swap_example { amount_b: u64, minimum_lp_tokens_out: u64, ) -> Result<()> { - instructions::handle_deposit_liquidity( - context, - amount_a, - amount_b, - minimum_lp_tokens_out, - ) + instructions::handle_deposit_liquidity(context, amount_a, amount_b, minimum_lp_tokens_out) } pub fn withdraw_liquidity( @@ -58,12 +53,7 @@ pub mod swap_example { input_amount: u64, min_output_amount: u64, ) -> Result<()> { - instructions::handle_swap_tokens( - context, - input_is_token_a, - input_amount, - min_output_amount, - ) + instructions::handle_swap_tokens(context, input_is_token_a, input_amount, min_output_amount) } pub fn claim_admin_fees(context: &mut Context) -> Result<()> { diff --git a/finance/token-swap/anchor/programs/token-swap/tests/test_swap.rs b/finance/token-swap/anchor/programs/token-swap/tests/test_swap.rs index 14820efa3..178d1f418 100644 --- a/finance/token-swap/anchor/programs/token-swap/tests/test_swap.rs +++ b/finance/token-swap/anchor/programs/token-swap/tests/test_swap.rs @@ -1,38 +1,39 @@ use { anchor_lang::{ - solana_program::{instruction::Instruction, pubkey::Pubkey, system_program}, - InstructionData, ToAccountMetas, + solana_program::instruction::Instruction, system_program, Address, InstructionData, + ToAccountMetas, }, litesvm::LiteSVM, solana_keypair::Keypair, solana_kite::{ create_associated_token_account, create_token_mint, create_wallet, - get_token_account_balance, mint_tokens_to_token_account, send_transaction_from_instructions, + get_token_account_balance, mint_tokens_to_token_account, + send_transaction_from_instructions, }, solana_signer::Signer, }; -fn token_program_id() -> Pubkey { +fn token_program_id() -> Address { "TokenkegQfeZyiNwAJbNbGKPFXCWuBvf9Ss623VQ5DA" .parse() .unwrap() } -fn ata_program_id() -> Pubkey { +fn ata_program_id() -> Address { "ATokenGPvbdGVxr1b2hvZbsiqW5xWH25efTNsLJA8knL" .parse() .unwrap() } -fn derive_ata(wallet: &Pubkey, mint: &Pubkey) -> Pubkey { - let (ata, _bump) = Pubkey::find_program_address( +fn derive_ata(wallet: &Address, mint: &Address) -> Address { + let (ata, _bump) = Address::find_program_address( &[wallet.as_ref(), token_program_id().as_ref(), mint.as_ref()], &ata_program_id(), ); ata } -fn setup() -> (LiteSVM, Pubkey, Keypair) { +fn setup() -> (LiteSVM, Address, Keypair) { let program_id = swap_example::id(); let mut svm = LiteSVM::new(); @@ -44,7 +45,7 @@ fn setup() -> (LiteSVM, Pubkey, Keypair) { } /// Ensure mint_a < mint_b by pubkey ordering (the program may require this). -fn ordered_mints(svm: &mut LiteSVM, authority: &Keypair, decimals: u8) -> (Pubkey, Pubkey) { +fn ordered_mints(svm: &mut LiteSVM, authority: &Keypair, decimals: u8) -> (Address, Address) { loop { let a = create_token_mint(svm, authority, decimals, None).unwrap(); let b = create_token_mint(svm, authority, decimals, None).unwrap(); @@ -56,20 +57,20 @@ fn ordered_mints(svm: &mut LiteSVM, authority: &Keypair, decimals: u8) -> (Pubke struct TestSetup { svm: LiteSVM, - program_id: Pubkey, + program_id: Address, payer: Keypair, admin: Keypair, - config_key: Pubkey, - mint_a: Pubkey, - mint_b: Pubkey, - pool_config_key: Pubkey, - pool_authority: Pubkey, - liquidity_provider_mint: Pubkey, - pool_a: Pubkey, - pool_b: Pubkey, - holder_account_a: Pubkey, - holder_account_b: Pubkey, - liquidity_account: Pubkey, + config_key: Address, + mint_a: Address, + mint_b: Address, + pool_config_key: Address, + pool_authority: Address, + liquidity_provider_mint: Address, + pool_a: Address, + pool_b: Address, + holder_account_a: Address, + holder_account_b: Address, + liquidity_account: Address, } fn full_setup() -> TestSetup { @@ -87,12 +88,12 @@ fn full_setup() -> TestSetup { // Derive the singleton Config PDA (seeds = [b"config"]). One config per // deployed program. - let (config_key, _) = Pubkey::find_program_address(&[b"config"], &program_id); - let (pool_config_key, _) = Pubkey::find_program_address( + let (config_key, _) = Address::find_program_address(&[b"config"], &program_id); + let (pool_config_key, _) = Address::find_program_address( &[config_key.as_ref(), mint_a.as_ref(), mint_b.as_ref()], &program_id, ); - let (pool_authority, _) = Pubkey::find_program_address( + let (pool_authority, _) = Address::find_program_address( &[ config_key.as_ref(), mint_a.as_ref(), @@ -101,7 +102,7 @@ fn full_setup() -> TestSetup { ], &program_id, ); - let (liquidity_provider_mint, _) = Pubkey::find_program_address( + let (liquidity_provider_mint, _) = Address::find_program_address( &[ config_key.as_ref(), mint_a.as_ref(), @@ -121,18 +122,24 @@ fn full_setup() -> TestSetup { let holder_account_b = create_associated_token_account(&mut svm, &admin.pubkey(), &mint_b, &payer).unwrap(); - mint_tokens_to_token_account(&mut svm, &mint_a, &holder_account_a, minted_amount, &admin).unwrap(); - mint_tokens_to_token_account(&mut svm, &mint_b, &holder_account_b, minted_amount, &admin).unwrap(); + mint_tokens_to_token_account(&mut svm, &mint_a, &holder_account_a, minted_amount, &admin) + .unwrap(); + mint_tokens_to_token_account(&mut svm, &mint_b, &holder_account_b, minted_amount, &admin) + .unwrap(); // Create AMM let initialize_config_ix = Instruction::new_with_bytes( program_id, - &swap_example::instruction::InitializeConfig { fee, admin_share_bps }.data(), + &swap_example::instruction::InitializeConfig { + fee, + admin_share_bps, + } + .data(), swap_example::accounts::InitializeConfigAccountConstraints { config: config_key, admin: admin.pubkey(), payer: payer.pubkey(), - system_program: system_program::id(), + system_program: system_program::ID, } .to_account_metas(None), ); @@ -160,7 +167,7 @@ fn full_setup() -> TestSetup { payer: payer.pubkey(), token_program: token_program_id(), associated_token_program: ata_program_id(), - system_program: system_program::id(), + system_program: system_program::ID, } .to_account_metas(None), ); @@ -198,16 +205,20 @@ fn test_initialize_config() { let admin_share_bps: u16 = 1667; let admin = Keypair::new(); - let (config_key, _) = Pubkey::find_program_address(&[b"config"], &program_id); + let (config_key, _) = Address::find_program_address(&[b"config"], &program_id); let initialize_config_ix = Instruction::new_with_bytes( program_id, - &swap_example::instruction::InitializeConfig { fee, admin_share_bps }.data(), + &swap_example::instruction::InitializeConfig { + fee, + admin_share_bps, + } + .data(), swap_example::accounts::InitializeConfigAccountConstraints { config: config_key, admin: admin.pubkey(), payer: payer.pubkey(), - system_program: system_program::id(), + system_program: system_program::ID, } .to_account_metas(None), ); @@ -257,7 +268,7 @@ fn test_deposit_liquidity() { payer: ts.payer.pubkey(), token_program: token_program_id(), associated_token_program: ata_program_id(), - system_program: system_program::id(), + system_program: system_program::ID, } .to_account_metas(None), ); @@ -304,7 +315,7 @@ fn test_swap_a_to_b() { payer: ts.payer.pubkey(), token_program: token_program_id(), associated_token_program: ata_program_id(), - system_program: system_program::id(), + system_program: system_program::ID, } .to_account_metas(None), ); @@ -342,7 +353,7 @@ fn test_swap_a_to_b() { payer: ts.payer.pubkey(), token_program: token_program_id(), associated_token_program: ata_program_id(), - system_program: system_program::id(), + system_program: system_program::ID, } .to_account_metas(None), ); @@ -391,7 +402,7 @@ fn test_withdraw_liquidity() { payer: ts.payer.pubkey(), token_program: token_program_id(), associated_token_program: ata_program_id(), - system_program: system_program::id(), + system_program: system_program::ID, } .to_account_metas(None), ); @@ -433,7 +444,7 @@ fn test_withdraw_liquidity() { payer: ts.payer.pubkey(), token_program: token_program_id(), associated_token_program: ata_program_id(), - system_program: system_program::id(), + system_program: system_program::ID, } .to_account_metas(None), ); @@ -452,7 +463,12 @@ fn test_withdraw_liquidity() { /// Helper: do a deposit and one A->B swap on top of `full_setup`. /// Returns the swap input amount (token A base units) for fee-arithmetic checks. -fn deposit_and_swap_a_to_b(ts: &mut TestSetup, deposit_a: u64, deposit_b: u64, swap_in_a: u64) -> u64 { +fn deposit_and_swap_a_to_b( + ts: &mut TestSetup, + deposit_a: u64, + deposit_b: u64, + swap_in_a: u64, +) -> u64 { let deposit_ix = Instruction::new_with_bytes( ts.program_id, &swap_example::instruction::DepositLiquidity { @@ -477,7 +493,7 @@ fn deposit_and_swap_a_to_b(ts: &mut TestSetup, deposit_a: u64, deposit_b: u64, s payer: ts.payer.pubkey(), token_program: token_program_id(), associated_token_program: ata_program_id(), - system_program: system_program::id(), + system_program: system_program::ID, } .to_account_metas(None), ); @@ -511,7 +527,7 @@ fn deposit_and_swap_a_to_b(ts: &mut TestSetup, deposit_a: u64, deposit_b: u64, s payer: ts.payer.pubkey(), token_program: token_program_id(), associated_token_program: ata_program_id(), - system_program: system_program::id(), + system_program: system_program::ID, } .to_account_metas(None), ); @@ -572,7 +588,7 @@ fn swap_a_to_b(ts: &mut TestSetup, input_amount: u64) { payer: ts.payer.pubkey(), token_program: token_program_id(), associated_token_program: ata_program_id(), - system_program: system_program::id(), + system_program: system_program::ID, } .to_account_metas(None), ); @@ -600,10 +616,8 @@ fn test_claim_admin_fees() { assert!(expected_admin_a > 0, "expected admin portion > 0"); // ---- Phase 1: first claim transfers the accumulated A-side fees ---- - let admin_balance_a_before = - get_token_account_balance(&ts.svm, &ts.holder_account_a).unwrap(); - let admin_balance_b_before = - get_token_account_balance(&ts.svm, &ts.holder_account_b).unwrap(); + let admin_balance_a_before = get_token_account_balance(&ts.svm, &ts.holder_account_a).unwrap(); + let admin_balance_b_before = get_token_account_balance(&ts.svm, &ts.holder_account_b).unwrap(); let claim_ix_first = claim_admin_fees_ix(&ts); send_transaction_from_instructions( @@ -614,10 +628,8 @@ fn test_claim_admin_fees() { ) .unwrap(); - let admin_balance_a_after = - get_token_account_balance(&ts.svm, &ts.holder_account_a).unwrap(); - let admin_balance_b_after = - get_token_account_balance(&ts.svm, &ts.holder_account_b).unwrap(); + let admin_balance_a_after = get_token_account_balance(&ts.svm, &ts.holder_account_a).unwrap(); + let admin_balance_b_after = get_token_account_balance(&ts.svm, &ts.holder_account_b).unwrap(); assert_eq!( admin_balance_a_after - admin_balance_a_before, @@ -640,8 +652,7 @@ fn test_claim_admin_fees() { let expected_admin_a_2 = fee_amount_2 * 1667 / 10_000; assert!(expected_admin_a_2 > 0, "expected second admin portion > 0"); - let balance_a_pre_claim_2 = - get_token_account_balance(&ts.svm, &ts.holder_account_a).unwrap(); + let balance_a_pre_claim_2 = get_token_account_balance(&ts.svm, &ts.holder_account_a).unwrap(); // Bump the blockhash so this claim-ix tx isn't byte-identical to the // earlier one (same accounts + same payload → same signature → @@ -656,8 +667,7 @@ fn test_claim_admin_fees() { ) .unwrap(); - let balance_a_post_claim_2 = - get_token_account_balance(&ts.svm, &ts.holder_account_a).unwrap(); + let balance_a_post_claim_2 = get_token_account_balance(&ts.svm, &ts.holder_account_a).unwrap(); assert_eq!( balance_a_post_claim_2 - balance_a_pre_claim_2, expected_admin_a_2, @@ -701,7 +711,9 @@ fn test_claim_admin_fees() { ); let err_msg = format!("{:?}", result.unwrap_err()); assert!( - err_msg.contains("NothingToClaim") || err_msg.contains("0x1777") || err_msg.contains("6007"), + err_msg.contains("NothingToClaim") + || err_msg.contains("0x1777") + || err_msg.contains("6007"), "expected NothingToClaim error, got: {err_msg}" ); @@ -791,7 +803,7 @@ fn deposit_ix(ts: &TestSetup, amount_a: u64, amount_b: u64) -> Instruction { payer: ts.payer.pubkey(), token_program: token_program_id(), associated_token_program: ata_program_id(), - system_program: system_program::id(), + system_program: system_program::ID, } .to_account_metas(None), ) @@ -847,7 +859,10 @@ fn test_deposit_into_funded_pool_at_correct_ratio() { 2_000_000, "pool_b should grow by the full requested amount_b" ); - assert!(lp_after > lp_before, "LP tokens should be minted to depositor"); + assert!( + lp_after > lp_before, + "LP tokens should be minted to depositor" + ); } /// Test B: depositor offers more token B than the ratio needs. `amount_b` @@ -964,7 +979,7 @@ fn test_deposit_after_swap_uses_shifted_effective_ratio() { payer: ts.payer.pubkey(), token_program: token_program_id(), associated_token_program: ata_program_id(), - system_program: system_program::id(), + system_program: system_program::ID, } .to_account_metas(None), ); @@ -980,8 +995,8 @@ fn test_deposit_after_swap_uses_shifted_effective_ratio() { let pool_b_after_swap = get_token_account_balance(&ts.svm, &ts.pool_b).unwrap(); let admin_owed_a: u64 = { let account = ts.svm.get_account(&ts.pool_config_key).unwrap(); - // PoolConfig layout: 8-byte anchor discriminator, then Pubkey config - // (32), Pubkey mint_a (32), Pubkey mint_b (32), u64 + // PoolConfig layout: 8-byte anchor discriminator, then Address config + // (32), Address mint_a (32), Address mint_b (32), u64 // admin_fees_owed_a (8), u64 admin_fees_owed_b (8), u8 bump. let start = 8 + 32 * 3; u64::from_le_bytes(account.data[start..start + 8].try_into().unwrap()) @@ -999,8 +1014,8 @@ fn test_deposit_after_swap_uses_shifted_effective_ratio() { // ratio so both sides should be pulled in full. Pick a base of 1M on the // B side and compute the matching A side from effective reserves. let deposit_b = 1_000_000u64; - let deposit_a = ((deposit_b as u128) * (effective_pool_a as u128) - / (effective_pool_b as u128)) as u64; + let deposit_a = + ((deposit_b as u128) * (effective_pool_a as u128) / (effective_pool_b as u128)) as u64; let holder_a_before = get_token_account_balance(&ts.svm, &ts.holder_account_a).unwrap(); let holder_b_before = get_token_account_balance(&ts.svm, &ts.holder_account_b).unwrap(); @@ -1121,8 +1136,8 @@ fn test_lp_mint_after_swap_uses_effective_reserves() { // Deposit at exactly the effective ratio. Pick deposit_b, derive deposit_a. let deposit_b: u64 = 1_000_000; - let deposit_a = ((deposit_b as u128) * (effective_pool_a as u128) - / (effective_pool_b as u128)) as u64; + let deposit_a = + ((deposit_b as u128) * (effective_pool_a as u128) / (effective_pool_b as u128)) as u64; // Expected LP minted = min(a*supply/pool_a, b*supply/pool_b) using the // *clamped* (a, b) the program actually transfers. After clamp at the @@ -1131,12 +1146,12 @@ fn test_lp_mint_after_swap_uses_effective_reserves() { // We pass `deposit_a` exactly, so amount_b_required = deposit_a * pool_b // / pool_a, which rounds down to ≤ deposit_b. The program then uses // (deposit_a, amount_b_required). Compute the expected LP from that. - let amount_b_used = ((deposit_a as u128) * (effective_pool_b as u128) - / (effective_pool_a as u128)) as u64; - let expected_liquidity_from_a = (deposit_a as u128) * (total_supply_before_second as u128) - / (effective_pool_a as u128); - let expected_liquidity_from_b = (amount_b_used as u128) * (total_supply_before_second as u128) - / (effective_pool_b as u128); + let amount_b_used = + ((deposit_a as u128) * (effective_pool_b as u128) / (effective_pool_a as u128)) as u64; + let expected_liquidity_from_a = + (deposit_a as u128) * (total_supply_before_second as u128) / (effective_pool_a as u128); + let expected_liquidity_from_b = + (amount_b_used as u128) * (total_supply_before_second as u128) / (effective_pool_b as u128); let expected_liquidity = expected_liquidity_from_a.min(expected_liquidity_from_b) as u64; let lp_before = get_token_account_balance(&ts.svm, &ts.liquidity_account).unwrap(); @@ -1178,7 +1193,7 @@ fn swap_a_to_b_ix(ts: &TestSetup, input_amount: u64, min_output_amount: u64) -> payer: ts.payer.pubkey(), token_program: token_program_id(), associated_token_program: ata_program_id(), - system_program: system_program::id(), + system_program: system_program::ID, } .to_account_metas(None), ) @@ -1214,7 +1229,7 @@ fn deposit_ix_with_min_lp( payer: ts.payer.pubkey(), token_program: token_program_id(), associated_token_program: ata_program_id(), - system_program: system_program::id(), + system_program: system_program::ID, } .to_account_metas(None), ) @@ -1251,7 +1266,7 @@ fn withdraw_ix_with_min( payer: ts.payer.pubkey(), token_program: token_program_id(), associated_token_program: ata_program_id(), - system_program: system_program::id(), + system_program: system_program::ID, } .to_account_metas(None), ) @@ -1321,8 +1336,7 @@ fn test_deposit_reverts_when_lp_below_min() { let achievable_lp = lp_from_a.min(lp_from_b) as u64; // Require *strictly more* than that - the deposit must revert. - let strict_ix = - deposit_ix_with_min_lp(&ts, 4_000_000, 1_000_000, achievable_lp + 1); + let strict_ix = deposit_ix_with_min_lp(&ts, 4_000_000, 1_000_000, achievable_lp + 1); let result = send_transaction_from_instructions( &mut ts.svm, vec![strict_ix], @@ -1431,7 +1445,7 @@ fn swap_b_to_a_ix(ts: &TestSetup, input_amount: u64, min_output_amount: u64) -> payer: ts.payer.pubkey(), token_program: token_program_id(), associated_token_program: ata_program_id(), - system_program: system_program::id(), + system_program: system_program::ID, } .to_account_metas(None), ) diff --git a/finance/vault-strategy/anchor/programs/mock-swap-router/src/instructions/swap_usdc_for_asset.rs b/finance/vault-strategy/anchor/programs/mock-swap-router/src/instructions/swap_usdc_for_asset.rs index 6e6032a1f..a91cadec1 100644 --- a/finance/vault-strategy/anchor/programs/mock-swap-router/src/instructions/swap_usdc_for_asset.rs +++ b/finance/vault-strategy/anchor/programs/mock-swap-router/src/instructions/swap_usdc_for_asset.rs @@ -97,7 +97,11 @@ pub fn handle_swap_usdc_for_asset( authority: context.accounts.caller.cpi_handle(), }; let cpi_ctx = CpiContext::new(context.accounts.token_program.address(), transfer_accounts); - transfer_checked(cpi_ctx, usdc_amount_in, context.accounts.usdc_mint.decimals())?; + transfer_checked( + cpi_ctx, + usdc_amount_in, + context.accounts.usdc_mint.decimals(), + )?; // Mint asset tokens to caller - router_authority PDA signs let router_authority_bump = context.bumps.router_authority; diff --git a/finance/vault-strategy/anchor/programs/vault-strategy/src/instructions/withdraw.rs b/finance/vault-strategy/anchor/programs/vault-strategy/src/instructions/withdraw.rs index 87cc2bb42..cc3c21697 100644 --- a/finance/vault-strategy/anchor/programs/vault-strategy/src/instructions/withdraw.rs +++ b/finance/vault-strategy/anchor/programs/vault-strategy/src/instructions/withdraw.rs @@ -165,7 +165,11 @@ pub fn handle_withdraw<'info>( config.vault, VaultError::InvalidAssetAccount ); - require_keys_eq!(mint_ai.address(), config.mint, VaultError::InvalidAssetAccount); + require_keys_eq!( + mint_ai.address(), + config.mint, + VaultError::InvalidAssetAccount + ); let (recipient_mint, recipient_owner) = read_token_mint_and_owner(user_ata_ai)?; require_keys_eq!(recipient_owner, user_key, VaultError::InvalidRecipient); diff --git a/finance/vault-strategy/anchor/programs/vault-strategy/src/lib.rs b/finance/vault-strategy/anchor/programs/vault-strategy/src/lib.rs index 9f3e82e97..550cbf771 100644 --- a/finance/vault-strategy/anchor/programs/vault-strategy/src/lib.rs +++ b/finance/vault-strategy/anchor/programs/vault-strategy/src/lib.rs @@ -49,7 +49,10 @@ pub mod vault_strategy { } /// Add a curator-approved asset to the strategy at the next index. Manager only. - pub fn add_asset(context: &mut Context, weight_bps: u16) -> Result<()> { + pub fn add_asset( + context: &mut Context, + weight_bps: u16, + ) -> Result<()> { instructions::add_asset::handle_add_asset(context, weight_bps) } diff --git a/finance/vault-strategy/anchor/programs/vault-strategy/tests/vault_strategy.rs b/finance/vault-strategy/anchor/programs/vault-strategy/tests/vault_strategy.rs index 469eb2b41..e77864f34 100644 --- a/finance/vault-strategy/anchor/programs/vault-strategy/tests/vault_strategy.rs +++ b/finance/vault-strategy/anchor/programs/vault-strategy/tests/vault_strategy.rs @@ -1,10 +1,7 @@ use { anchor_lang::{ - solana_program::{ - clock::Clock, instruction::AccountMeta, instruction::Instruction, pubkey::Pubkey, - system_program, - }, - AccountDeserialize, InstructionData, ToAccountMetas, + solana_program::{clock::Clock, instruction::AccountMeta, instruction::Instruction}, + system_program, AccountDeserialize, Address, InstructionData, ToAccountMetas, }, anchor_spl::token::spl_token, litesvm::LiteSVM, @@ -18,26 +15,26 @@ use { solana_signer::Signer, }; -fn token_program_id() -> Pubkey { +fn token_program_id() -> Address { "TokenkegQfeZyiNwAJbNbGKPFXCWuBvf9Ss623VQ5DA" .parse() .unwrap() } -fn ata_program_id() -> Pubkey { +fn ata_program_id() -> Address { "ATokenGPvbdGVxr1b2hvZbsiqW5xWH25efTNsLJA8knL" .parse() .unwrap() } -fn pyth_receiver_program_id() -> Pubkey { +fn pyth_receiver_program_id() -> Address { "rec5EKMGg6MxZYaMdyBfgwp4d5rB9T1VQH5pJv5LtFJ" .parse() .unwrap() } -fn derive_ata(wallet: &Pubkey, mint: &Pubkey) -> Pubkey { - let (ata, _bump) = Pubkey::find_program_address( +fn derive_ata(wallet: &Address, mint: &Address) -> Address { + let (ata, _bump) = Address::find_program_address( &[wallet.as_ref(), token_program_id().as_ref(), mint.as_ref()], &ata_program_id(), ); @@ -64,7 +61,7 @@ fn build_mock_price_update_account(price: i64, exponent: i32, publish_time: i64) data } -fn set_price_feed(svm: &mut LiteSVM, key: Pubkey, price: i64) { +fn set_price_feed(svm: &mut LiteSVM, key: Address, price: i64) { let data = build_mock_price_update_account(price, -8, PUBLISH_TIME); let rent = svm.minimum_balance_for_rent_exemption(data.len()); svm.set_account( @@ -95,33 +92,33 @@ const STRATEGY_INDEX: u64 = 0; // strategy PDA seed: "strategy" + 0 struct TestContext { svm: LiteSVM, - vault_program_id: Pubkey, - router_program_id: Pubkey, + vault_program_id: Address, + router_program_id: Address, manager: Keypair, payer: Keypair, - usdc_mint: Pubkey, - tsla_mint: Pubkey, - nvda_mint: Pubkey, - strategy_pda: Pubkey, - share_mint_pda: Pubkey, - registry_pda: Pubkey, - approved_tsla: Pubkey, - approved_nvda: Pubkey, - router_config_pda: Pubkey, - router_authority_pda: Pubkey, - tsla_rate_pda: Pubkey, - nvda_rate_pda: Pubkey, - vault_usdc: Pubkey, - vault_tsla: Pubkey, - vault_nvda: Pubkey, - router_usdc_treasury: Pubkey, - price_feed_tsla: Pubkey, - price_feed_nvda: Pubkey, + usdc_mint: Address, + tsla_mint: Address, + nvda_mint: Address, + strategy_pda: Address, + share_mint_pda: Address, + registry_pda: Address, + approved_tsla: Address, + approved_nvda: Address, + router_config_pda: Address, + router_authority_pda: Address, + tsla_rate_pda: Address, + nvda_rate_pda: Address, + vault_usdc: Address, + vault_tsla: Address, + vault_nvda: Address, + router_usdc_treasury: Address, + price_feed_tsla: Address, + price_feed_nvda: Address, } impl TestContext { - fn asset_config(&self, index: u8) -> Pubkey { - Pubkey::find_program_address( + fn asset_config(&self, index: u8) -> Address { + Address::find_program_address( &[b"asset", self.strategy_pda.as_ref(), &[index]], &self.vault_program_id, ) @@ -168,7 +165,7 @@ fn setup_full() -> TestContext { let nvda_mint = create_token_mint(&mut svm, &payer, TOKEN_DECIMALS, None).unwrap(); let (router_authority_pda, _) = - Pubkey::find_program_address(&[b"router_authority"], &router_program_id); + Address::find_program_address(&[b"router_authority"], &router_program_id); // The router mints basket assets on swap, so it must hold their mint authority. for basket_mint in [&tsla_mint, &nvda_mint] { @@ -184,28 +181,28 @@ fn setup_full() -> TestContext { send_transaction_from_instructions(&mut svm, vec![ix], &[&payer], &payer.pubkey()).unwrap(); } - let (strategy_pda, _) = Pubkey::find_program_address( + let (strategy_pda, _) = Address::find_program_address( &[b"strategy", STRATEGY_INDEX.to_le_bytes().as_ref()], &vault_program_id, ); let (share_mint_pda, _) = - Pubkey::find_program_address(&[b"share_mint", strategy_pda.as_ref()], &vault_program_id); + Address::find_program_address(&[b"share_mint", strategy_pda.as_ref()], &vault_program_id); let (registry_pda, _) = - Pubkey::find_program_address(&[b"registry", payer.pubkey().as_ref()], &vault_program_id); - let (approved_tsla, _) = Pubkey::find_program_address( + Address::find_program_address(&[b"registry", payer.pubkey().as_ref()], &vault_program_id); + let (approved_tsla, _) = Address::find_program_address( &[b"approved_asset", registry_pda.as_ref(), tsla_mint.as_ref()], &vault_program_id, ); - let (approved_nvda, _) = Pubkey::find_program_address( + let (approved_nvda, _) = Address::find_program_address( &[b"approved_asset", registry_pda.as_ref(), nvda_mint.as_ref()], &vault_program_id, ); let (router_config_pda, _) = - Pubkey::find_program_address(&[b"router_config"], &router_program_id); + Address::find_program_address(&[b"router_config"], &router_program_id); let (tsla_rate_pda, _) = - Pubkey::find_program_address(&[b"rate", tsla_mint.as_ref()], &router_program_id); + Address::find_program_address(&[b"rate", tsla_mint.as_ref()], &router_program_id); let (nvda_rate_pda, _) = - Pubkey::find_program_address(&[b"rate", nvda_mint.as_ref()], &router_program_id); + Address::find_program_address(&[b"rate", nvda_mint.as_ref()], &router_program_id); let vault_usdc = derive_ata(&strategy_pda, &usdc_mint); let vault_tsla = derive_ata(&strategy_pda, &tsla_mint); @@ -227,7 +224,7 @@ fn setup_full() -> TestContext { router_config: router_config_pda, router_authority: router_authority_pda, token_program: token_program_id(), - system_program: system_program::id(), + system_program: system_program::ID, } .to_account_metas(None), ); @@ -255,7 +252,7 @@ fn setup_full() -> TestContext { router_usdc_treasury, associated_token_program: ata_program_id(), token_program: token_program_id(), - system_program: system_program::id(), + system_program: system_program::ID, } .to_account_metas(None), ); @@ -278,7 +275,7 @@ fn setup_full() -> TestContext { vault_strategy::accounts::InitializeRegistryAccountConstraints { authority: payer.pubkey(), registry: registry_pda, - system_program: system_program::id(), + system_program: system_program::ID, } .to_account_metas(None), ); @@ -302,7 +299,7 @@ fn setup_full() -> TestContext { registry: registry_pda, asset_mint: mint, approved_asset: entry, - system_program: system_program::id(), + system_program: system_program::ID, } .to_account_metas(None), ); @@ -336,7 +333,7 @@ fn setup_full() -> TestContext { } } -fn init_strategy(ctx: &mut TestContext, fee_bps: u16, slippage_bps: u16, router: Pubkey) { +fn init_strategy(ctx: &mut TestContext, fee_bps: u16, slippage_bps: u16, router: Address) { let ix = Instruction::new_with_bytes( ctx.vault_program_id, &vault_strategy::instruction::InitializeStrategy { @@ -355,7 +352,7 @@ fn init_strategy(ctx: &mut TestContext, fee_bps: u16, slippage_bps: u16, router: vault_usdc: ctx.vault_usdc, associated_token_program: ata_program_id(), token_program: token_program_id(), - system_program: system_program::id(), + system_program: system_program::ID, } .to_account_metas(None), ); @@ -371,9 +368,9 @@ fn init_strategy(ctx: &mut TestContext, fee_bps: u16, slippage_bps: u16, router: fn add_asset( ctx: &mut TestContext, index: u8, - mint: Pubkey, - approved_asset: Pubkey, - vault: Pubkey, + mint: Address, + approved_asset: Address, + vault: Address, weight_bps: u16, ) -> Result<(), solana_kite::SolanaKiteError> { let asset_config = ctx.asset_config(index); @@ -390,7 +387,7 @@ fn add_asset( vault_asset: vault, associated_token_program: ata_program_id(), token_program: token_program_id(), - system_program: system_program::id(), + system_program: system_program::ID, } .to_account_metas(None), ); @@ -416,11 +413,11 @@ fn standard_strategy(ctx: &mut TestContext) { /// [asset_config, vault, mint, rate, price_feed]. Deposit deploys into the asset, /// so vault and mint must be writable. fn asset_deposit_metas( - config: Pubkey, - vault: Pubkey, - mint: Pubkey, - rate: Pubkey, - feed: Pubkey, + config: Address, + vault: Address, + mint: Address, + rate: Address, + feed: Address, ) -> Vec { vec![ AccountMeta::new_readonly(config, false), @@ -470,7 +467,7 @@ fn deposit_named_metas(ctx: &TestContext, user: &Keypair) -> Vec { swap_router_program: ctx.router_program_id, associated_token_program: ata_program_id(), token_program: token_program_id(), - system_program: system_program::id(), + system_program: system_program::ID, } .to_account_metas(None) } @@ -501,7 +498,7 @@ fn do_deposit( user: &Keypair, usdc_amount: u64, minimum_shares: u64, -) -> Pubkey { +) -> Address { let remaining = deposit_remaining(ctx); let ix = deposit_instruction(ctx, user, usdc_amount, minimum_shares, remaining); send_transaction_from_instructions(&mut ctx.svm, vec![ix], &[user], &user.pubkey()).unwrap(); @@ -514,7 +511,7 @@ fn do_deposit_tsla_only( user: &Keypair, usdc_amount: u64, minimum_shares: u64, -) -> Pubkey { +) -> Address { let remaining = deposit_remaining_tsla(ctx); let ix = deposit_instruction(ctx, user, usdc_amount, minimum_shares, remaining); send_transaction_from_instructions(&mut ctx.svm, vec![ix], &[user], &user.pubkey()).unwrap(); @@ -523,7 +520,7 @@ fn do_deposit_tsla_only( /// Update the router's exchange rate for a mint (and its Pyth feed stays the caller's /// job). Used to keep the router quote in step with a price move. -fn set_router_rate(ctx: &mut TestContext, mint: Pubkey, rate: u64, rate_pda: Pubkey) { +fn set_router_rate(ctx: &mut TestContext, mint: Address, rate: u64, rate_pda: Address) { let ix = Instruction::new_with_bytes( ctx.router_program_id, &mock_swap_router::instruction::SetRate { @@ -541,7 +538,7 @@ fn set_router_rate(ctx: &mut TestContext, mint: Pubkey, rate: u64, rate_pda: Pub router_usdc_treasury: ctx.router_usdc_treasury, associated_token_program: ata_program_id(), token_program: token_program_id(), - system_program: system_program::id(), + system_program: system_program::ID, } .to_account_metas(None), ); @@ -601,7 +598,7 @@ fn read_asset_config(ctx: &TestContext, index: u8) -> vault_strategy::state::Ass /// (mint, asset_config, price_feed, vault, rate_pda) for an asset in the two-asset /// standard strategy: index 0 is TSLAx, index 1 is NVDAx. -fn asset_accounts(ctx: &TestContext, index: u8) -> (Pubkey, Pubkey, Pubkey, Pubkey, Pubkey) { +fn asset_accounts(ctx: &TestContext, index: u8) -> (Address, Address, Address, Address, Address) { match index { 0 => ( ctx.tsla_mint, @@ -659,7 +656,7 @@ fn do_rebalance( swap_router_program: ctx.router_program_id, associated_token_program: ata_program_id(), token_program: token_program_id(), - system_program: system_program::id(), + system_program: system_program::ID, } .to_account_metas(None), ); @@ -683,7 +680,7 @@ fn advance_one_year(ctx: &mut TestContext) { }); } -fn do_collect_fees(ctx: &mut TestContext) -> Pubkey { +fn do_collect_fees(ctx: &mut TestContext) -> Address { let manager_share = derive_ata(&ctx.manager.pubkey(), &ctx.share_mint_pda); let ix = Instruction::new_with_bytes( ctx.vault_program_id, @@ -696,7 +693,7 @@ fn do_collect_fees(ctx: &mut TestContext) -> Pubkey { payer: ctx.payer.pubkey(), associated_token_program: ata_program_id(), token_program: token_program_id(), - system_program: system_program::id(), + system_program: system_program::ID, } .to_account_metas(None), ); @@ -753,7 +750,7 @@ fn test_add_asset_rejects_unapproved() { // A mint that was never approved: its approved_asset PDA does not exist. let rogue_mint = create_token_mint(&mut ctx.svm, &ctx.payer, TOKEN_DECIMALS, None).unwrap(); - let (rogue_entry, _) = Pubkey::find_program_address( + let (rogue_entry, _) = Address::find_program_address( &[ b"approved_asset", ctx.registry_pda.as_ref(), @@ -782,9 +779,9 @@ fn test_add_asset_rejects_weight_overflow() { /// Create a fresh mint and approve it in the registry. The bound price feed is an /// arbitrary pubkey: callers that never value this asset (e.g. the cap boundary test) /// do not need a real feed account. -fn create_and_approve_mint(ctx: &mut TestContext) -> (Pubkey, Pubkey) { +fn create_and_approve_mint(ctx: &mut TestContext) -> (Address, Address) { let mint = create_token_mint(&mut ctx.svm, &ctx.payer, TOKEN_DECIMALS, None).unwrap(); - let (entry, _) = Pubkey::find_program_address( + let (entry, _) = Address::find_program_address( &[b"approved_asset", ctx.registry_pda.as_ref(), mint.as_ref()], &ctx.vault_program_id, ); @@ -799,7 +796,7 @@ fn create_and_approve_mint(ctx: &mut TestContext) -> (Pubkey, Pubkey) { registry: ctx.registry_pda, asset_mint: mint, approved_asset: entry, - system_program: system_program::id(), + system_program: system_program::ID, } .to_account_metas(None), ); @@ -853,7 +850,7 @@ fn test_initialize_rejects_excessive_fee() { vault_usdc: ctx.vault_usdc, associated_token_program: ata_program_id(), token_program: token_program_id(), - system_program: system_program::id(), + system_program: system_program::ID, } .to_account_metas(None), ); @@ -888,7 +885,7 @@ fn test_initialize_rejects_excessive_slippage() { vault_usdc: ctx.vault_usdc, associated_token_program: ata_program_id(), token_program: token_program_id(), - system_program: system_program::id(), + system_program: system_program::ID, } .to_account_metas(None), ); @@ -988,7 +985,7 @@ fn test_deposit_rejects_slippage() { fn test_deposit_rejects_unregistered_router() { let mut ctx = setup_full(); // Register a different router than the deployed mock, then fully allocate 40/60. - let bogus_router = Pubkey::new_unique(); + let bogus_router = Address::new_unique(); init_strategy(&mut ctx, FEE_BPS, SLIPPAGE_BPS, bogus_router); let (tm, wt, vt) = (ctx.tsla_mint, ctx.approved_tsla, ctx.vault_tsla); add_asset(&mut ctx, 0, tm, wt, vt, 4000).unwrap(); @@ -1088,7 +1085,7 @@ fn test_collect_fees() { ); } -fn withdraw_remaining(ctx: &TestContext, user: &Pubkey) -> Vec { +fn withdraw_remaining(ctx: &TestContext, user: &Address) -> Vec { vec![ AccountMeta::new_readonly(ctx.asset_config(0), false), AccountMeta::new(ctx.vault_tsla, false), @@ -1128,7 +1125,7 @@ fn test_withdraw() { vault_usdc: ctx.vault_usdc, associated_token_program: ata_program_id(), token_program: token_program_id(), - system_program: system_program::id(), + system_program: system_program::ID, } .to_account_metas(None); metas.extend(withdraw_remaining(&ctx, &user.pubkey())); @@ -1181,7 +1178,7 @@ fn test_withdraw_rejects_slippage() { vault_usdc: ctx.vault_usdc, associated_token_program: ata_program_id(), token_program: token_program_id(), - system_program: system_program::id(), + system_program: system_program::ID, } .to_account_metas(None); metas.extend(withdraw_remaining(&ctx, &user.pubkey())); @@ -1232,7 +1229,7 @@ fn do_withdraw(ctx: &mut TestContext, user: &Keypair, shares: u64, min_usdc_out: vault_usdc: ctx.vault_usdc, associated_token_program: ata_program_id(), token_program: token_program_id(), - system_program: system_program::id(), + system_program: system_program::ID, } .to_account_metas(None); metas.extend(withdraw_remaining(ctx, &user.pubkey())); diff --git a/tokens/create-token/anchor/programs/create-token/tests/test_create_token.rs b/tokens/create-token/anchor/programs/create-token/tests/test_create_token.rs index 11e7bf7a0..8c4658322 100644 --- a/tokens/create-token/anchor/programs/create-token/tests/test_create_token.rs +++ b/tokens/create-token/anchor/programs/create-token/tests/test_create_token.rs @@ -1,7 +1,7 @@ use { anchor_lang::{ - solana_program::{instruction::Instruction, pubkey::Pubkey, system_program}, - InstructionData, ToAccountMetas, + solana_program::instruction::Instruction, system_program, Address, InstructionData, + ToAccountMetas, }, litesvm::LiteSVM, solana_keypair::Keypair, @@ -9,25 +9,25 @@ use { solana_signer::Signer, }; -fn metadata_program_id() -> Pubkey { +fn metadata_program_id() -> Address { "metaqbxxUerdq28cj1RbAWkYQm3ybzjb6a8bt518x1s" .parse() .unwrap() } -fn token_program_id() -> Pubkey { +fn token_program_id() -> Address { "TokenkegQfeZyiNwAJbNbGKPFXCWuBvf9Ss623VQ5DA" .parse() .unwrap() } -fn rent_sysvar_id() -> Pubkey { +fn rent_sysvar_id() -> Address { "SysvarRent111111111111111111111111111111111" .parse() .unwrap() } -fn setup() -> (LiteSVM, Pubkey, Keypair) { +fn setup() -> (LiteSVM, Address, Keypair) { let program_id = create_token::id(); let mut svm = LiteSVM::new(); @@ -42,9 +42,9 @@ fn setup() -> (LiteSVM, Pubkey, Keypair) { (svm, program_id, payer) } -fn derive_metadata_pda(mint: &Pubkey) -> Pubkey { +fn derive_metadata_pda(mint: &Address) -> Address { let metadata_pid = metadata_program_id(); - let (pda, _bump) = Pubkey::find_program_address( + let (pda, _bump) = Address::find_program_address( &[b"metadata", metadata_pid.as_ref(), mint.as_ref()], &metadata_pid, ); @@ -72,7 +72,7 @@ fn test_create_spl_token() { mint_account: mint_keypair.pubkey(), token_metadata_program: metadata_program_id(), token_program: token_program_id(), - system_program: system_program::id(), + system_program: system_program::ID, rent: rent_sysvar_id(), } .to_account_metas(None), @@ -90,7 +90,10 @@ fn test_create_spl_token() { let mint_account = svm .get_account(&mint_keypair.pubkey()) .expect("Mint account should exist"); - assert!(!mint_account.data.is_empty(), "Mint account should have data"); + assert!( + !mint_account.data.is_empty(), + "Mint account should have data" + ); // Verify the metadata account was created let meta_account = svm @@ -123,7 +126,7 @@ fn test_create_nft() { mint_account: mint_keypair.pubkey(), token_metadata_program: metadata_program_id(), token_program: token_program_id(), - system_program: system_program::id(), + system_program: system_program::ID, rent: rent_sysvar_id(), } .to_account_metas(None), @@ -141,5 +144,8 @@ fn test_create_nft() { let mint_account = svm .get_account(&mint_keypair.pubkey()) .expect("Mint account should exist"); - assert!(!mint_account.data.is_empty(), "Mint account should have data"); + assert!( + !mint_account.data.is_empty(), + "Mint account should have data" + ); } diff --git a/tokens/external-delegate-token-master/anchor/programs/external-delegate-token-master/src/instructions/authority_transfer.rs b/tokens/external-delegate-token-master/anchor/programs/external-delegate-token-master/src/instructions/authority_transfer.rs index b309b2178..dd08df8d7 100644 --- a/tokens/external-delegate-token-master/anchor/programs/external-delegate-token-master/src/instructions/authority_transfer.rs +++ b/tokens/external-delegate-token-master/anchor/programs/external-delegate-token-master/src/instructions/authority_transfer.rs @@ -29,7 +29,10 @@ pub struct AuthorityTransferAccountConstraints { pub token_program: Interface<'static, TokenInterface>, } -pub fn handler(context: &mut Context, amount: u64) -> Result<()> { +pub fn handler( + context: &mut Context, + amount: u64, +) -> Result<()> { let transfer_accounts = TransferChecked { from: context.accounts.user_token_account.cpi_handle_mut(), mint: context.accounts.mint.cpi_handle(), diff --git a/tokens/external-delegate-token-master/anchor/programs/external-delegate-token-master/src/instructions/transfer_tokens.rs b/tokens/external-delegate-token-master/anchor/programs/external-delegate-token-master/src/instructions/transfer_tokens.rs index f277837c4..c9a87950a 100644 --- a/tokens/external-delegate-token-master/anchor/programs/external-delegate-token-master/src/instructions/transfer_tokens.rs +++ b/tokens/external-delegate-token-master/anchor/programs/external-delegate-token-master/src/instructions/transfer_tokens.rs @@ -3,7 +3,9 @@ use anchor_spl::token_interface::{ transfer_checked, Mint, TokenAccount, TokenInterface, TransferChecked, }; -use crate::{build_transfer_authorization_message, verify_ethereum_signature, ErrorCode, UserAccount}; +use crate::{ + build_transfer_authorization_message, verify_ethereum_signature, ErrorCode, UserAccount, +}; #[derive(Accounts)] pub struct TransferTokensAccountConstraints { diff --git a/tokens/external-delegate-token-master/anchor/programs/external-delegate-token-master/tests/test_external_delegate.rs b/tokens/external-delegate-token-master/anchor/programs/external-delegate-token-master/tests/test_external_delegate.rs index 4b6d73bdc..840fc67e5 100644 --- a/tokens/external-delegate-token-master/anchor/programs/external-delegate-token-master/tests/test_external_delegate.rs +++ b/tokens/external-delegate-token-master/anchor/programs/external-delegate-token-master/tests/test_external_delegate.rs @@ -1,7 +1,7 @@ use { anchor_lang::{ - solana_program::{instruction::Instruction, pubkey::Pubkey, system_program}, - InstructionData, ToAccountMetas, + solana_program::instruction::Instruction, system_program, Address, InstructionData, + ToAccountMetas, }, borsh::BorshDeserialize, litesvm::LiteSVM, @@ -9,7 +9,8 @@ use { solana_keypair::Keypair, solana_kite::{ create_associated_token_account, create_token_mint, create_wallet, - get_token_account_balance, mint_tokens_to_token_account, send_transaction_from_instructions, + get_token_account_balance, mint_tokens_to_token_account, + send_transaction_from_instructions, }, solana_signer::Signer, }; @@ -23,7 +24,7 @@ const TRANSFER_AMOUNT: u64 = 500_000_000; /// the secp256k1 curve order works. const DELEGATE_SECP256K1_PRIVATE_KEY: [u8; 32] = [0x42; 32]; -fn token_program_id() -> Pubkey { +fn token_program_id() -> Address { "TokenkegQfeZyiNwAJbNbGKPFXCWuBvf9Ss623VQ5DA" .parse() .unwrap() @@ -38,7 +39,7 @@ struct UserAccountState { nonce: u64, } -fn read_user_account(svm: &LiteSVM, address: &Pubkey) -> UserAccountState { +fn read_user_account(svm: &LiteSVM, address: &Address) -> UserAccountState { let account = svm.get_account(address).expect("user account should exist"); let anchor_discriminator_len = 8; UserAccountState::try_from_slice(&account.data[anchor_discriminator_len..]).unwrap() @@ -62,10 +63,10 @@ fn ethereum_address_of(secret_key: &libsecp256k1::SecretKey) -> [u8; 20] { /// Builds the exact preimage the program reconstructs onchain: /// keccak256(program id || user account || amount LE || recipient token account || nonce LE). fn build_transfer_authorization_message( - program_id: &Pubkey, - user_account: &Pubkey, + program_id: &Address, + user_account: &Address, amount: u64, - recipient_token_account: &Pubkey, + recipient_token_account: &Address, nonce: u64, ) -> [u8; 32] { let mut hasher = Keccak256::new(); @@ -90,7 +91,7 @@ fn sign_transfer_authorization( bytes } -fn setup() -> (LiteSVM, Pubkey, Keypair) { +fn setup() -> (LiteSVM, Address, Keypair) { let program_id = external_delegate_token_master::id(); let mut svm = LiteSVM::new(); @@ -103,7 +104,7 @@ fn setup() -> (LiteSVM, Pubkey, Keypair) { fn initialize_user_account( svm: &mut LiteSVM, - program_id: &Pubkey, + program_id: &Address, authority: &Keypair, user_account: &Keypair, ) { @@ -113,7 +114,7 @@ fn initialize_user_account( external_delegate_token_master::accounts::InitializeAccountConstraints { user_account: user_account.pubkey(), authority: authority.pubkey(), - system_program: system_program::id(), + system_program: system_program::ID, } .to_account_metas(None), ); @@ -128,9 +129,9 @@ fn initialize_user_account( fn set_ethereum_address( svm: &mut LiteSVM, - program_id: &Pubkey, + program_id: &Address, authority: &Keypair, - user_account: &Pubkey, + user_account: &Address, ethereum_address: [u8; 20], ) { let set_address_instruction = Instruction::new_with_bytes( @@ -157,13 +158,13 @@ fn set_ethereum_address( /// recipient token account. struct TransferFixture { svm: LiteSVM, - program_id: Pubkey, + program_id: Address, authority: Keypair, - user_account: Pubkey, - user_pda: Pubkey, - mint: Pubkey, - user_pda_token_account: Pubkey, - recipient_token_account: Pubkey, + user_account: Address, + user_pda: Address, + mint: Address, + user_pda_token_account: Address, + recipient_token_account: Address, } fn setup_transfer_fixture() -> TransferFixture { @@ -180,13 +181,19 @@ fn setup_transfer_fixture() -> TransferFixture { ethereum_address_of(&delegate_secret_key()), ); - let (user_pda, _bump) = Pubkey::find_program_address(&[user_account.as_ref()], &program_id); + let (user_pda, _bump) = Address::find_program_address(&[user_account.as_ref()], &program_id); let mint = create_token_mint(&mut svm, &authority, MINT_DECIMALS, None).unwrap(); let user_pda_token_account = create_associated_token_account(&mut svm, &user_pda, &mint, &authority).unwrap(); - mint_tokens_to_token_account(&mut svm, &mint, &user_pda_token_account, MINT_AMOUNT, &authority) - .unwrap(); + mint_tokens_to_token_account( + &mut svm, + &mint, + &user_pda_token_account, + MINT_AMOUNT, + &authority, + ) + .unwrap(); let recipient = Keypair::new(); let recipient_token_account = @@ -206,8 +213,8 @@ fn setup_transfer_fixture() -> TransferFixture { fn build_transfer_tokens_instruction( fixture: &TransferFixture, - authority: &Pubkey, - recipient_token_account: &Pubkey, + authority: &Address, + recipient_token_account: &Address, amount: u64, signature: [u8; 65], ) -> Instruction { @@ -295,7 +302,10 @@ fn test_transfer_tokens_with_valid_signature_moves_tokens_and_increments_nonce() get_token_account_balance(&fixture.svm, &fixture.user_pda_token_account).unwrap(), MINT_AMOUNT - TRANSFER_AMOUNT ); - assert_eq!(read_user_account(&fixture.svm, &fixture.user_account).nonce, 1); + assert_eq!( + read_user_account(&fixture.svm, &fixture.user_account).nonce, + 1 + ); } #[test] @@ -336,14 +346,20 @@ fn test_transfer_tokens_replayed_signature_fails() { &[&fixture.authority], &authority_pubkey, ); - assert!(replay_result.is_err(), "replayed signature must be rejected"); + assert!( + replay_result.is_err(), + "replayed signature must be rejected" + ); // Exactly one transfer happened. assert_eq!( get_token_account_balance(&fixture.svm, &fixture.recipient_token_account).unwrap(), TRANSFER_AMOUNT ); - assert_eq!(read_user_account(&fixture.svm, &fixture.user_account).nonce, 1); + assert_eq!( + read_user_account(&fixture.svm, &fixture.user_account).nonce, + 1 + ); } #[test] @@ -383,7 +399,10 @@ fn test_transfer_tokens_signature_over_different_amount_fails() { get_token_account_balance(&fixture.svm, &fixture.recipient_token_account).unwrap(), 0 ); - assert_eq!(read_user_account(&fixture.svm, &fixture.user_account).nonce, 0); + assert_eq!( + read_user_account(&fixture.svm, &fixture.user_account).nonce, + 0 + ); } #[test] diff --git a/tokens/nft-minter/anchor/programs/nft-minter/tests/test_nft_minter.rs b/tokens/nft-minter/anchor/programs/nft-minter/tests/test_nft_minter.rs index 733c91afe..f850acc46 100644 --- a/tokens/nft-minter/anchor/programs/nft-minter/tests/test_nft_minter.rs +++ b/tokens/nft-minter/anchor/programs/nft-minter/tests/test_nft_minter.rs @@ -1,60 +1,58 @@ use { anchor_lang::{ - solana_program::{instruction::Instruction, pubkey::Pubkey, system_program}, - InstructionData, ToAccountMetas, + solana_program::instruction::Instruction, system_program, Address, InstructionData, + ToAccountMetas, }, litesvm::LiteSVM, solana_keypair::Keypair, - solana_kite::{ - create_wallet, get_token_account_balance, send_transaction_from_instructions, - }, + solana_kite::{create_wallet, get_token_account_balance, send_transaction_from_instructions}, solana_signer::Signer, }; -fn token_program_id() -> Pubkey { +fn token_program_id() -> Address { "TokenkegQfeZyiNwAJbNbGKPFXCWuBvf9Ss623VQ5DA" .parse() .unwrap() } -fn ata_program_id() -> Pubkey { +fn ata_program_id() -> Address { "ATokenGPvbdGVxr1b2hvZbsiqW5xWH25efTNsLJA8knL" .parse() .unwrap() } -fn metadata_program_id() -> Pubkey { +fn metadata_program_id() -> Address { "metaqbxxUerdq28cj1RbAWkYQm3ybzjb6a8bt518x1s" .parse() .unwrap() } -fn rent_sysvar_id() -> Pubkey { +fn rent_sysvar_id() -> Address { "SysvarRent111111111111111111111111111111111" .parse() .unwrap() } -fn derive_ata(wallet: &Pubkey, mint: &Pubkey) -> Pubkey { - let (ata, _bump) = Pubkey::find_program_address( +fn derive_ata(wallet: &Address, mint: &Address) -> Address { + let (ata, _bump) = Address::find_program_address( &[wallet.as_ref(), token_program_id().as_ref(), mint.as_ref()], &ata_program_id(), ); ata } -fn derive_metadata_pda(mint: &Pubkey) -> Pubkey { +fn derive_metadata_pda(mint: &Address) -> Address { let metadata_pid = metadata_program_id(); - let (pda, _bump) = Pubkey::find_program_address( + let (pda, _bump) = Address::find_program_address( &[b"metadata", metadata_pid.as_ref(), mint.as_ref()], &metadata_pid, ); pda } -fn derive_edition_pda(mint: &Pubkey) -> Pubkey { +fn derive_edition_pda(mint: &Address) -> Address { let metadata_pid = metadata_program_id(); - let (pda, _bump) = Pubkey::find_program_address( + let (pda, _bump) = Address::find_program_address( &[ b"metadata", metadata_pid.as_ref(), @@ -66,7 +64,7 @@ fn derive_edition_pda(mint: &Pubkey) -> Pubkey { pda } -fn setup() -> (LiteSVM, Pubkey, Keypair) { +fn setup() -> (LiteSVM, Address, Keypair) { let program_id = nft_minter::id(); let mut svm = LiteSVM::new(); @@ -107,7 +105,7 @@ fn test_mint_nft() { token_program: token_program_id(), token_metadata_program: metadata_program_id(), associated_token_program: ata_program_id(), - system_program: system_program::id(), + system_program: system_program::ID, rent: rent_sysvar_id(), } .to_account_metas(None), diff --git a/tokens/nft-operations/anchor/programs/mint-nft/src/instructions/verify_collection.rs b/tokens/nft-operations/anchor/programs/mint-nft/src/instructions/verify_collection.rs index 667b243db..ec2157e1b 100644 --- a/tokens/nft-operations/anchor/programs/mint-nft/src/instructions/verify_collection.rs +++ b/tokens/nft-operations/anchor/programs/mint-nft/src/instructions/verify_collection.rs @@ -1,19 +1,13 @@ use anchor_lang::prelude::*; use anchor_spl::metadata::mpl_token_metadata::instructions::{ - VerifyCollectionV1Cpi, - VerifyCollectionV1CpiAccounts, -}; -use anchor_spl::metadata::{ - MasterEditionAccount, - MetadataAccount, -}; -use anchor_spl::{ - token::Mint, - metadata::Metadata, + VerifyCollectionV1Cpi, VerifyCollectionV1CpiAccounts, }; +use anchor_spl::metadata::{MasterEditionAccount, MetadataAccount}; +use anchor_spl::{metadata::Metadata, token::Mint}; // In Anchor 1.0, sysvar::instructions::ID moved - use the well-known address directly -const INSTRUCTIONS_SYSVAR_ID: Address = anchor_lang::solana_program::pubkey::pubkey!("Sysvar1nstructions1111111111111111111111111"); +const INSTRUCTIONS_SYSVAR_ID: Address = + anchor_lang::solana_program::pubkey::pubkey!("Sysvar1nstructions1111111111111111111111111"); #[derive(Accounts)] pub struct VerifyCollectionMintAccountConstraints { @@ -42,23 +36,20 @@ pub fn handle_verify_collection( accounts: &mut VerifyCollectionMintAccountConstraints, bumps: &VerifyCollectionMintAccountConstraintsBumps, ) -> Result<()> { - let metadata = &accounts.metadata.cpi_handle_mut(); - let authority = &accounts.mint_authority.cpi_handle_mut(); - let collection_mint = &accounts.collection_mint.cpi_handle_mut(); - let collection_metadata = &accounts.collection_metadata.cpi_handle_mut(); - let collection_master_edition = &accounts.collection_master_edition.cpi_handle_mut(); - let system_program = &accounts.system_program.cpi_handle_mut(); - let sysvar_instructions = &accounts.sysvar_instruction.cpi_handle_mut(); - let spl_metadata_program = &accounts.token_metadata_program.cpi_handle_mut(); + let metadata = &accounts.metadata.cpi_handle_mut(); + let authority = &accounts.mint_authority.cpi_handle_mut(); + let collection_mint = &accounts.collection_mint.cpi_handle_mut(); + let collection_metadata = &accounts.collection_metadata.cpi_handle_mut(); + let collection_master_edition = &accounts.collection_master_edition.cpi_handle_mut(); + let system_program = &accounts.system_program.cpi_handle_mut(); + let sysvar_instructions = &accounts.sysvar_instruction.cpi_handle_mut(); + let spl_metadata_program = &accounts.token_metadata_program.cpi_handle_mut(); - let seeds = &[ - &b"authority"[..], - &[bumps.mint_authority] - ]; - let signer_seeds = &[&seeds[..]]; + let seeds = &[&b"authority"[..], &[bumps.mint_authority]]; + let signer_seeds = &[&seeds[..]]; - let verify_collection = VerifyCollectionV1Cpi::new( - spl_metadata_program, + let verify_collection = VerifyCollectionV1Cpi::new( + spl_metadata_program, VerifyCollectionV1CpiAccounts { authority, delegate_record: None, @@ -68,10 +59,11 @@ pub fn handle_verify_collection( collection_master_edition: Some(collection_master_edition), system_program, sysvar_instructions, - }); - verify_collection.invoke_signed(signer_seeds)?; + }, + ); + verify_collection.invoke_signed(signer_seeds)?; - msg!("Collection Verified!"); - - Ok(()) - } + msg!("Collection Verified!"); + + Ok(()) +} diff --git a/tokens/nft-operations/anchor/programs/mint-nft/src/lib.rs b/tokens/nft-operations/anchor/programs/mint-nft/src/lib.rs index a26577160..a44799564 100644 --- a/tokens/nft-operations/anchor/programs/mint-nft/src/lib.rs +++ b/tokens/nft-operations/anchor/programs/mint-nft/src/lib.rs @@ -35,7 +35,13 @@ pub mod mint_nft { symbol: String, uri: String, ) -> Result<()> { - instructions::mint_nft::handle_mint_nft(&mut context.accounts, &context.bumps, name, symbol, uri) + instructions::mint_nft::handle_mint_nft( + &mut context.accounts, + &context.bumps, + name, + symbol, + uri, + ) } /// Verify an NFT as a member of the collection. diff --git a/tokens/nft-operations/anchor/programs/mint-nft/tests/test_nft_operations.rs b/tokens/nft-operations/anchor/programs/mint-nft/tests/test_nft_operations.rs index 93bb7e51c..739719c4c 100644 --- a/tokens/nft-operations/anchor/programs/mint-nft/tests/test_nft_operations.rs +++ b/tokens/nft-operations/anchor/programs/mint-nft/tests/test_nft_operations.rs @@ -1,60 +1,58 @@ use { anchor_lang::{ - solana_program::{instruction::Instruction, pubkey::Pubkey, system_program}, - InstructionData, ToAccountMetas, + solana_program::instruction::Instruction, system_program, Address, InstructionData, + ToAccountMetas, }, litesvm::LiteSVM, solana_keypair::Keypair, - solana_kite::{ - create_wallet, get_token_account_balance, send_transaction_from_instructions, - }, + solana_kite::{create_wallet, get_token_account_balance, send_transaction_from_instructions}, solana_signer::Signer, }; -fn token_program_id() -> Pubkey { +fn token_program_id() -> Address { "TokenkegQfeZyiNwAJbNbGKPFXCWuBvf9Ss623VQ5DA" .parse() .unwrap() } -fn ata_program_id() -> Pubkey { +fn ata_program_id() -> Address { "ATokenGPvbdGVxr1b2hvZbsiqW5xWH25efTNsLJA8knL" .parse() .unwrap() } -fn metadata_program_id() -> Pubkey { +fn metadata_program_id() -> Address { "metaqbxxUerdq28cj1RbAWkYQm3ybzjb6a8bt518x1s" .parse() .unwrap() } -fn instructions_sysvar_id() -> Pubkey { +fn instructions_sysvar_id() -> Address { "Sysvar1nstructions1111111111111111111111111" .parse() .unwrap() } -fn derive_ata(wallet: &Pubkey, mint: &Pubkey) -> Pubkey { - let (ata, _bump) = Pubkey::find_program_address( +fn derive_ata(wallet: &Address, mint: &Address) -> Address { + let (ata, _bump) = Address::find_program_address( &[wallet.as_ref(), token_program_id().as_ref(), mint.as_ref()], &ata_program_id(), ); ata } -fn derive_metadata_pda(mint: &Pubkey) -> Pubkey { +fn derive_metadata_pda(mint: &Address) -> Address { let metadata_pid = metadata_program_id(); - let (pda, _bump) = Pubkey::find_program_address( + let (pda, _bump) = Address::find_program_address( &[b"metadata", metadata_pid.as_ref(), mint.as_ref()], &metadata_pid, ); pda } -fn derive_edition_pda(mint: &Pubkey) -> Pubkey { +fn derive_edition_pda(mint: &Address) -> Address { let metadata_pid = metadata_program_id(); - let (pda, _bump) = Pubkey::find_program_address( + let (pda, _bump) = Address::find_program_address( &[ b"metadata", metadata_pid.as_ref(), @@ -70,10 +68,12 @@ fn derive_edition_pda(mint: &Pubkey) -> Pubkey { /// caller-supplied metadata strings landed in the Metaplex metadata account /// without fully deserializing the Metaplex layout. fn contains_bytes(haystack: &[u8], needle: &[u8]) -> bool { - haystack.windows(needle.len()).any(|window| window == needle) + haystack + .windows(needle.len()) + .any(|window| window == needle) } -fn setup() -> (LiteSVM, Pubkey, Keypair) { +fn setup() -> (LiteSVM, Address, Keypair) { let program_id = mint_nft::id(); let mut svm = LiteSVM::new(); @@ -93,7 +93,7 @@ fn test_create_collection() { let (mut svm, program_id, payer) = setup(); let collection_keypair = Keypair::new(); - let (mint_authority, _) = Pubkey::find_program_address(&[b"authority"], &program_id); + let (mint_authority, _) = Address::find_program_address(&[b"authority"], &program_id); let metadata = derive_metadata_pda(&collection_keypair.pubkey()); let master_edition = derive_edition_pda(&collection_keypair.pubkey()); @@ -114,7 +114,7 @@ fn test_create_collection() { metadata, master_edition, destination, - system_program: system_program::id(), + system_program: system_program::ID, token_program: token_program_id(), associated_token_program: ata_program_id(), token_metadata_program: metadata_program_id(), @@ -137,9 +137,7 @@ fn test_create_collection() { assert!(!mint_account.data.is_empty()); // Verify metadata exists and carries the caller-supplied name - let meta_account = svm - .get_account(&metadata) - .expect("Metadata should exist"); + let meta_account = svm.get_account(&metadata).expect("Metadata should exist"); assert!(!meta_account.data.is_empty()); assert!( contains_bytes(&meta_account.data, b"Example Collection"), @@ -161,7 +159,7 @@ fn test_create_collection() { fn test_mint_nft_to_collection() { let (mut svm, program_id, payer) = setup(); - let (mint_authority, _) = Pubkey::find_program_address(&[b"authority"], &program_id); + let (mint_authority, _) = Address::find_program_address(&[b"authority"], &program_id); // Step 1: Create the collection let collection_keypair = Keypair::new(); @@ -184,7 +182,7 @@ fn test_mint_nft_to_collection() { metadata: collection_metadata, master_edition: collection_master_edition, destination: collection_destination, - system_program: system_program::id(), + system_program: system_program::ID, token_program: token_program_id(), associated_token_program: ata_program_id(), token_metadata_program: metadata_program_id(), @@ -223,7 +221,7 @@ fn test_mint_nft_to_collection() { master_edition: nft_master_edition, mint_authority, collection_mint: collection_keypair.pubkey(), - system_program: system_program::id(), + system_program: system_program::ID, token_program: token_program_id(), associated_token_program: ata_program_id(), token_metadata_program: metadata_program_id(), @@ -258,7 +256,7 @@ fn test_mint_nft_to_collection() { fn test_verify_collection() { let (mut svm, program_id, payer) = setup(); - let (mint_authority, _) = Pubkey::find_program_address(&[b"authority"], &program_id); + let (mint_authority, _) = Address::find_program_address(&[b"authority"], &program_id); // Step 1: Create collection let collection_keypair = Keypair::new(); @@ -281,7 +279,7 @@ fn test_verify_collection() { metadata: collection_metadata, master_edition: collection_master_edition, destination: collection_destination, - system_program: system_program::id(), + system_program: system_program::ID, token_program: token_program_id(), associated_token_program: ata_program_id(), token_metadata_program: metadata_program_id(), @@ -320,7 +318,7 @@ fn test_verify_collection() { master_edition: nft_master_edition, mint_authority, collection_mint: collection_keypair.pubkey(), - system_program: system_program::id(), + system_program: system_program::ID, token_program: token_program_id(), associated_token_program: ata_program_id(), token_metadata_program: metadata_program_id(), @@ -349,20 +347,15 @@ fn test_verify_collection() { collection_mint: collection_keypair.pubkey(), collection_metadata, collection_master_edition, - system_program: system_program::id(), + system_program: system_program::ID, sysvar_instruction: instructions_sysvar_id(), token_metadata_program: metadata_program_id(), } .to_account_metas(None), ); - send_transaction_from_instructions( - &mut svm, - vec![verify_ix], - &[&payer], - &payer.pubkey(), - ) - .unwrap(); + send_transaction_from_instructions(&mut svm, vec![verify_ix], &[&payer], &payer.pubkey()) + .unwrap(); // Verify the metadata still exists after verification let nft_meta = svm diff --git a/tokens/pda-mint-authority/anchor/programs/token-minter/src/instructions/create.rs b/tokens/pda-mint-authority/anchor/programs/token-minter/src/instructions/create.rs index 21b0f275f..97253e307 100644 --- a/tokens/pda-mint-authority/anchor/programs/token-minter/src/instructions/create.rs +++ b/tokens/pda-mint-authority/anchor/programs/token-minter/src/instructions/create.rs @@ -69,7 +69,7 @@ pub fn handle_create_token( update_authority: context.accounts.mint_account.cpi_handle(), // PDA is update authority payer: context.accounts.payer.cpi_handle_mut(), system_program: context.accounts.system_program.cpi_handle(), - update_authority_is_signer: true, + update_authority_is_signer: true, }, ) .with_signer(signer_seeds), diff --git a/tokens/pda-mint-authority/anchor/programs/token-minter/tests/test_pda_mint.rs b/tokens/pda-mint-authority/anchor/programs/token-minter/tests/test_pda_mint.rs index f451e5a09..1c5f80a28 100644 --- a/tokens/pda-mint-authority/anchor/programs/token-minter/tests/test_pda_mint.rs +++ b/tokens/pda-mint-authority/anchor/programs/token-minter/tests/test_pda_mint.rs @@ -1,13 +1,11 @@ use { anchor_lang::{ - solana_program::{instruction::Instruction, pubkey::Pubkey, system_program}, - InstructionData, ToAccountMetas, + solana_program::instruction::Instruction, system_program, Address, InstructionData, + ToAccountMetas, }, litesvm::LiteSVM, solana_keypair::Keypair, - solana_kite::{ - create_wallet, get_token_account_balance, send_transaction_from_instructions, - }, + solana_kite::{create_wallet, get_token_account_balance, send_transaction_from_instructions}, solana_signer::Signer, }; @@ -21,52 +19,48 @@ fn to_minor_units(major_units: u64) -> u64 { major_units.checked_mul(10u64.pow(MINT_DECIMALS)).unwrap() } -fn metadata_program_id() -> Pubkey { +fn metadata_program_id() -> Address { "metaqbxxUerdq28cj1RbAWkYQm3ybzjb6a8bt518x1s" .parse() .unwrap() } -fn token_program_id() -> Pubkey { +fn token_program_id() -> Address { "TokenkegQfeZyiNwAJbNbGKPFXCWuBvf9Ss623VQ5DA" .parse() .unwrap() } -fn associated_token_program_id() -> Pubkey { +fn associated_token_program_id() -> Address { "ATokenGPvbdGVxr1b2hvZbsiqW5xWH25efTNsLJA8knL" .parse() .unwrap() } -fn rent_sysvar_id() -> Pubkey { +fn rent_sysvar_id() -> Address { "SysvarRent111111111111111111111111111111111" .parse() .unwrap() } -fn derive_metadata_pda(mint: &Pubkey) -> Pubkey { +fn derive_metadata_pda(mint: &Address) -> Address { let metadata_pid = metadata_program_id(); - let (pda, _bump) = Pubkey::find_program_address( + let (pda, _bump) = Address::find_program_address( &[b"metadata", metadata_pid.as_ref(), mint.as_ref()], &metadata_pid, ); pda } -fn derive_ata(wallet: &Pubkey, mint: &Pubkey) -> Pubkey { - let (ata, _bump) = Pubkey::find_program_address( - &[ - wallet.as_ref(), - token_program_id().as_ref(), - mint.as_ref(), - ], +fn derive_ata(wallet: &Address, mint: &Address) -> Address { + let (ata, _bump) = Address::find_program_address( + &[wallet.as_ref(), token_program_id().as_ref(), mint.as_ref()], &associated_token_program_id(), ); ata } -fn setup() -> (LiteSVM, Pubkey, Keypair) { +fn setup() -> (LiteSVM, Address, Keypair) { let program_id = token_minter::id(); let mut svm = LiteSVM::new(); @@ -86,7 +80,7 @@ fn test_create_token_and_mint() { let (mut svm, program_id, payer) = setup(); // Derive the PDA mint account (seeds = [b"mint"]) - let (mint_pda, _bump) = Pubkey::find_program_address(&[b"mint"], &program_id); + let (mint_pda, _bump) = Address::find_program_address(&[b"mint"], &program_id); let metadata_account = derive_metadata_pda(&mint_pda); // 1. Create token @@ -104,18 +98,13 @@ fn test_create_token_and_mint() { metadata_account, token_program: token_program_id(), token_metadata_program: metadata_program_id(), - system_program: system_program::id(), + system_program: system_program::ID, rent: rent_sysvar_id(), } .to_account_metas(None), ); - send_transaction_from_instructions( - &mut svm, - vec![create_ix], - &[&payer], - &payer.pubkey(), - ) - .unwrap(); + send_transaction_from_instructions(&mut svm, vec![create_ix], &[&payer], &payer.pubkey()) + .unwrap(); // Verify mint created let mint_account = svm.get_account(&mint_pda).expect("Mint PDA should exist"); @@ -143,17 +132,12 @@ fn test_create_token_and_mint() { associated_token_account: ata, token_program: token_program_id(), associated_token_program: associated_token_program_id(), - system_program: system_program::id(), + system_program: system_program::ID, } .to_account_metas(None), ); - send_transaction_from_instructions( - &mut svm, - vec![mint_ix], - &[&payer], - &payer.pubkey(), - ) - .unwrap(); + send_transaction_from_instructions(&mut svm, vec![mint_ix], &[&payer], &payer.pubkey()) + .unwrap(); // Verify 100 tokens minted (in minor units) let balance = get_token_account_balance(&svm, &ata).unwrap(); diff --git a/tokens/token-extensions/basics/anchor/programs/basics/src/instructions/create_associated_token_account.rs b/tokens/token-extensions/basics/anchor/programs/basics/src/instructions/create_associated_token_account.rs index b28a32b14..bc2c293fd 100644 --- a/tokens/token-extensions/basics/anchor/programs/basics/src/instructions/create_associated_token_account.rs +++ b/tokens/token-extensions/basics/anchor/programs/basics/src/instructions/create_associated_token_account.rs @@ -19,7 +19,9 @@ pub struct CreateAssociatedTokenAccountAccountConstraints { pub associated_token_program: Program, } -pub fn handler(_context: &mut Context) -> Result<()> { +pub fn handler( + _context: &mut Context, +) -> Result<()> { msg!("Create Associated Token Account"); Ok(()) } diff --git a/tokens/token-extensions/basics/anchor/programs/basics/src/instructions/create_token.rs b/tokens/token-extensions/basics/anchor/programs/basics/src/instructions/create_token.rs index 9e0f6707d..2e84bffff 100644 --- a/tokens/token-extensions/basics/anchor/programs/basics/src/instructions/create_token.rs +++ b/tokens/token-extensions/basics/anchor/programs/basics/src/instructions/create_token.rs @@ -20,7 +20,10 @@ pub struct CreateTokenAccountConstraints { pub token_program: Interface<'static, TokenInterface>, } -pub fn handler(_context: &mut Context, _token_name: String) -> Result<()> { +pub fn handler( + _context: &mut Context, + _token_name: String, +) -> Result<()> { msg!("Create Token"); Ok(()) } diff --git a/tokens/token-extensions/basics/anchor/programs/basics/src/lib.rs b/tokens/token-extensions/basics/anchor/programs/basics/src/lib.rs index 8c86d1db5..94521ea83 100644 --- a/tokens/token-extensions/basics/anchor/programs/basics/src/lib.rs +++ b/tokens/token-extensions/basics/anchor/programs/basics/src/lib.rs @@ -10,11 +10,16 @@ pub mod anchor { use super::*; - pub fn create_token(context: &mut Context, token_name: String) -> Result<()> { + pub fn create_token( + context: &mut Context, + token_name: String, + ) -> Result<()> { instructions::create_token::handler(context, token_name) } - pub fn create_token_account(context: &mut Context) -> Result<()> { + pub fn create_token_account( + context: &mut Context, + ) -> Result<()> { instructions::create_token_account::handler(context) } @@ -24,11 +29,17 @@ pub mod anchor { instructions::create_associated_token_account::handler(context) } - pub fn transfer_token(context: &mut Context, amount: u64) -> Result<()> { + pub fn transfer_token( + context: &mut Context, + amount: u64, + ) -> Result<()> { instructions::transfer_token::handler(context, amount) } - pub fn mint_token(context: &mut Context, amount: u64) -> Result<()> { + pub fn mint_token( + context: &mut Context, + amount: u64, + ) -> Result<()> { instructions::mint_token::handler(context, amount) } } diff --git a/tokens/token-extensions/basics/anchor/programs/basics/tests/test_basics.rs b/tokens/token-extensions/basics/anchor/programs/basics/tests/test_basics.rs index 21b5218c8..589ed2506 100644 --- a/tokens/token-extensions/basics/anchor/programs/basics/tests/test_basics.rs +++ b/tokens/token-extensions/basics/anchor/programs/basics/tests/test_basics.rs @@ -1,26 +1,24 @@ use { anchor_lang::{ - solana_program::{instruction::Instruction, pubkey::Pubkey, system_program}, - InstructionData, ToAccountMetas, + solana_program::instruction::Instruction, system_program, Address, InstructionData, + ToAccountMetas, }, litesvm::LiteSVM, + solana_keypair::Keypair, solana_kite::{ assert_token_account_balance, create_wallet, send_transaction_from_instructions, - token_extensions::{ - get_token_extensions_account_address, TOKEN_EXTENSIONS_PROGRAM_ID, - }, + token_extensions::{get_token_extensions_account_address, TOKEN_EXTENSIONS_PROGRAM_ID}, }, - solana_keypair::Keypair, solana_signer::Signer, }; -fn associated_token_program_id() -> Pubkey { +fn associated_token_program_id() -> Address { "ATokenGPvbdGVxr1b2hvZbsiqW5xWH25efTNsLJA8knL" .parse() .unwrap() } -fn setup() -> (LiteSVM, Pubkey, Keypair) { +fn setup() -> (LiteSVM, Address, Keypair) { let program_id = anchor::id(); let mut svm = LiteSVM::new(); @@ -38,7 +36,7 @@ fn test_create_token_and_mint_and_transfer() { let token_name = "TestToken".to_string(); // Derive the mint PDA - let (mint, _bump) = Pubkey::find_program_address( + let (mint, _bump) = Address::find_program_address( &[ b"token-2022-token", payer.pubkey().as_ref(), @@ -57,13 +55,14 @@ fn test_create_token_and_mint_and_transfer() { anchor::accounts::CreateTokenAccountConstraints { signer: payer.pubkey(), mint, - system_program: system_program::id(), + system_program: system_program::ID, token_program: TOKEN_EXTENSIONS_PROGRAM_ID, } .to_account_metas(None), ); - send_transaction_from_instructions(&mut svm, vec![create_token_ix], &[&payer], &payer.pubkey()).unwrap(); + send_transaction_from_instructions(&mut svm, vec![create_token_ix], &[&payer], &payer.pubkey()) + .unwrap(); // Verify mint account exists let mint_account = svm.get_account(&mint).expect("Mint account should exist"); @@ -81,19 +80,18 @@ fn test_create_token_and_mint_and_transfer() { signer: payer.pubkey(), mint, token_account: payer_ata, - system_program: system_program::id(), + system_program: system_program::ID, token_program: TOKEN_EXTENSIONS_PROGRAM_ID, associated_token_program: associated_token_program_id(), } .to_account_metas(None), ); - send_transaction_from_instructions(&mut svm, vec![create_ata_ix], &[&payer], &payer.pubkey()).unwrap(); + send_transaction_from_instructions(&mut svm, vec![create_ata_ix], &[&payer], &payer.pubkey()) + .unwrap(); // Verify ATA exists - let ata_account = svm - .get_account(&payer_ata) - .expect("Payer ATA should exist"); + let ata_account = svm.get_account(&payer_ata).expect("Payer ATA should exist"); assert!(!ata_account.data.is_empty(), "ATA should have data"); svm.expire_blockhash(); @@ -116,7 +114,8 @@ fn test_create_token_and_mint_and_transfer() { .to_account_metas(None), ); - send_transaction_from_instructions(&mut svm, vec![mint_token_ix], &[&payer], &payer.pubkey()).unwrap(); + send_transaction_from_instructions(&mut svm, vec![mint_token_ix], &[&payer], &payer.pubkey()) + .unwrap(); assert_token_account_balance( &svm, @@ -146,13 +145,14 @@ fn test_create_token_and_mint_and_transfer() { to_ata: receiver_ata, mint, token_program: TOKEN_EXTENSIONS_PROGRAM_ID, - system_program: system_program::id(), + system_program: system_program::ID, associated_token_program: associated_token_program_id(), } .to_account_metas(None), ); - send_transaction_from_instructions(&mut svm, vec![transfer_ix], &[&payer], &payer.pubkey()).unwrap(); + send_transaction_from_instructions(&mut svm, vec![transfer_ix], &[&payer], &payer.pubkey()) + .unwrap(); assert_token_account_balance( &svm, diff --git a/tokens/token-extensions/cpi-guard/anchor/programs/cpi-guard/tests/test_cpi_guard.rs b/tokens/token-extensions/cpi-guard/anchor/programs/cpi-guard/tests/test_cpi_guard.rs index 6f2145cc8..8c8305fa7 100644 --- a/tokens/token-extensions/cpi-guard/anchor/programs/cpi-guard/tests/test_cpi_guard.rs +++ b/tokens/token-extensions/cpi-guard/anchor/programs/cpi-guard/tests/test_cpi_guard.rs @@ -2,12 +2,13 @@ use { anchor_lang::{ solana_program::{ instruction::{AccountMeta, Instruction}, - pubkey::Pubkey, + pubkey::Address, system_program, }, InstructionData, ToAccountMetas, }, litesvm::LiteSVM, + solana_keypair::Keypair, solana_kite::{ assert_token_account_balance, create_wallet, send_transaction_from_instructions, token_extensions::{ @@ -15,11 +16,10 @@ use { TOKEN_EXTENSIONS_PROGRAM_ID, }, }, - solana_keypair::Keypair, solana_signer::Signer, }; -fn setup() -> (LiteSVM, Pubkey, Keypair) { +fn setup() -> (LiteSVM, Address, Keypair) { let program_id = cpi_guard::id(); let mut svm = LiteSVM::new(); @@ -34,12 +34,12 @@ fn setup() -> (LiteSVM, Pubkey, Keypair) { /// Uses explicit keypair - kite's ATA creation won't work here because /// we need to reallocate and add the CPI Guard extension later. fn create_basic_token_account_instructions( - payer: &Pubkey, - token_account: &Pubkey, - mint: &Pubkey, - owner: &Pubkey, + payer: &Address, + token_account: &Address, + mint: &Address, + owner: &Address, ) -> Vec { - let rent_sysvar: Pubkey = "SysvarRent111111111111111111111111111111111" + let rent_sysvar: Address = "SysvarRent111111111111111111111111111111111" .parse() .unwrap(); let create_account_ix = anchor_lang::solana_program::system_instruction::create_account( @@ -64,9 +64,9 @@ fn create_basic_token_account_instructions( /// Reallocate instruction (instruction 29) to add extension types to a token account. fn reallocate_instruction( - token_account: &Pubkey, - payer: &Pubkey, - owner: &Pubkey, + token_account: &Address, + payer: &Address, + owner: &Address, extension_types: &[u16], ) -> Instruction { let mut data = vec![29u8]; @@ -78,7 +78,7 @@ fn reallocate_instruction( accounts: vec![ AccountMeta::new(*token_account, false), AccountMeta::new(*payer, true), - AccountMeta::new_readonly(system_program::id(), false), + AccountMeta::new_readonly(system_program::ID, false), AccountMeta::new_readonly(*owner, true), ], data, @@ -86,7 +86,7 @@ fn reallocate_instruction( } /// EnableCpiGuard instruction (instruction 34, sub-instruction 0). -fn enable_cpi_guard_instruction(token_account: &Pubkey, owner: &Pubkey) -> Instruction { +fn enable_cpi_guard_instruction(token_account: &Address, owner: &Address) -> Instruction { Instruction { program_id: TOKEN_EXTENSIONS_PROGRAM_ID, accounts: vec![ @@ -98,7 +98,7 @@ fn enable_cpi_guard_instruction(token_account: &Pubkey, owner: &Pubkey) -> Instr } /// DisableCpiGuard instruction (instruction 34, sub-instruction 1). -fn disable_cpi_guard_instruction(token_account: &Pubkey, owner: &Pubkey) -> Instruction { +fn disable_cpi_guard_instruction(token_account: &Address, owner: &Address) -> Instruction { Instruction { program_id: TOKEN_EXTENSIONS_PROGRAM_ID, accounts: vec![ @@ -125,7 +125,13 @@ fn test_cpi_guard_prevents_transfer_then_allows_after_disable() { &mint, &payer.pubkey(), ); - send_transaction_from_instructions(&mut svm, token_ixs, &[&payer, &token_keypair], &payer.pubkey()).unwrap(); + send_transaction_from_instructions( + &mut svm, + token_ixs, + &[&payer, &token_keypair], + &payer.pubkey(), + ) + .unwrap(); svm.expire_blockhash(); // Step 3: Reallocate to add CPI Guard extension space @@ -136,27 +142,23 @@ fn test_cpi_guard_prevents_transfer_then_allows_after_disable() { &payer.pubkey(), &[cpi_guard_extension_type], ); - send_transaction_from_instructions(&mut svm, vec![reallocate_ix], &[&payer], &payer.pubkey()).unwrap(); + send_transaction_from_instructions(&mut svm, vec![reallocate_ix], &[&payer], &payer.pubkey()) + .unwrap(); svm.expire_blockhash(); // Step 4: Enable CPI Guard let enable_ix = enable_cpi_guard_instruction(&token_keypair.pubkey(), &payer.pubkey()); - send_transaction_from_instructions(&mut svm, vec![enable_ix], &[&payer], &payer.pubkey()).unwrap(); + send_transaction_from_instructions(&mut svm, vec![enable_ix], &[&payer], &payer.pubkey()) + .unwrap(); svm.expire_blockhash(); // Step 5: Mint 1 token to the token account - mint_tokens_to_token_extensions_account( - &mut svm, - &mint, - &token_keypair.pubkey(), - 1, - &payer, - ).unwrap(); + mint_tokens_to_token_extensions_account(&mut svm, &mint, &token_keypair.pubkey(), 1, &payer) + .unwrap(); svm.expire_blockhash(); // Step 6: Try CPI transfer - should fail because CPI Guard is enabled - let (recipient_token_account, _bump) = - Pubkey::find_program_address(&[b"pda"], &program_id); + let (recipient_token_account, _bump) = Address::find_program_address(&[b"pda"], &program_id); let transfer_ix = Instruction::new_with_bytes( program_id, @@ -167,12 +169,13 @@ fn test_cpi_guard_prevents_transfer_then_allows_after_disable() { recipient_token_account, mint_account: mint, token_program: TOKEN_EXTENSIONS_PROGRAM_ID, - system_program: system_program::id(), + system_program: system_program::ID, } .to_account_metas(None), ); - let result = send_transaction_from_instructions(&mut svm, vec![transfer_ix], &[&payer], &payer.pubkey()); + let result = + send_transaction_from_instructions(&mut svm, vec![transfer_ix], &[&payer], &payer.pubkey()); assert!( result.is_err(), "Transfer should fail when CPI Guard is enabled" @@ -181,7 +184,8 @@ fn test_cpi_guard_prevents_transfer_then_allows_after_disable() { // Step 7: Disable CPI Guard let disable_ix = disable_cpi_guard_instruction(&token_keypair.pubkey(), &payer.pubkey()); - send_transaction_from_instructions(&mut svm, vec![disable_ix], &[&payer], &payer.pubkey()).unwrap(); + send_transaction_from_instructions(&mut svm, vec![disable_ix], &[&payer], &payer.pubkey()) + .unwrap(); svm.expire_blockhash(); // Step 8: Transfer should now succeed @@ -194,12 +198,18 @@ fn test_cpi_guard_prevents_transfer_then_allows_after_disable() { recipient_token_account, mint_account: mint, token_program: TOKEN_EXTENSIONS_PROGRAM_ID, - system_program: system_program::id(), + system_program: system_program::ID, } .to_account_metas(None), ); - send_transaction_from_instructions(&mut svm, vec![transfer_ix2], &[&payer], &payer.pubkey()).unwrap(); + send_transaction_from_instructions(&mut svm, vec![transfer_ix2], &[&payer], &payer.pubkey()) + .unwrap(); - assert_token_account_balance(&svm, &recipient_token_account, 1, "Recipient should have 1 token"); + assert_token_account_balance( + &svm, + &recipient_token_account, + 1, + "Recipient should have 1 token", + ); } diff --git a/tokens/token-extensions/default-account-state/anchor/programs/default-account-state/src/instructions/initialize.rs b/tokens/token-extensions/default-account-state/anchor/programs/default-account-state/src/instructions/initialize.rs index 5044e43ed..168392910 100644 --- a/tokens/token-extensions/default-account-state/anchor/programs/default-account-state/src/instructions/initialize.rs +++ b/tokens/token-extensions/default-account-state/anchor/programs/default-account-state/src/instructions/initialize.rs @@ -6,9 +6,7 @@ use anchor_spl::{ spl_token_2022::{extension::ExtensionType, pod::PodMint, state::AccountState}, InitializeMint2, }, - token_interface::{ - default_account_state_initialize, DefaultAccountStateInitialize, Token2022, - }, + token_interface::{default_account_state_initialize, DefaultAccountStateInitialize, Token2022}, }; #[derive(Accounts)] @@ -26,9 +24,8 @@ pub struct InitializeAccountConstraints { // We can manually create and initialize the mint account via CPIs in the instruction handler pub fn handler(context: &mut Context) -> Result<()> { // Calculate space required for mint and extension data - let mint_size = ExtensionType::try_calculate_account_len::(&[ - ExtensionType::DefaultAccountState, - ])?; + let mint_size = + ExtensionType::try_calculate_account_len::(&[ExtensionType::DefaultAccountState])?; // Calculate minimum lamports required for size of mint account with extensions let lamports = Rent::get()?.try_minimum_balance(mint_size)?; @@ -42,8 +39,8 @@ pub fn handler(context: &mut Context) -> Result<() to: context.accounts.mint_account.cpi_handle_mut(), }, ), - lamports, // Lamports - mint_size as u64, // Space + lamports, // Lamports + mint_size as u64, // Space &context.accounts.token_program.address(), // Owner Program )?; @@ -67,7 +64,7 @@ pub fn handler(context: &mut Context) -> Result<() mint: context.accounts.mint_account.cpi_handle_mut(), }, ), - 2, // decimals + 2, // decimals &context.accounts.payer.address(), // mint authority Some(&context.accounts.payer.address()), // freeze authority )?; diff --git a/tokens/token-extensions/default-account-state/anchor/programs/default-account-state/tests/test_default_account_state.rs b/tokens/token-extensions/default-account-state/anchor/programs/default-account-state/tests/test_default_account_state.rs index c23a86ef3..a2b05e277 100644 --- a/tokens/token-extensions/default-account-state/anchor/programs/default-account-state/tests/test_default_account_state.rs +++ b/tokens/token-extensions/default-account-state/anchor/programs/default-account-state/tests/test_default_account_state.rs @@ -2,31 +2,29 @@ use { anchor_lang::{ solana_program::{ instruction::{AccountMeta, Instruction}, - pubkey::Pubkey, + pubkey::Address, system_program, }, InstructionData, ToAccountMetas, }, litesvm::LiteSVM, + solana_keypair::Keypair, solana_kite::{ assert_token_account_balance, create_wallet, send_transaction_from_instructions, - token_extensions::{ - mint_tokens_to_token_extensions_account, TOKEN_EXTENSIONS_PROGRAM_ID, - }, + token_extensions::{mint_tokens_to_token_extensions_account, TOKEN_EXTENSIONS_PROGRAM_ID}, }, - solana_keypair::Keypair, solana_signer::Signer, }; /// Create a Token Extensions token account (165 bytes, no extra extensions). /// Uses explicit keypair - not an ATA - so we can inspect account state bytes. fn create_token_account_instruction( - payer: &Pubkey, - token_account: &Pubkey, - mint: &Pubkey, - owner: &Pubkey, + payer: &Address, + token_account: &Address, + mint: &Address, + owner: &Address, ) -> Vec { - let rent_sysvar: Pubkey = "SysvarRent111111111111111111111111111111111" + let rent_sysvar: Address = "SysvarRent111111111111111111111111111111111" .parse() .unwrap(); let create_ix = anchor_lang::solana_program::system_instruction::create_account( @@ -49,7 +47,7 @@ fn create_token_account_instruction( vec![create_ix, init_ix] } -fn setup() -> (LiteSVM, Pubkey, Keypair) { +fn setup() -> (LiteSVM, Address, Keypair) { let program_id = default_account_state::id(); let mut svm = LiteSVM::new(); @@ -73,11 +71,17 @@ fn test_default_account_state() { payer: payer.pubkey(), mint_account: mint_keypair.pubkey(), token_program: TOKEN_EXTENSIONS_PROGRAM_ID, - system_program: system_program::id(), + system_program: system_program::ID, } .to_account_metas(None), ); - send_transaction_from_instructions(&mut svm, vec![initialize_ix], &[&payer, &mint_keypair], &payer.pubkey()).unwrap(); + send_transaction_from_instructions( + &mut svm, + vec![initialize_ix], + &[&payer, &mint_keypair], + &payer.pubkey(), + ) + .unwrap(); svm.expire_blockhash(); // Verify mint exists @@ -92,7 +96,13 @@ fn test_default_account_state() { &mint_keypair.pubkey(), &payer.pubkey(), ); - send_transaction_from_instructions(&mut svm, create_token1_ixs, &[&payer, &token1], &payer.pubkey()).unwrap(); + send_transaction_from_instructions( + &mut svm, + create_token1_ixs, + &[&payer, &token1], + &payer.pubkey(), + ) + .unwrap(); svm.expire_blockhash(); // Verify token account state is frozen (byte 108 = account state: 0=uninitialized, 1=initialized, 2=frozen) @@ -110,10 +120,7 @@ fn test_default_account_state() { 1, &payer, ); - assert!( - result.is_err(), - "Minting to a frozen account should fail" - ); + assert!(result.is_err(), "Minting to a frozen account should fail"); svm.expire_blockhash(); // Step 4: Update default state to Initialized @@ -127,11 +134,12 @@ fn test_default_account_state() { freeze_authority: payer.pubkey(), mint_account: mint_keypair.pubkey(), token_program: TOKEN_EXTENSIONS_PROGRAM_ID, - system_program: system_program::id(), + system_program: system_program::ID, } .to_account_metas(None), ); - send_transaction_from_instructions(&mut svm, vec![update_ix], &[&payer], &payer.pubkey()).unwrap(); + send_transaction_from_instructions(&mut svm, vec![update_ix], &[&payer], &payer.pubkey()) + .unwrap(); svm.expire_blockhash(); // Step 5: Create a new token account - should be initialized (not frozen) now @@ -142,7 +150,13 @@ fn test_default_account_state() { &mint_keypair.pubkey(), &payer.pubkey(), ); - send_transaction_from_instructions(&mut svm, create_token2_ixs, &[&payer, &token2], &payer.pubkey()).unwrap(); + send_transaction_from_instructions( + &mut svm, + create_token2_ixs, + &[&payer, &token2], + &payer.pubkey(), + ) + .unwrap(); svm.expire_blockhash(); // Verify token2 is initialized (not frozen) @@ -159,7 +173,8 @@ fn test_default_account_state() { &token2.pubkey(), 1, &payer, - ).unwrap(); + ) + .unwrap(); assert_token_account_balance(&svm, &token2.pubkey(), 1, "Should have minted 1 token"); } diff --git a/tokens/token-extensions/group/anchor/programs/group/src/instructions/test_initialize_group.rs b/tokens/token-extensions/group/anchor/programs/group/src/instructions/test_initialize_group.rs index 3c958a065..4e4543408 100644 --- a/tokens/token-extensions/group/anchor/programs/group/src/instructions/test_initialize_group.rs +++ b/tokens/token-extensions/group/anchor/programs/group/src/instructions/test_initialize_group.rs @@ -1,12 +1,22 @@ use anchor_lang::prelude::*; -use anchor_spl::mint; -use anchor_spl::token_2022::spl_token_2022::extension::group_pointer::GroupPointer; -use anchor_spl::token_interface::{ - spl_token_2022::{ - extension::{BaseStateWithExtensions, StateWithExtensions}, - state::Mint as MintState, +use anchor_lang::system_program::{create_account, CreateAccount}; +use anchor_spl::{ + token_2022::{ + initialize_mint2, + spl_token_2022::{ + extension::{group_pointer::GroupPointer, ExtensionType}, + pod::PodMint, + }, + InitializeMint2, + }, + token_2022_extensions::group_pointer::{group_pointer_initialize, GroupPointerInitialize}, + token_interface::{ + spl_token_2022::{ + extension::{BaseStateWithExtensions, StateWithExtensions}, + state::Mint as MintState, + }, + Token2022, }, - Mint, Token2022, }; #[derive(Accounts)] @@ -14,54 +24,93 @@ pub struct InitializeGroupAccountConstraints { #[account(mut)] pub payer: Signer, + /// CHECK: created and initialized by this instruction as a Token-2022 mint + /// carrying the GroupPointer extension. #[account( - init, + mut, seeds = [b"group"], bump, - payer = payer, - mint::decimals = 2, - mint::authority = mint_account, - mint::freeze_authority = mint_account, - extensions::group_pointer::authority = mint_account, - extensions::group_pointer::group_address = mint_account, )] - pub mint_account: InterfaceAccount, + pub mint_account: UncheckedAccount, pub token_program: Program, pub system_program: Program, } -fn check_mint_data(accounts: &mut InitializeGroupAccountConstraints) -> Result<()> { - let mint = &accounts.mint_account.cpi_handle_mut(); - let mint_data = mint.data.borrow(); +fn check_mint_data(context: &Context) -> Result<()> { + let mint = context.accounts.mint_account.account(); + let mint_data = mint.try_borrow()?; let mint_with_extension = StateWithExtensions::::unpack(&mint_data)?; let extension_data = mint_with_extension.get_extension::()?; - msg!("{:?}", mint_with_extension); msg!("{:?}", extension_data); Ok(()) } -pub fn handler(mut context: &mut Context) -> Result<()> { - check_mint_data(&mut context.accounts)?; +// There is currently not an anchor constraint to automatically initialize the +// GroupPointer extension. We can manually create and initialize the mint +// account via CPIs in the instruction handler. The mint is a PDA, so it signs +// its own creation with its seeds. +pub fn handler(context: &mut Context) -> Result<()> { + let bump = context.bumps.mint_account; + let signer_seeds: &[&[&[u8]]] = &[&[b"group", &[bump]]]; + + // Calculate space required for mint and extension data + let mint_size = + ExtensionType::try_calculate_account_len::(&[ExtensionType::GroupPointer])?; + + // Calculate minimum lamports required for size of mint account with extensions + let lamports = Rent::get()?.try_minimum_balance(mint_size)?; + + // The mint PDA is both the authority and the group address, so take a copy + // of its `AccountView` for the read-only uses — v2's typed handles enforce + // borrow exclusivity at compile time. + let mint_address = *context.accounts.mint_account.address(); + + // Invoke System Program to create new account with space for mint and extension data + create_account( + CpiContext::new( + context.accounts.system_program.address(), + CreateAccount { + from: context.accounts.payer.cpi_handle_mut(), + to: context.accounts.mint_account.cpi_handle_mut(), + }, + ) + .with_signer(signer_seeds), + lamports, // Lamports + mint_size as u64, // Space + context.accounts.token_program.address(), // Owner Program + )?; + + // Initialize the GroupPointer extension + // This instruction must come before the instruction to initialize the mint data + group_pointer_initialize( + CpiContext::new( + context.accounts.token_program.address(), + GroupPointerInitialize { + mint: context.accounts.mint_account.cpi_handle_mut(), + }, + ), + Some(&mint_address), + Some(&mint_address), + )?; + + // Initialize the standard mint account data + initialize_mint2( + CpiContext::new( + context.accounts.token_program.address(), + InitializeMint2 { + mint: context.accounts.mint_account.cpi_handle_mut(), + }, + ), + 2, // decimals + &mint_address, // mint authority + Some(&mint_address), // freeze authority + )?; - // // Token Group and Token Member extensions features not enabled yet on the Token2022 program - // // This is temporary placeholder to update one extensions are live - // // Initializing the "pointers" works, but you can't initialize the group/member data yet + check_mint_data(context)?; - // let signer_seeds: &[&[&[u8]]] = &[&[b"group", &[context.bumps.mint_account]]]; - // token_group_initialize( - // CpiContext::new( - // context.accounts.token_program.cpi_handle_mut(), - // TokenGroupInitialize { - // token_program_id: context.accounts.token_program.cpi_handle_mut(), - // group: context.accounts.mint_account.cpi_handle_mut(), - // mint: context.accounts.mint_account.cpi_handle_mut(), - // mint_authority: context.accounts.mint_account.cpi_handle_mut(), - // }, - // ) - // .with_signer(signer_seeds), - // Some(context.accounts.payer.address()), // update_authority - // 10, // max_size - // )?; + // Token Group and Token Member extensions features not enabled yet on the Token2022 program + // This is temporary placeholder to update once extensions are live + // Initializing the "pointers" works, but you can't initialize the group/member data yet Ok(()) } diff --git a/tokens/token-extensions/group/anchor/programs/group/src/lib.rs b/tokens/token-extensions/group/anchor/programs/group/src/lib.rs index 895270caf..f96a07fab 100644 --- a/tokens/token-extensions/group/anchor/programs/group/src/lib.rs +++ b/tokens/token-extensions/group/anchor/programs/group/src/lib.rs @@ -10,7 +10,9 @@ pub mod group { use super::*; - pub fn test_initialize_group(context: &mut Context) -> Result<()> { + pub fn test_initialize_group( + context: &mut Context, + ) -> Result<()> { instructions::test_initialize_group::handler(context) } } diff --git a/tokens/token-extensions/group/anchor/programs/group/tests/test_group.rs b/tokens/token-extensions/group/anchor/programs/group/tests/test_group.rs index 331ca1fb3..786d957f3 100644 --- a/tokens/token-extensions/group/anchor/programs/group/tests/test_group.rs +++ b/tokens/token-extensions/group/anchor/programs/group/tests/test_group.rs @@ -1,7 +1,7 @@ use { anchor_lang::{ - solana_program::{instruction::Instruction, pubkey::Pubkey, system_program}, - InstructionData, ToAccountMetas, + solana_program::instruction::Instruction, system_program, Address, InstructionData, + ToAccountMetas, }, litesvm::LiteSVM, solana_kite::{ @@ -22,7 +22,7 @@ fn test_initialize_group() { let payer = create_wallet(&mut svm, 10_000_000_000).unwrap(); // Derive the mint PDA - let (mint_account, _bump) = Pubkey::find_program_address(&[b"group"], &program_id); + let (mint_account, _bump) = Address::find_program_address(&[b"group"], &program_id); let instruction = Instruction::new_with_bytes( program_id, @@ -31,12 +31,13 @@ fn test_initialize_group() { payer: payer.pubkey(), mint_account, token_program: TOKEN_EXTENSIONS_PROGRAM_ID, - system_program: system_program::id(), + system_program: system_program::ID, } .to_account_metas(None), ); - send_transaction_from_instructions(&mut svm, vec![instruction], &[&payer], &payer.pubkey()).unwrap(); + send_transaction_from_instructions(&mut svm, vec![instruction], &[&payer], &payer.pubkey()) + .unwrap(); // Verify mint was created with group pointer extension let mint_data = svm diff --git a/tokens/token-extensions/immutable-owner/anchor/programs/immutable-owner/src/lib.rs b/tokens/token-extensions/immutable-owner/anchor/programs/immutable-owner/src/lib.rs index 95badb006..621a0b59b 100644 --- a/tokens/token-extensions/immutable-owner/anchor/programs/immutable-owner/src/lib.rs +++ b/tokens/token-extensions/immutable-owner/anchor/programs/immutable-owner/src/lib.rs @@ -35,8 +35,8 @@ pub mod immutable_owner { to: context.accounts.token_account.cpi_handle_mut(), }, ), - lamports, // Lamports - token_account_size as u64, // Space + lamports, // Lamports + token_account_size as u64, // Space &context.accounts.token_program.address(), // Owner Program )?; diff --git a/tokens/token-extensions/immutable-owner/anchor/programs/immutable-owner/tests/test_immutable_owner.rs b/tokens/token-extensions/immutable-owner/anchor/programs/immutable-owner/tests/test_immutable_owner.rs index 90710e4c9..722d18f5a 100644 --- a/tokens/token-extensions/immutable-owner/anchor/programs/immutable-owner/tests/test_immutable_owner.rs +++ b/tokens/token-extensions/immutable-owner/anchor/programs/immutable-owner/tests/test_immutable_owner.rs @@ -2,25 +2,25 @@ use { anchor_lang::{ solana_program::{ instruction::{AccountMeta, Instruction}, - pubkey::Pubkey, + pubkey::Address, system_program, }, InstructionData, ToAccountMetas, }, litesvm::LiteSVM, + solana_keypair::Keypair, solana_kite::{ create_wallet, send_transaction_from_instructions, token_extensions::{create_token_extensions_mint, TOKEN_EXTENSIONS_PROGRAM_ID}, }, - solana_keypair::Keypair, solana_signer::Signer, }; /// SetAuthority instruction for Token Extensions (instruction 6). fn set_authority_instruction( - account: &Pubkey, - current_authority: &Pubkey, - new_authority: Option<&Pubkey>, + account: &Address, + current_authority: &Address, + new_authority: Option<&Address>, authority_type: u8, ) -> Instruction { let mut data = vec![6u8, authority_type]; @@ -44,7 +44,7 @@ fn set_authority_instruction( } } -fn setup() -> (LiteSVM, Pubkey, Keypair) { +fn setup() -> (LiteSVM, Address, Keypair) { let program_id = immutable_owner::id(); let mut svm = LiteSVM::new(); @@ -73,11 +73,17 @@ fn test_create_token_account_with_immutable_owner() { token_account: token_keypair.pubkey(), mint_account: mint, token_program: TOKEN_EXTENSIONS_PROGRAM_ID, - system_program: system_program::id(), + system_program: system_program::ID, } .to_account_metas(None), ); - send_transaction_from_instructions(&mut svm, vec![initialize_ix], &[&payer, &token_keypair], &payer.pubkey()).unwrap(); + send_transaction_from_instructions( + &mut svm, + vec![initialize_ix], + &[&payer, &token_keypair], + &payer.pubkey(), + ) + .unwrap(); svm.expire_blockhash(); // Verify token account was created @@ -98,7 +104,12 @@ fn test_create_token_account_with_immutable_owner() { Some(&new_owner.pubkey()), 2, // AuthorityType::AccountOwner ); - let result = send_transaction_from_instructions(&mut svm, vec![set_authority_ix], &[&payer], &payer.pubkey()); + let result = send_transaction_from_instructions( + &mut svm, + vec![set_authority_ix], + &[&payer], + &payer.pubkey(), + ); assert!( result.is_err(), "Setting a new owner should fail due to ImmutableOwner extension" diff --git a/tokens/token-extensions/interest-bearing/anchor/programs/interest-bearing/src/instructions/initialize.rs b/tokens/token-extensions/interest-bearing/anchor/programs/interest-bearing/src/instructions/initialize.rs index 08f2ea99c..c70e1c645 100644 --- a/tokens/token-extensions/interest-bearing/anchor/programs/interest-bearing/src/instructions/initialize.rs +++ b/tokens/token-extensions/interest-bearing/anchor/programs/interest-bearing/src/instructions/initialize.rs @@ -40,8 +40,8 @@ pub fn handler(context: &mut Context, rate: i16) - to: context.accounts.mint_account.cpi_handle_mut(), }, ), - lamports, // Lamports - mint_size as u64, // Space + lamports, // Lamports + mint_size as u64, // Space &context.accounts.token_program.address(), // Owner Program )?; @@ -66,7 +66,7 @@ pub fn handler(context: &mut Context, rate: i16) - mint: context.accounts.mint_account.cpi_handle_mut(), }, ), - 2, // decimals + 2, // decimals &context.accounts.payer.address(), // mint authority Some(&context.accounts.payer.address()), // freeze authority )?; diff --git a/tokens/token-extensions/interest-bearing/anchor/programs/interest-bearing/src/lib.rs b/tokens/token-extensions/interest-bearing/anchor/programs/interest-bearing/src/lib.rs index fffe7e2d6..42b0a12ff 100644 --- a/tokens/token-extensions/interest-bearing/anchor/programs/interest-bearing/src/lib.rs +++ b/tokens/token-extensions/interest-bearing/anchor/programs/interest-bearing/src/lib.rs @@ -1,8 +1,7 @@ use anchor_lang::prelude::*; use anchor_spl::token_2022::spl_token_2022::{ extension::{ - interest_bearing_mint::InterestBearingConfig, BaseStateWithExtensions, - StateWithExtensions, + interest_bearing_mint::InterestBearingConfig, BaseStateWithExtensions, StateWithExtensions, }, state::Mint as MintState, }; @@ -18,11 +17,17 @@ pub mod interest_bearing { use super::*; - pub fn initialize(context: &mut Context, rate: i16) -> Result<()> { + pub fn initialize( + context: &mut Context, + rate: i16, + ) -> Result<()> { instructions::initialize::handler(context, rate) } - pub fn update_rate(context: &mut Context, rate: i16) -> Result<()> { + pub fn update_rate( + context: &mut Context, + rate: i16, + ) -> Result<()> { instructions::update_rate::handler(context, rate) } } diff --git a/tokens/token-extensions/interest-bearing/anchor/programs/interest-bearing/tests/test_interest_bearing.rs b/tokens/token-extensions/interest-bearing/anchor/programs/interest-bearing/tests/test_interest_bearing.rs index c9954bccb..e62e0418c 100644 --- a/tokens/token-extensions/interest-bearing/anchor/programs/interest-bearing/tests/test_interest_bearing.rs +++ b/tokens/token-extensions/interest-bearing/anchor/programs/interest-bearing/tests/test_interest_bearing.rs @@ -1,18 +1,18 @@ use { anchor_lang::{ - solana_program::{instruction::Instruction, pubkey::Pubkey, system_program}, - InstructionData, ToAccountMetas, + solana_program::instruction::Instruction, system_program, Address, InstructionData, + ToAccountMetas, }, litesvm::LiteSVM, + solana_keypair::Keypair, solana_kite::{ create_wallet, send_transaction_from_instructions, token_extensions::TOKEN_EXTENSIONS_PROGRAM_ID, }, - solana_keypair::Keypair, solana_signer::Signer, }; -fn setup() -> (LiteSVM, Pubkey, Keypair) { +fn setup() -> (LiteSVM, Address, Keypair) { let program_id = interest_bearing::id(); let mut svm = LiteSVM::new(); @@ -36,11 +36,17 @@ fn test_initialize_and_update_rate() { payer: payer.pubkey(), mint_account: mint_keypair.pubkey(), token_program: TOKEN_EXTENSIONS_PROGRAM_ID, - system_program: system_program::id(), + system_program: system_program::ID, } .to_account_metas(None), ); - send_transaction_from_instructions(&mut svm, vec![initialize_ix], &[&payer, &mint_keypair], &payer.pubkey()).unwrap(); + send_transaction_from_instructions( + &mut svm, + vec![initialize_ix], + &[&payer, &mint_keypair], + &payer.pubkey(), + ) + .unwrap(); // Verify mint account exists let mint_account = svm @@ -58,11 +64,12 @@ fn test_initialize_and_update_rate() { authority: payer.pubkey(), mint_account: mint_keypair.pubkey(), token_program: TOKEN_EXTENSIONS_PROGRAM_ID, - system_program: system_program::id(), + system_program: system_program::ID, } .to_account_metas(None), ); - send_transaction_from_instructions(&mut svm, vec![update_rate_ix], &[&payer], &payer.pubkey()).unwrap(); + send_transaction_from_instructions(&mut svm, vec![update_rate_ix], &[&payer], &payer.pubkey()) + .unwrap(); // Verify mint still exists after rate update let mint_account = svm diff --git a/tokens/token-extensions/memo-transfer/anchor/programs/memo-transfer/src/instructions/initialize.rs b/tokens/token-extensions/memo-transfer/anchor/programs/memo-transfer/src/instructions/initialize.rs index fc68fb039..be23a6b94 100644 --- a/tokens/token-extensions/memo-transfer/anchor/programs/memo-transfer/src/instructions/initialize.rs +++ b/tokens/token-extensions/memo-transfer/anchor/programs/memo-transfer/src/instructions/initialize.rs @@ -38,8 +38,8 @@ pub fn handler(context: &mut Context) -> Result<() to: context.accounts.token_account.cpi_handle_mut(), }, ), - lamports, // Lamports - token_account_size as u64, // Space + lamports, // Lamports + token_account_size as u64, // Space &context.accounts.token_program.address(), // Owner Program )?; diff --git a/tokens/token-extensions/memo-transfer/anchor/programs/memo-transfer/tests/test_memo_transfer.rs b/tokens/token-extensions/memo-transfer/anchor/programs/memo-transfer/tests/test_memo_transfer.rs index e410f2347..005e328cd 100644 --- a/tokens/token-extensions/memo-transfer/anchor/programs/memo-transfer/tests/test_memo_transfer.rs +++ b/tokens/token-extensions/memo-transfer/anchor/programs/memo-transfer/tests/test_memo_transfer.rs @@ -2,12 +2,13 @@ use { anchor_lang::{ solana_program::{ instruction::{AccountMeta, Instruction}, - pubkey::Pubkey, + pubkey::Address, system_program, }, InstructionData, ToAccountMetas, }, litesvm::LiteSVM, + solana_keypair::Keypair, solana_kite::{ assert_token_account_balance, create_wallet, send_transaction_from_instructions, token_extensions::{ @@ -15,11 +16,10 @@ use { TOKEN_EXTENSIONS_PROGRAM_ID, }, }, - solana_keypair::Keypair, solana_signer::Signer, }; -fn memo_program_id() -> Pubkey { +fn memo_program_id() -> Address { "MemoSq4gqABAXKb96qnH8TysNcWxMyWCqXgDLGmfcHr" .parse() .unwrap() @@ -29,12 +29,12 @@ fn memo_program_id() -> Pubkey { /// Uses explicit keypair - not an ATA - because the test needs multiple /// source accounts for the same owner+mint. fn create_token_account_instructions( - payer: &Pubkey, - token_account: &Pubkey, - mint: &Pubkey, - owner: &Pubkey, + payer: &Address, + token_account: &Address, + mint: &Address, + owner: &Address, ) -> Vec { - let rent_sysvar: Pubkey = "SysvarRent111111111111111111111111111111111" + let rent_sysvar: Address = "SysvarRent111111111111111111111111111111111" .parse() .unwrap(); let create_ix = anchor_lang::solana_program::system_instruction::create_account( @@ -59,9 +59,9 @@ fn create_token_account_instructions( /// Transfer instruction for Token Extensions (instruction 3). fn transfer_instruction( - source: &Pubkey, - dest: &Pubkey, - authority: &Pubkey, + source: &Address, + dest: &Address, + authority: &Address, amount: u64, ) -> Instruction { let mut data = vec![3u8]; @@ -78,7 +78,7 @@ fn transfer_instruction( } /// Memo instruction: just the memo text as bytes. -fn memo_instruction(memo_text: &str, signers: &[&Pubkey]) -> Instruction { +fn memo_instruction(memo_text: &str, signers: &[&Address]) -> Instruction { let accounts: Vec = signers .iter() .map(|s| AccountMeta::new_readonly(**s, true)) @@ -90,7 +90,7 @@ fn memo_instruction(memo_text: &str, signers: &[&Pubkey]) -> Instruction { } } -fn setup() -> (LiteSVM, Pubkey, Keypair) { +fn setup() -> (LiteSVM, Address, Keypair) { let program_id = memo_transfer::id(); let mut svm = LiteSVM::new(); @@ -124,11 +124,17 @@ fn test_memo_transfer() { token_account: token_keypair.pubkey(), mint_account: mint, token_program: TOKEN_EXTENSIONS_PROGRAM_ID, - system_program: system_program::id(), + system_program: system_program::ID, } .to_account_metas(None), ); - send_transaction_from_instructions(&mut svm, vec![initialize_ix], &[&payer, &token_keypair], &payer.pubkey()).unwrap(); + send_transaction_from_instructions( + &mut svm, + vec![initialize_ix], + &[&payer, &token_keypair], + &payer.pubkey(), + ) + .unwrap(); // Verify token account exists let token_account = svm @@ -149,16 +155,17 @@ fn test_memo_transfer() { &mint, &payer.pubkey(), ); - send_transaction_from_instructions(&mut svm, create_source_ixs, &[&payer, &source_keypair], &payer.pubkey()).unwrap(); + send_transaction_from_instructions( + &mut svm, + create_source_ixs, + &[&payer, &source_keypair], + &payer.pubkey(), + ) + .unwrap(); svm.expire_blockhash(); - mint_tokens_to_token_extensions_account( - &mut svm, - &mint, - &source_keypair.pubkey(), - 100, - &payer, - ).unwrap(); + mint_tokens_to_token_extensions_account(&mut svm, &mint, &source_keypair.pubkey(), 100, &payer) + .unwrap(); svm.expire_blockhash(); // Step 4: Transfer without memo - should fail @@ -168,11 +175,9 @@ fn test_memo_transfer() { &payer.pubkey(), 1, ); - let result = send_transaction_from_instructions(&mut svm, vec![transfer_ix], &[&payer], &payer.pubkey()); - assert!( - result.is_err(), - "Transfer without memo should fail" - ); + let result = + send_transaction_from_instructions(&mut svm, vec![transfer_ix], &[&payer], &payer.pubkey()); + assert!(result.is_err(), "Transfer without memo should fail"); svm.expire_blockhash(); // Step 5: Transfer with memo - should succeed @@ -183,10 +188,21 @@ fn test_memo_transfer() { &payer.pubkey(), 1, ); - send_transaction_from_instructions(&mut svm, vec![memo_ix, transfer_ix], &[&payer], &payer.pubkey()).unwrap(); + send_transaction_from_instructions( + &mut svm, + vec![memo_ix, transfer_ix], + &[&payer], + &payer.pubkey(), + ) + .unwrap(); svm.expire_blockhash(); - assert_token_account_balance(&svm, &token_keypair.pubkey(), 1, "Should have 1 token after transfer with memo"); + assert_token_account_balance( + &svm, + &token_keypair.pubkey(), + 1, + "Should have 1 token after transfer with memo", + ); // Step 6: Disable RequiredMemo extension let disable_ix = Instruction::new_with_bytes( @@ -199,7 +215,8 @@ fn test_memo_transfer() { } .to_account_metas(None), ); - send_transaction_from_instructions(&mut svm, vec![disable_ix], &[&payer], &payer.pubkey()).unwrap(); + send_transaction_from_instructions(&mut svm, vec![disable_ix], &[&payer], &payer.pubkey()) + .unwrap(); svm.expire_blockhash(); // Step 7: Transfer without memo should now succeed (memo disabled) @@ -210,7 +227,13 @@ fn test_memo_transfer() { &mint, &payer.pubkey(), ); - send_transaction_from_instructions(&mut svm, create_source2_ixs, &[&payer, &source2_keypair], &payer.pubkey()).unwrap(); + send_transaction_from_instructions( + &mut svm, + create_source2_ixs, + &[&payer, &source2_keypair], + &payer.pubkey(), + ) + .unwrap(); svm.expire_blockhash(); mint_tokens_to_token_extensions_account( @@ -219,7 +242,8 @@ fn test_memo_transfer() { &source2_keypair.pubkey(), 100, &payer, - ).unwrap(); + ) + .unwrap(); svm.expire_blockhash(); let transfer_ix2 = transfer_instruction( @@ -228,7 +252,8 @@ fn test_memo_transfer() { &payer.pubkey(), 1, ); - send_transaction_from_instructions(&mut svm, vec![transfer_ix2], &[&payer], &payer.pubkey()).unwrap(); + send_transaction_from_instructions(&mut svm, vec![transfer_ix2], &[&payer], &payer.pubkey()) + .unwrap(); assert_token_account_balance( &svm, diff --git a/tokens/token-extensions/metadata/anchor/programs/metadata/src/instructions/initialize.rs b/tokens/token-extensions/metadata/anchor/programs/metadata/src/instructions/initialize.rs index ec8df6d6c..9748d8a8c 100644 --- a/tokens/token-extensions/metadata/anchor/programs/metadata/src/instructions/initialize.rs +++ b/tokens/token-extensions/metadata/anchor/programs/metadata/src/instructions/initialize.rs @@ -1,8 +1,15 @@ use anchor_lang::prelude::*; -use anchor_spl::mint; -use anchor_lang::system_program::{transfer, Transfer}; -use anchor_spl::token_interface::{ - token_metadata_initialize, Mint, Token2022, TokenMetadataInitialize, +use anchor_lang::system_program::{create_account, transfer, CreateAccount, Transfer}; +use anchor_spl::{ + token_2022::{ + initialize_mint2, + spl_token_2022::{extension::ExtensionType, pod::PodMint}, + InitializeMint2, + }, + token_2022_extensions::metadata_pointer::{ + metadata_pointer_initialize, MetadataPointerInitialize, + }, + token_interface::{token_metadata_initialize, Token2022, TokenMetadataInitialize}, }; use spl_token_metadata_interface::state::TokenMetadata; use spl_type_length_value::variable_len_pack::VariableLenPack; @@ -12,22 +19,64 @@ pub struct InitializeAccountConstraints { #[account(mut)] pub payer: Signer, - #[account( - init, - payer = payer, - mint::decimals = 2, - mint::authority = payer, - extensions::metadata_pointer::authority = payer, - extensions::metadata_pointer::metadata_address = mint_account, - )] - pub mint_account: InterfaceAccount, + #[account(mut)] + pub mint_account: Signer, pub token_program: Program, pub system_program: Program, } -pub fn process_initialize(context: &mut Context, args: TokenMetadataArgs) -> Result<()> { +pub fn process_initialize( + context: &mut Context, + args: TokenMetadataArgs, +) -> Result<()> { let TokenMetadataArgs { name, symbol, uri } = args; + // There is currently not an anchor constraint to automatically initialize + // the MetadataPointer extension, so create and initialize the mint by hand: + // allocate with room for the extension, initialize the extension, then + // initialize the mint data. + let mint_size = + ExtensionType::try_calculate_account_len::(&[ExtensionType::MetadataPointer])?; + let mint_lamports = Rent::get()?.try_minimum_balance(mint_size)?; + let mint_address = *context.accounts.mint_account.address(); + + create_account( + CpiContext::new( + context.accounts.system_program.address(), + CreateAccount { + from: context.accounts.payer.cpi_handle_mut(), + to: context.accounts.mint_account.cpi_handle_mut(), + }, + ), + mint_lamports, + mint_size as u64, + context.accounts.token_program.address(), + )?; + + // The metadata lives in the mint account itself, so the pointer points at it. + metadata_pointer_initialize( + CpiContext::new( + context.accounts.token_program.address(), + MetadataPointerInitialize { + mint: context.accounts.mint_account.cpi_handle_mut(), + }, + ), + Some(context.accounts.payer.address()), + Some(&mint_address), + )?; + + initialize_mint2( + CpiContext::new( + context.accounts.token_program.address(), + InitializeMint2 { + mint: context.accounts.mint_account.cpi_handle_mut(), + }, + ), + 2, + context.accounts.payer.address(), + None, + )?; + // Define token metadata let token_metadata = TokenMetadata { name: name.clone(), @@ -54,16 +103,20 @@ pub fn process_initialize(context: &mut Context, a lamports, )?; - // Initialize token metadata + // Initialize token metadata. `AccountView` is Copy and a copy still points + // at the same account, so the read-only slots come from copies — v2's typed + // handles enforce borrow exclusivity at compile time. + let payer_view = *context.accounts.payer.account(); + let mint_view = *context.accounts.mint_account.account(); + token_metadata_initialize( CpiContext::new( context.accounts.token_program.address(), TokenMetadataInitialize { - program_id: context.accounts.token_program.cpi_handle_mut(), - mint: context.accounts.mint_account.cpi_handle(), + mint: CpiHandle::readonly(&mint_view), metadata: context.accounts.mint_account.cpi_handle_mut(), - mint_authority: context.accounts.payer.cpi_handle(), - update_authority: context.accounts.payer.cpi_handle(), + mint_authority: CpiHandle::readonly(&payer_view), + update_authority: CpiHandle::readonly(&payer_view), }, ), name, diff --git a/tokens/token-extensions/metadata/anchor/programs/metadata/src/instructions/remove_key.rs b/tokens/token-extensions/metadata/anchor/programs/metadata/src/instructions/remove_key.rs index d378fef0d..fde27f08c 100644 --- a/tokens/token-extensions/metadata/anchor/programs/metadata/src/instructions/remove_key.rs +++ b/tokens/token-extensions/metadata/anchor/programs/metadata/src/instructions/remove_key.rs @@ -19,13 +19,16 @@ pub struct RemoveKeyAccountConstraints { // Invoke the remove_key instruction from spl_token_metadata_interface directly // There is not an anchor CpiContext for this instruction -pub fn process_remove_key(context: &mut Context, key: String) -> Result<()> { +pub fn process_remove_key( + context: &mut Context, + key: String, +) -> Result<()> { invoke( &remove_key( - &context.accounts.token_program.address(), // token program id - &context.accounts.mint_account.address(), // "metadata" account + &context.accounts.token_program.address(), // token program id + &context.accounts.mint_account.address(), // "metadata" account &context.accounts.update_authority.address(), // update authority - key, // key to remove + key, // key to remove true, // idempotent flag, if true transaction will not fail if key does not exist ), &[ diff --git a/tokens/token-extensions/metadata/anchor/programs/metadata/src/instructions/update_authority.rs b/tokens/token-extensions/metadata/anchor/programs/metadata/src/instructions/update_authority.rs index 083d13258..d90adeb02 100644 --- a/tokens/token-extensions/metadata/anchor/programs/metadata/src/instructions/update_authority.rs +++ b/tokens/token-extensions/metadata/anchor/programs/metadata/src/instructions/update_authority.rs @@ -18,7 +18,9 @@ pub struct UpdateAuthorityAccountConstraints { pub system_program: Program, } -pub fn process_update_authority(context: &mut Context) -> Result<()> { +pub fn process_update_authority( + context: &mut Context, +) -> Result<()> { let new_authority_key = match &context.accounts.new_authority { Some(account) => OptionalNonZeroPubkey::try_from(Some(account.address()))?, None => OptionalNonZeroPubkey::try_from(None)?, diff --git a/tokens/token-extensions/metadata/anchor/programs/metadata/src/instructions/update_field.rs b/tokens/token-extensions/metadata/anchor/programs/metadata/src/instructions/update_field.rs index 9425ad561..b6365e4ab 100644 --- a/tokens/token-extensions/metadata/anchor/programs/metadata/src/instructions/update_field.rs +++ b/tokens/token-extensions/metadata/anchor/programs/metadata/src/instructions/update_field.rs @@ -23,7 +23,10 @@ pub struct UpdateFieldAccountConstraints { pub system_program: Program, } -pub fn process_update_field(context: &mut Context, args: UpdateFieldArgs) -> Result<()> { +pub fn process_update_field( + context: &mut Context, + args: UpdateFieldArgs, +) -> Result<()> { let UpdateFieldArgs { field, value } = args; // Convert to Field type from spl_token_metadata_interface diff --git a/tokens/token-extensions/metadata/anchor/programs/metadata/src/lib.rs b/tokens/token-extensions/metadata/anchor/programs/metadata/src/lib.rs index 158e336e5..ac2288d1b 100644 --- a/tokens/token-extensions/metadata/anchor/programs/metadata/src/lib.rs +++ b/tokens/token-extensions/metadata/anchor/programs/metadata/src/lib.rs @@ -11,15 +11,24 @@ declare_id!("BJHEDXSQfD9kBFvhw8ZCGmPFRihzvbMoxoHUKpXdpn4D"); pub mod metadata { use super::*; - pub fn initialize(context: &mut Context, args: TokenMetadataArgs) -> Result<()> { + pub fn initialize( + context: &mut Context, + args: TokenMetadataArgs, + ) -> Result<()> { process_initialize(context, args) } - pub fn update_field(context: &mut Context, args: UpdateFieldArgs) -> Result<()> { + pub fn update_field( + context: &mut Context, + args: UpdateFieldArgs, + ) -> Result<()> { process_update_field(context, args) } - pub fn remove_key(context: &mut Context, key: String) -> Result<()> { + pub fn remove_key( + context: &mut Context, + key: String, + ) -> Result<()> { process_remove_key(context, key) } @@ -27,7 +36,9 @@ pub mod metadata { process_emit(context) } - pub fn update_authority(context: &mut Context) -> Result<()> { + pub fn update_authority( + context: &mut Context, + ) -> Result<()> { process_update_authority(context) } } diff --git a/tokens/token-extensions/metadata/anchor/programs/metadata/tests/test_metadata.rs b/tokens/token-extensions/metadata/anchor/programs/metadata/tests/test_metadata.rs index 8439e9d3c..b545fc66d 100644 --- a/tokens/token-extensions/metadata/anchor/programs/metadata/tests/test_metadata.rs +++ b/tokens/token-extensions/metadata/anchor/programs/metadata/tests/test_metadata.rs @@ -1,7 +1,7 @@ use { anchor_lang::{ - solana_program::{instruction::Instruction, pubkey::Pubkey, system_program}, - InstructionData, ToAccountMetas, + solana_program::instruction::Instruction, system_program, Address, InstructionData, + ToAccountMetas, }, litesvm::LiteSVM, solana_keypair::Keypair, @@ -12,7 +12,7 @@ use { solana_signer::Signer, }; -fn setup() -> (LiteSVM, Pubkey, Keypair) { +fn setup() -> (LiteSVM, Address, Keypair) { let program_id = metadata::id(); let mut svm = LiteSVM::new(); @@ -43,7 +43,7 @@ fn test_metadata_full_flow() { payer: payer.pubkey(), mint_account: mint_keypair.pubkey(), token_program: TOKEN_EXTENSIONS_PROGRAM_ID, - system_program: system_program::id(), + system_program: system_program::ID, } .to_account_metas(None), ); @@ -77,7 +77,7 @@ fn test_metadata_full_flow() { authority: payer.pubkey(), mint_account: mint_keypair.pubkey(), token_program: TOKEN_EXTENSIONS_PROGRAM_ID, - system_program: system_program::id(), + system_program: system_program::ID, } .to_account_metas(None), ); @@ -99,7 +99,7 @@ fn test_metadata_full_flow() { authority: payer.pubkey(), mint_account: mint_keypair.pubkey(), token_program: TOKEN_EXTENSIONS_PROGRAM_ID, - system_program: system_program::id(), + system_program: system_program::ID, } .to_account_metas(None), ); @@ -123,7 +123,7 @@ fn test_metadata_full_flow() { update_authority: payer.pubkey(), mint_account: mint_keypair.pubkey(), token_program: TOKEN_EXTENSIONS_PROGRAM_ID, - system_program: system_program::id(), + system_program: system_program::ID, } .to_account_metas(None), ); @@ -140,7 +140,7 @@ fn test_metadata_full_flow() { new_authority: None, mint_account: mint_keypair.pubkey(), token_program: TOKEN_EXTENSIONS_PROGRAM_ID, - system_program: system_program::id(), + system_program: system_program::ID, } .to_account_metas(None), ); diff --git a/tokens/token-extensions/mint-close-authority/anchor/programs/mint-close-authority/src/instructions/close.rs b/tokens/token-extensions/mint-close-authority/anchor/programs/mint-close-authority/src/instructions/close.rs index 8b8da31cb..fb60596ed 100644 --- a/tokens/token-extensions/mint-close-authority/anchor/programs/mint-close-authority/src/instructions/close.rs +++ b/tokens/token-extensions/mint-close-authority/anchor/programs/mint-close-authority/src/instructions/close.rs @@ -9,15 +9,20 @@ pub struct CloseAccountConstraints { #[account(mut)] pub authority: Signer, - #[account( - mut, - extensions::close_authority::authority = authority, - )] + // Token-2022 checks the mint's close authority against the signer when the + // CloseAccount CPI below runs, so no constraint is needed here. + #[account(mut)] pub mint_account: InterfaceAccount, pub token_program: Program, } pub fn handler(context: &mut Context) -> Result<()> { + // `authority` fills both the destination and authority CPI slots. v2's typed + // handles enforce borrow exclusivity at compile time, so the read-only slot + // is built from a copy of the `AccountView` — it still points at the same + // underlying account. + let authority_view = *context.accounts.authority.account(); + // cpi to token extensions programs to close mint account // alternatively, this can also be done in the client close_account(CpiContext::new( @@ -25,7 +30,7 @@ pub fn handler(context: &mut Context) -> Result<()> { CloseAccount { account: context.accounts.mint_account.cpi_handle_mut(), destination: context.accounts.authority.cpi_handle_mut(), - authority: context.accounts.authority.cpi_handle(), + authority: CpiHandle::readonly(&authority_view), }, ))?; Ok(()) diff --git a/tokens/token-extensions/mint-close-authority/anchor/programs/mint-close-authority/src/instructions/initialize.rs b/tokens/token-extensions/mint-close-authority/anchor/programs/mint-close-authority/src/instructions/initialize.rs index 759866321..27ed82525 100644 --- a/tokens/token-extensions/mint-close-authority/anchor/programs/mint-close-authority/src/instructions/initialize.rs +++ b/tokens/token-extensions/mint-close-authority/anchor/programs/mint-close-authority/src/instructions/initialize.rs @@ -1,15 +1,22 @@ use anchor_lang::prelude::*; -use anchor_spl::mint; -use anchor_spl::token_interface::{ - spl_pod::optional_keys::OptionalNonZeroPubkey, - spl_token_2022::{ - extension::{ - mint_close_authority::MintCloseAuthority, BaseStateWithExtensions, - StateWithExtensions, +use anchor_lang::system_program::{create_account, CreateAccount}; +use anchor_spl::{ + token_2022::{ + initialize_mint2, + spl_token_2022::{ + extension::{ + mint_close_authority::MintCloseAuthority, BaseStateWithExtensions, ExtensionType, + StateWithExtensions, + }, + pod::PodMint, + state::Mint as MintState, }, - state::Mint as MintState, + InitializeMint2, + }, + token_interface::{ + mint_close_authority_initialize, spl_pod::optional_keys::OptionalNonZeroPubkey, + MintCloseAuthorityInitialize, Token2022, }, - Mint, Token2022, }; #[derive(Accounts)] @@ -17,33 +24,77 @@ pub struct InitializeAccountConstraints { #[account(mut)] pub payer: Signer, - #[account( - init, - payer = payer, - mint::decimals = 2, - mint::authority = payer, - extensions::close_authority::authority = payer, - )] - pub mint_account: InterfaceAccount, + #[account(mut)] + pub mint_account: Signer, + pub token_program: Program, pub system_program: Program, } -pub fn handler(mut context: &mut Context) -> Result<()> { - handle_check_mint_data(&mut context.accounts)?; +// There is currently not an anchor constraint to automatically initialize the +// MintCloseAuthority extension. We can manually create and initialize the mint +// account via CPIs in the instruction handler. +pub fn handler(context: &mut Context) -> Result<()> { + // Calculate space required for mint and extension data + let mint_size = + ExtensionType::try_calculate_account_len::(&[ExtensionType::MintCloseAuthority])?; + + // Calculate minimum lamports required for size of mint account with extensions + let lamports = Rent::get()?.try_minimum_balance(mint_size)?; + + // Invoke System Program to create new account with space for mint and extension data + create_account( + CpiContext::new( + context.accounts.system_program.address(), + CreateAccount { + from: context.accounts.payer.cpi_handle_mut(), + to: context.accounts.mint_account.cpi_handle_mut(), + }, + ), + lamports, // Lamports + mint_size as u64, // Space + context.accounts.token_program.address(), // Owner Program + )?; + + // Initialize the MintCloseAuthority extension + // This instruction must come before the instruction to initialize the mint data + mint_close_authority_initialize( + CpiContext::new( + context.accounts.token_program.address(), + MintCloseAuthorityInitialize { + mint: context.accounts.mint_account.cpi_handle_mut(), + }, + ), + Some(context.accounts.payer.address()), + )?; + + // Initialize the standard mint account data + initialize_mint2( + CpiContext::new( + context.accounts.token_program.address(), + InitializeMint2 { + mint: context.accounts.mint_account.cpi_handle_mut(), + }, + ), + 2, // decimals + context.accounts.payer.address(), // mint authority + None, // freeze authority + )?; + + handle_check_mint_data(context)?; Ok(()) } // helper to check mint data, and demonstrate how to read mint extension data within a program -fn handle_check_mint_data(accounts: &mut InitializeAccountConstraints) -> Result<()> { - let mint = &accounts.mint_account.cpi_handle_mut(); - let mint_data = mint.data.borrow(); +fn handle_check_mint_data(context: &Context) -> Result<()> { + let mint = context.accounts.mint_account.account(); + let mint_data = mint.try_borrow()?; let mint_with_extension = StateWithExtensions::::unpack(&mint_data)?; let extension_data = mint_with_extension.get_extension::()?; assert_eq!( extension_data.close_authority, - OptionalNonZeroPubkey::try_from(Some(accounts.payer.address()))? + OptionalNonZeroPubkey::try_from(Some(*context.accounts.payer.address()))? ); msg!("{:?}", extension_data); diff --git a/tokens/token-extensions/mint-close-authority/anchor/programs/mint-close-authority/tests/test_mint_close_authority.rs b/tokens/token-extensions/mint-close-authority/anchor/programs/mint-close-authority/tests/test_mint_close_authority.rs index 156c92040..f6ba86211 100644 --- a/tokens/token-extensions/mint-close-authority/anchor/programs/mint-close-authority/tests/test_mint_close_authority.rs +++ b/tokens/token-extensions/mint-close-authority/anchor/programs/mint-close-authority/tests/test_mint_close_authority.rs @@ -1,18 +1,18 @@ use { anchor_lang::{ - solana_program::{instruction::Instruction, pubkey::Pubkey, system_program}, - InstructionData, ToAccountMetas, + solana_program::instruction::Instruction, system_program, Address, InstructionData, + ToAccountMetas, }, litesvm::LiteSVM, + solana_keypair::Keypair, solana_kite::{ create_wallet, send_transaction_from_instructions, token_extensions::TOKEN_EXTENSIONS_PROGRAM_ID, }, - solana_keypair::Keypair, solana_signer::Signer, }; -fn setup() -> (LiteSVM, Pubkey, Keypair) { +fn setup() -> (LiteSVM, Address, Keypair) { let program_id = mint_close_authority::id(); let mut svm = LiteSVM::new(); @@ -36,11 +36,17 @@ fn test_create_and_close_mint() { payer: payer.pubkey(), mint_account: mint_keypair.pubkey(), token_program: TOKEN_EXTENSIONS_PROGRAM_ID, - system_program: system_program::id(), + system_program: system_program::ID, } .to_account_metas(None), ); - send_transaction_from_instructions(&mut svm, vec![initialize_ix], &[&payer, &mint_keypair], &payer.pubkey()).unwrap(); + send_transaction_from_instructions( + &mut svm, + vec![initialize_ix], + &[&payer, &mint_keypair], + &payer.pubkey(), + ) + .unwrap(); // Verify mint exists let mint_account = svm @@ -61,14 +67,12 @@ fn test_create_and_close_mint() { } .to_account_metas(None), ); - send_transaction_from_instructions(&mut svm, vec![close_ix], &[&payer], &payer.pubkey()).unwrap(); + send_transaction_from_instructions(&mut svm, vec![close_ix], &[&payer], &payer.pubkey()) + .unwrap(); // Verify mint no longer exists (lamports returned to authority) let mint_account = svm.get_account(&mint_keypair.pubkey()); - assert!( - mint_account.is_none(), - "Mint account should be closed" - ); + assert!(mint_account.is_none(), "Mint account should be closed"); svm.expire_blockhash(); @@ -80,11 +84,17 @@ fn test_create_and_close_mint() { payer: payer.pubkey(), mint_account: mint_keypair.pubkey(), token_program: TOKEN_EXTENSIONS_PROGRAM_ID, - system_program: system_program::id(), + system_program: system_program::ID, } .to_account_metas(None), ); - send_transaction_from_instructions(&mut svm, vec![initialize_ix2], &[&payer, &mint_keypair], &payer.pubkey()).unwrap(); + send_transaction_from_instructions( + &mut svm, + vec![initialize_ix2], + &[&payer, &mint_keypair], + &payer.pubkey(), + ) + .unwrap(); // Verify mint exists again let mint_account = svm @@ -105,10 +115,7 @@ fn test_create_and_close_mint() { mint_keypair.pubkey(), false, ), - anchor_lang::solana_program::instruction::AccountMeta::new( - payer.pubkey(), - false, - ), + anchor_lang::solana_program::instruction::AccountMeta::new(payer.pubkey(), false), anchor_lang::solana_program::instruction::AccountMeta::new_readonly( payer.pubkey(), true, @@ -116,7 +123,8 @@ fn test_create_and_close_mint() { ], data: vec![9], // CloseAccount }; - send_transaction_from_instructions(&mut svm, vec![close_direct_ix], &[&payer], &payer.pubkey()).unwrap(); + send_transaction_from_instructions(&mut svm, vec![close_direct_ix], &[&payer], &payer.pubkey()) + .unwrap(); // Verify mint is closed again let mint_account = svm.get_account(&mint_keypair.pubkey()); diff --git a/tokens/token-extensions/non-transferable/anchor/programs/non-transferable/src/lib.rs b/tokens/token-extensions/non-transferable/anchor/programs/non-transferable/src/lib.rs index c792efb70..ff931d076 100644 --- a/tokens/token-extensions/non-transferable/anchor/programs/non-transferable/src/lib.rs +++ b/tokens/token-extensions/non-transferable/anchor/programs/non-transferable/src/lib.rs @@ -34,8 +34,8 @@ pub mod non_transferable { to: context.accounts.mint_account.cpi_handle_mut(), }, ), - lamports, // Lamports - mint_size as u64, // Space + lamports, // Lamports + mint_size as u64, // Space &context.accounts.token_program.address(), // Owner Program )?; @@ -56,7 +56,7 @@ pub mod non_transferable { mint: context.accounts.mint_account.cpi_handle_mut(), }, ), - 2, // decimals + 2, // decimals &context.accounts.payer.address(), // mint authority Some(&context.accounts.payer.address()), // freeze authority )?; diff --git a/tokens/token-extensions/non-transferable/anchor/programs/non-transferable/tests/test_non_transferable.rs b/tokens/token-extensions/non-transferable/anchor/programs/non-transferable/tests/test_non_transferable.rs index 65f05d6a1..7727dfc11 100644 --- a/tokens/token-extensions/non-transferable/anchor/programs/non-transferable/tests/test_non_transferable.rs +++ b/tokens/token-extensions/non-transferable/anchor/programs/non-transferable/tests/test_non_transferable.rs @@ -1,13 +1,10 @@ use { anchor_lang::{ - solana_program::{ - instruction::Instruction, - pubkey::Pubkey, - system_program, - }, - InstructionData, ToAccountMetas, + solana_program::instruction::Instruction, system_program, Address, InstructionData, + ToAccountMetas, }, litesvm::LiteSVM, + solana_keypair::Keypair, solana_kite::{ create_wallet, send_transaction_from_instructions, token_extensions::{ @@ -16,11 +13,10 @@ use { TOKEN_EXTENSIONS_PROGRAM_ID, }, }, - solana_keypair::Keypair, solana_signer::Signer, }; -fn setup() -> (LiteSVM, Pubkey, Keypair) { +fn setup() -> (LiteSVM, Address, Keypair) { let program_id = non_transferable::id(); let mut svm = LiteSVM::new(); @@ -44,11 +40,17 @@ fn test_create_non_transferable_mint_and_attempt_transfer() { payer: payer.pubkey(), mint_account: mint_keypair.pubkey(), token_program: TOKEN_EXTENSIONS_PROGRAM_ID, - system_program: system_program::id(), + system_program: system_program::ID, } .to_account_metas(None), ); - send_transaction_from_instructions(&mut svm, vec![initialize_ix], &[&payer, &mint_keypair], &payer.pubkey()).unwrap(); + send_transaction_from_instructions( + &mut svm, + vec![initialize_ix], + &[&payer, &mint_keypair], + &payer.pubkey(), + ) + .unwrap(); svm.expire_blockhash(); // Verify mint account was created and has extension data @@ -63,12 +65,9 @@ fn test_create_non_transferable_mint_and_attempt_transfer() { // Step 2: Create ATAs for sender and recipient let recipient = Keypair::new(); - let source_ata = create_token_extensions_account( - &mut svm, - &payer.pubkey(), - &mint_keypair.pubkey(), - &payer, - ).unwrap(); + let source_ata = + create_token_extensions_account(&mut svm, &payer.pubkey(), &mint_keypair.pubkey(), &payer) + .unwrap(); svm.expire_blockhash(); let dest_ata = create_token_extensions_account( @@ -76,7 +75,8 @@ fn test_create_non_transferable_mint_and_attempt_transfer() { &recipient.pubkey(), &mint_keypair.pubkey(), &payer, - ).unwrap(); + ) + .unwrap(); svm.expire_blockhash(); // Step 3: Mint 1 token to sender @@ -86,7 +86,8 @@ fn test_create_non_transferable_mint_and_attempt_transfer() { &source_ata, 1, &payer, - ).unwrap(); + ) + .unwrap(); svm.expire_blockhash(); // Step 4: Attempt transfer - should fail because mint is NonTransferable diff --git a/tokens/token-extensions/permanent-delegate/anchor/programs/permanent-delegate/src/instructions/initialize.rs b/tokens/token-extensions/permanent-delegate/anchor/programs/permanent-delegate/src/instructions/initialize.rs index fa9e387c6..8add30eb9 100644 --- a/tokens/token-extensions/permanent-delegate/anchor/programs/permanent-delegate/src/instructions/initialize.rs +++ b/tokens/token-extensions/permanent-delegate/anchor/programs/permanent-delegate/src/instructions/initialize.rs @@ -1,14 +1,24 @@ use anchor_lang::prelude::*; -use anchor_spl::mint; +use anchor_lang::system_program::{create_account, CreateAccount}; use anchor_spl::{ - token_2022::spl_token_2022::extension::permanent_delegate::PermanentDelegate, + token_2022::{ + initialize_mint2, + spl_token_2022::{ + extension::{permanent_delegate::PermanentDelegate, ExtensionType}, + pod::PodMint, + }, + InitializeMint2, + }, + token_2022_extensions::permanent_delegate::{ + permanent_delegate_initialize, PermanentDelegateInitialize, + }, token_interface::{ spl_pod::optional_keys::OptionalNonZeroPubkey, spl_token_2022::{ extension::{BaseStateWithExtensions, StateWithExtensions}, state::Mint as MintState, }, - Mint, Token2022, + Token2022, }, }; @@ -17,35 +27,79 @@ pub struct InitializeAccountConstraints { #[account(mut)] pub payer: Signer, - #[account( - init, - payer = payer, - mint::decimals = 2, - mint::authority = payer, - extensions::permanent_delegate::delegate = payer, - )] - pub mint_account: InterfaceAccount, + #[account(mut)] + pub mint_account: Signer, + pub token_program: Program, pub system_program: Program, } // helper to check mint data, and demonstrate how to read mint extension data within a program -fn check_mint_data(accounts: &mut InitializeAccountConstraints) -> Result<()> { - let mint = &accounts.mint_account.cpi_handle_mut(); - let mint_data = mint.data.borrow(); +fn check_mint_data(context: &Context) -> Result<()> { + let mint = context.accounts.mint_account.account(); + let mint_data = mint.try_borrow()?; let mint_with_extension = StateWithExtensions::::unpack(&mint_data)?; let extension_data = mint_with_extension.get_extension::()?; assert_eq!( extension_data.delegate, - OptionalNonZeroPubkey::try_from(Some(accounts.payer.address()))? + OptionalNonZeroPubkey::try_from(Some(*context.accounts.payer.address()))? ); msg!("{:?}", extension_data); Ok(()) } -pub fn handler(mut context: &mut Context) -> Result<()> { - check_mint_data(&mut context.accounts)?; +// There is currently not an anchor constraint to automatically initialize the +// PermanentDelegate extension. We can manually create and initialize the mint +// account via CPIs in the instruction handler. +pub fn handler(context: &mut Context) -> Result<()> { + // Calculate space required for mint and extension data + let mint_size = + ExtensionType::try_calculate_account_len::(&[ExtensionType::PermanentDelegate])?; + + // Calculate minimum lamports required for size of mint account with extensions + let lamports = Rent::get()?.try_minimum_balance(mint_size)?; + + // Invoke System Program to create new account with space for mint and extension data + create_account( + CpiContext::new( + context.accounts.system_program.address(), + CreateAccount { + from: context.accounts.payer.cpi_handle_mut(), + to: context.accounts.mint_account.cpi_handle_mut(), + }, + ), + lamports, // Lamports + mint_size as u64, // Space + context.accounts.token_program.address(), // Owner Program + )?; + + // Initialize the PermanentDelegate extension + // This instruction must come before the instruction to initialize the mint data + permanent_delegate_initialize( + CpiContext::new( + context.accounts.token_program.address(), + PermanentDelegateInitialize { + mint: context.accounts.mint_account.cpi_handle_mut(), + }, + ), + context.accounts.payer.address(), + )?; + + // Initialize the standard mint account data + initialize_mint2( + CpiContext::new( + context.accounts.token_program.address(), + InitializeMint2 { + mint: context.accounts.mint_account.cpi_handle_mut(), + }, + ), + 2, // decimals + context.accounts.payer.address(), // mint authority + None, // freeze authority + )?; + + check_mint_data(context)?; Ok(()) } diff --git a/tokens/token-extensions/permanent-delegate/anchor/programs/permanent-delegate/tests/test_permanent_delegate.rs b/tokens/token-extensions/permanent-delegate/anchor/programs/permanent-delegate/tests/test_permanent_delegate.rs index 034ac53f7..c8c47e3ff 100644 --- a/tokens/token-extensions/permanent-delegate/anchor/programs/permanent-delegate/tests/test_permanent_delegate.rs +++ b/tokens/token-extensions/permanent-delegate/anchor/programs/permanent-delegate/tests/test_permanent_delegate.rs @@ -2,23 +2,21 @@ use { anchor_lang::{ solana_program::{ instruction::{AccountMeta, Instruction}, - pubkey::Pubkey, + pubkey::Address, system_program, }, InstructionData, ToAccountMetas, }, litesvm::LiteSVM, + solana_keypair::Keypair, solana_kite::{ assert_token_account_balance, create_wallet, send_transaction_from_instructions, - token_extensions::{ - mint_tokens_to_token_extensions_account, TOKEN_EXTENSIONS_PROGRAM_ID, - }, + token_extensions::{mint_tokens_to_token_extensions_account, TOKEN_EXTENSIONS_PROGRAM_ID}, }, - solana_keypair::Keypair, solana_signer::Signer, }; -fn setup() -> (LiteSVM, Pubkey, Keypair) { +fn setup() -> (LiteSVM, Address, Keypair) { let program_id = permanent_delegate::id(); let mut svm = LiteSVM::new(); @@ -32,15 +30,19 @@ fn setup() -> (LiteSVM, Pubkey, Keypair) { /// Create a Token Extensions token account (CreateAccount + InitializeAccount3). /// This creates a non-ATA token account with explicit keypair, which kite doesn't provide. fn create_token_account_instructions( - payer: &Pubkey, - account: &Pubkey, - mint: &Pubkey, - owner: &Pubkey, + payer: &Address, + account: &Address, + mint: &Address, + owner: &Address, ) -> Vec { let space: u64 = 200; let lamports: u64 = 3_000_000; let create_account_ix = anchor_lang::solana_program::system_instruction::create_account( - payer, account, lamports, space, &TOKEN_EXTENSIONS_PROGRAM_ID, + payer, + account, + lamports, + space, + &TOKEN_EXTENSIONS_PROGRAM_ID, ); // InitializeAccount3 (instruction 18): [18, owner(32)] let mut init_data = vec![18u8]; @@ -58,9 +60,9 @@ fn create_token_account_instructions( /// BurnChecked instruction for Token Extensions (instruction 15). fn burn_checked_ix( - account: &Pubkey, - mint: &Pubkey, - authority: &Pubkey, + account: &Address, + mint: &Address, + authority: &Address, amount: u64, decimals: u8, ) -> Instruction { @@ -91,11 +93,17 @@ fn test_create_mint_with_permanent_delegate_and_burn() { payer: payer.pubkey(), mint_account: mint_keypair.pubkey(), token_program: TOKEN_EXTENSIONS_PROGRAM_ID, - system_program: system_program::id(), + system_program: system_program::ID, } .to_account_metas(None), ); - send_transaction_from_instructions(&mut svm, vec![initialize_ix], &[&payer, &mint_keypair], &payer.pubkey()).unwrap(); + send_transaction_from_instructions( + &mut svm, + vec![initialize_ix], + &[&payer, &mint_keypair], + &payer.pubkey(), + ) + .unwrap(); svm.expire_blockhash(); // Step 2: Create a token account owned by a random keypair @@ -107,7 +115,13 @@ fn test_create_mint_with_permanent_delegate_and_burn() { &mint_keypair.pubkey(), &random_owner.pubkey(), ); - send_transaction_from_instructions(&mut svm, create_ata_ixs, &[&payer, &token_account], &payer.pubkey()).unwrap(); + send_transaction_from_instructions( + &mut svm, + create_ata_ixs, + &[&payer, &token_account], + &payer.pubkey(), + ) + .unwrap(); svm.expire_blockhash(); // Step 3: Mint 100 tokens to the token account @@ -117,7 +131,8 @@ fn test_create_mint_with_permanent_delegate_and_burn() { &token_account.pubkey(), 100, &payer, - ).unwrap(); + ) + .unwrap(); svm.expire_blockhash(); // Step 4: Burn all 100 tokens using the permanent delegate (payer) @@ -128,7 +143,13 @@ fn test_create_mint_with_permanent_delegate_and_burn() { 100, 2, // decimals ); - send_transaction_from_instructions(&mut svm, vec![burn_ix], &[&payer], &payer.pubkey()).unwrap(); + send_transaction_from_instructions(&mut svm, vec![burn_ix], &[&payer], &payer.pubkey()) + .unwrap(); - assert_token_account_balance(&svm, &token_account.pubkey(), 0, "Token account balance should be 0 after burn"); + assert_token_account_balance( + &svm, + &token_account.pubkey(), + 0, + "Token account balance should be 0 after burn", + ); } diff --git a/tokens/token-extensions/transfer-fee/anchor/programs/transfer-fee/src/instructions/harvest.rs b/tokens/token-extensions/transfer-fee/anchor/programs/transfer-fee/src/instructions/harvest.rs index 13ba01b0b..73debe459 100644 --- a/tokens/token-extensions/transfer-fee/anchor/programs/transfer-fee/src/instructions/harvest.rs +++ b/tokens/token-extensions/transfer-fee/anchor/programs/transfer-fee/src/instructions/harvest.rs @@ -12,7 +12,9 @@ pub struct HarvestAccountConstraints { // transfer fees are stored directly on the recipient token account and must be "harvested" // "harvesting" transfers fees accumulated on token accounts to the mint account -pub fn process_harvest<'info>(context: &mut Context<'info, HarvestAccountConstraints<'info>>) -> Result<()> { +pub fn process_harvest<'info>( + context: &mut Context<'info, HarvestAccountConstraints<'info>>, +) -> Result<()> { // Using remaining accounts to allow for passing in an unknown number of token accounts to harvest from // Check that remaining accounts are token accounts for the mint to harvest to let sources = context @@ -21,7 +23,9 @@ pub fn process_harvest<'info>(context: &mut Context<'info, HarvestAccountConstra .filter_map(|account| { InterfaceAccount::::try_from(account) .ok() - .filter(|token_account| token_account.mint == context.accounts.mint_account.address()) + .filter(|token_account| { + token_account.mint == context.accounts.mint_account.address() + }) .map(|_| account.cpi_handle_mut()) }) .collect::>(); diff --git a/tokens/token-extensions/transfer-fee/anchor/programs/transfer-fee/src/instructions/initialize.rs b/tokens/token-extensions/transfer-fee/anchor/programs/transfer-fee/src/instructions/initialize.rs index 92521103a..edf6cc14c 100644 --- a/tokens/token-extensions/transfer-fee/anchor/programs/transfer-fee/src/instructions/initialize.rs +++ b/tokens/token-extensions/transfer-fee/anchor/programs/transfer-fee/src/instructions/initialize.rs @@ -53,8 +53,8 @@ pub fn handle_process_initialize( to: context.accounts.mint_account.cpi_handle_mut(), }, ), - lamports, // Lamports - mint_size as u64, // Space + lamports, // Lamports + mint_size as u64, // Space &context.accounts.token_program.address(), // Owner Program )?; @@ -69,8 +69,8 @@ pub fn handle_process_initialize( ), Some(&context.accounts.payer.address()), // transfer fee config authority (update fee) Some(&context.accounts.payer.address()), // withdraw authority (withdraw fees) - transfer_fee_basis_points, // transfer fee basis points (% fee per transfer) - maximum_fee, // maximum fee (maximum units of token per transfer) + transfer_fee_basis_points, // transfer fee basis points (% fee per transfer) + maximum_fee, // maximum fee (maximum units of token per transfer) )?; // Initialize the standard mint account data @@ -81,7 +81,7 @@ pub fn handle_process_initialize( mint: context.accounts.mint_account.cpi_handle_mut(), }, ), - 2, // decimals + 2, // decimals &context.accounts.payer.address(), // mint authority Some(&context.accounts.payer.address()), // freeze authority )?; @@ -110,4 +110,3 @@ pub fn handle_check_mint_data(accounts: &InitializeAccountConstraints) -> Result msg!("{:?}", extension_data); Ok(()) } - diff --git a/tokens/token-extensions/transfer-fee/anchor/programs/transfer-fee/src/instructions/transfer.rs b/tokens/token-extensions/transfer-fee/anchor/programs/transfer-fee/src/instructions/transfer.rs index 9df0d972c..30a0ebf42 100644 --- a/tokens/token-extensions/transfer-fee/anchor/programs/transfer-fee/src/instructions/transfer.rs +++ b/tokens/token-extensions/transfer-fee/anchor/programs/transfer-fee/src/instructions/transfer.rs @@ -43,7 +43,10 @@ pub struct TransferAccountConstraints { // transfer fees are automatically deducted from the transfer amount // recipients receives (transfer amount - fees) // transfer fees are stored directly on the recipient token account and must be "harvested" -pub fn handle_process_transfer(context: &mut Context, amount: u64) -> Result<()> { +pub fn handle_process_transfer( + context: &mut Context, + amount: u64, +) -> Result<()> { // read mint account extension data let mint = &context.accounts.mint_account.cpi_handle_mut(); let mint_data = mint.data.borrow(); diff --git a/tokens/token-extensions/transfer-fee/anchor/programs/transfer-fee/src/lib.rs b/tokens/token-extensions/transfer-fee/anchor/programs/transfer-fee/src/lib.rs index 53305dc85..475ce91f5 100644 --- a/tokens/token-extensions/transfer-fee/anchor/programs/transfer-fee/src/lib.rs +++ b/tokens/token-extensions/transfer-fee/anchor/programs/transfer-fee/src/lib.rs @@ -21,7 +21,9 @@ pub mod transfer_fee { handle_process_transfer(context, amount) } - pub fn harvest<'info>(context: &mut Context<'info, HarvestAccountConstraints<'info>>) -> Result<()> { + pub fn harvest<'info>( + context: &mut Context<'info, HarvestAccountConstraints<'info>>, + ) -> Result<()> { process_harvest(context) } diff --git a/tokens/token-extensions/transfer-fee/anchor/programs/transfer-fee/tests/test_transfer_fee.rs b/tokens/token-extensions/transfer-fee/anchor/programs/transfer-fee/tests/test_transfer_fee.rs index 087f1ae6d..c88fcf2ba 100644 --- a/tokens/token-extensions/transfer-fee/anchor/programs/transfer-fee/tests/test_transfer_fee.rs +++ b/tokens/token-extensions/transfer-fee/anchor/programs/transfer-fee/tests/test_transfer_fee.rs @@ -2,12 +2,13 @@ use { anchor_lang::{ solana_program::{ instruction::{AccountMeta, Instruction}, - pubkey::Pubkey, + pubkey::Address, system_program, }, InstructionData, ToAccountMetas, }, litesvm::LiteSVM, + solana_keypair::Keypair, solana_kite::{ create_wallet, send_transaction_from_instructions, token_extensions::{ @@ -15,17 +16,16 @@ use { mint_tokens_to_token_extensions_account, TOKEN_EXTENSIONS_PROGRAM_ID, }, }, - solana_keypair::Keypair, solana_signer::Signer, }; -fn associated_token_program_id() -> Pubkey { +fn associated_token_program_id() -> Address { "ATokenGPvbdGVxr1b2hvZbsiqW5xWH25efTNsLJA8knL" .parse() .unwrap() } -fn setup() -> (LiteSVM, Pubkey, Keypair) { +fn setup() -> (LiteSVM, Address, Keypair) { let program_id = transfer_fee::id(); let mut svm = LiteSVM::new(); @@ -44,7 +44,8 @@ fn test_transfer_fee_full_flow() { let ata_program = associated_token_program_id(); let sender_ata = get_token_extensions_account_address(&payer.pubkey(), &mint_keypair.pubkey()); - let recipient_ata = get_token_extensions_account_address(&recipient.pubkey(), &mint_keypair.pubkey()); + let recipient_ata = + get_token_extensions_account_address(&recipient.pubkey(), &mint_keypair.pubkey()); // Step 1: Create mint with transfer fee (100 basis points = 1%, max fee = 1) let initialize_ix = Instruction::new_with_bytes( @@ -58,20 +59,22 @@ fn test_transfer_fee_full_flow() { payer: payer.pubkey(), mint_account: mint_keypair.pubkey(), token_program: TOKEN_EXTENSIONS_PROGRAM_ID, - system_program: system_program::id(), + system_program: system_program::ID, } .to_account_metas(None), ); - send_transaction_from_instructions(&mut svm, vec![initialize_ix], &[&payer, &mint_keypair], &payer.pubkey()).unwrap(); + send_transaction_from_instructions( + &mut svm, + vec![initialize_ix], + &[&payer, &mint_keypair], + &payer.pubkey(), + ) + .unwrap(); svm.expire_blockhash(); // Step 2: Create sender ATA and mint 300 tokens - create_token_extensions_account( - &mut svm, - &payer.pubkey(), - &mint_keypair.pubkey(), - &payer, - ).unwrap(); + create_token_extensions_account(&mut svm, &payer.pubkey(), &mint_keypair.pubkey(), &payer) + .unwrap(); svm.expire_blockhash(); mint_tokens_to_token_extensions_account( @@ -80,7 +83,8 @@ fn test_transfer_fee_full_flow() { &sender_ata, 300, &payer, - ).unwrap(); + ) + .unwrap(); svm.expire_blockhash(); // Step 3: Transfer 100 tokens (fee = min(1% * 100 = 1, max_fee = 1) = 1) @@ -95,11 +99,12 @@ fn test_transfer_fee_full_flow() { recipient_token_account: recipient_ata, token_program: TOKEN_EXTENSIONS_PROGRAM_ID, associated_token_program: ata_program, - system_program: system_program::id(), + system_program: system_program::ID, } .to_account_metas(None), ); - send_transaction_from_instructions(&mut svm, vec![transfer_ix], &[&payer], &payer.pubkey()).unwrap(); + send_transaction_from_instructions(&mut svm, vec![transfer_ix], &[&payer], &payer.pubkey()) + .unwrap(); svm.expire_blockhash(); // Step 4: Transfer 200 tokens (fee = min(1% * 200 = 2, max_fee = 1) = 1, capped by maximumFee) @@ -114,18 +119,17 @@ fn test_transfer_fee_full_flow() { recipient_token_account: recipient_ata, token_program: TOKEN_EXTENSIONS_PROGRAM_ID, associated_token_program: ata_program, - system_program: system_program::id(), + system_program: system_program::ID, } .to_account_metas(None), ); - send_transaction_from_instructions(&mut svm, vec![transfer_ix2], &[&payer], &payer.pubkey()).unwrap(); + send_transaction_from_instructions(&mut svm, vec![transfer_ix2], &[&payer], &payer.pubkey()) + .unwrap(); svm.expire_blockhash(); // Step 5: Harvest transfer fees from recipient token account to mint - let harvest_ix = Instruction::new_with_bytes( - program_id, - &transfer_fee::instruction::Harvest {}.data(), - { + let harvest_ix = + Instruction::new_with_bytes(program_id, &transfer_fee::instruction::Harvest {}.data(), { let mut metas = transfer_fee::accounts::HarvestAccountConstraints { mint_account: mint_keypair.pubkey(), token_program: TOKEN_EXTENSIONS_PROGRAM_ID, @@ -133,9 +137,9 @@ fn test_transfer_fee_full_flow() { .to_account_metas(None); metas.push(AccountMeta::new(recipient_ata, false)); metas - }, - ); - send_transaction_from_instructions(&mut svm, vec![harvest_ix], &[&payer], &payer.pubkey()).unwrap(); + }); + send_transaction_from_instructions(&mut svm, vec![harvest_ix], &[&payer], &payer.pubkey()) + .unwrap(); svm.expire_blockhash(); // Step 6: Withdraw harvested fees from mint to sender's token account @@ -150,7 +154,8 @@ fn test_transfer_fee_full_flow() { } .to_account_metas(None), ); - send_transaction_from_instructions(&mut svm, vec![withdraw_ix], &[&payer], &payer.pubkey()).unwrap(); + send_transaction_from_instructions(&mut svm, vec![withdraw_ix], &[&payer], &payer.pubkey()) + .unwrap(); svm.expire_blockhash(); // Step 7: Update transfer fee to 0 @@ -168,5 +173,6 @@ fn test_transfer_fee_full_flow() { } .to_account_metas(None), ); - send_transaction_from_instructions(&mut svm, vec![update_fee_ix], &[&payer], &payer.pubkey()).unwrap(); + send_transaction_from_instructions(&mut svm, vec![update_fee_ix], &[&payer], &payer.pubkey()) + .unwrap(); } diff --git a/tokens/token-extensions/transfer-hook/account-data-as-seed/anchor/programs/transfer-hook/src/instructions/initialize_extra_account_meta_list.rs b/tokens/token-extensions/transfer-hook/account-data-as-seed/anchor/programs/transfer-hook/src/instructions/initialize_extra_account_meta_list.rs index b308473f4..a19d9ffa5 100644 --- a/tokens/token-extensions/transfer-hook/account-data-as-seed/anchor/programs/transfer-hook/src/instructions/initialize_extra_account_meta_list.rs +++ b/tokens/token-extensions/transfer-hook/account-data-as-seed/anchor/programs/transfer-hook/src/instructions/initialize_extra_account_meta_list.rs @@ -1,9 +1,5 @@ use anchor_lang::prelude::*; -use anchor_spl::{ - associated_token::AssociatedToken, - token_2022::Token2022, - token_interface::Mint, -}; +use anchor_spl::{associated_token::AssociatedToken, token_2022::Token2022, token_interface::Mint}; use spl_tlv_account_resolution::state::ExtraAccountMetaList; use spl_transfer_hook_interface::instruction::ExecuteInstruction; @@ -34,16 +30,20 @@ pub struct InitializeExtraAccountMetaListAccountConstraints { pub system_program: Program, } -pub fn handler(mut context: &mut Context) -> Result<()> { +pub fn handler( + mut context: &mut Context, +) -> Result<()> { let extra_account_metas = handle_extra_account_metas()?; // initialize ExtraAccountMetaList account with extra accounts // .map_err() needed because spl-tlv-account-resolution uses solana-program-error 2.x // while anchor-lang 1.0 uses 3.x - structurally identical but different semver types - ExtraAccountMetaList::init::( - &mut context.accounts.extra_account_meta_list.try_borrow_mut_data()?, - &extra_account_metas, - ).map_err(|_| ProgramError::InvalidAccountData)?; + // `AccountView` is Copy, and a copy still points at the same backing + // buffer, so the borrow writes through to the real account. + let mut meta_list_view = *context.accounts.extra_account_meta_list.account(); + let mut meta_list_data = meta_list_view.try_borrow_mut()?; + ExtraAccountMetaList::init::(&mut meta_list_data, &extra_account_metas) + .map_err(|_| ProgramError::InvalidAccountData)?; context.accounts.counter_account.bump = context.bumps.counter_account; diff --git a/tokens/token-extensions/transfer-hook/account-data-as-seed/anchor/programs/transfer-hook/src/lib.rs b/tokens/token-extensions/transfer-hook/account-data-as-seed/anchor/programs/transfer-hook/src/lib.rs index 12418ae2e..60b76e728 100644 --- a/tokens/token-extensions/transfer-hook/account-data-as-seed/anchor/programs/transfer-hook/src/lib.rs +++ b/tokens/token-extensions/transfer-hook/account-data-as-seed/anchor/programs/transfer-hook/src/lib.rs @@ -3,13 +3,12 @@ use std::cell::RefMut; use anchor_lang::prelude::*; use anchor_spl::token_2022::spl_token_2022::{ extension::{ - transfer_hook::TransferHookAccount, BaseStateWithExtensionsMut, - PodStateWithExtensionsMut, + transfer_hook::TransferHookAccount, BaseStateWithExtensionsMut, PodStateWithExtensionsMut, }, pod::PodAccount, }; -use spl_tlv_account_resolution::{account::ExtraAccountMeta, seeds::Seed}; use spl_discriminator::SplDiscriminate; +use spl_tlv_account_resolution::{account::ExtraAccountMeta, seeds::Seed}; use spl_transfer_hook_interface::instruction::{ ExecuteInstruction, InitializeExtraAccountMetaListInstruction, }; @@ -41,19 +40,23 @@ pub mod transfer_hook { // sha256("spl-transfer-hook-interface:execute")[..8] #[discrim = [105, 37, 101, 197, 75, 251, 102, 26]] - pub fn transfer_hook(context: &mut Context, amount: u64) -> Result<()> { + pub fn transfer_hook( + context: &mut Context, + amount: u64, + ) -> Result<()> { instructions::transfer_hook::handler(context, amount) } } pub fn check_is_transferring(context: &Context) -> Result<()> { - let source_token_info = context.accounts.source_token.cpi_handle_mut(); - let mut account_data_ref: RefMut<&mut [u8]> = source_token_info.try_borrow_mut_data()?; + let mut source_token_info = *context.accounts.source_token.account(); + let mut account_data_ref = source_token_info.try_borrow_mut()?; // .map_err() needed because spl-token-2022 uses solana-program-error 2.x // while anchor-lang 1.0 uses 3.x - structurally identical but different semver types - let mut account = PodStateWithExtensionsMut::::unpack(*account_data_ref) + let mut account = PodStateWithExtensionsMut::::unpack(&mut account_data_ref) .map_err(|_| ProgramError::InvalidAccountData)?; - let account_extension = account.get_extension_mut::() + let account_extension = account + .get_extension_mut::() .map_err(|_| ProgramError::InvalidAccountData)?; if !bool::from(account_extension.transferring) { @@ -80,7 +83,8 @@ pub fn handle_extra_account_metas() -> Result> { ], false, // is_signer true, // is_writable - ).map_err(|_| ProgramError::InvalidArgument)?]) + ) + .map_err(|_| ProgramError::InvalidArgument)?]) } /// Returns the count of extra account metas (avoids the error conversion issue in #[account] attributes) diff --git a/tokens/token-extensions/transfer-hook/account-data-as-seed/anchor/programs/transfer-hook/tests/test_transfer_hook.rs b/tokens/token-extensions/transfer-hook/account-data-as-seed/anchor/programs/transfer-hook/tests/test_transfer_hook.rs index 4b083e01c..c60b466ef 100644 --- a/tokens/token-extensions/transfer-hook/account-data-as-seed/anchor/programs/transfer-hook/tests/test_transfer_hook.rs +++ b/tokens/token-extensions/transfer-hook/account-data-as-seed/anchor/programs/transfer-hook/tests/test_transfer_hook.rs @@ -1,13 +1,10 @@ use { anchor_lang::{ - solana_program::{ - instruction::Instruction, - pubkey::Pubkey, - system_program, - }, - InstructionData, ToAccountMetas, + solana_program::instruction::Instruction, system_program, Address, InstructionData, + ToAccountMetas, }, litesvm::LiteSVM, + solana_keypair::Keypair, solana_kite::{ create_wallet, send_transaction_from_instructions, token_extensions::{ @@ -17,17 +14,16 @@ use { }, transfer_hook::{build_hook_accounts, get_hook_accounts_address, HookAccount}, }, - solana_keypair::Keypair, solana_signer::Signer, }; -fn associated_token_program_id() -> Pubkey { +fn associated_token_program_id() -> Address { "ATokenGPvbdGVxr1b2hvZbsiqW5xWH25efTNsLJA8knL" .parse() .unwrap() } -fn setup() -> (LiteSVM, Pubkey, Keypair) { +fn setup() -> (LiteSVM, Address, Keypair) { let program_id = transfer_hook::id(); let mut svm = LiteSVM::new(); @@ -46,7 +42,7 @@ fn test_transfer_hook_account_data_as_seed() { // PDAs let (counter_pda, _) = - Pubkey::find_program_address(&[b"counter", payer.pubkey().as_ref()], &program_id); + Address::find_program_address(&[b"counter", payer.pubkey().as_ref()], &program_id); // Step 1: Create mint with TransferHook extension let mint = create_token_extensions_mint( @@ -61,34 +57,19 @@ fn test_transfer_hook_account_data_as_seed() { .unwrap(); svm.expire_blockhash(); - let extra_account_meta_list = - get_hook_accounts_address(&mint, &program_id); + let extra_account_meta_list = get_hook_accounts_address(&mint, &program_id); // Step 2: Create token accounts and mint tokens let amount: u64 = 100 * 10u64.pow(decimals as u32); - let source_ata = create_token_extensions_account( - &mut svm, - &payer.pubkey(), - &mint, - &payer, - ).unwrap(); + let source_ata = + create_token_extensions_account(&mut svm, &payer.pubkey(), &mint, &payer).unwrap(); svm.expire_blockhash(); - let dest_ata = create_token_extensions_account( - &mut svm, - &recipient.pubkey(), - &mint, - &payer, - ).unwrap(); + let dest_ata = + create_token_extensions_account(&mut svm, &recipient.pubkey(), &mint, &payer).unwrap(); svm.expire_blockhash(); - mint_tokens_to_token_extensions_account( - &mut svm, - &mint, - &source_ata, - amount, - &payer, - ).unwrap(); + mint_tokens_to_token_extensions_account(&mut svm, &mint, &source_ata, amount, &payer).unwrap(); svm.expire_blockhash(); // Step 3: Initialize ExtraAccountMetaList (also creates counter PDA) @@ -102,11 +83,12 @@ fn test_transfer_hook_account_data_as_seed() { counter_account: counter_pda, token_program: TOKEN_EXTENSIONS_PROGRAM_ID, associated_token_program: associated_token_program_id(), - system_program: system_program::id(), + system_program: system_program::ID, } .to_account_metas(None), ); - send_transaction_from_instructions(&mut svm, vec![init_extra_ix], &[&payer], &payer.pubkey()).unwrap(); + send_transaction_from_instructions(&mut svm, vec![init_extra_ix], &[&payer], &payer.pubkey()) + .unwrap(); svm.expire_blockhash(); // Step 4: Transfer with hook @@ -129,7 +111,8 @@ fn test_transfer_hook_account_data_as_seed() { transfer_amount, decimals, &extra_accounts, - ).unwrap(); + ) + .unwrap(); svm.expire_blockhash(); // Step 5: Try calling transfer_hook directly (should fail - not transferring) @@ -146,7 +129,12 @@ fn test_transfer_hook_account_data_as_seed() { } .to_account_metas(None), ); - let result = send_transaction_from_instructions(&mut svm, vec![direct_hook_ix], &[&payer], &payer.pubkey()); + let result = send_transaction_from_instructions( + &mut svm, + vec![direct_hook_ix], + &[&payer], + &payer.pubkey(), + ); assert!( result.is_err(), "Calling transfer_hook directly should fail because token is not transferring" diff --git a/tokens/token-extensions/transfer-hook/allow-block-list-token/anchor/programs/abl-token/src/lib.rs b/tokens/token-extensions/transfer-hook/allow-block-list-token/anchor/programs/abl-token/src/lib.rs index 077534b4d..c5b987c6b 100644 --- a/tokens/token-extensions/transfer-hook/allow-block-list-token/anchor/programs/abl-token/src/lib.rs +++ b/tokens/token-extensions/transfer-hook/allow-block-list-token/anchor/programs/abl-token/src/lib.rs @@ -20,7 +20,10 @@ pub mod abl_token { use super::*; - pub fn init_mint(context: &mut Context, args: InitMintArgs) -> Result<()> { + pub fn init_mint( + context: &mut Context, + args: InitMintArgs, + ) -> Result<()> { context.accounts.init_mint(args) } @@ -38,7 +41,10 @@ pub mod abl_token { context.accounts.tx_hook(amount) } - pub fn init_wallet(context: &mut Context, args: InitWalletArgs) -> Result<()> { + pub fn init_wallet( + context: &mut Context, + args: InitWalletArgs, + ) -> Result<()> { let bump = context.bumps.ab_wallet; context.accounts.init_wallet(args, bump) } @@ -47,7 +53,10 @@ pub mod abl_token { context.accounts.remove_wallet() } - pub fn change_mode(context: &mut Context, args: ChangeModeArgs) -> Result<()> { + pub fn change_mode( + context: &mut Context, + args: ChangeModeArgs, + ) -> Result<()> { context.accounts.change_mode(args) } } diff --git a/tokens/token-extensions/transfer-hook/allow-block-list-token/anchor/programs/abl-token/src/utils.rs b/tokens/token-extensions/transfer-hook/allow-block-list-token/anchor/programs/abl-token/src/utils.rs index 7ffc952ad..cd51f2db6 100644 --- a/tokens/token-extensions/transfer-hook/allow-block-list-token/anchor/programs/abl-token/src/utils.rs +++ b/tokens/token-extensions/transfer-hook/allow-block-list-token/anchor/programs/abl-token/src/utils.rs @@ -26,6 +26,7 @@ pub fn get_extra_account_metas() -> Result> { ], false, false, - ).map_err(|_| ProgramError::InvalidArgument)?, // [2] destination token account + ) + .map_err(|_| ProgramError::InvalidArgument)?, // [2] destination token account ]) } diff --git a/tokens/token-extensions/transfer-hook/allow-block-list-token/anchor/programs/abl-token/tests/test_abl_token.rs b/tokens/token-extensions/transfer-hook/allow-block-list-token/anchor/programs/abl-token/tests/test_abl_token.rs index ccc5712cd..bc3b7e4e2 100644 --- a/tokens/token-extensions/transfer-hook/allow-block-list-token/anchor/programs/abl-token/tests/test_abl_token.rs +++ b/tokens/token-extensions/transfer-hook/allow-block-list-token/anchor/programs/abl-token/tests/test_abl_token.rs @@ -1,22 +1,18 @@ use { anchor_lang::{ - solana_program::{ - instruction::Instruction, - pubkey::Pubkey, - system_program, - }, - InstructionData, ToAccountMetas, + solana_program::instruction::Instruction, system_program, Address, InstructionData, + ToAccountMetas, }, litesvm::LiteSVM, + solana_keypair::Keypair, solana_kite::{ create_wallet, send_transaction_from_instructions, token_extensions::TOKEN_EXTENSIONS_PROGRAM_ID, }, - solana_keypair::Keypair, solana_signer::Signer, }; -fn setup() -> (LiteSVM, Pubkey, Keypair) { +fn setup() -> (LiteSVM, Address, Keypair) { let program_id = abl_token::id(); let mut svm = LiteSVM::new(); @@ -33,9 +29,8 @@ fn test_init_config_and_init_mint() { let mint_keypair = Keypair::new(); // Derive PDAs - let (config_pda, _) = - Pubkey::find_program_address(&[b"config"], &program_id); - let (extra_account_meta_list, _) = Pubkey::find_program_address( + let (config_pda, _) = Address::find_program_address(&[b"config"], &program_id); + let (extra_account_meta_list, _) = Address::find_program_address( &[b"extra-account-metas", mint_keypair.pubkey().as_ref()], &program_id, ); @@ -47,11 +42,12 @@ fn test_init_config_and_init_mint() { abl_token::accounts::InitConfigAccountConstraints { payer: payer.pubkey(), config: config_pda, - system_program: system_program::id(), + system_program: system_program::ID, } .to_account_metas(None), ); - send_transaction_from_instructions(&mut svm, vec![init_config_ix], &[&payer], &payer.pubkey()).unwrap(); + send_transaction_from_instructions(&mut svm, vec![init_config_ix], &[&payer], &payer.pubkey()) + .unwrap(); svm.expire_blockhash(); // Step 2: Initialize mint with transfer hook and metadata @@ -77,10 +73,16 @@ fn test_init_config_and_init_mint() { payer: payer.pubkey(), mint: mint_keypair.pubkey(), extra_metas_account: extra_account_meta_list, - system_program: system_program::id(), + system_program: system_program::ID, token_program: TOKEN_EXTENSIONS_PROGRAM_ID, } .to_account_metas(None), ); - send_transaction_from_instructions(&mut svm, vec![init_mint_ix], &[&payer, &mint_keypair], &payer.pubkey()).unwrap(); + send_transaction_from_instructions( + &mut svm, + vec![init_mint_ix], + &[&payer, &mint_keypair], + &payer.pubkey(), + ) + .unwrap(); } diff --git a/tokens/token-extensions/transfer-hook/counter/anchor/programs/transfer-hook/src/instructions/initialize_extra_account_meta_list.rs b/tokens/token-extensions/transfer-hook/counter/anchor/programs/transfer-hook/src/instructions/initialize_extra_account_meta_list.rs index 9cdd30b7d..236ee3ccd 100644 --- a/tokens/token-extensions/transfer-hook/counter/anchor/programs/transfer-hook/src/instructions/initialize_extra_account_meta_list.rs +++ b/tokens/token-extensions/transfer-hook/counter/anchor/programs/transfer-hook/src/instructions/initialize_extra_account_meta_list.rs @@ -1,9 +1,5 @@ use anchor_lang::prelude::*; -use anchor_spl::{ - associated_token::AssociatedToken, - token_2022::Token2022, - token_interface::Mint, -}; +use anchor_spl::{associated_token::AssociatedToken, token_2022::Token2022, token_interface::Mint}; use spl_tlv_account_resolution::state::ExtraAccountMetaList; use spl_transfer_hook_interface::instruction::ExecuteInstruction; @@ -34,16 +30,20 @@ pub struct InitializeExtraAccountMetaListAccountConstraints { pub system_program: Program, } -pub fn handler(mut context: &mut Context) -> Result<()> { +pub fn handler( + mut context: &mut Context, +) -> Result<()> { let extra_account_metas = handle_extra_account_metas()?; // initialize ExtraAccountMetaList account with extra accounts // .map_err() needed because spl-tlv-account-resolution uses solana-program-error 2.x // while anchor-lang 1.0 uses 3.x - structurally identical but different semver types - ExtraAccountMetaList::init::( - &mut context.accounts.extra_account_meta_list.try_borrow_mut_data()?, - &extra_account_metas, - ).map_err(|_| ProgramError::InvalidAccountData)?; + // `AccountView` is Copy, and a copy still points at the same backing + // buffer, so the borrow writes through to the real account. + let mut meta_list_view = *context.accounts.extra_account_meta_list.account(); + let mut meta_list_data = meta_list_view.try_borrow_mut()?; + ExtraAccountMetaList::init::(&mut meta_list_data, &extra_account_metas) + .map_err(|_| ProgramError::InvalidAccountData)?; context.accounts.counter_account.bump = context.bumps.counter_account; diff --git a/tokens/token-extensions/transfer-hook/counter/anchor/programs/transfer-hook/src/lib.rs b/tokens/token-extensions/transfer-hook/counter/anchor/programs/transfer-hook/src/lib.rs index f621ad193..b0397b1fe 100644 --- a/tokens/token-extensions/transfer-hook/counter/anchor/programs/transfer-hook/src/lib.rs +++ b/tokens/token-extensions/transfer-hook/counter/anchor/programs/transfer-hook/src/lib.rs @@ -3,13 +3,12 @@ use std::cell::RefMut; use anchor_lang::prelude::*; use anchor_spl::token_2022::spl_token_2022::{ extension::{ - transfer_hook::TransferHookAccount, BaseStateWithExtensionsMut, - PodStateWithExtensionsMut, + transfer_hook::TransferHookAccount, BaseStateWithExtensionsMut, PodStateWithExtensionsMut, }, pod::PodAccount, }; -use spl_tlv_account_resolution::{account::ExtraAccountMeta, seeds::Seed}; use spl_discriminator::SplDiscriminate; +use spl_tlv_account_resolution::{account::ExtraAccountMeta, seeds::Seed}; use spl_transfer_hook_interface::instruction::{ ExecuteInstruction, InitializeExtraAccountMetaListInstruction, }; @@ -41,15 +40,18 @@ pub mod transfer_hook { // sha256("spl-transfer-hook-interface:execute")[..8] #[discrim = [105, 37, 101, 197, 75, 251, 102, 26]] - pub fn transfer_hook(context: &mut Context, amount: u64) -> Result<()> { + pub fn transfer_hook( + context: &mut Context, + amount: u64, + ) -> Result<()> { instructions::transfer_hook::handler(context, amount) } } pub fn check_is_transferring(context: &Context) -> Result<()> { - let source_token_info = context.accounts.source_token.cpi_handle_mut(); - let mut account_data_ref: RefMut<&mut [u8]> = source_token_info.try_borrow_mut_data()?; - let mut account = PodStateWithExtensionsMut::::unpack(*account_data_ref)?; + let mut source_token_info = *context.accounts.source_token.account(); + let mut account_data_ref = source_token_info.try_borrow_mut()?; + let mut account = PodStateWithExtensionsMut::::unpack(&mut account_data_ref)?; let account_extension = account.get_extension_mut::()?; if !bool::from(account_extension.transferring) { @@ -69,7 +71,8 @@ pub fn handle_extra_account_metas() -> Result> { }], false, // is_signer true, // is_writable - ).map_err(|_| ProgramError::InvalidArgument)?]) + ) + .map_err(|_| ProgramError::InvalidArgument)?]) } /// Returns the count of extra account metas (avoids the error conversion issue in #[account] attributes) diff --git a/tokens/token-extensions/transfer-hook/counter/anchor/programs/transfer-hook/tests/test_transfer_hook_counter.rs b/tokens/token-extensions/transfer-hook/counter/anchor/programs/transfer-hook/tests/test_transfer_hook_counter.rs index ae927cd15..a7fa1c4d7 100644 --- a/tokens/token-extensions/transfer-hook/counter/anchor/programs/transfer-hook/tests/test_transfer_hook_counter.rs +++ b/tokens/token-extensions/transfer-hook/counter/anchor/programs/transfer-hook/tests/test_transfer_hook_counter.rs @@ -1,13 +1,10 @@ use { anchor_lang::{ - solana_program::{ - instruction::Instruction, - pubkey::Pubkey, - system_program, - }, - InstructionData, ToAccountMetas, + solana_program::instruction::Instruction, system_program, Address, InstructionData, + ToAccountMetas, }, litesvm::LiteSVM, + solana_keypair::Keypair, solana_kite::{ create_wallet, send_transaction_from_instructions, token_extensions::{ @@ -17,17 +14,16 @@ use { }, transfer_hook::{build_hook_accounts, get_hook_accounts_address, HookAccount}, }, - solana_keypair::Keypair, solana_signer::Signer, }; -fn associated_token_program_id() -> Pubkey { +fn associated_token_program_id() -> Address { "ATokenGPvbdGVxr1b2hvZbsiqW5xWH25efTNsLJA8knL" .parse() .unwrap() } -fn setup() -> (LiteSVM, Pubkey, Keypair) { +fn setup() -> (LiteSVM, Address, Keypair) { let program_id = transfer_hook::id(); let mut svm = LiteSVM::new(); @@ -44,7 +40,7 @@ fn test_transfer_hook_counter() { let decimals: u8 = 9; // PDAs - let (counter_pda, _) = Pubkey::find_program_address(&[b"counter"], &program_id); + let (counter_pda, _) = Address::find_program_address(&[b"counter"], &program_id); // Step 1: Create mint with TransferHook extension via kite let mint = create_token_extensions_mint( @@ -59,36 +55,21 @@ fn test_transfer_hook_counter() { .unwrap(); svm.expire_blockhash(); - let extra_account_meta_list = - get_hook_accounts_address(&mint, &program_id); + let extra_account_meta_list = get_hook_accounts_address(&mint, &program_id); // Step 2: Create token accounts and mint tokens let recipient = Keypair::new(); let amount: u64 = 100 * 10u64.pow(decimals as u32); - let source_ata = create_token_extensions_account( - &mut svm, - &payer.pubkey(), - &mint, - &payer, - ).unwrap(); + let source_ata = + create_token_extensions_account(&mut svm, &payer.pubkey(), &mint, &payer).unwrap(); svm.expire_blockhash(); - let dest_ata = create_token_extensions_account( - &mut svm, - &recipient.pubkey(), - &mint, - &payer, - ).unwrap(); + let dest_ata = + create_token_extensions_account(&mut svm, &recipient.pubkey(), &mint, &payer).unwrap(); svm.expire_blockhash(); - mint_tokens_to_token_extensions_account( - &mut svm, - &mint, - &source_ata, - amount, - &payer, - ).unwrap(); + mint_tokens_to_token_extensions_account(&mut svm, &mint, &source_ata, amount, &payer).unwrap(); svm.expire_blockhash(); // Step 3: Initialize ExtraAccountMetaList (also creates counter PDA) @@ -102,11 +83,12 @@ fn test_transfer_hook_counter() { counter_account: counter_pda, token_program: TOKEN_EXTENSIONS_PROGRAM_ID, associated_token_program: associated_token_program_id(), - system_program: system_program::id(), + system_program: system_program::ID, } .to_account_metas(None), ); - send_transaction_from_instructions(&mut svm, vec![init_extra_ix], &[&payer], &payer.pubkey()).unwrap(); + send_transaction_from_instructions(&mut svm, vec![init_extra_ix], &[&payer], &payer.pubkey()) + .unwrap(); svm.expire_blockhash(); // Step 4: Transfer with hook (this triggers the counter increment) @@ -129,7 +111,8 @@ fn test_transfer_hook_counter() { transfer_amount, decimals, &extra_accounts, - ).unwrap(); + ) + .unwrap(); svm.expire_blockhash(); // Step 5: Try calling transfer_hook directly (should fail - not transferring) @@ -146,7 +129,12 @@ fn test_transfer_hook_counter() { } .to_account_metas(None), ); - let result = send_transaction_from_instructions(&mut svm, vec![direct_hook_ix], &[&payer], &payer.pubkey()); + let result = send_transaction_from_instructions( + &mut svm, + vec![direct_hook_ix], + &[&payer], + &payer.pubkey(), + ); assert!( result.is_err(), "Calling transfer_hook directly should fail because token is not transferring" diff --git a/tokens/token-extensions/transfer-hook/hello-world/anchor/programs/transfer-hook/src/instructions/initialize.rs b/tokens/token-extensions/transfer-hook/hello-world/anchor/programs/transfer-hook/src/instructions/initialize.rs index e4a747526..51f780fac 100644 --- a/tokens/token-extensions/transfer-hook/hello-world/anchor/programs/transfer-hook/src/instructions/initialize.rs +++ b/tokens/token-extensions/transfer-hook/hello-world/anchor/programs/transfer-hook/src/instructions/initialize.rs @@ -1,63 +1,104 @@ use anchor_lang::prelude::*; -use anchor_spl::mint; -use anchor_spl::token_interface::{ - spl_pod::optional_keys::OptionalNonZeroPubkey, - spl_token_2022::{ - extension::{ - transfer_hook::TransferHook as TransferHookExtension, BaseStateWithExtensions, - StateWithExtensions, +use anchor_lang::system_program::{create_account, CreateAccount}; +use anchor_spl::{ + token_2022::{ + initialize_mint2, + spl_token_2022::{extension::ExtensionType, pod::PodMint}, + InitializeMint2, + }, + token_2022_extensions::transfer_hook::{transfer_hook_initialize, TransferHookInitialize}, + token_interface::{ + spl_pod::optional_keys::OptionalNonZeroPubkey, + spl_token_2022::{ + extension::{ + transfer_hook::TransferHook as TransferHookExtension, BaseStateWithExtensions, + StateWithExtensions, + }, + state::Mint as MintState, }, - state::Mint as MintState, + Token2022, }, - Mint, Token2022, }; #[derive(Accounts)] -#[instruction(_decimals: u8)] pub struct InitializeAccountConstraints { #[account(mut)] pub payer: Signer, - #[account( - init, - payer = payer, - mint::decimals = _decimals, - mint::authority = payer, - extensions::transfer_hook::authority = payer, - extensions::transfer_hook::program_id = crate::ID - )] - pub mint_account: InterfaceAccount, + #[account(mut)] + pub mint_account: Signer, + pub token_program: Program, pub system_program: Program, } // create a mint account that specifies this program as the transfer hook program -pub fn handler(mut context: &mut Context, _decimals: u8) -> Result<()> { - handle_check_mint_data(&mut context.accounts)?; +// +// There is currently not an anchor constraint to automatically initialize the +// TransferHook extension. We can manually create and initialize the mint +// account via CPIs in the instruction handler. +pub fn handler(context: &mut Context, decimals: u8) -> Result<()> { + // Calculate space required for mint and extension data + let mint_size = + ExtensionType::try_calculate_account_len::(&[ExtensionType::TransferHook])?; + + // Calculate minimum lamports required for size of mint account with extensions + let lamports = Rent::get()?.try_minimum_balance(mint_size)?; + + // Invoke System Program to create new account with space for mint and extension data + create_account( + CpiContext::new( + context.accounts.system_program.address(), + CreateAccount { + from: context.accounts.payer.cpi_handle_mut(), + to: context.accounts.mint_account.cpi_handle_mut(), + }, + ), + lamports, // Lamports + mint_size as u64, // Space + context.accounts.token_program.address(), // Owner Program + )?; + + // Initialize the TransferHook extension, pointing at this program + // This instruction must come before the instruction to initialize the mint data + transfer_hook_initialize( + CpiContext::new( + context.accounts.token_program.address(), + TransferHookInitialize { + mint: context.accounts.mint_account.cpi_handle_mut(), + }, + ), + Some(context.accounts.payer.address()), + Some(&crate::ID), + )?; + + // Initialize the standard mint account data + initialize_mint2( + CpiContext::new( + context.accounts.token_program.address(), + InitializeMint2 { + mint: context.accounts.mint_account.cpi_handle_mut(), + }, + ), + decimals, // decimals + context.accounts.payer.address(), // mint authority + None, // freeze authority + )?; + + handle_check_mint_data(context)?; Ok(()) } // helper to check mint data, and demonstrate how to read mint extension data within a program -fn handle_check_mint_data(accounts: &mut InitializeAccountConstraints) -> Result<()> { - let mint = &accounts.mint_account.cpi_handle_mut(); - let mint_data = mint.data.borrow(); - // .map_err() needed because spl-token-2022 uses solana-program-error 2.x - // while anchor-lang 1.0 uses 3.x - structurally identical but different semver types - let mint_with_extension = StateWithExtensions::::unpack(&mint_data) - .map_err(|_| ProgramError::InvalidAccountData)?; - let extension_data = mint_with_extension.get_extension::() - .map_err(|_| ProgramError::InvalidAccountData)?; - - assert_eq!( - extension_data.authority, - OptionalNonZeroPubkey::try_from(Some(accounts.payer.address())) - .map_err(|_| ProgramError::InvalidArgument)? - ); +fn handle_check_mint_data(context: &Context) -> Result<()> { + let mint = context.accounts.mint_account.account(); + let mint_data = mint.try_borrow()?; + let mint_with_extension = StateWithExtensions::::unpack(&mint_data)?; + let extension_data = mint_with_extension.get_extension::()?; assert_eq!( extension_data.program_id, - OptionalNonZeroPubkey::try_from(Some(crate::ID)) - .map_err(|_| ProgramError::InvalidArgument)? + OptionalNonZeroPubkey::try_from(Some(crate::ID))? ); msg!("{:?}", extension_data); diff --git a/tokens/token-extensions/transfer-hook/hello-world/anchor/programs/transfer-hook/src/instructions/initialize_extra_account_meta_list.rs b/tokens/token-extensions/transfer-hook/hello-world/anchor/programs/transfer-hook/src/instructions/initialize_extra_account_meta_list.rs index 418073bfd..b22372d0e 100644 --- a/tokens/token-extensions/transfer-hook/hello-world/anchor/programs/transfer-hook/src/instructions/initialize_extra_account_meta_list.rs +++ b/tokens/token-extensions/transfer-hook/hello-world/anchor/programs/transfer-hook/src/instructions/initialize_extra_account_meta_list.rs @@ -31,16 +31,20 @@ pub struct InitializeExtraAccountMetaListAccountConstraints { pub system_program: Program, } -pub fn handler(mut context: &mut Context) -> Result<()> { +pub fn handler( + mut context: &mut Context, +) -> Result<()> { let extra_account_metas = handle_extra_account_metas()?; // initialize ExtraAccountMetaList account with extra accounts // .map_err() needed because spl-tlv-account-resolution uses solana-program-error 2.x // while anchor-lang 1.0 uses 3.x - structurally identical but different semver types - ExtraAccountMetaList::init::( - &mut context.accounts.extra_account_meta_list.try_borrow_mut_data()?, - &extra_account_metas, - ).map_err(|_| ProgramError::InvalidAccountData)?; + // `AccountView` is Copy, and a copy still points at the same backing + // buffer, so the borrow writes through to the real account. + let mut meta_list_view = *context.accounts.extra_account_meta_list.account(); + let mut meta_list_data = meta_list_view.try_borrow_mut()?; + ExtraAccountMetaList::init::(&mut meta_list_data, &extra_account_metas) + .map_err(|_| ProgramError::InvalidAccountData)?; Ok(()) } diff --git a/tokens/token-extensions/transfer-hook/hello-world/anchor/programs/transfer-hook/src/lib.rs b/tokens/token-extensions/transfer-hook/hello-world/anchor/programs/transfer-hook/src/lib.rs index 04cc568d4..e26e7b59a 100644 --- a/tokens/token-extensions/transfer-hook/hello-world/anchor/programs/transfer-hook/src/lib.rs +++ b/tokens/token-extensions/transfer-hook/hello-world/anchor/programs/transfer-hook/src/lib.rs @@ -3,13 +3,12 @@ use std::cell::RefMut; use anchor_lang::prelude::*; use anchor_spl::token_2022::spl_token_2022::{ extension::{ - transfer_hook::TransferHookAccount, BaseStateWithExtensionsMut, - PodStateWithExtensionsMut, + transfer_hook::TransferHookAccount, BaseStateWithExtensionsMut, PodStateWithExtensionsMut, }, pod::PodAccount, }; -use spl_tlv_account_resolution::account::ExtraAccountMeta; use spl_discriminator::SplDiscriminate; +use spl_tlv_account_resolution::account::ExtraAccountMeta; use spl_transfer_hook_interface::instruction::{ ExecuteInstruction, InitializeExtraAccountMetaListInstruction, }; @@ -29,7 +28,10 @@ pub enum TransferError { pub mod transfer_hook { use super::*; - pub fn initialize(context: &mut Context, decimals: u8) -> Result<()> { + pub fn initialize( + context: &mut Context, + decimals: u8, + ) -> Result<()> { instructions::initialize::handler(context, decimals) } @@ -43,19 +45,23 @@ pub mod transfer_hook { // sha256("spl-transfer-hook-interface:execute")[..8] #[discrim = [105, 37, 101, 197, 75, 251, 102, 26]] - pub fn transfer_hook(context: &mut Context, amount: u64) -> Result<()> { + pub fn transfer_hook( + context: &mut Context, + amount: u64, + ) -> Result<()> { instructions::transfer_hook::handler(context, amount) } } pub fn check_is_transferring(context: &Context) -> Result<()> { - let source_token_info = context.accounts.source_token.cpi_handle_mut(); - let mut account_data_ref: RefMut<&mut [u8]> = source_token_info.try_borrow_mut_data()?; + let mut source_token_info = *context.accounts.source_token.account(); + let mut account_data_ref = source_token_info.try_borrow_mut()?; // .map_err() needed because spl-token-2022 uses solana-program-error 2.x // while anchor-lang 1.0 uses 3.x - structurally identical but different semver types - let mut account = PodStateWithExtensionsMut::::unpack(*account_data_ref) + let mut account = PodStateWithExtensionsMut::::unpack(&mut account_data_ref) .map_err(|_| ProgramError::InvalidAccountData)?; - let account_extension = account.get_extension_mut::() + let account_extension = account + .get_extension_mut::() .map_err(|_| ProgramError::InvalidAccountData)?; if !bool::from(account_extension.transferring) { diff --git a/tokens/token-extensions/transfer-hook/hello-world/anchor/programs/transfer-hook/tests/test_transfer_hook.rs b/tokens/token-extensions/transfer-hook/hello-world/anchor/programs/transfer-hook/tests/test_transfer_hook.rs index 7471118f0..f839dcea8 100644 --- a/tokens/token-extensions/transfer-hook/hello-world/anchor/programs/transfer-hook/tests/test_transfer_hook.rs +++ b/tokens/token-extensions/transfer-hook/hello-world/anchor/programs/transfer-hook/tests/test_transfer_hook.rs @@ -1,13 +1,10 @@ use { anchor_lang::{ - solana_program::{ - instruction::Instruction, - pubkey::Pubkey, - system_program, - }, - InstructionData, ToAccountMetas, + solana_program::instruction::Instruction, system_program, Address, InstructionData, + ToAccountMetas, }, litesvm::LiteSVM, + solana_keypair::Keypair, solana_kite::{ create_wallet, send_transaction_from_instructions, token_extensions::{ @@ -17,17 +14,16 @@ use { }, transfer_hook::{build_hook_accounts, get_hook_accounts_address}, }, - solana_keypair::Keypair, solana_signer::Signer, }; -fn associated_token_program_id() -> Pubkey { +fn associated_token_program_id() -> Address { "ATokenGPvbdGVxr1b2hvZbsiqW5xWH25efTNsLJA8knL" .parse() .unwrap() } -fn setup() -> (LiteSVM, Pubkey, Keypair) { +fn setup() -> (LiteSVM, Address, Keypair) { let program_id = transfer_hook::id(); let mut svm = LiteSVM::new(); @@ -47,37 +43,36 @@ fn test_transfer_hook_hello_world() { let decimals: u8 = 2; // ExtraAccountMetaList PDA - let extra_account_meta_list = - get_hook_accounts_address(&mint_keypair.pubkey(), &program_id); + let extra_account_meta_list = get_hook_accounts_address(&mint_keypair.pubkey(), &program_id); // Step 1: Create mint with transfer hook extension pointing to our program // (uses the program's own Initialize instruction, not kite, since it sets up // the mint with the program as the hook authority) let initialize_ix = Instruction::new_with_bytes( program_id, - &transfer_hook::instruction::Initialize { - decimals, - } - .data(), + &transfer_hook::instruction::Initialize { decimals }.data(), transfer_hook::accounts::InitializeAccountConstraints { payer: payer.pubkey(), mint_account: mint_keypair.pubkey(), token_program: TOKEN_EXTENSIONS_PROGRAM_ID, - system_program: system_program::id(), + system_program: system_program::ID, } .to_account_metas(None), ); - send_transaction_from_instructions(&mut svm, vec![initialize_ix], &[&payer, &mint_keypair], &payer.pubkey()).unwrap(); + send_transaction_from_instructions( + &mut svm, + vec![initialize_ix], + &[&payer, &mint_keypair], + &payer.pubkey(), + ) + .unwrap(); svm.expire_blockhash(); // Step 2: Create token accounts and mint tokens let amount: u64 = 100 * 10u64.pow(decimals as u32); - let source_ata = create_token_extensions_account( - &mut svm, - &payer.pubkey(), - &mint_keypair.pubkey(), - &payer, - ).unwrap(); + let source_ata = + create_token_extensions_account(&mut svm, &payer.pubkey(), &mint_keypair.pubkey(), &payer) + .unwrap(); svm.expire_blockhash(); let dest_ata = create_token_extensions_account( @@ -85,7 +80,8 @@ fn test_transfer_hook_hello_world() { &recipient.pubkey(), &mint_keypair.pubkey(), &payer, - ).unwrap(); + ) + .unwrap(); svm.expire_blockhash(); mint_tokens_to_token_extensions_account( @@ -94,7 +90,8 @@ fn test_transfer_hook_hello_world() { &source_ata, amount, &payer, - ).unwrap(); + ) + .unwrap(); svm.expire_blockhash(); // Step 3: Create ExtraAccountMetaList account @@ -107,11 +104,12 @@ fn test_transfer_hook_hello_world() { mint: mint_keypair.pubkey(), token_program: TOKEN_EXTENSIONS_PROGRAM_ID, associated_token_program: ata_program, - system_program: system_program::id(), + system_program: system_program::ID, } .to_account_metas(None), ); - send_transaction_from_instructions(&mut svm, vec![init_extra_ix], &[&payer], &payer.pubkey()).unwrap(); + send_transaction_from_instructions(&mut svm, vec![init_extra_ix], &[&payer], &payer.pubkey()) + .unwrap(); svm.expire_blockhash(); // Step 4: Transfer with transfer hook @@ -130,7 +128,8 @@ fn test_transfer_hook_hello_world() { transfer_amount, decimals, &extra_accounts, - ).unwrap(); + ) + .unwrap(); svm.expire_blockhash(); // Step 5: Try calling transfer_hook directly (should fail - not transferring) @@ -146,7 +145,12 @@ fn test_transfer_hook_hello_world() { } .to_account_metas(None), ); - let result = send_transaction_from_instructions(&mut svm, vec![direct_hook_ix], &[&payer], &payer.pubkey()); + let result = send_transaction_from_instructions( + &mut svm, + vec![direct_hook_ix], + &[&payer], + &payer.pubkey(), + ); assert!( result.is_err(), "Calling transfer_hook directly should fail because token is not transferring" diff --git a/tokens/token-extensions/transfer-hook/transfer-cost/anchor/programs/transfer-hook/src/instructions/initialize_extra_account_meta_list.rs b/tokens/token-extensions/transfer-hook/transfer-cost/anchor/programs/transfer-hook/src/instructions/initialize_extra_account_meta_list.rs index 7b4b21329..b2adb571b 100644 --- a/tokens/token-extensions/transfer-hook/transfer-cost/anchor/programs/transfer-hook/src/instructions/initialize_extra_account_meta_list.rs +++ b/tokens/token-extensions/transfer-hook/transfer-cost/anchor/programs/transfer-hook/src/instructions/initialize_extra_account_meta_list.rs @@ -28,15 +28,18 @@ pub struct InitializeExtraAccountMetaListAccountConstraints { pub system_program: Program, } -pub fn handler(mut context: &mut Context) -> Result<()> { +pub fn handler( + mut context: &mut Context, +) -> Result<()> { let extra_account_metas = handle_extra_account_metas()?; // initialize ExtraAccountMetaList account with extra accounts - ExtraAccountMetaList::init::( - &mut context.accounts.extra_account_meta_list.try_borrow_mut_data()?, - &extra_account_metas, - ) - .map_err(|_| ProgramError::InvalidAccountData)?; + // `AccountView` is Copy, and a copy still points at the same backing + // buffer, so the borrow writes through to the real account. + let mut meta_list_view = *context.accounts.extra_account_meta_list.account(); + let mut meta_list_data = meta_list_view.try_borrow_mut()?; + ExtraAccountMetaList::init::(&mut meta_list_data, &extra_account_metas) + .map_err(|_| ProgramError::InvalidAccountData)?; context.accounts.counter_account.bump = context.bumps.counter_account; diff --git a/tokens/token-extensions/transfer-hook/transfer-cost/anchor/programs/transfer-hook/src/instructions/transfer_hook.rs b/tokens/token-extensions/transfer-hook/transfer-cost/anchor/programs/transfer-hook/src/instructions/transfer_hook.rs index 11da2ebfd..30d35062b 100644 --- a/tokens/token-extensions/transfer-hook/transfer-cost/anchor/programs/transfer-hook/src/instructions/transfer_hook.rs +++ b/tokens/token-extensions/transfer-hook/transfer-cost/anchor/programs/transfer-hook/src/instructions/transfer_hook.rs @@ -74,7 +74,11 @@ pub fn handler(context: &mut Context, amount: u6 ); msg!( "Is destination mint {0}", - context.accounts.destination_token.cpi_handle_mut().is_writable + context + .accounts + .destination_token + .cpi_handle_mut() + .is_writable ); msg!( "Is source mint {0}", @@ -91,7 +95,10 @@ pub fn handler(context: &mut Context, amount: u6 TransferChecked { from: context.accounts.sender_wsol_token_account.cpi_handle_mut(), mint: context.accounts.wsol_mint.cpi_handle(), - to: context.accounts.delegate_wsol_token_account.cpi_handle_mut(), + to: context + .accounts + .delegate_wsol_token_account + .cpi_handle_mut(), authority: context.accounts.delegate.cpi_handle(), }, ) diff --git a/tokens/token-extensions/transfer-hook/transfer-cost/anchor/programs/transfer-hook/src/lib.rs b/tokens/token-extensions/transfer-hook/transfer-cost/anchor/programs/transfer-hook/src/lib.rs index b3b626d15..218247283 100644 --- a/tokens/token-extensions/transfer-hook/transfer-cost/anchor/programs/transfer-hook/src/lib.rs +++ b/tokens/token-extensions/transfer-hook/transfer-cost/anchor/programs/transfer-hook/src/lib.rs @@ -50,15 +50,18 @@ pub mod transfer_hook { // sha256("spl-transfer-hook-interface:execute")[..8] #[discrim = [105, 37, 101, 197, 75, 251, 102, 26]] - pub fn transfer_hook(context: &mut Context, amount: u64) -> Result<()> { + pub fn transfer_hook( + context: &mut Context, + amount: u64, + ) -> Result<()> { instructions::transfer_hook::handler(context, amount) } } pub fn check_is_transferring(context: &Context) -> Result<()> { - let source_token_info = context.accounts.source_token.cpi_handle_mut(); - let mut account_data_ref: RefMut<&mut [u8]> = source_token_info.try_borrow_mut_data()?; - let mut account = PodStateWithExtensionsMut::::unpack(*account_data_ref) + let mut source_token_info = *context.accounts.source_token.account(); + let mut account_data_ref = source_token_info.try_borrow_mut()?; + let mut account = PodStateWithExtensionsMut::::unpack(&mut account_data_ref) .map_err(|_| ProgramError::InvalidAccountData)?; let account_extension = account .get_extension_mut::() diff --git a/tokens/token-extensions/transfer-hook/transfer-cost/anchor/programs/transfer-hook/tests/test_transfer_hook.rs b/tokens/token-extensions/transfer-hook/transfer-cost/anchor/programs/transfer-hook/tests/test_transfer_hook.rs index 3912dd921..f67139775 100644 --- a/tokens/token-extensions/transfer-hook/transfer-cost/anchor/programs/transfer-hook/tests/test_transfer_hook.rs +++ b/tokens/token-extensions/transfer-hook/transfer-cost/anchor/programs/transfer-hook/tests/test_transfer_hook.rs @@ -1,13 +1,10 @@ use { anchor_lang::{ - solana_program::{ - instruction::Instruction, - pubkey::Pubkey, - system_program, - }, - InstructionData, ToAccountMetas, + solana_program::instruction::Instruction, system_program, Address, InstructionData, + ToAccountMetas, }, litesvm::LiteSVM, + solana_keypair::Keypair, solana_kite::{ create_wallet, send_transaction_from_instructions, token_extensions::{ @@ -15,11 +12,10 @@ use { }, transfer_hook::get_hook_accounts_address, }, - solana_keypair::Keypair, solana_signer::Signer, }; -fn setup() -> (LiteSVM, Pubkey, Keypair) { +fn setup() -> (LiteSVM, Address, Keypair) { let program_id = transfer_hook::id(); let mut svm = LiteSVM::new(); @@ -49,9 +45,8 @@ fn test_initialize_extra_account_meta_list() { svm.expire_blockhash(); // PDAs - let extra_account_meta_list = - get_hook_accounts_address(&mint, &program_id); - let (counter_pda, _) = Pubkey::find_program_address(&[b"counter"], &program_id); + let extra_account_meta_list = get_hook_accounts_address(&mint, &program_id); + let (counter_pda, _) = Address::find_program_address(&[b"counter"], &program_id); // Step 2: Initialize ExtraAccountMetaList (also creates counter PDA) let init_extra_ix = Instruction::new_with_bytes( @@ -62,11 +57,12 @@ fn test_initialize_extra_account_meta_list() { extra_account_meta_list, mint, counter_account: counter_pda, - system_program: system_program::id(), + system_program: system_program::ID, } .to_account_metas(None), ); - send_transaction_from_instructions(&mut svm, vec![init_extra_ix], &[&payer], &payer.pubkey()).unwrap(); + send_transaction_from_instructions(&mut svm, vec![init_extra_ix], &[&payer], &payer.pubkey()) + .unwrap(); // Verify the ExtraAccountMetaList account was created let account = svm.get_account(&extra_account_meta_list); diff --git a/tokens/token-extensions/transfer-hook/transfer-switch/anchor/programs/transfer-switch/src/instructions/configure_admin.rs b/tokens/token-extensions/transfer-hook/transfer-switch/anchor/programs/transfer-switch/src/instructions/configure_admin.rs index f7199eb77..43155ecb9 100644 --- a/tokens/token-extensions/transfer-hook/transfer-switch/anchor/programs/transfer-switch/src/instructions/configure_admin.rs +++ b/tokens/token-extensions/transfer-hook/transfer-switch/anchor/programs/transfer-switch/src/instructions/configure_admin.rs @@ -23,27 +23,29 @@ pub struct ConfigureAdminAccountConstraints { } pub fn handle_is_admin(accounts: &mut ConfigureAdminAccountConstraints) -> Result<()> { - // check if we are not creating the account for the first time, - // ensure it's the admin that is making the change + // check if we are not creating the account for the first time, + // ensure it's the admin that is making the change + // + if accounts.admin_config.is_initialised { + // make sure it's the admin // - if accounts.admin_config.is_initialised { - // make sure it's the admin - // - require_keys_eq!(accounts.admin.address(), accounts.admin_config.admin,); + require_keys_eq!(accounts.admin.address(), accounts.admin_config.admin,); - // make sure the admin is not reentering their key - // - require_keys_neq!(accounts.admin.address(), accounts.new_admin.address()); - } - Ok(()) - } - -pub fn handle_configure_admin(accounts: &mut ConfigureAdminAccountConstraints, bump: u8) -> Result<()> { - *accounts.admin_config = (AdminConfig { - admin: *accounts.new_admin.address(), // set the admin pubkey that can switch transfers on/off - is_initialised: true, // let us know an admin has been set - bump, // canonical bump for the admin-config PDA - }); - Ok(()) + // make sure the admin is not reentering their key + // + require_keys_neq!(accounts.admin.address(), accounts.new_admin.address()); } + Ok(()) +} +pub fn handle_configure_admin( + accounts: &mut ConfigureAdminAccountConstraints, + bump: u8, +) -> Result<()> { + *accounts.admin_config = (AdminConfig { + admin: *accounts.new_admin.address(), // set the admin pubkey that can switch transfers on/off + is_initialised: true, // let us know an admin has been set + bump, // canonical bump for the admin-config PDA + }); + Ok(()) +} diff --git a/tokens/token-extensions/transfer-hook/transfer-switch/anchor/programs/transfer-switch/src/instructions/initialise_extra_account_metas_list.rs b/tokens/token-extensions/transfer-hook/transfer-switch/anchor/programs/transfer-switch/src/instructions/initialise_extra_account_metas_list.rs index 76b1ab64c..1b78d462b 100644 --- a/tokens/token-extensions/transfer-hook/transfer-switch/anchor/programs/transfer-switch/src/instructions/initialise_extra_account_metas_list.rs +++ b/tokens/token-extensions/transfer-hook/transfer-switch/anchor/programs/transfer-switch/src/instructions/initialise_extra_account_metas_list.rs @@ -29,54 +29,58 @@ pub struct InitializeExtraAccountMetasAccountConstraints { pub system_program: Program, } -pub fn handle_initialize_extra_account_metas_list(accounts: &mut InitializeExtraAccountMetasAccountConstraints, bumps: InitializeExtraAccountMetasAccountConstraintsBumps) -> Result<()> { - // .map_err() needed because spl-tlv-account-resolution uses solana-program-error 2.x - // while anchor-lang 1.0 uses 3.x - structurally identical but different semver types - let account_metas = vec![ - // 5 - wallet (sender) config account - ExtraAccountMeta::new_with_seeds( - &[ - Seed::AccountKey { index: 3 }, // sender index - ], - false, // is_signer - false, // is_writable - ).map_err(|_| ProgramError::InvalidArgument)?, - ]; +pub fn handle_initialize_extra_account_metas_list( + accounts: &mut InitializeExtraAccountMetasAccountConstraints, + bumps: InitializeExtraAccountMetasAccountConstraintsBumps, +) -> Result<()> { + // .map_err() needed because spl-tlv-account-resolution uses solana-program-error 2.x + // while anchor-lang 1.0 uses 3.x - structurally identical but different semver types + let account_metas = vec![ + // 5 - wallet (sender) config account + ExtraAccountMeta::new_with_seeds( + &[ + Seed::AccountKey { index: 3 }, // sender index + ], + false, // is_signer + false, // is_writable + ) + .map_err(|_| ProgramError::InvalidArgument)?, + ]; - // calculate account size - // unwrap is safe for known-good input (count of metas we just created) - let account_size = ExtraAccountMetaList::size_of(account_metas.len()).unwrap() as u64; + // calculate account size + // unwrap is safe for known-good input (count of metas we just created) + let account_size = ExtraAccountMetaList::size_of(account_metas.len()).unwrap() as u64; - // calculate minimum required lamports - let lamports = Rent::get()?.try_minimum_balance(account_size as usize)?; + // calculate minimum required lamports + let lamports = Rent::get()?.try_minimum_balance(account_size as usize)?; - let mint = accounts.token_mint.address(); - let signer_seeds: &[&[&[u8]]] = &[&[ - b"extra-account-metas", - mint.as_ref(), - &[bumps.extra_account_metas_list], - ]]; + let mint = accounts.token_mint.address(); + let signer_seeds: &[&[&[u8]]] = &[&[ + b"extra-account-metas", + mint.as_ref(), + &[bumps.extra_account_metas_list], + ]]; - create_account( - CpiContext::new( - accounts.system_program.address(), - CreateAccount { - from: accounts.payer.cpi_handle_mut(), - to: accounts.extra_account_metas_list.cpi_handle_mut(), - }, - ) - .with_signer(signer_seeds), - lamports, - account_size, - &crate::ID, - )?; + create_account( + CpiContext::new( + accounts.system_program.address(), + CreateAccount { + from: accounts.payer.cpi_handle_mut(), + to: accounts.extra_account_metas_list.cpi_handle_mut(), + }, + ) + .with_signer(signer_seeds), + lamports, + account_size, + &crate::ID, + )?; - // Initialize the account data to store the list of ExtraAccountMetas - ExtraAccountMetaList::init::( - &mut accounts.extra_account_metas_list.try_borrow_mut_data()?, - &account_metas, - ).map_err(|_| ProgramError::InvalidAccountData)?; - - Ok(()) - } + // Initialize the account data to store the list of ExtraAccountMetas + ExtraAccountMetaList::init::( + &mut accounts.extra_account_metas_list.try_borrow_mut_data()?, + &account_metas, + ) + .map_err(|_| ProgramError::InvalidAccountData)?; + Ok(()) +} diff --git a/tokens/token-extensions/transfer-hook/transfer-switch/anchor/programs/transfer-switch/src/instructions/switch.rs b/tokens/token-extensions/transfer-hook/transfer-switch/anchor/programs/transfer-switch/src/instructions/switch.rs index 40ddf9c6c..d9a0fde54 100644 --- a/tokens/token-extensions/transfer-hook/transfer-switch/anchor/programs/transfer-switch/src/instructions/switch.rs +++ b/tokens/token-extensions/transfer-hook/transfer-switch/anchor/programs/transfer-switch/src/instructions/switch.rs @@ -35,15 +35,15 @@ pub struct SwitchAccountConstraints { } pub fn handle_switch(accounts: &mut SwitchAccountConstraints, on: bool, bump: u8) -> Result<()> { - // toggle switch on/off for the given wallet - // - *accounts.wallet_switch = (TransferSwitch { - wallet: *accounts.wallet.address(), - on, - bump, // canonical bump for this wallet's PDA - }); - Ok(()) - } + // toggle switch on/off for the given wallet + // + *accounts.wallet_switch = (TransferSwitch { + wallet: *accounts.wallet.address(), + on, + bump, // canonical bump for this wallet's PDA + }); + Ok(()) +} // admin_config is validated via `seeds=[b"admin-config"], bump` - Anchor // re-derives it and fails if it doesn't match, so storing AdminConfig.bump @@ -51,4 +51,3 @@ pub fn handle_switch(accounts: &mut SwitchAccountConstraints, on: bool, bump: u8 // bump field on AdminConfig is still populated on creation to satisfy the // 'every PDA struct stores its bump' rule and save derivation cost in any // future call sites). - diff --git a/tokens/token-extensions/transfer-hook/transfer-switch/anchor/programs/transfer-switch/src/instructions/transfer_hook.rs b/tokens/token-extensions/transfer-hook/transfer-switch/anchor/programs/transfer-switch/src/instructions/transfer_hook.rs index 35c5e9ef0..877f0b64e 100644 --- a/tokens/token-extensions/transfer-hook/transfer-switch/anchor/programs/transfer-switch/src/instructions/transfer_hook.rs +++ b/tokens/token-extensions/transfer-hook/transfer-switch/anchor/programs/transfer-switch/src/instructions/transfer_hook.rs @@ -47,26 +47,26 @@ pub struct TransferHookAccountConstraints { } pub fn handle_assert_switch_is_on(accounts: &mut TransferHookAccountConstraints) -> Result<()> { - if !accounts.wallet_switch.on { - return err!(TransferError::SwitchNotOn); - } - Ok(()) + if !accounts.wallet_switch.on { + return err!(TransferError::SwitchNotOn); } + Ok(()) +} pub fn handle_assert_is_transferring(accounts: &mut TransferHookAccountConstraints) -> Result<()> { - let source_token_info = accounts.source_token_account.cpi_handle_mut(); - let mut account_data_ref = source_token_info.try_borrow_mut_data()?; - // .map_err() needed because spl-token-2022 uses solana-program-error 2.x - // while anchor-lang 1.0 uses 3.x - structurally identical but different semver types - let mut account = PodStateWithExtensionsMut::::unpack(*account_data_ref) - .map_err(|_| ProgramError::InvalidAccountData)?; - let account_extension = account.get_extension_mut::() - .map_err(|_| ProgramError::InvalidAccountData)?; - - if !bool::from(account_extension.transferring) { - return err!(TransferError::IsNotCurrentlyTransferring); - } + let source_token_info = accounts.source_token_account.cpi_handle_mut(); + let mut account_data_ref = source_token_info.try_borrow_mut_data()?; + // .map_err() needed because spl-token-2022 uses solana-program-error 2.x + // while anchor-lang 1.0 uses 3.x - structurally identical but different semver types + let mut account = PodStateWithExtensionsMut::::unpack(&mut account_data_ref) + .map_err(|_| ProgramError::InvalidAccountData)?; + let account_extension = account + .get_extension_mut::() + .map_err(|_| ProgramError::InvalidAccountData)?; - Ok(()) + if !bool::from(account_extension.transferring) { + return err!(TransferError::IsNotCurrentlyTransferring); } + Ok(()) +} diff --git a/tokens/token-extensions/transfer-hook/transfer-switch/anchor/programs/transfer-switch/src/lib.rs b/tokens/token-extensions/transfer-hook/transfer-switch/anchor/programs/transfer-switch/src/lib.rs index ff6a8d886..3ca716a74 100644 --- a/tokens/token-extensions/transfer-hook/transfer-switch/anchor/programs/transfer-switch/src/lib.rs +++ b/tokens/token-extensions/transfer-hook/transfer-switch/anchor/programs/transfer-switch/src/lib.rs @@ -15,7 +15,9 @@ declare_id!("FjcHckEgXcBhFmSGai3FRpDLiT6hbpV893n8iTxVd81g"); pub mod transfer_switch { use super::*; - pub fn configure_admin(mut context: &mut Context) -> Result<()> { + pub fn configure_admin( + mut context: &mut Context, + ) -> Result<()> { let bump = context.bumps.admin_config; handle_is_admin(&mut context.accounts)?; handle_configure_admin(&mut context.accounts, bump) @@ -36,7 +38,10 @@ pub mod transfer_switch { // sha256("spl-transfer-hook-interface:execute")[..8] #[discrim = [105, 37, 101, 197, 75, 251, 102, 26]] - pub fn transfer_hook(mut context: &mut Context, _amount: u64) -> Result<()> { + pub fn transfer_hook( + mut context: &mut Context, + _amount: u64, + ) -> Result<()> { handle_assert_is_transferring(&mut context.accounts)?; handle_assert_switch_is_on(&mut context.accounts) } diff --git a/tokens/token-extensions/transfer-hook/transfer-switch/anchor/programs/transfer-switch/tests/test_transfer_switch.rs b/tokens/token-extensions/transfer-hook/transfer-switch/anchor/programs/transfer-switch/tests/test_transfer_switch.rs index 5696596ed..f81d50ba1 100644 --- a/tokens/token-extensions/transfer-hook/transfer-switch/anchor/programs/transfer-switch/tests/test_transfer_switch.rs +++ b/tokens/token-extensions/transfer-hook/transfer-switch/anchor/programs/transfer-switch/tests/test_transfer_switch.rs @@ -1,13 +1,10 @@ use { anchor_lang::{ - solana_program::{ - instruction::Instruction, - pubkey::Pubkey, - system_program, - }, - InstructionData, ToAccountMetas, + solana_program::instruction::Instruction, system_program, Address, InstructionData, + ToAccountMetas, }, litesvm::LiteSVM, + solana_keypair::Keypair, solana_kite::{ create_wallet, send_transaction_from_instructions, token_extensions::{ @@ -17,11 +14,10 @@ use { }, transfer_hook::{build_hook_accounts, get_hook_accounts_address, HookAccount}, }, - solana_keypair::Keypair, solana_signer::Signer, }; -fn setup() -> (LiteSVM, Pubkey, Keypair) { +fn setup() -> (LiteSVM, Address, Keypair) { let program_id = transfer_switch::id(); let mut svm = LiteSVM::new(); @@ -40,9 +36,9 @@ fn test_transfer_switch() { let decimals: u8 = 9; // Derive PDAs - let (admin_config, _) = Pubkey::find_program_address(&[b"admin-config"], &program_id); + let (admin_config, _) = Address::find_program_address(&[b"admin-config"], &program_id); let (sender_switch, _) = - Pubkey::find_program_address(&[sender.pubkey().as_ref()], &program_id); + Address::find_program_address(&[sender.pubkey().as_ref()], &program_id); // Step 1: Create mint with TransferHook extension let mint = create_token_extensions_mint( @@ -57,34 +53,19 @@ fn test_transfer_switch() { .unwrap(); svm.expire_blockhash(); - let extra_account_meta_list = - get_hook_accounts_address(&mint, &program_id); + let extra_account_meta_list = get_hook_accounts_address(&mint, &program_id); // Step 2: Create token accounts and mint tokens let amount: u64 = 100 * 10u64.pow(decimals as u32); - let source_ata = create_token_extensions_account( - &mut svm, - &sender.pubkey(), - &mint, - &payer, - ).unwrap(); + let source_ata = + create_token_extensions_account(&mut svm, &sender.pubkey(), &mint, &payer).unwrap(); svm.expire_blockhash(); - let dest_ata = create_token_extensions_account( - &mut svm, - &recipient.pubkey(), - &mint, - &payer, - ).unwrap(); + let dest_ata = + create_token_extensions_account(&mut svm, &recipient.pubkey(), &mint, &payer).unwrap(); svm.expire_blockhash(); - mint_tokens_to_token_extensions_account( - &mut svm, - &mint, - &source_ata, - amount, - &payer, - ).unwrap(); + mint_tokens_to_token_extensions_account(&mut svm, &mint, &source_ata, amount, &payer).unwrap(); svm.expire_blockhash(); // Step 3: Configure admin @@ -95,11 +76,17 @@ fn test_transfer_switch() { admin: payer.pubkey(), new_admin: payer.pubkey(), admin_config, - system_program: system_program::id(), + system_program: system_program::ID, } .to_account_metas(None), ); - send_transaction_from_instructions(&mut svm, vec![configure_admin_ix], &[&payer], &payer.pubkey()).unwrap(); + send_transaction_from_instructions( + &mut svm, + vec![configure_admin_ix], + &[&payer], + &payer.pubkey(), + ) + .unwrap(); svm.expire_blockhash(); // Step 4: Initialize extra account metas list @@ -110,11 +97,12 @@ fn test_transfer_switch() { payer: payer.pubkey(), token_mint: mint, extra_account_metas_list: extra_account_meta_list, - system_program: system_program::id(), + system_program: system_program::ID, } .to_account_metas(None), ); - send_transaction_from_instructions(&mut svm, vec![init_extra_ix], &[&payer], &payer.pubkey()).unwrap(); + send_transaction_from_instructions(&mut svm, vec![init_extra_ix], &[&payer], &payer.pubkey()) + .unwrap(); svm.expire_blockhash(); // Step 5: Turn transfers OFF for sender @@ -126,11 +114,12 @@ fn test_transfer_switch() { wallet: sender.pubkey(), admin_config, wallet_switch: sender_switch, - system_program: system_program::id(), + system_program: system_program::ID, } .to_account_metas(None), ); - send_transaction_from_instructions(&mut svm, vec![switch_off_ix], &[&payer], &payer.pubkey()).unwrap(); + send_transaction_from_instructions(&mut svm, vec![switch_off_ix], &[&payer], &payer.pubkey()) + .unwrap(); svm.expire_blockhash(); // Step 6: Try transfer - should FAIL (switch is off) @@ -154,10 +143,7 @@ fn test_transfer_switch() { decimals, &extra_accounts, ); - assert!( - result.is_err(), - "Transfer should fail when switch is off" - ); + assert!(result.is_err(), "Transfer should fail when switch is off"); svm.expire_blockhash(); // Step 7: Turn transfers ON for sender @@ -169,11 +155,12 @@ fn test_transfer_switch() { wallet: sender.pubkey(), admin_config, wallet_switch: sender_switch, - system_program: system_program::id(), + system_program: system_program::ID, } .to_account_metas(None), ); - send_transaction_from_instructions(&mut svm, vec![switch_on_ix], &[&payer], &payer.pubkey()).unwrap(); + send_transaction_from_instructions(&mut svm, vec![switch_on_ix], &[&payer], &payer.pubkey()) + .unwrap(); svm.expire_blockhash(); // Step 8: Transfer - should SUCCEED (switch is on) @@ -186,5 +173,6 @@ fn test_transfer_switch() { transfer_amount, decimals, &extra_accounts, - ).unwrap(); + ) + .unwrap(); } diff --git a/tokens/token-extensions/transfer-hook/whitelist/anchor/programs/transfer-hook/src/instructions/add_to_whitelist.rs b/tokens/token-extensions/transfer-hook/whitelist/anchor/programs/transfer-hook/src/instructions/add_to_whitelist.rs index 574e455e3..4b7d6803b 100644 --- a/tokens/token-extensions/transfer-hook/whitelist/anchor/programs/transfer-hook/src/instructions/add_to_whitelist.rs +++ b/tokens/token-extensions/transfer-hook/whitelist/anchor/programs/transfer-hook/src/instructions/add_to_whitelist.rs @@ -22,7 +22,8 @@ pub fn handler(context: &mut Context) -> Resul panic!("Only the authority can add to the white list!"); } - context.accounts + context + .accounts .white_list .white_list .push(*context.accounts.new_account.address()); diff --git a/tokens/token-extensions/transfer-hook/whitelist/anchor/programs/transfer-hook/src/instructions/initialize_extra_account_meta_list.rs b/tokens/token-extensions/transfer-hook/whitelist/anchor/programs/transfer-hook/src/instructions/initialize_extra_account_meta_list.rs index f02badec1..aa692605b 100644 --- a/tokens/token-extensions/transfer-hook/whitelist/anchor/programs/transfer-hook/src/instructions/initialize_extra_account_meta_list.rs +++ b/tokens/token-extensions/transfer-hook/whitelist/anchor/programs/transfer-hook/src/instructions/initialize_extra_account_meta_list.rs @@ -28,7 +28,9 @@ pub struct InitializeExtraAccountMetaListAccountConstraints { pub white_list: BorshAccount, } -pub fn handler(mut context: &mut Context) -> Result<()> { +pub fn handler( + mut context: &mut Context, +) -> Result<()> { // set authority field on white_list account as payer address context.accounts.white_list.authority = *context.accounts.payer.address(); context.accounts.white_list.bump = context.bumps.white_list; @@ -38,9 +40,11 @@ pub fn handler(mut context: &mut Context( - &mut context.accounts.extra_account_meta_list.try_borrow_mut_data()?, - &extra_account_metas, - ).map_err(|_| ProgramError::InvalidAccountData)?; + // `AccountView` is Copy, and a copy still points at the same backing + // buffer, so the borrow writes through to the real account. + let mut meta_list_view = *context.accounts.extra_account_meta_list.account(); + let mut meta_list_data = meta_list_view.try_borrow_mut()?; + ExtraAccountMetaList::init::(&mut meta_list_data, &extra_account_metas) + .map_err(|_| ProgramError::InvalidAccountData)?; Ok(()) } diff --git a/tokens/token-extensions/transfer-hook/whitelist/anchor/programs/transfer-hook/src/lib.rs b/tokens/token-extensions/transfer-hook/whitelist/anchor/programs/transfer-hook/src/lib.rs index 956b2e7d8..7ae332eb5 100644 --- a/tokens/token-extensions/transfer-hook/whitelist/anchor/programs/transfer-hook/src/lib.rs +++ b/tokens/token-extensions/transfer-hook/whitelist/anchor/programs/transfer-hook/src/lib.rs @@ -3,13 +3,12 @@ use std::cell::RefMut; use anchor_lang::prelude::*; use anchor_spl::token_2022::spl_token_2022::{ extension::{ - transfer_hook::TransferHookAccount, BaseStateWithExtensionsMut, - PodStateWithExtensionsMut, + transfer_hook::TransferHookAccount, BaseStateWithExtensionsMut, PodStateWithExtensionsMut, }, pod::PodAccount, }; -use spl_tlv_account_resolution::{account::ExtraAccountMeta, seeds::Seed}; use spl_discriminator::SplDiscriminate; +use spl_tlv_account_resolution::{account::ExtraAccountMeta, seeds::Seed}; use spl_transfer_hook_interface::instruction::{ ExecuteInstruction, InitializeExtraAccountMetaListInstruction, }; @@ -39,7 +38,10 @@ pub mod transfer_hook { // sha256("spl-transfer-hook-interface:execute")[..8] #[discrim = [105, 37, 101, 197, 75, 251, 102, 26]] - pub fn transfer_hook(context: &mut Context, amount: u64) -> Result<()> { + pub fn transfer_hook( + context: &mut Context, + amount: u64, + ) -> Result<()> { instructions::transfer_hook::handler(context, amount) } @@ -49,13 +51,14 @@ pub mod transfer_hook { } pub fn check_is_transferring(context: &Context) -> Result<()> { - let source_token_info = context.accounts.source_token.cpi_handle_mut(); - let mut account_data_ref: RefMut<&mut [u8]> = source_token_info.try_borrow_mut_data()?; + let mut source_token_info = *context.accounts.source_token.account(); + let mut account_data_ref = source_token_info.try_borrow_mut()?; // .map_err() needed because spl-token-2022 uses solana-program-error 2.x // while anchor-lang 1.0 uses 3.x - structurally identical but different semver types - let mut account = PodStateWithExtensionsMut::::unpack(*account_data_ref) + let mut account = PodStateWithExtensionsMut::::unpack(&mut account_data_ref) .map_err(|_| ProgramError::InvalidAccountData)?; - let account_extension = account.get_extension_mut::() + let account_extension = account + .get_extension_mut::() .map_err(|_| ProgramError::InvalidAccountData)?; if !bool::from(account_extension.transferring) { @@ -75,7 +78,8 @@ pub fn handle_extra_account_metas() -> Result> { }], false, // is_signer true, // is_writable - ).map_err(|_| ProgramError::InvalidArgument)?]) + ) + .map_err(|_| ProgramError::InvalidArgument)?]) } /// Returns the count of extra account metas (avoids the error conversion issue in #[account] attributes) diff --git a/tokens/token-extensions/transfer-hook/whitelist/anchor/programs/transfer-hook/tests/test_transfer_hook.rs b/tokens/token-extensions/transfer-hook/whitelist/anchor/programs/transfer-hook/tests/test_transfer_hook.rs index 8911293e8..ece754b6d 100644 --- a/tokens/token-extensions/transfer-hook/whitelist/anchor/programs/transfer-hook/tests/test_transfer_hook.rs +++ b/tokens/token-extensions/transfer-hook/whitelist/anchor/programs/transfer-hook/tests/test_transfer_hook.rs @@ -1,13 +1,10 @@ use { anchor_lang::{ - solana_program::{ - instruction::Instruction, - pubkey::Pubkey, - system_program, - }, - InstructionData, ToAccountMetas, + solana_program::instruction::Instruction, system_program, Address, InstructionData, + ToAccountMetas, }, litesvm::LiteSVM, + solana_keypair::Keypair, solana_kite::{ create_wallet, send_transaction_from_instructions, token_extensions::{ @@ -17,11 +14,10 @@ use { }, transfer_hook::{build_hook_accounts, get_hook_accounts_address, HookAccount}, }, - solana_keypair::Keypair, solana_signer::Signer, }; -fn setup() -> (LiteSVM, Pubkey, Keypair) { +fn setup() -> (LiteSVM, Address, Keypair) { let program_id = transfer_hook::id(); let mut svm = LiteSVM::new(); @@ -39,7 +35,7 @@ fn test_whitelist_transfer_hook() { let decimals: u8 = 9; // Derive PDAs - let (white_list_pda, _) = Pubkey::find_program_address(&[b"white_list"], &program_id); + let (white_list_pda, _) = Address::find_program_address(&[b"white_list"], &program_id); // Step 1: Create mint with TransferHook extension let mint = create_token_extensions_mint( @@ -54,34 +50,19 @@ fn test_whitelist_transfer_hook() { .unwrap(); svm.expire_blockhash(); - let extra_account_meta_list = - get_hook_accounts_address(&mint, &program_id); + let extra_account_meta_list = get_hook_accounts_address(&mint, &program_id); // Step 2: Create token accounts and mint tokens let amount: u64 = 100 * 10u64.pow(decimals as u32); - let source_ata = create_token_extensions_account( - &mut svm, - &payer.pubkey(), - &mint, - &payer, - ).unwrap(); + let source_ata = + create_token_extensions_account(&mut svm, &payer.pubkey(), &mint, &payer).unwrap(); svm.expire_blockhash(); - let dest_ata = create_token_extensions_account( - &mut svm, - &recipient.pubkey(), - &mint, - &payer, - ).unwrap(); + let dest_ata = + create_token_extensions_account(&mut svm, &recipient.pubkey(), &mint, &payer).unwrap(); svm.expire_blockhash(); - mint_tokens_to_token_extensions_account( - &mut svm, - &mint, - &source_ata, - amount, - &payer, - ).unwrap(); + mint_tokens_to_token_extensions_account(&mut svm, &mint, &source_ata, amount, &payer).unwrap(); svm.expire_blockhash(); // Step 3: Initialize ExtraAccountMetaList (also creates whitelist) @@ -92,12 +73,13 @@ fn test_whitelist_transfer_hook() { payer: payer.pubkey(), extra_account_meta_list, mint, - system_program: system_program::id(), + system_program: system_program::ID, white_list: white_list_pda, } .to_account_metas(None), ); - send_transaction_from_instructions(&mut svm, vec![init_extra_ix], &[&payer], &payer.pubkey()).unwrap(); + send_transaction_from_instructions(&mut svm, vec![init_extra_ix], &[&payer], &payer.pubkey()) + .unwrap(); svm.expire_blockhash(); // Step 4: Add destination token account to whitelist @@ -111,7 +93,13 @@ fn test_whitelist_transfer_hook() { } .to_account_metas(None), ); - send_transaction_from_instructions(&mut svm, vec![add_to_whitelist_ix], &[&payer], &payer.pubkey()).unwrap(); + send_transaction_from_instructions( + &mut svm, + vec![add_to_whitelist_ix], + &[&payer], + &payer.pubkey(), + ) + .unwrap(); svm.expire_blockhash(); // Step 5: Transfer - should succeed (destination is whitelisted) @@ -134,5 +122,6 @@ fn test_whitelist_transfer_hook() { transfer_amount, decimals, &extra_accounts, - ).unwrap(); + ) + .unwrap(); } diff --git a/tokens/token-minter/anchor/programs/token-minter/src/instructions/create.rs b/tokens/token-minter/anchor/programs/token-minter/src/instructions/create.rs index 842a9c381..e57563c8c 100644 --- a/tokens/token-minter/anchor/programs/token-minter/src/instructions/create.rs +++ b/tokens/token-minter/anchor/programs/token-minter/src/instructions/create.rs @@ -59,7 +59,7 @@ pub fn handle_create_token( update_authority: context.accounts.payer.cpi_handle(), payer: context.accounts.payer.cpi_handle_mut(), system_program: context.accounts.system_program.cpi_handle(), - update_authority_is_signer: true, + update_authority_is_signer: true, }, ), DataV2 { diff --git a/tokens/token-minter/anchor/programs/token-minter/tests/test_token_minter.rs b/tokens/token-minter/anchor/programs/token-minter/tests/test_token_minter.rs index 487a185f3..389cff2b0 100644 --- a/tokens/token-minter/anchor/programs/token-minter/tests/test_token_minter.rs +++ b/tokens/token-minter/anchor/programs/token-minter/tests/test_token_minter.rs @@ -1,13 +1,11 @@ use { anchor_lang::{ - solana_program::{instruction::Instruction, pubkey::Pubkey, system_program}, - InstructionData, ToAccountMetas, + solana_program::instruction::Instruction, system_program, Address, InstructionData, + ToAccountMetas, }, litesvm::LiteSVM, solana_keypair::Keypair, - solana_kite::{ - create_wallet, get_token_account_balance, send_transaction_from_instructions, - }, + solana_kite::{create_wallet, get_token_account_balance, send_transaction_from_instructions}, solana_signer::Signer, }; @@ -21,52 +19,48 @@ fn to_minor_units(major_units: u64) -> u64 { major_units.checked_mul(10u64.pow(MINT_DECIMALS)).unwrap() } -fn metadata_program_id() -> Pubkey { +fn metadata_program_id() -> Address { "metaqbxxUerdq28cj1RbAWkYQm3ybzjb6a8bt518x1s" .parse() .unwrap() } -fn token_program_id() -> Pubkey { +fn token_program_id() -> Address { "TokenkegQfeZyiNwAJbNbGKPFXCWuBvf9Ss623VQ5DA" .parse() .unwrap() } -fn associated_token_program_id() -> Pubkey { +fn associated_token_program_id() -> Address { "ATokenGPvbdGVxr1b2hvZbsiqW5xWH25efTNsLJA8knL" .parse() .unwrap() } -fn rent_sysvar_id() -> Pubkey { +fn rent_sysvar_id() -> Address { "SysvarRent111111111111111111111111111111111" .parse() .unwrap() } -fn derive_metadata_pda(mint: &Pubkey) -> Pubkey { +fn derive_metadata_pda(mint: &Address) -> Address { let metadata_pid = metadata_program_id(); - let (pda, _bump) = Pubkey::find_program_address( + let (pda, _bump) = Address::find_program_address( &[b"metadata", metadata_pid.as_ref(), mint.as_ref()], &metadata_pid, ); pda } -fn derive_ata(wallet: &Pubkey, mint: &Pubkey) -> Pubkey { - let (ata, _bump) = Pubkey::find_program_address( - &[ - wallet.as_ref(), - token_program_id().as_ref(), - mint.as_ref(), - ], +fn derive_ata(wallet: &Address, mint: &Address) -> Address { + let (ata, _bump) = Address::find_program_address( + &[wallet.as_ref(), token_program_id().as_ref(), mint.as_ref()], &associated_token_program_id(), ); ata } -fn setup() -> (LiteSVM, Pubkey, Keypair) { +fn setup() -> (LiteSVM, Address, Keypair) { let program_id = token_minter::id(); let mut svm = LiteSVM::new(); @@ -101,7 +95,7 @@ fn test_create_token() { metadata_account, token_program: token_program_id(), token_metadata_program: metadata_program_id(), - system_program: system_program::id(), + system_program: system_program::ID, rent: rent_sysvar_id(), } .to_account_metas(None), @@ -148,7 +142,7 @@ fn test_create_and_mint_tokens() { metadata_account, token_program: token_program_id(), token_metadata_program: metadata_program_id(), - system_program: system_program::id(), + system_program: system_program::ID, rent: rent_sysvar_id(), } .to_account_metas(None), @@ -178,17 +172,12 @@ fn test_create_and_mint_tokens() { associated_token_account: ata, token_program: token_program_id(), associated_token_program: associated_token_program_id(), - system_program: system_program::id(), + system_program: system_program::ID, } .to_account_metas(None), ); - send_transaction_from_instructions( - &mut svm, - vec![mint_ix], - &[&payer], - &payer.pubkey(), - ) - .unwrap(); + send_transaction_from_instructions(&mut svm, vec![mint_ix], &[&payer], &payer.pubkey()) + .unwrap(); // Verify 100 tokens minted (in minor units) let balance = get_token_account_balance(&svm, &ata).unwrap(); diff --git a/tokens/transfer-tokens/anchor/programs/transfer-tokens/src/instructions/create.rs b/tokens/transfer-tokens/anchor/programs/transfer-tokens/src/instructions/create.rs index ddc002dec..33a74de7e 100644 --- a/tokens/transfer-tokens/anchor/programs/transfer-tokens/src/instructions/create.rs +++ b/tokens/transfer-tokens/anchor/programs/transfer-tokens/src/instructions/create.rs @@ -59,7 +59,7 @@ pub fn handle_create_token( update_authority: context.accounts.payer.cpi_handle(), payer: context.accounts.payer.cpi_handle_mut(), system_program: context.accounts.system_program.cpi_handle(), - update_authority_is_signer: true, + update_authority_is_signer: true, }, ), DataV2 { diff --git a/tokens/transfer-tokens/anchor/programs/transfer-tokens/tests/test_transfer_tokens.rs b/tokens/transfer-tokens/anchor/programs/transfer-tokens/tests/test_transfer_tokens.rs index bf0052e93..55a1c6e35 100644 --- a/tokens/transfer-tokens/anchor/programs/transfer-tokens/tests/test_transfer_tokens.rs +++ b/tokens/transfer-tokens/anchor/programs/transfer-tokens/tests/test_transfer_tokens.rs @@ -1,7 +1,7 @@ use { anchor_lang::{ - solana_program::{instruction::Instruction, pubkey::Pubkey, system_program}, - InstructionData, ToAccountMetas, + solana_program::instruction::Instruction, system_program, Address, InstructionData, + ToAccountMetas, }, litesvm::LiteSVM, solana_keypair::Keypair, @@ -19,52 +19,48 @@ fn to_minor_units(major_units: u64) -> u64 { major_units.checked_mul(10u64.pow(MINT_DECIMALS)).unwrap() } -fn metadata_program_id() -> Pubkey { +fn metadata_program_id() -> Address { "metaqbxxUerdq28cj1RbAWkYQm3ybzjb6a8bt518x1s" .parse() .unwrap() } -fn token_program_id() -> Pubkey { +fn token_program_id() -> Address { "TokenkegQfeZyiNwAJbNbGKPFXCWuBvf9Ss623VQ5DA" .parse() .unwrap() } -fn associated_token_program_id() -> Pubkey { +fn associated_token_program_id() -> Address { "ATokenGPvbdGVxr1b2hvZbsiqW5xWH25efTNsLJA8knL" .parse() .unwrap() } -fn rent_sysvar_id() -> Pubkey { +fn rent_sysvar_id() -> Address { "SysvarRent111111111111111111111111111111111" .parse() .unwrap() } -fn derive_metadata_pda(mint: &Pubkey) -> Pubkey { +fn derive_metadata_pda(mint: &Address) -> Address { let metadata_pid = metadata_program_id(); - let (pda, _bump) = Pubkey::find_program_address( + let (pda, _bump) = Address::find_program_address( &[b"metadata", metadata_pid.as_ref(), mint.as_ref()], &metadata_pid, ); pda } -fn derive_ata(wallet: &Pubkey, mint: &Pubkey) -> Pubkey { - let (ata, _bump) = Pubkey::find_program_address( - &[ - wallet.as_ref(), - token_program_id().as_ref(), - mint.as_ref(), - ], +fn derive_ata(wallet: &Address, mint: &Address) -> Address { + let (ata, _bump) = Address::find_program_address( + &[wallet.as_ref(), token_program_id().as_ref(), mint.as_ref()], &associated_token_program_id(), ); ata } -fn setup() -> (LiteSVM, Pubkey, Keypair) { +fn setup() -> (LiteSVM, Address, Keypair) { let program_id = transfer_tokens::id(); let mut svm = LiteSVM::new(); @@ -100,7 +96,7 @@ fn test_create_mint_and_transfer() { metadata_account, token_program: token_program_id(), token_metadata_program: metadata_program_id(), - system_program: system_program::id(), + system_program: system_program::ID, rent: rent_sysvar_id(), } .to_account_metas(None), @@ -136,17 +132,12 @@ fn test_create_mint_and_transfer() { associated_token_account: sender_ata, token_program: token_program_id(), associated_token_program: associated_token_program_id(), - system_program: system_program::id(), + system_program: system_program::ID, } .to_account_metas(None), ); - send_transaction_from_instructions( - &mut svm, - vec![mint_ix], - &[&payer], - &payer.pubkey(), - ) - .unwrap(); + send_transaction_from_instructions(&mut svm, vec![mint_ix], &[&payer], &payer.pubkey()) + .unwrap(); // Verify 100 tokens minted (in minor units) assert_eq!( @@ -173,17 +164,12 @@ fn test_create_mint_and_transfer() { recipient_token_account: recipient_ata, token_program: token_program_id(), associated_token_program: associated_token_program_id(), - system_program: system_program::id(), + system_program: system_program::ID, } .to_account_metas(None), ); - send_transaction_from_instructions( - &mut svm, - vec![transfer_ix], - &[&payer], - &payer.pubkey(), - ) - .unwrap(); + send_transaction_from_instructions(&mut svm, vec![transfer_ix], &[&payer], &payer.pubkey()) + .unwrap(); // Verify: sender 50 tokens, recipient 50 tokens (in minor units) assert_eq!( From 4f89276ee9ce3615a1d11b6b36c47fc1303d7edc Mon Sep 17 00:00:00 2001 From: Claude Date: Fri, 14 Aug 2026 13:51:01 +0000 Subject: [PATCH 10/59] tokens: finish metadata and allow-block-list-token The last two of the six extension examples. metadata: the mint is created by hand with the MetadataPointer extension (pointing at the mint itself, which is where Token-2022 stores the metadata). v2's `TokenMetadataInitialize` / `UpdateField` / `UpdateAuthority` structs drop the `program_id` slot, the program comes from the `CpiContext`, and `token_metadata_update_authority` now takes a plain `Option<&Address>` and does the `OptionalNonZeroPubkey` conversion itself. allow-block-list-token: three extensions on one mint (PermanentDelegate, TransferHook, MetadataPointer), so the allocation sizes for all three and initializes each before `InitializeMint2`. Its lamport top-up after the metadata write goes through the `system_program::transfer` CPI helper rather than a hand-rolled `invoke`. Also dropped the `extensions::*` *validation* constraints (as opposed to the init ones) from metadata's update_field/remove_key/update_authority, v2 has no such constraint, and Token-2022 checks the metadata pointer itself when the CPI runs. --- .../metadata/src/instructions/remove_key.rs | 5 +- .../src/instructions/update_authority.rs | 31 ++-- .../metadata/src/instructions/update_field.rs | 13 +- .../abl-token/src/instructions/init_mint.rs | 165 +++++++++++++----- 4 files changed, 140 insertions(+), 74 deletions(-) diff --git a/tokens/token-extensions/metadata/anchor/programs/metadata/src/instructions/remove_key.rs b/tokens/token-extensions/metadata/anchor/programs/metadata/src/instructions/remove_key.rs index fde27f08c..1b5862bf0 100644 --- a/tokens/token-extensions/metadata/anchor/programs/metadata/src/instructions/remove_key.rs +++ b/tokens/token-extensions/metadata/anchor/programs/metadata/src/instructions/remove_key.rs @@ -8,10 +8,7 @@ pub struct RemoveKeyAccountConstraints { #[account(mut)] pub update_authority: Signer, - #[account( - mut, - extensions::metadata_pointer::metadata_address = mint_account, - )] + #[account(mut)] pub mint_account: InterfaceAccount, pub token_program: Program, pub system_program: Program, diff --git a/tokens/token-extensions/metadata/anchor/programs/metadata/src/instructions/update_authority.rs b/tokens/token-extensions/metadata/anchor/programs/metadata/src/instructions/update_authority.rs index d90adeb02..b6485a687 100644 --- a/tokens/token-extensions/metadata/anchor/programs/metadata/src/instructions/update_authority.rs +++ b/tokens/token-extensions/metadata/anchor/programs/metadata/src/instructions/update_authority.rs @@ -1,7 +1,6 @@ use anchor_lang::prelude::*; use anchor_spl::token_interface::{ - spl_pod::optional_keys::OptionalNonZeroPubkey, token_metadata_update_authority, Mint, - Token2022, TokenMetadataUpdateAuthority, + token_metadata_update_authority, Mint, Token2022, TokenMetadataUpdateAuthority, }; #[derive(Accounts)] @@ -9,10 +8,9 @@ pub struct UpdateAuthorityAccountConstraints { pub current_authority: Signer, pub new_authority: Option, - #[account( - mut, - extensions::metadata_pointer::metadata_address = mint_account, - )] + // v2 has no `extensions::*` validation constraint; Token-2022 checks the + // metadata pointer and the update authority itself when the CPI runs. + #[account(mut)] pub mint_account: InterfaceAccount, pub token_program: Program, pub system_program: Program, @@ -21,26 +19,25 @@ pub struct UpdateAuthorityAccountConstraints { pub fn process_update_authority( context: &mut Context, ) -> Result<()> { - let new_authority_key = match &context.accounts.new_authority { - Some(account) => OptionalNonZeroPubkey::try_from(Some(account.address()))?, - None => OptionalNonZeroPubkey::try_from(None)?, - }; + // v2 takes the new authority as a plain `Option<&Address>` and does the + // `OptionalNonZeroPubkey` conversion itself. + let new_authority = context + .accounts + .new_authority + .as_ref() + .map(|account| *account.address()); - // Change update authority + // Change update authority. v2's struct drops the program-id and + // new-authority slots — neither is passed as an account. token_metadata_update_authority( CpiContext::new( context.accounts.token_program.address(), TokenMetadataUpdateAuthority { - program_id: context.accounts.token_program.cpi_handle_mut(), metadata: context.accounts.mint_account.cpi_handle_mut(), current_authority: context.accounts.current_authority.cpi_handle(), - - // new authority isn't actually needed as account in the CPI - // using current_authority as a placeholder to satisfy the struct - new_authority: context.accounts.current_authority.cpi_handle_mut(), }, ), - new_authority_key, + new_authority.as_ref(), )?; Ok(()) } diff --git a/tokens/token-extensions/metadata/anchor/programs/metadata/src/instructions/update_field.rs b/tokens/token-extensions/metadata/anchor/programs/metadata/src/instructions/update_field.rs index b6365e4ab..6f38d284a 100644 --- a/tokens/token-extensions/metadata/anchor/programs/metadata/src/instructions/update_field.rs +++ b/tokens/token-extensions/metadata/anchor/programs/metadata/src/instructions/update_field.rs @@ -14,10 +14,7 @@ pub struct UpdateFieldAccountConstraints { #[account(mut)] pub authority: Signer, - #[account( - mut, - extensions::metadata_pointer::metadata_address = mint_account, - )] + #[account(mut)] pub mint_account: InterfaceAccount, pub token_program: Program, pub system_program: Program, @@ -34,9 +31,10 @@ pub fn process_update_field( msg!("Field: {:?}, Value: {}", field, value); let (current_lamports, required_lamports) = { - // Get the current state of the mint account - let mint = &context.accounts.mint_account.cpi_handle_mut(); - let buffer = mint.try_borrow_data()?; + // Get the current state of the mint account. `AccountView` is Copy and + // a copy still points at the same backing buffer. + let mint = *context.accounts.mint_account.account(); + let buffer = mint.try_borrow()?; let state = PodStateWithExtensions::::unpack(&buffer)?; // Get and update the token metadata @@ -83,7 +81,6 @@ pub fn process_update_field( CpiContext::new( context.accounts.token_program.address(), TokenMetadataUpdateField { - program_id: context.accounts.token_program.cpi_handle_mut(), metadata: context.accounts.mint_account.cpi_handle_mut(), update_authority: context.accounts.authority.cpi_handle(), }, diff --git a/tokens/token-extensions/transfer-hook/allow-block-list-token/anchor/programs/abl-token/src/instructions/init_mint.rs b/tokens/token-extensions/transfer-hook/allow-block-list-token/anchor/programs/abl-token/src/instructions/init_mint.rs index d95b3ec03..02a68ce3b 100644 --- a/tokens/token-extensions/transfer-hook/allow-block-list-token/anchor/programs/abl-token/src/instructions/init_mint.rs +++ b/tokens/token-extensions/transfer-hook/allow-block-list-token/anchor/programs/abl-token/src/instructions/init_mint.rs @@ -1,11 +1,19 @@ -use anchor_lang::{ - prelude::*, solana_program::program::invoke, solana_program::system_instruction::transfer, -}; +use anchor_lang::prelude::*; +use anchor_lang::system_program::{create_account, transfer, CreateAccount, Transfer}; use anchor_spl::{ - token_2022::Token2022, + token_2022::{ + initialize_mint2, + spl_token_2022::{extension::ExtensionType, pod::PodMint}, + InitializeMint2, Token2022, + }, + token_2022_extensions::{ + metadata_pointer::{metadata_pointer_initialize, MetadataPointerInitialize}, + permanent_delegate::{permanent_delegate_initialize, PermanentDelegateInitialize}, + transfer_hook::{transfer_hook_initialize, TransferHookInitialize}, + }, token_interface::{ spl_token_metadata_interface::state::Field, token_metadata_initialize, - token_metadata_update_field, Mint, TokenMetadataInitialize, TokenMetadataUpdateField, + token_metadata_update_field, TokenMetadataInitialize, TokenMetadataUpdateField, }, }; @@ -20,20 +28,12 @@ pub struct InitMintAccountConstraints { #[account(mut)] pub payer: Signer, - #[account( - init, - payer = payer, - mint::token_program = token_program, - mint::decimals = args.decimals(), - mint::authority = payer, - mint::freeze_authority = args.freeze_authority, - extensions::permanent_delegate::delegate = args.permanent_delegate, - extensions::transfer_hook::authority = args.transfer_hook_authority, - extensions::transfer_hook::program_id = crate::id(), - extensions::metadata_pointer::authority = payer.address(), - extensions::metadata_pointer::metadata_address = mint.address(), - )] - pub mint: Box>, + /// CHECK: created and initialized by this instruction as a Token-2022 mint + /// carrying the PermanentDelegate, TransferHook and MetadataPointer + /// extensions. anchor-spl has no init constraints for those, so the mint is + /// built by hand in `init_mint` below. + #[account(mut)] + pub mint: Signer, #[account( init, @@ -50,24 +50,100 @@ pub struct InitMintAccountConstraints { pub token_program: Program, } -impl InitMintAccountConstraints<'_> { +impl InitMintAccountConstraints { pub fn init_mint(&mut self, args: InitMintArgs) -> Result<()> { + let mint_address = *self.mint.address(); + let payer_address = *self.payer.address(); + + // Allocate the mint with room for all three extensions, initialize each + // extension, then initialize the mint data. Extension initialization has + // to happen before InitializeMint2. + let mint_size = ExtensionType::try_calculate_account_len::(&[ + ExtensionType::PermanentDelegate, + ExtensionType::TransferHook, + ExtensionType::MetadataPointer, + ])?; + let lamports = Rent::get()?.try_minimum_balance(mint_size)?; + + create_account( + CpiContext::new( + self.system_program.address(), + CreateAccount { + from: self.payer.cpi_handle_mut(), + to: self.mint.cpi_handle_mut(), + }, + ), + lamports, + mint_size as u64, + self.token_program.address(), + )?; + + permanent_delegate_initialize( + CpiContext::new( + self.token_program.address(), + PermanentDelegateInitialize { + mint: self.mint.cpi_handle_mut(), + }, + ), + &args.permanent_delegate, + )?; + + transfer_hook_initialize( + CpiContext::new( + self.token_program.address(), + TransferHookInitialize { + mint: self.mint.cpi_handle_mut(), + }, + ), + Some(&args.transfer_hook_authority), + Some(&crate::ID), + )?; + + // metadata is stored in the mint itself, so the pointer points at it + metadata_pointer_initialize( + CpiContext::new( + self.token_program.address(), + MetadataPointerInitialize { + mint: self.mint.cpi_handle_mut(), + }, + ), + Some(&payer_address), + Some(&mint_address), + )?; + + initialize_mint2( + CpiContext::new( + self.token_program.address(), + InitializeMint2 { + mint: self.mint.cpi_handle_mut(), + }, + ), + args.decimals, + &payer_address, + Some(&args.freeze_authority), + )?; + + // `payer` and `mint` each fill more than one CPI slot below. v2's typed + // handles enforce borrow exclusivity at compile time, so the read-only + // slots are built from copies of the `AccountView` — each copy still + // points at the same underlying account. + let payer_view = *self.payer.account(); + let mint_view = *self.mint.account(); + let cpi_accounts = TokenMetadataInitialize { - program_id: self.token_program.cpi_handle_mut(), - mint: self.mint.cpi_handle_mut(), - metadata: self.mint.cpi_handle_mut(), // metadata account is the mint, since data is stored in mint - mint_authority: self.payer.cpi_handle_mut(), - update_authority: self.payer.cpi_handle_mut(), + // metadata account is the mint, since data is stored in mint + metadata: self.mint.cpi_handle_mut(), + update_authority: CpiHandle::readonly(&payer_view), + mint: CpiHandle::readonly(&mint_view), + mint_authority: CpiHandle::readonly(&payer_view), }; let cpi_ctx = CpiContext::new(self.token_program.address(), cpi_accounts); token_metadata_initialize(cpi_ctx, args.name, args.symbol, args.uri)?; let cpi_accounts = TokenMetadataUpdateField { metadata: self.mint.cpi_handle_mut(), - update_authority: self.payer.cpi_handle_mut(), - program_id: self.token_program.cpi_handle_mut(), + update_authority: CpiHandle::readonly(&payer_view), }; - let cpi_ctx = CpiContext::new(self.token_program.address(), cpi_accounts); token_metadata_update_field(cpi_ctx, Field::Key("AB".to_string()), args.mode.to_string())?; @@ -75,8 +151,7 @@ impl InitMintAccountConstraints<'_> { if args.mode == Mode::Mixed { let cpi_accounts = TokenMetadataUpdateField { metadata: self.mint.cpi_handle_mut(), - update_authority: self.payer.cpi_handle_mut(), - program_id: self.token_program.cpi_handle_mut(), + update_authority: CpiHandle::readonly(&payer_view), }; let cpi_ctx = CpiContext::new(self.token_program.address(), cpi_accounts); @@ -87,27 +162,27 @@ impl InitMintAccountConstraints<'_> { )?; } - let data = self.mint.cpi_handle_mut().data_len(); - let min_balance = Rent::get()?.try_minimum_balance(data)?; - if min_balance > self.mint.cpi_handle_mut().get_lamports() { - invoke( - &transfer( - &self.payer.address(), - &self.mint.cpi_handle_mut().address(), - min_balance - self.mint.cpi_handle_mut().get_lamports(), + // Writing the metadata grew the mint, so top it back up to rent exemption. + let data_len = self.mint.account().data_len(); + let min_balance = Rent::get()?.try_minimum_balance(data_len)?; + let current = self.mint.account().lamports(); + if min_balance > current { + transfer( + CpiContext::new( + self.system_program.address(), + Transfer { + from: self.payer.cpi_handle_mut(), + to: self.mint.cpi_handle_mut(), + }, ), - &[ - self.payer.cpi_handle_mut(), - self.mint.cpi_handle_mut(), - self.system_program.cpi_handle_mut(), - ], + min_balance - current, )?; } // initialize the extra metas account - let extra_metas_account = &self.extra_metas_account; let metas = get_extra_account_metas()?; - let mut data = extra_metas_account.try_borrow_mut_data()?; + let mut extra_metas_view = *self.extra_metas_account.account(); + let mut data = extra_metas_view.try_borrow_mut()?; ExtraAccountMetaList::init::(&mut data, &metas) .map_err(|_| ProgramError::InvalidAccountData)?; From 9ddd88cea81f98c5e53a4b2177a4ce56658afb4b Mon Sep 17 00:00:00 2001 From: Claude Date: Fri, 14 Aug 2026 13:54:56 +0000 Subject: [PATCH 11/59] tokens/finance: automate the CPI borrow-exclusivity rewrite v2's typed CPI handles make aliasing a compile error rather than latent UB, so any handler passing one account into both a writable and a read-only CPI slot stops building. That pattern recurs across the token and finance examples, so this adds a fixer that works per function body: when a field is used both ways inside the same function, it binds a copied `AccountView` and rebuilds the read-only slots over it with `CpiHandle::readonly`. `AccountView` is `Copy` and a copy still points at the same backing buffer, so behaviour is unchanged. Other fixes in this pass: - Files using the brace-form `use { .., anchor_spl::{..} }` never got the `mint` / `token` module imports the namespaced constraints expand to; the earlier pass only handled a standalone prelude import. Deduplicated where that then collided with an existing `mint::{self, ..}`. - `create_metadata_accounts_v3` takes four arguments in v2, not five, `update_authority_is_signer` moved into the accounts struct, and the optional `rent` account is gone. - escrow's `shared.rs` helpers took `&AccountView` and called `cpi_handle_mut` on it; building a writable handle needs a `&mut`, so they now take the view by value (it is `Copy`) and use `CpiHandleMut::writable` / `CpiHandle::readonly` explicitly, with the accounts themselves passed as `&mut`. --- .../escrow/src/instructions/cancel_offer.rs | 12 +++---- .../escrow/src/instructions/make_offer.rs | 2 +- .../escrow/src/instructions/shared.rs | 25 +++++++------- .../escrow/src/instructions/take_offer.rs | 12 +++---- .../src/instructions/place_order.rs | 5 ++- .../anchor/programs/nft-minter/src/lib.rs | 18 ++++++---- .../token-minter/src/instructions/create.rs | 1 - .../token-minter/src/instructions/mint.rs | 5 ++- .../programs/immutable-owner/src/lib.rs | 5 ++- .../src/instructions/initialize.rs | 7 ++-- .../metadata/src/instructions/update_field.rs | 5 ++- .../src/instructions/chop_tree.rs | 14 ++++++-- .../src/instructions/mint_nft.rs | 34 ++++++++++++------- .../anchor/programs/extension_nft/src/lib.rs | 11 ++++-- .../extension_nft/tests/test_extension_nft.rs | 5 ++- .../transfer-fee/src/instructions/transfer.rs | 5 ++- .../abl-token/src/instructions/change_mode.rs | 1 + .../token-minter/src/instructions/create.rs | 8 +++-- .../src/instructions/create.rs | 9 +++-- .../src/instructions/transfer.rs | 5 ++- 20 files changed, 124 insertions(+), 65 deletions(-) diff --git a/finance/escrow/anchor/programs/escrow/src/instructions/cancel_offer.rs b/finance/escrow/anchor/programs/escrow/src/instructions/cancel_offer.rs index 9ed0a4079..15b0fe917 100644 --- a/finance/escrow/anchor/programs/escrow/src/instructions/cancel_offer.rs +++ b/finance/escrow/anchor/programs/escrow/src/instructions/cancel_offer.rs @@ -59,20 +59,20 @@ pub fn handle_cancel_offer(context: &mut Context) // Move all tokens back from the vault to the maker. transfer_tokens( - &context.accounts.vault, - &context.accounts.maker_token_account_a, + &mut context.accounts.vault, + &mut context.accounts.maker_token_account_a, &context.accounts.vault.amount(), &context.accounts.token_mint_a, - &context.accounts.offer.cpi_handle_mut(), + *context.accounts.offer.account(), &context.accounts.token_program, Some(offer_seeds), )?; // Close the vault, sending its rent lamports back to the maker. close_token_account( - &context.accounts.vault, - &context.accounts.maker.cpi_handle_mut(), - &context.accounts.offer.cpi_handle_mut(), + &mut context.accounts.vault, + *context.accounts.maker.account(), + *context.accounts.offer.account(), &context.accounts.token_program, Some(offer_seeds), )?; diff --git a/finance/escrow/anchor/programs/escrow/src/instructions/make_offer.rs b/finance/escrow/anchor/programs/escrow/src/instructions/make_offer.rs index dd77dee47..dd5279635 100644 --- a/finance/escrow/anchor/programs/escrow/src/instructions/make_offer.rs +++ b/finance/escrow/anchor/programs/escrow/src/instructions/make_offer.rs @@ -76,7 +76,7 @@ pub fn handle_send_offered_tokens_to_vault( &context.accounts.vault, &token_a_offered_amount, &context.accounts.token_mint_a, - &context.accounts.maker.cpi_handle_mut(), + *context.accounts.maker.account(), &context.accounts.token_program, None, ) diff --git a/finance/escrow/anchor/programs/escrow/src/instructions/shared.rs b/finance/escrow/anchor/programs/escrow/src/instructions/shared.rs index 75df4defa..3e2634f23 100644 --- a/finance/escrow/anchor/programs/escrow/src/instructions/shared.rs +++ b/finance/escrow/anchor/programs/escrow/src/instructions/shared.rs @@ -8,20 +8,21 @@ use anchor_spl::token_interface::{ // Transfer tokens from one token account to another. // When transferring out of a token account owned by a PDA, pass the PDA's // signer seeds via owning_pda_seeds; otherwise pass None. -pub fn transfer_tokens<'info>( - from: &InterfaceAccount, - to: &InterfaceAccount, +pub fn transfer_tokens( + from: &mut InterfaceAccount, + to: &mut InterfaceAccount, amount: &u64, mint: &InterfaceAccount, - authority: &AccountView, + authority: AccountView, token_program: &Interface<'static, TokenInterface>, owning_pda_seeds: Option<&[&[u8]]>, ) -> Result<()> { + let decimals = mint.decimals(); let transfer_accounts = TransferChecked { from: from.cpi_handle_mut(), mint: mint.cpi_handle(), to: to.cpi_handle_mut(), - authority: authority.cpi_handle_mut(), + authority: CpiHandle::readonly(&authority), }; let signer_seeds = owning_pda_seeds.map(|seeds| [seeds]); @@ -32,23 +33,23 @@ pub fn transfer_tokens<'info>( None => CpiContext::new(token_program.address(), transfer_accounts), }; - transfer_checked(cpi_context, *amount, mint.decimals()) + transfer_checked(cpi_context, *amount, decimals) } // Close a token account, sending its rent lamports to destination. // When the token account is owned by a PDA, pass the PDA's signer seeds via // owning_pda_seeds; otherwise pass None. -pub fn close_token_account<'info>( - token_account: &InterfaceAccount, - destination: &AccountView, - authority: &AccountView, +pub fn close_token_account( + token_account: &mut InterfaceAccount, + mut destination: AccountView, + authority: AccountView, token_program: &Interface<'static, TokenInterface>, owning_pda_seeds: Option<&[&[u8]]>, ) -> Result<()> { let close_accounts = CloseAccount { account: token_account.cpi_handle_mut(), - destination: destination.cpi_handle_mut(), - authority: authority.cpi_handle_mut(), + destination: CpiHandleMut::writable(&mut destination), + authority: CpiHandle::readonly(&authority), }; let signer_seeds = owning_pda_seeds.map(|seeds| [seeds]); diff --git a/finance/escrow/anchor/programs/escrow/src/instructions/take_offer.rs b/finance/escrow/anchor/programs/escrow/src/instructions/take_offer.rs index 6bf739357..5441f1c3e 100644 --- a/finance/escrow/anchor/programs/escrow/src/instructions/take_offer.rs +++ b/finance/escrow/anchor/programs/escrow/src/instructions/take_offer.rs @@ -75,11 +75,11 @@ pub fn handle_send_wanted_tokens_to_maker( context: &Context, ) -> Result<()> { transfer_tokens( - &context.accounts.taker_token_account_b, - &context.accounts.maker_token_account_b, + &mut context.accounts.taker_token_account_b, + &mut context.accounts.maker_token_account_b, &context.accounts.offer.token_b_wanted_amount, &context.accounts.token_mint_b, - &context.accounts.taker.cpi_handle_mut(), + *context.accounts.taker.account(), &context.accounts.token_program, None, ) @@ -98,7 +98,7 @@ pub fn handle_withdraw_and_close_vault( &context.accounts.taker_token_account_a, &context.accounts.vault.amount(), &context.accounts.token_mint_a, - &context.accounts.offer.cpi_handle_mut(), + *context.accounts.offer.account(), &context.accounts.token_program, Some(offer_seeds), )?; @@ -107,8 +107,8 @@ pub fn handle_withdraw_and_close_vault( // to the maker (the offer account does the same via `close = maker`). close_token_account( &context.accounts.vault, - &context.accounts.maker.cpi_handle_mut(), - &context.accounts.offer.cpi_handle_mut(), + *context.accounts.maker.account(), + *context.accounts.offer.account(), &context.accounts.token_program, Some(offer_seeds), ) diff --git a/finance/order-book/anchor/programs/order-book/src/instructions/place_order.rs b/finance/order-book/anchor/programs/order-book/src/instructions/place_order.rs index f117e543f..90aa2a774 100644 --- a/finance/order-book/anchor/programs/order-book/src/instructions/place_order.rs +++ b/finance/order-book/anchor/programs/order-book/src/instructions/place_order.rs @@ -31,6 +31,9 @@ pub fn handle_place_order<'info>( price: u64, quantity: u64, ) -> Result<()> { + // `AccountView` is Copy, and a copy still points at the same + // account — v2's typed handles make the aliasing a compile error. + let quote_mint_view = *context.accounts.quote_mint.account(); let market = &context.accounts.market; require!(market.is_active, ErrorCode::MarketPaused); @@ -342,7 +345,7 @@ pub fn handle_place_order<'info>( context.accounts.token_program.address(), TransferChecked { from: context.accounts.quote_vault.cpi_handle_mut(), - mint: context.accounts.quote_mint.cpi_handle(), + mint: CpiHandle::readonly("e_mint_view), to: context.accounts.fee_vault.cpi_handle_mut(), authority: market.cpi_handle(), }, diff --git a/tokens/nft-minter/anchor/programs/nft-minter/src/lib.rs b/tokens/nft-minter/anchor/programs/nft-minter/src/lib.rs index edc362e5e..205a847f7 100644 --- a/tokens/nft-minter/anchor/programs/nft-minter/src/lib.rs +++ b/tokens/nft-minter/anchor/programs/nft-minter/src/lib.rs @@ -7,6 +7,7 @@ use { mpl_token_metadata::types::DataV2, CreateMasterEditionV3, CreateMetadataAccountsV3, Metadata, }, + mint, token::{mint_to, Mint, MintTo, Token, TokenAccount}, }, }; @@ -23,6 +24,10 @@ pub mod nft_minter { nft_symbol: String, nft_uri: String, ) -> Result<()> { + // `AccountView` is Copy, and a copy still points at the same + // account — v2's typed handles make the aliasing a compile error. + let mint_account_view = *context.accounts.mint_account.account(); + let payer_view = *context.accounts.payer.account(); msg!("Minting Token"); // Cross Program Invocation (CPI) // Invoking the mint_to instruction on the token program @@ -32,7 +37,7 @@ pub mod nft_minter { MintTo { mint: context.accounts.mint_account.cpi_handle_mut(), to: context.accounts.associated_token_account.cpi_handle_mut(), - authority: context.accounts.payer.cpi_handle(), + authority: CpiHandle::readonly(&payer_view), }, ), 1, @@ -46,9 +51,9 @@ pub mod nft_minter { context.accounts.token_metadata_program.address(), CreateMetadataAccountsV3 { metadata: context.accounts.metadata_account.cpi_handle_mut(), - mint: context.accounts.mint_account.cpi_handle(), - mint_authority: context.accounts.payer.cpi_handle(), - update_authority: context.accounts.payer.cpi_handle(), + mint: CpiHandle::readonly(&mint_account_view), + mint_authority: CpiHandle::readonly(&payer_view), + update_authority: CpiHandle::readonly(&payer_view), payer: context.accounts.payer.cpi_handle_mut(), system_program: context.accounts.system_program.cpi_handle(), update_authority_is_signer: true, @@ -64,7 +69,6 @@ pub mod nft_minter { uses: None, }, false, // Is mutable - true, // Update authority is signer None, // Collection details )?; @@ -77,8 +81,8 @@ pub mod nft_minter { CreateMasterEditionV3 { edition: context.accounts.edition_account.cpi_handle_mut(), mint: context.accounts.mint_account.cpi_handle_mut(), - update_authority: context.accounts.payer.cpi_handle(), - mint_authority: context.accounts.payer.cpi_handle(), + update_authority: CpiHandle::readonly(&payer_view), + mint_authority: CpiHandle::readonly(&payer_view), payer: context.accounts.payer.cpi_handle_mut(), metadata: context.accounts.metadata_account.cpi_handle_mut(), token_program: context.accounts.token_program.cpi_handle(), diff --git a/tokens/pda-mint-authority/anchor/programs/token-minter/src/instructions/create.rs b/tokens/pda-mint-authority/anchor/programs/token-minter/src/instructions/create.rs index 97253e307..b8f76094a 100644 --- a/tokens/pda-mint-authority/anchor/programs/token-minter/src/instructions/create.rs +++ b/tokens/pda-mint-authority/anchor/programs/token-minter/src/instructions/create.rs @@ -83,7 +83,6 @@ pub fn handle_create_token( uses: None, }, false, // Is mutable - true, // Update authority is signer None, // Collection details )?; diff --git a/tokens/pda-mint-authority/anchor/programs/token-minter/src/instructions/mint.rs b/tokens/pda-mint-authority/anchor/programs/token-minter/src/instructions/mint.rs index e5fe9b33a..a5bc342bf 100644 --- a/tokens/pda-mint-authority/anchor/programs/token-minter/src/instructions/mint.rs +++ b/tokens/pda-mint-authority/anchor/programs/token-minter/src/instructions/mint.rs @@ -44,6 +44,9 @@ pub fn handle_mint_token( context: &mut Context, amount: u64, ) -> Result<()> { + // `AccountView` is Copy, and a copy still points at the same + // account — v2's typed handles make the aliasing a compile error. + let mint_account_view = *context.accounts.mint_account.account(); msg!("Minting token to associated token account..."); msg!("Mint: {}", &context.accounts.mint_account.address()); msg!( @@ -61,7 +64,7 @@ pub fn handle_mint_token( MintTo { mint: context.accounts.mint_account.cpi_handle_mut(), to: context.accounts.associated_token_account.cpi_handle_mut(), - authority: context.accounts.mint_account.cpi_handle(), // PDA mint authority, required as signer + authority: CpiHandle::readonly(&mint_account_view), // PDA mint authority, required as signer }, ) .with_signer(signer_seeds), // using PDA to sign diff --git a/tokens/token-extensions/immutable-owner/anchor/programs/immutable-owner/src/lib.rs b/tokens/token-extensions/immutable-owner/anchor/programs/immutable-owner/src/lib.rs index 621a0b59b..9daa0ea2e 100644 --- a/tokens/token-extensions/immutable-owner/anchor/programs/immutable-owner/src/lib.rs +++ b/tokens/token-extensions/immutable-owner/anchor/programs/immutable-owner/src/lib.rs @@ -18,6 +18,9 @@ pub mod immutable_owner { // There is currently not an anchor constraint to automatically initialize the ImmutableOwner extension // We can manually create and initialize the token account via CPIs in the instruction handler pub fn initialize(context: &mut Context) -> Result<()> { + // `AccountView` is Copy, and a copy still points at the same + // account — v2's typed handles make the aliasing a compile error. + let payer_view = *context.accounts.payer.account(); // Calculate space required for token and extension data let token_account_size = ExtensionType::try_calculate_account_len::(&[ ExtensionType::ImmutableOwner, @@ -54,7 +57,7 @@ pub mod immutable_owner { InitializeAccount3 { account: context.accounts.token_account.cpi_handle_mut(), mint: context.accounts.mint_account.cpi_handle(), - authority: context.accounts.payer.cpi_handle(), + authority: CpiHandle::readonly(&payer_view), }, ))?; Ok(()) diff --git a/tokens/token-extensions/memo-transfer/anchor/programs/memo-transfer/src/instructions/initialize.rs b/tokens/token-extensions/memo-transfer/anchor/programs/memo-transfer/src/instructions/initialize.rs index be23a6b94..b260b9acf 100644 --- a/tokens/token-extensions/memo-transfer/anchor/programs/memo-transfer/src/instructions/initialize.rs +++ b/tokens/token-extensions/memo-transfer/anchor/programs/memo-transfer/src/instructions/initialize.rs @@ -22,6 +22,9 @@ pub struct InitializeAccountConstraints { } pub fn handler(context: &mut Context) -> Result<()> { + // `AccountView` is Copy, and a copy still points at the same + // account — v2's typed handles make the aliasing a compile error. + let payer_view = *context.accounts.payer.account(); // Calculate space required for token and extension data let token_account_size = ExtensionType::try_calculate_account_len::(&[ExtensionType::MemoTransfer])?; @@ -49,7 +52,7 @@ pub fn handler(context: &mut Context) -> Result<() InitializeAccount3 { account: context.accounts.token_account.cpi_handle_mut(), mint: context.accounts.mint_account.cpi_handle(), - authority: context.accounts.payer.cpi_handle(), + authority: CpiHandle::readonly(&payer_view), }, ))?; @@ -59,7 +62,7 @@ pub fn handler(context: &mut Context) -> Result<() context.accounts.token_program.address(), MemoTransfer { account: context.accounts.token_account.cpi_handle_mut(), - owner: context.accounts.payer.cpi_handle(), + owner: CpiHandle::readonly(&payer_view), }, ))?; Ok(()) diff --git a/tokens/token-extensions/metadata/anchor/programs/metadata/src/instructions/update_field.rs b/tokens/token-extensions/metadata/anchor/programs/metadata/src/instructions/update_field.rs index 6f38d284a..73e4b6d5f 100644 --- a/tokens/token-extensions/metadata/anchor/programs/metadata/src/instructions/update_field.rs +++ b/tokens/token-extensions/metadata/anchor/programs/metadata/src/instructions/update_field.rs @@ -24,6 +24,9 @@ pub fn process_update_field( context: &mut Context, args: UpdateFieldArgs, ) -> Result<()> { + // `AccountView` is Copy, and a copy still points at the same + // account — v2's typed handles make the aliasing a compile error. + let authority_view = *context.accounts.authority.account(); let UpdateFieldArgs { field, value } = args; // Convert to Field type from spl_token_metadata_interface @@ -82,7 +85,7 @@ pub fn process_update_field( context.accounts.token_program.address(), TokenMetadataUpdateField { metadata: context.accounts.mint_account.cpi_handle_mut(), - update_authority: context.accounts.authority.cpi_handle(), + update_authority: CpiHandle::readonly(&authority_view), }, ), field, diff --git a/tokens/token-extensions/nft-meta-data-pointer/anchor-example/anchor/programs/extension_nft/src/instructions/chop_tree.rs b/tokens/token-extensions/nft-meta-data-pointer/anchor-example/anchor/programs/extension_nft/src/instructions/chop_tree.rs index fe43fbd2c..031bc68d6 100644 --- a/tokens/token-extensions/nft-meta-data-pointer/anchor-example/anchor/programs/extension_nft/src/instructions/chop_tree.rs +++ b/tokens/token-extensions/nft-meta-data-pointer/anchor-example/anchor/programs/extension_nft/src/instructions/chop_tree.rs @@ -7,7 +7,15 @@ use anchor_spl::token_2022_extensions::spl_token_metadata_interface; use anchor_spl::token_interface::{spl_token_2022, Token2022}; use session_keys::{Session, SessionToken}; -pub fn chop_tree(context: &mut Context, counter: u16, amount: u64) -> Result<()> { +pub fn chop_tree( + context: &mut Context, + counter: u16, + amount: u64, +) -> Result<()> { + // `AccountView` is Copy, and a copy still points at the same + // account — v2's typed handles make the aliasing a compile error. + let mint_view = *context.accounts.mint.account(); + let nft_authority_view = *context.accounts.nft_authority.account(); // Save game_data bump on first creation (init_if_needed). See init_player.rs // for the same pattern. let game_data_bump = context.bumps.game_data; @@ -48,8 +56,8 @@ pub fn chop_tree(context: &mut Context, counter: u16 context.accounts.player.wood.to_string(), ), &[ - context.accounts.mint.cpi_handle().clone(), - context.accounts.nft_authority.cpi_handle().clone(), + CpiHandle::readonly(&mint_view).clone(), + CpiHandle::readonly(&nft_authority_view).clone(), ], signer, )?; diff --git a/tokens/token-extensions/nft-meta-data-pointer/anchor-example/anchor/programs/extension_nft/src/instructions/mint_nft.rs b/tokens/token-extensions/nft-meta-data-pointer/anchor-example/anchor/programs/extension_nft/src/instructions/mint_nft.rs index 6335506d8..04600874d 100644 --- a/tokens/token-extensions/nft-meta-data-pointer/anchor-example/anchor/programs/extension_nft/src/instructions/mint_nft.rs +++ b/tokens/token-extensions/nft-meta-data-pointer/anchor-example/anchor/programs/extension_nft/src/instructions/mint_nft.rs @@ -14,6 +14,11 @@ use anchor_spl::{ }; pub fn handle_mint_nft(context: &mut Context) -> Result<()> { + // `AccountView` is Copy, and a copy still points at the same + // account — v2's typed handles make the aliasing a compile error. + let mint_view = *context.accounts.mint.account(); + let nft_authority_view = *context.accounts.nft_authority.account(); + let signer_view = *context.accounts.signer.account(); msg!("Mint nft with meta data extension and additional meta data"); let space = @@ -78,8 +83,8 @@ pub fn handle_mint_nft(context: &mut Context) -> Resu invoke( &init_meta_data_pointer_ix, &[ - context.accounts.mint.cpi_handle(), - context.accounts.nft_authority.cpi_handle(), + CpiHandle::readonly(&mint_view), + CpiHandle::readonly(&nft_authority_view), ], )?; @@ -91,8 +96,13 @@ pub fn handle_mint_nft(context: &mut Context) -> Resu }, ); - token_2022::initialize_mint2(mint_cpi_ix, 0, &context.accounts.nft_authority.address(), None) - .unwrap(); + token_2022::initialize_mint2( + mint_cpi_ix, + 0, + &context.accounts.nft_authority.address(), + None, + ) + .unwrap(); // We use a PDA as a mint authority for the metadata account because // we want to be able to update the NFT from the program. @@ -120,8 +130,8 @@ pub fn handle_mint_nft(context: &mut Context) -> Resu invoke_signed( init_token_meta_data_ix, &[ - context.accounts.mint.cpi_handle().clone(), - context.accounts.nft_authority.cpi_handle().clone(), + CpiHandle::readonly(&mint_view).clone(), + CpiHandle::readonly(&nft_authority_view).clone(), ], signer, )?; @@ -136,8 +146,8 @@ pub fn handle_mint_nft(context: &mut Context) -> Resu "1".to_string(), ), &[ - context.accounts.mint.cpi_handle().clone(), - context.accounts.nft_authority.cpi_handle().clone(), + CpiHandle::readonly(&mint_view).clone(), + CpiHandle::readonly(&nft_authority_view).clone(), ], signer, )?; @@ -148,8 +158,8 @@ pub fn handle_mint_nft(context: &mut Context) -> Resu associated_token::Create { payer: context.accounts.signer.cpi_handle_mut(), associated_token: context.accounts.token_account.cpi_handle_mut(), - authority: context.accounts.signer.cpi_handle(), - mint: context.accounts.mint.cpi_handle(), + authority: CpiHandle::readonly(&signer_view), + mint: CpiHandle::readonly(&mint_view), system_program: context.accounts.system_program.cpi_handle(), token_program: context.accounts.token_program.cpi_handle(), }, @@ -162,7 +172,7 @@ pub fn handle_mint_nft(context: &mut Context) -> Resu token_2022::MintTo { mint: context.accounts.mint.cpi_handle_mut(), to: context.accounts.token_account.cpi_handle_mut(), - authority: context.accounts.nft_authority.cpi_handle(), + authority: CpiHandle::readonly(&nft_authority_view), }, signer, ), @@ -174,7 +184,7 @@ pub fn handle_mint_nft(context: &mut Context) -> Resu CpiContext::new_with_signer( context.accounts.token_program.address(), token_2022::SetAuthority { - current_authority: context.accounts.nft_authority.cpi_handle(), + current_authority: CpiHandle::readonly(&nft_authority_view), account_or_mint: context.accounts.mint.cpi_handle_mut(), }, signer, diff --git a/tokens/token-extensions/nft-meta-data-pointer/anchor-example/anchor/programs/extension_nft/src/lib.rs b/tokens/token-extensions/nft-meta-data-pointer/anchor-example/anchor/programs/extension_nft/src/lib.rs index 9d153b573..1ef39d0ff 100644 --- a/tokens/token-extensions/nft-meta-data-pointer/anchor-example/anchor/programs/extension_nft/src/lib.rs +++ b/tokens/token-extensions/nft-meta-data-pointer/anchor-example/anchor/programs/extension_nft/src/lib.rs @@ -24,7 +24,10 @@ declare_id!("9aZZ7TJ2fQZxY8hMtWXywp5y6BgqC4N2BPcr9FDT47sW"); pub mod extension_nft { use super::*; - pub fn init_player(context: &mut Context, _level_seed: String) -> Result<()> { + pub fn init_player( + context: &mut Context, + _level_seed: String, + ) -> Result<()> { init_player::handle_init_player(context) } @@ -39,7 +42,11 @@ pub mod extension_nft { ctx.accounts.player.authority.address() == ctx.accounts.signer.address(), GameErrorCode::WrongAuthority )] - pub fn chop_tree(ctx: &mut Context, _level_seed: String, counter: u16) -> Result<()> { + pub fn chop_tree( + ctx: &mut Context, + _level_seed: String, + counter: u16, + ) -> Result<()> { chop_tree::chop_tree(ctx, counter, 1) } diff --git a/tokens/token-extensions/nft-meta-data-pointer/anchor-example/anchor/programs/extension_nft/tests/test_extension_nft.rs b/tokens/token-extensions/nft-meta-data-pointer/anchor-example/anchor/programs/extension_nft/tests/test_extension_nft.rs index 74a58e906..ef982a143 100644 --- a/tokens/token-extensions/nft-meta-data-pointer/anchor-example/anchor/programs/extension_nft/tests/test_extension_nft.rs +++ b/tokens/token-extensions/nft-meta-data-pointer/anchor-example/anchor/programs/extension_nft/tests/test_extension_nft.rs @@ -207,7 +207,10 @@ fn test_init_player_mint_and_chop() { // The associated token account should exist and hold the single NFT. let ata = associated_token_address(&signer, &mint.pubkey()); let ata_account = svm.get_account(&ata).expect("ATA created"); - assert_eq!(ata_account.owner, TOKEN_2022_ID, "ATA owned by Token Extensions"); + assert_eq!( + ata_account.owner, TOKEN_2022_ID, + "ATA owned by Token Extensions" + ); // 3. chop_tree - needs the existing mint so it can push the new wood total // into the NFT metadata. Signed by the player's main wallet (no session). diff --git a/tokens/token-extensions/transfer-fee/anchor/programs/transfer-fee/src/instructions/transfer.rs b/tokens/token-extensions/transfer-fee/anchor/programs/transfer-fee/src/instructions/transfer.rs index 30a0ebf42..c93d53c6a 100644 --- a/tokens/token-extensions/transfer-fee/anchor/programs/transfer-fee/src/instructions/transfer.rs +++ b/tokens/token-extensions/transfer-fee/anchor/programs/transfer-fee/src/instructions/transfer.rs @@ -47,6 +47,9 @@ pub fn handle_process_transfer( context: &mut Context, amount: u64, ) -> Result<()> { + // `AccountView` is Copy, and a copy still points at the same + // account — v2's typed handles make the aliasing a compile error. + let mint_account_view = *context.accounts.mint_account.account(); // read mint account extension data let mint = &context.accounts.mint_account.cpi_handle_mut(); let mint_data = mint.data.borrow(); @@ -65,7 +68,7 @@ pub fn handle_process_transfer( context.accounts.token_program.address(), TransferCheckedWithFee { source: context.accounts.sender_token_account.cpi_handle_mut(), - mint: context.accounts.mint_account.cpi_handle(), + mint: CpiHandle::readonly(&mint_account_view), destination: context.accounts.recipient_token_account.cpi_handle_mut(), authority: context.accounts.sender.cpi_handle(), }, diff --git a/tokens/token-extensions/transfer-hook/allow-block-list-token/anchor/programs/abl-token/src/instructions/change_mode.rs b/tokens/token-extensions/transfer-hook/allow-block-list-token/anchor/programs/abl-token/src/instructions/change_mode.rs index 35bcf5666..10bf008d8 100644 --- a/tokens/token-extensions/transfer-hook/allow-block-list-token/anchor/programs/abl-token/src/instructions/change_mode.rs +++ b/tokens/token-extensions/transfer-hook/allow-block-list-token/anchor/programs/abl-token/src/instructions/change_mode.rs @@ -2,6 +2,7 @@ use anchor_lang::solana_program::program::invoke; use anchor_lang::{prelude::*, solana_program::system_instruction::transfer}; use anchor_spl::token_interface::spl_token_metadata_interface::state::TokenMetadata; use anchor_spl::{ + mint, token_2022::{ spl_token_2022::extension::{BaseStateWithExtensions, StateWithExtensions}, spl_token_2022::state::Mint, diff --git a/tokens/token-minter/anchor/programs/token-minter/src/instructions/create.rs b/tokens/token-minter/anchor/programs/token-minter/src/instructions/create.rs index e57563c8c..1ce1de6a1 100644 --- a/tokens/token-minter/anchor/programs/token-minter/src/instructions/create.rs +++ b/tokens/token-minter/anchor/programs/token-minter/src/instructions/create.rs @@ -45,6 +45,9 @@ pub fn handle_create_token( token_symbol: String, token_uri: String, ) -> Result<()> { + // `AccountView` is Copy, and a copy still points at the same + // account — v2's typed handles make the aliasing a compile error. + let payer_view = *context.accounts.payer.account(); msg!("Creating metadata account"); // Cross Program Invocation (CPI) @@ -55,8 +58,8 @@ pub fn handle_create_token( CreateMetadataAccountsV3 { metadata: context.accounts.metadata_account.cpi_handle_mut(), mint: context.accounts.mint_account.cpi_handle(), - mint_authority: context.accounts.payer.cpi_handle(), - update_authority: context.accounts.payer.cpi_handle(), + mint_authority: CpiHandle::readonly(&payer_view), + update_authority: CpiHandle::readonly(&payer_view), payer: context.accounts.payer.cpi_handle_mut(), system_program: context.accounts.system_program.cpi_handle(), update_authority_is_signer: true, @@ -72,7 +75,6 @@ pub fn handle_create_token( uses: None, }, false, // Is mutable - true, // Update authority is signer None, // Collection details )?; diff --git a/tokens/transfer-tokens/anchor/programs/transfer-tokens/src/instructions/create.rs b/tokens/transfer-tokens/anchor/programs/transfer-tokens/src/instructions/create.rs index 33a74de7e..628219363 100644 --- a/tokens/transfer-tokens/anchor/programs/transfer-tokens/src/instructions/create.rs +++ b/tokens/transfer-tokens/anchor/programs/transfer-tokens/src/instructions/create.rs @@ -5,6 +5,7 @@ use { create_metadata_accounts_v3, mpl_token_metadata::types::DataV2, CreateMetadataAccountsV3, Metadata, }, + mint, token_interface::{Mint, TokenInterface}, }, }; @@ -45,6 +46,9 @@ pub fn handle_create_token( token_symbol: String, token_uri: String, ) -> Result<()> { + // `AccountView` is Copy, and a copy still points at the same + // account — v2's typed handles make the aliasing a compile error. + let payer_view = *context.accounts.payer.account(); msg!("Creating metadata account"); // Cross Program Invocation (CPI) @@ -55,8 +59,8 @@ pub fn handle_create_token( CreateMetadataAccountsV3 { metadata: context.accounts.metadata_account.cpi_handle_mut(), mint: context.accounts.mint_account.cpi_handle(), - mint_authority: context.accounts.payer.cpi_handle(), - update_authority: context.accounts.payer.cpi_handle(), + mint_authority: CpiHandle::readonly(&payer_view), + update_authority: CpiHandle::readonly(&payer_view), payer: context.accounts.payer.cpi_handle_mut(), system_program: context.accounts.system_program.cpi_handle(), update_authority_is_signer: true, @@ -72,7 +76,6 @@ pub fn handle_create_token( uses: None, }, false, // Is mutable - true, // Update authority is signer None, // Collection details )?; diff --git a/tokens/transfer-tokens/anchor/programs/transfer-tokens/src/instructions/transfer.rs b/tokens/transfer-tokens/anchor/programs/transfer-tokens/src/instructions/transfer.rs index f4a627531..eb69a917c 100644 --- a/tokens/transfer-tokens/anchor/programs/transfer-tokens/src/instructions/transfer.rs +++ b/tokens/transfer-tokens/anchor/programs/transfer-tokens/src/instructions/transfer.rs @@ -50,6 +50,9 @@ pub fn handle_transfer_tokens( context: &mut Context, amount: u64, ) -> Result<()> { + // `AccountView` is Copy, and a copy still points at the same + // account — v2's typed handles make the aliasing a compile error. + let mint_account_view = *context.accounts.mint_account.account(); msg!("Transferring tokens..."); msg!( "Mint: {}", @@ -70,7 +73,7 @@ pub fn handle_transfer_tokens( context.accounts.token_program.address(), TransferChecked { from: context.accounts.sender_token_account.cpi_handle_mut(), - mint: context.accounts.mint_account.cpi_handle(), + mint: CpiHandle::readonly(&mint_account_view), to: context.accounts.recipient_token_account.cpi_handle_mut(), authority: context.accounts.sender.cpi_handle(), }, From 2d1f98876f36123d8d797c98c2a91f42f4e2718a Mon Sep 17 00:00:00 2001 From: Claude Date: Fri, 14 Aug 2026 14:03:46 +0000 Subject: [PATCH 12/59] finance: escrow ports and passes; fundraiser and betting-market compile escrow is the important one, it exposed a real bug in my earlier approach. Passing one account into several CPI slots by copying its `AccountView` gets past the *compile-time* exclusivity check, but v2 still tracks borrows at runtime: a data account (`Account` / `BorshAccount` / `InterfaceAccount`) holds a live borrow on its buffer, so the CPI's own borrow fails with `AccountBorrowFailed`. escrow built cleanly and then failed two of its four tests that way. The correct mechanism is `release_borrow()` before the CPI and `reacquire_borrow_mut()` after, `reacquire_borrow_mut` re-runs the load-time owner and discriminator checks, because a CPI in the release window could have mutated either. escrow now does that around the vault transfer and close, and passes 4/4. The copied-view shortcut stays correct where the account holds no data borrow (`Signer`, `UncheckedAccount`, `Program`), which is most of its uses. Other fixes: - v2 takes a byte array directly in a seeds list and binds it itself; `id.to_le_bytes().as_ref()` produced a temporary that died before the derive used it (E0716). Applied repo-wide. - `associated_token::mint` resolves a sibling account field, not a field read off another account. - betting-market's `Event` was ambiguous, the crate root glob-re-exports the state struct while the prelude brings in anchor's `Event` trait. An explicit `use crate::state::Event;` resolves it. - The shared token helpers in escrow and betting-market took `&AccountView` and called `cpi_handle_mut` on it; a writable handle needs `&mut`, so they take the view by value (it is `Copy`) and build handles explicitly. --- .../src/instructions/add_outcome.rs | 8 +++-- .../src/instructions/cancel_event.rs | 6 ++-- .../src/instructions/claim_refund.rs | 12 ++++--- .../src/instructions/claim_winnings.rs | 12 ++++--- .../src/instructions/close_losing_bet.rs | 6 ++-- .../src/instructions/initialize_event.rs | 6 ++-- .../src/instructions/place_bet.rs | 28 +++++++-------- .../src/instructions/settle_event.rs | 12 ++++--- .../betting-market/src/instructions/shared.rs | 24 +++++++------ .../escrow/src/instructions/cancel_offer.rs | 17 ++++++--- .../escrow/src/instructions/make_offer.rs | 8 ++--- .../escrow/src/instructions/take_offer.rs | 36 +++++++++++++------ .../escrow/anchor/programs/escrow/src/lib.rs | 4 +-- .../src/instructions/cancel_order.rs | 2 +- .../src/instructions/place_order.rs | 2 +- .../fundraiser/src/instructions/checker.rs | 13 +++---- .../fundraiser/src/instructions/close.rs | 13 +++---- .../fundraiser/src/instructions/contribute.rs | 2 +- .../src/instructions/add_asset.rs | 2 +- .../src/instructions/collect_fees.rs | 2 +- .../src/instructions/deposit.rs | 2 +- .../src/instructions/initialize_strategy.rs | 2 +- .../src/instructions/rebalance.rs | 2 +- .../src/instructions/set_weight.rs | 2 +- .../src/instructions/withdraw.rs | 2 +- 25 files changed, 134 insertions(+), 91 deletions(-) diff --git a/finance/betting-market/anchor/programs/betting-market/src/instructions/add_outcome.rs b/finance/betting-market/anchor/programs/betting-market/src/instructions/add_outcome.rs index 075a9a8ae..23a3aae4d 100644 --- a/finance/betting-market/anchor/programs/betting-market/src/instructions/add_outcome.rs +++ b/finance/betting-market/anchor/programs/betting-market/src/instructions/add_outcome.rs @@ -1,6 +1,8 @@ use anchor_lang::prelude::*; -use crate::{error::BettingError, Config, Event, EventStatus, Outcome}; +use crate::state::Event; + +use crate::{error::BettingError, Config, EventStatus, Outcome}; pub const MAX_LABEL_LEN: usize = 64; @@ -18,7 +20,7 @@ pub struct AddOutcomeAccountConstraints { #[account( mut, - seeds = [b"event", event.event_id.to_le_bytes().as_ref()], + seeds = [b"event", event.event_id.to_le_bytes()], bump = event.bump, )] pub event: BorshAccount, @@ -53,7 +55,7 @@ pub fn handle_add_outcome( let index = context.accounts.event.outcome_count; *context.accounts.outcome = (Outcome { - event: context.accounts.event.address(), + event: *context.accounts.event.address(), index, label, total_amount: 0, diff --git a/finance/betting-market/anchor/programs/betting-market/src/instructions/cancel_event.rs b/finance/betting-market/anchor/programs/betting-market/src/instructions/cancel_event.rs index e352aef15..fee309937 100644 --- a/finance/betting-market/anchor/programs/betting-market/src/instructions/cancel_event.rs +++ b/finance/betting-market/anchor/programs/betting-market/src/instructions/cancel_event.rs @@ -1,6 +1,8 @@ use anchor_lang::prelude::*; -use crate::{error::BettingError, Config, Event, EventStatus}; +use crate::state::Event; + +use crate::{error::BettingError, Config, EventStatus}; // Abandon an event that can't be resolved (e.g. the real-world result is void). // Bettors then reclaim their exact stakes via `claim_refund`; no fee is taken. @@ -17,7 +19,7 @@ pub struct CancelEventAccountConstraints { #[account( mut, - seeds = [b"event", event.event_id.to_le_bytes().as_ref()], + seeds = [b"event", event.event_id.to_le_bytes()], bump = event.bump, )] pub event: BorshAccount, diff --git a/finance/betting-market/anchor/programs/betting-market/src/instructions/claim_refund.rs b/finance/betting-market/anchor/programs/betting-market/src/instructions/claim_refund.rs index 13c780ee9..5fbf5fedf 100644 --- a/finance/betting-market/anchor/programs/betting-market/src/instructions/claim_refund.rs +++ b/finance/betting-market/anchor/programs/betting-market/src/instructions/claim_refund.rs @@ -1,8 +1,10 @@ use anchor_lang::prelude::*; + +use crate::state::Event; use anchor_spl::mint; use anchor_spl::token_interface::{Mint, TokenAccount, TokenInterface}; -use crate::{error::BettingError, Bet, Event, EventStatus, User}; +use crate::{error::BettingError, Bet, EventStatus, User}; use super::transfer_tokens_from_vault; @@ -15,7 +17,7 @@ pub struct ClaimRefundAccountConstraints { pub token_mint: InterfaceAccount, #[account( - seeds = [b"event", event.event_id.to_le_bytes().as_ref()], + seeds = [b"event", event.event_id.to_le_bytes()], bump = event.bump, )] pub event: BorshAccount, @@ -75,11 +77,11 @@ pub fn handle_claim_refund(context: &mut Context) let event_id = context.accounts.event.event_id; let event_bump = context.accounts.event.bump; transfer_tokens_from_vault( - &context.accounts.vault, - &context.accounts.bettor_token_account, + &mut context.accounts.vault, + &mut context.accounts.bettor_token_account, stake, &context.accounts.token_mint, - &context.accounts.event.cpi_handle_mut(), + *context.accounts.event.account(), &context.accounts.token_program, event_id, event_bump, diff --git a/finance/betting-market/anchor/programs/betting-market/src/instructions/claim_winnings.rs b/finance/betting-market/anchor/programs/betting-market/src/instructions/claim_winnings.rs index 7a78e3ef3..6e4fa3413 100644 --- a/finance/betting-market/anchor/programs/betting-market/src/instructions/claim_winnings.rs +++ b/finance/betting-market/anchor/programs/betting-market/src/instructions/claim_winnings.rs @@ -1,8 +1,10 @@ use anchor_lang::prelude::*; + +use crate::state::Event; use anchor_spl::mint; use anchor_spl::token_interface::{Mint, TokenAccount, TokenInterface}; -use crate::{error::BettingError, Bet, Event, EventStatus, User}; +use crate::{error::BettingError, Bet, EventStatus, User}; use super::transfer_tokens_from_vault; @@ -15,7 +17,7 @@ pub struct ClaimWinningsAccountConstraints { pub token_mint: InterfaceAccount, #[account( - seeds = [b"event", event.event_id.to_le_bytes().as_ref()], + seeds = [b"event", event.event_id.to_le_bytes()], bump = event.bump, )] pub event: BorshAccount, @@ -100,11 +102,11 @@ pub fn handle_claim_winnings(context: &mut Context, diff --git a/finance/betting-market/anchor/programs/betting-market/src/instructions/initialize_event.rs b/finance/betting-market/anchor/programs/betting-market/src/instructions/initialize_event.rs index 8251889a3..77ce9064a 100644 --- a/finance/betting-market/anchor/programs/betting-market/src/instructions/initialize_event.rs +++ b/finance/betting-market/anchor/programs/betting-market/src/instructions/initialize_event.rs @@ -1,11 +1,13 @@ use anchor_lang::prelude::*; + +use crate::state::Event; use anchor_spl::mint; use anchor_spl::{ associated_token::AssociatedToken, token_interface::{Mint, TokenAccount, TokenInterface}, }; -use crate::{error::BettingError, Config, Event, EventStatus}; +use crate::{error::BettingError, Config, EventStatus}; pub const MAX_DESCRIPTION_LEN: usize = 200; @@ -31,7 +33,7 @@ pub struct InitializeEventAccountConstraints { init, payer = admin, space = Event::DISCRIMINATOR.len() + Event::INIT_SPACE, - seeds = [b"event", event_id.to_le_bytes().as_ref()], + seeds = [b"event", event_id.to_le_bytes()], bump )] pub event: BorshAccount, diff --git a/finance/betting-market/anchor/programs/betting-market/src/instructions/place_bet.rs b/finance/betting-market/anchor/programs/betting-market/src/instructions/place_bet.rs index d6e74ebd9..803194cc7 100644 --- a/finance/betting-market/anchor/programs/betting-market/src/instructions/place_bet.rs +++ b/finance/betting-market/anchor/programs/betting-market/src/instructions/place_bet.rs @@ -1,13 +1,13 @@ use anchor_lang::prelude::*; + +use crate::state::Event; use anchor_spl::mint; use anchor_spl::{ associated_token::AssociatedToken, token_interface::{Mint, TokenAccount, TokenInterface}, }; -use crate::{ - error::BettingError, Bet, Config, Event, EventStatus, Outcome, User, MAX_BETS_PER_USER, -}; +use crate::{error::BettingError, Bet, Config, EventStatus, Outcome, User, MAX_BETS_PER_USER}; use super::transfer_tokens_to_vault; @@ -28,7 +28,7 @@ pub struct PlaceBetAccountConstraints { #[account( mut, - seeds = [b"event", event.event_id.to_le_bytes().as_ref()], + seeds = [b"event", event.event_id.to_le_bytes()], bump = event.bump, )] pub event: Box>, @@ -91,19 +91,19 @@ pub fn handle_place_bet( ); transfer_tokens_to_vault( - &context.accounts.bettor_token_account, - &context.accounts.vault, + &mut context.accounts.bettor_token_account, + &mut context.accounts.vault, amount, &context.accounts.token_mint, &context.accounts.bettor, &context.accounts.token_program, )?; - let bettor_key = context.accounts.bettor.address(); - let event_key = context.accounts.event.address(); - let outcome_key = context.accounts.outcome.address(); + let bettor_key = *context.accounts.bettor.address(); + let event_key = *context.accounts.event.address(); + let outcome_key = *context.accounts.outcome.address(); let outcome_index = context.accounts.outcome.index; - let bet_key = context.accounts.bet.address(); + let bet_key = *context.accounts.bet.address(); let bet_bump = context.bumps.bet; let user_bump = context.bumps.user; @@ -112,9 +112,9 @@ pub fn handle_place_bet( // on this outcome from a top-up, and it gates the per-outcome bookkeeping. let is_new_bet = bet.amount == 0; if is_new_bet { - bet.bettor = *bettor_key; + bet.bettor = bettor_key; bet.event = event_key; - bet.outcome = *outcome_key; + bet.outcome = outcome_key; bet.outcome_index = outcome_index; bet.bump = bet_bump; } @@ -143,7 +143,7 @@ pub fn handle_place_bet( let user = &mut context.accounts.user; if user.authority == Address::default() { - user.authority = *bettor_key; + user.authority = bettor_key; user.bump = user_bump; } if is_new_bet { @@ -151,7 +151,7 @@ pub fn handle_place_bet( user.bets.len() < MAX_BETS_PER_USER, BettingError::TooManyBets ); - user.bets.push(*bet_key); + user.bets.push(bet_key); } Ok(()) diff --git a/finance/betting-market/anchor/programs/betting-market/src/instructions/settle_event.rs b/finance/betting-market/anchor/programs/betting-market/src/instructions/settle_event.rs index d48816302..436b73b83 100644 --- a/finance/betting-market/anchor/programs/betting-market/src/instructions/settle_event.rs +++ b/finance/betting-market/anchor/programs/betting-market/src/instructions/settle_event.rs @@ -1,11 +1,13 @@ use anchor_lang::prelude::*; + +use crate::state::Event; use anchor_spl::mint; use anchor_spl::{ associated_token::AssociatedToken, token_interface::{Mint, TokenAccount, TokenInterface}, }; -use crate::{error::BettingError, Config, Event, EventStatus, Outcome}; +use crate::{error::BettingError, Config, EventStatus, Outcome}; use super::transfer_tokens_from_vault; @@ -31,7 +33,7 @@ pub struct SettleEventAccountConstraints { #[account( mut, - seeds = [b"event", event.event_id.to_le_bytes().as_ref()], + seeds = [b"event", event.event_id.to_le_bytes()], bump = event.bump, )] pub event: BorshAccount, @@ -95,11 +97,11 @@ pub fn handle_settle_event( let event_id = context.accounts.event.event_id; let event_bump = context.accounts.event.bump; transfer_tokens_from_vault( - &context.accounts.vault, - &context.accounts.fee_recipient_token_account, + &mut context.accounts.vault, + &mut context.accounts.fee_recipient_token_account, fee, &context.accounts.token_mint, - &context.accounts.event.cpi_handle_mut(), + *context.accounts.event.account(), &context.accounts.token_program, event_id, event_bump, diff --git a/finance/betting-market/anchor/programs/betting-market/src/instructions/shared.rs b/finance/betting-market/anchor/programs/betting-market/src/instructions/shared.rs index e9a9916db..0a64d113e 100644 --- a/finance/betting-market/anchor/programs/betting-market/src/instructions/shared.rs +++ b/finance/betting-market/anchor/programs/betting-market/src/instructions/shared.rs @@ -1,19 +1,22 @@ use anchor_lang::prelude::*; +use crate::state::Event; + use anchor_spl::token_interface::{ transfer_checked, Mint, TokenAccount, TokenInterface, TransferChecked, }; // Move tokens from a wallet-owned account into the vault. The authority is a // plain Signer (the bettor), so no PDA seeds are needed. -pub fn transfer_tokens_to_vault<'info>( - from: &InterfaceAccount, - to: &InterfaceAccount, +pub fn transfer_tokens_to_vault( + from: &mut InterfaceAccount, + to: &mut InterfaceAccount, amount: u64, mint: &InterfaceAccount, authority: &Signer, token_program: &Interface<'static, TokenInterface>, ) -> Result<()> { + let decimals = mint.decimals(); let transfer_accounts = TransferChecked { from: from.cpi_handle_mut(), mint: mint.cpi_handle(), @@ -21,17 +24,17 @@ pub fn transfer_tokens_to_vault<'info>( authority: authority.cpi_handle(), }; let cpi_context = CpiContext::new(token_program.address(), transfer_accounts); - transfer_checked(cpi_context, amount, mint.decimals()) + transfer_checked(cpi_context, amount, decimals) } // 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<'info>( - vault: &InterfaceAccount, - to: &InterfaceAccount, +pub fn transfer_tokens_from_vault( + vault: &mut InterfaceAccount, + to: &mut InterfaceAccount, amount: u64, mint: &InterfaceAccount, - event: &AccountView, + event: AccountView, token_program: &Interface<'static, TokenInterface>, event_id: u64, event_bump: u8, @@ -40,13 +43,14 @@ pub fn transfer_tokens_from_vault<'info>( 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: event.clone(), + authority: CpiHandle::readonly(&event), }; let cpi_context = CpiContext::new_with_signer(token_program.address(), transfer_accounts, &signer_seeds); - transfer_checked(cpi_context, amount, mint.decimals()) + transfer_checked(cpi_context, amount, decimals) } diff --git a/finance/escrow/anchor/programs/escrow/src/instructions/cancel_offer.rs b/finance/escrow/anchor/programs/escrow/src/instructions/cancel_offer.rs index 15b0fe917..af376ad4c 100644 --- a/finance/escrow/anchor/programs/escrow/src/instructions/cancel_offer.rs +++ b/finance/escrow/anchor/programs/escrow/src/instructions/cancel_offer.rs @@ -33,7 +33,7 @@ pub struct CancelOfferAccountConstraints { close = maker, has_one = maker, has_one = token_mint_a, - seeds = [b"offer", maker.address().as_ref(), offer.id.to_le_bytes().as_ref()], + seeds = [b"offer", maker.address().as_ref(), offer.id.to_le_bytes()], bump = offer.bump, )] pub offer: BorshAccount, @@ -57,13 +57,22 @@ pub fn handle_cancel_offer(context: &mut Context) let bump = [context.accounts.offer.bump]; let offer_seeds: &[&[u8]] = &[b"offer", maker_key.as_ref(), id_bytes.as_ref(), &bump]; + // Read the balance before taking the mutable borrow of the vault. + let vault_amount = context.accounts.vault.amount(); + + // `offer` signs both CPIs below. It is a data account, so it holds a live + // borrow on its buffer; the runtime rejects a CPI that borrows it again. + // Release the borrow for the duration of the CPIs and take it back after. + context.accounts.offer.release_borrow()?; + let offer_view = *context.accounts.offer.account(); + // Move all tokens back from the vault to the maker. transfer_tokens( &mut context.accounts.vault, &mut context.accounts.maker_token_account_a, - &context.accounts.vault.amount(), + &vault_amount, &context.accounts.token_mint_a, - *context.accounts.offer.account(), + offer_view, &context.accounts.token_program, Some(offer_seeds), )?; @@ -72,7 +81,7 @@ pub fn handle_cancel_offer(context: &mut Context) close_token_account( &mut context.accounts.vault, *context.accounts.maker.account(), - *context.accounts.offer.account(), + offer_view, &context.accounts.token_program, Some(offer_seeds), )?; diff --git a/finance/escrow/anchor/programs/escrow/src/instructions/make_offer.rs b/finance/escrow/anchor/programs/escrow/src/instructions/make_offer.rs index dd5279635..297b37b11 100644 --- a/finance/escrow/anchor/programs/escrow/src/instructions/make_offer.rs +++ b/finance/escrow/anchor/programs/escrow/src/instructions/make_offer.rs @@ -47,7 +47,7 @@ pub struct MakeOfferAccountConstraints { init, payer = maker, space = Offer::DISCRIMINATOR.len() + Offer::INIT_SPACE, - seeds = [b"offer", maker.address().as_ref(), id.to_le_bytes().as_ref()], + seeds = [b"offer", maker.address().as_ref(), id.to_le_bytes()], bump )] pub offer: BorshAccount, @@ -68,12 +68,12 @@ pub struct MakeOfferAccountConstraints { // Move the tokens from the maker's ATA to the vault pub fn handle_send_offered_tokens_to_vault( - context: &Context, + context: &mut Context, token_a_offered_amount: u64, ) -> Result<()> { transfer_tokens( - &context.accounts.maker_token_account_a, - &context.accounts.vault, + &mut context.accounts.maker_token_account_a, + &mut context.accounts.vault, &token_a_offered_amount, &context.accounts.token_mint_a, *context.accounts.maker.account(), diff --git a/finance/escrow/anchor/programs/escrow/src/instructions/take_offer.rs b/finance/escrow/anchor/programs/escrow/src/instructions/take_offer.rs index 5441f1c3e..3a9e8ec4c 100644 --- a/finance/escrow/anchor/programs/escrow/src/instructions/take_offer.rs +++ b/finance/escrow/anchor/programs/escrow/src/instructions/take_offer.rs @@ -53,7 +53,7 @@ pub struct TakeOfferAccountConstraints { has_one = maker, has_one = token_mint_a, has_one = token_mint_b, - seeds = [b"offer", maker.address().as_ref(), offer.id.to_le_bytes().as_ref()], + seeds = [b"offer", maker.address().as_ref(), offer.id.to_le_bytes()], bump = offer.bump )] offer: BorshAccount, @@ -72,14 +72,16 @@ pub struct TakeOfferAccountConstraints { } pub fn handle_send_wanted_tokens_to_maker( - context: &Context, + context: &mut Context, ) -> Result<()> { + let wanted_amount = context.accounts.offer.token_b_wanted_amount; + let taker_view = *context.accounts.taker.account(); transfer_tokens( &mut context.accounts.taker_token_account_b, &mut context.accounts.maker_token_account_b, - &context.accounts.offer.token_b_wanted_amount, + &wanted_amount, &context.accounts.token_mint_b, - *context.accounts.taker.account(), + taker_view, &context.accounts.token_program, None, ) @@ -93,12 +95,20 @@ pub fn handle_withdraw_and_close_vault( let bump = [context.accounts.offer.bump]; let offer_seeds: &[&[u8]] = &[b"offer", maker_key.as_ref(), id_bytes.as_ref(), &bump]; + // Read the balance before taking the mutable borrow of the vault. + let vault_amount = context.accounts.vault.amount(); + + // `offer` signs both CPIs below. It is a data account, so it holds a live + // borrow on its buffer; the runtime rejects a CPI that borrows it again. + context.accounts.offer.release_borrow()?; + let offer_view = *context.accounts.offer.account(); + transfer_tokens( - &context.accounts.vault, - &context.accounts.taker_token_account_a, - &context.accounts.vault.amount(), + &mut context.accounts.vault, + &mut context.accounts.taker_token_account_a, + &vault_amount, &context.accounts.token_mint_a, - *context.accounts.offer.account(), + offer_view, &context.accounts.token_program, Some(offer_seeds), )?; @@ -106,10 +116,14 @@ pub fn handle_withdraw_and_close_vault( // The maker paid the vault's rent in make_offer, so the vault closes back // to the maker (the offer account does the same via `close = maker`). close_token_account( - &context.accounts.vault, + &mut context.accounts.vault, *context.accounts.maker.account(), - *context.accounts.offer.account(), + offer_view, &context.accounts.token_program, Some(offer_seeds), - ) + )?; + + // Take the borrow back so the derive's exit path (and `close = maker`) can + // serialize and close the account. + context.accounts.offer.reacquire_borrow_mut() } diff --git a/finance/escrow/anchor/programs/escrow/src/lib.rs b/finance/escrow/anchor/programs/escrow/src/lib.rs index 366f77b2d..a09ab0bf9 100644 --- a/finance/escrow/anchor/programs/escrow/src/lib.rs +++ b/finance/escrow/anchor/programs/escrow/src/lib.rs @@ -20,14 +20,14 @@ pub mod escrow { token_b_wanted_amount: u64, ) -> Result<()> { instructions::make_offer::handle_send_offered_tokens_to_vault( - &context, + context, token_a_offered_amount, )?; instructions::make_offer::handle_save_offer(context, id, token_b_wanted_amount) } pub fn take_offer(context: &mut Context) -> Result<()> { - instructions::take_offer::handle_send_wanted_tokens_to_maker(&context)?; + instructions::take_offer::handle_send_wanted_tokens_to_maker(context)?; instructions::take_offer::handle_withdraw_and_close_vault(context) } diff --git a/finance/order-book/anchor/programs/order-book/src/instructions/cancel_order.rs b/finance/order-book/anchor/programs/order-book/src/instructions/cancel_order.rs index 979148b7e..8413f3fb0 100644 --- a/finance/order-book/anchor/programs/order-book/src/instructions/cancel_order.rs +++ b/finance/order-book/anchor/programs/order-book/src/instructions/cancel_order.rs @@ -82,7 +82,7 @@ pub struct CancelOrderAccountConstraints { #[account( mut, - seeds = [ORDER_SEED, market.address().as_ref(), order.order_id.to_le_bytes().as_ref()], + seeds = [ORDER_SEED, market.address().as_ref(), order.order_id.to_le_bytes()], bump = order.bump )] pub order: BorshAccount, diff --git a/finance/order-book/anchor/programs/order-book/src/instructions/place_order.rs b/finance/order-book/anchor/programs/order-book/src/instructions/place_order.rs index 90aa2a774..a9422e8b0 100644 --- a/finance/order-book/anchor/programs/order-book/src/instructions/place_order.rs +++ b/finance/order-book/anchor/programs/order-book/src/instructions/place_order.rs @@ -465,7 +465,7 @@ pub struct PlaceOrderAccountConstraints { seeds = [ ORDER_SEED, market.address().as_ref(), - order_book.load()?.next_order_id.to_le_bytes().as_ref() + order_book.load()?.next_order_id.to_le_bytes() ], bump )] diff --git a/finance/token-fundraiser/anchor/programs/fundraiser/src/instructions/checker.rs b/finance/token-fundraiser/anchor/programs/fundraiser/src/instructions/checker.rs index 6ce070440..9517a53ba 100644 --- a/finance/token-fundraiser/anchor/programs/fundraiser/src/instructions/checker.rs +++ b/finance/token-fundraiser/anchor/programs/fundraiser/src/instructions/checker.rs @@ -58,11 +58,16 @@ pub fn handle_check_contributions( FundraiserError::TargetNotMet ); + // Read these before any of the CPI handles below take their borrows. + let maker_address = *accounts.maker.address(); + let vault_amount = accounts.vault.amount(); + let mint_decimals = accounts.mint_to_raise.decimals(); + // The vault is owned by the fundraiser PDA, so both CPIs are signed with // its seeds. let signer_seeds: [&[&[u8]]; 1] = [&[ b"fundraiser".as_ref(), - accounts.maker.cpi_handle_mut().address().as_ref(), + maker_address.as_ref(), &[accounts.fundraiser.bump], ]]; @@ -78,11 +83,7 @@ pub fn handle_check_contributions( transfer_accounts, &signer_seeds, ); - transfer_checked( - transfer_context, - accounts.vault.amount(), - accounts.mint_to_raise.decimals(), - )?; + transfer_checked(transfer_context, vault_amount, mint_decimals)?; // Close the empty vault so its rent goes back to the maker. let close_accounts = CloseAccount { diff --git a/finance/token-fundraiser/anchor/programs/fundraiser/src/instructions/close.rs b/finance/token-fundraiser/anchor/programs/fundraiser/src/instructions/close.rs index 455c5596f..a37aac4f8 100644 --- a/finance/token-fundraiser/anchor/programs/fundraiser/src/instructions/close.rs +++ b/finance/token-fundraiser/anchor/programs/fundraiser/src/instructions/close.rs @@ -83,11 +83,16 @@ pub fn handle_close_fundraiser(accounts: &mut CloseFundraiserAccountConstraints) FundraiserError::RefundsOutstanding ); + // Read these before any of the CPI handles below take their borrows. + let maker_address = *accounts.maker.address(); + let vault_amount = accounts.vault.amount(); + let mint_decimals = accounts.mint_to_raise.decimals(); + // The vault is owned by the fundraiser PDA, so both CPIs are signed with // its seeds. let signer_seeds: [&[&[u8]]; 1] = [&[ b"fundraiser".as_ref(), - accounts.maker.cpi_handle_mut().address().as_ref(), + maker_address.as_ref(), &[accounts.fundraiser.bump], ]]; @@ -106,11 +111,7 @@ pub fn handle_close_fundraiser(accounts: &mut CloseFundraiserAccountConstraints) transfer_accounts, &signer_seeds, ); - transfer_checked( - transfer_context, - accounts.vault.amount(), - accounts.mint_to_raise.decimals(), - )?; + transfer_checked(transfer_context, vault_amount, mint_decimals)?; } // Close the empty vault so its rent goes back to the maker. The diff --git a/finance/token-fundraiser/anchor/programs/fundraiser/src/instructions/contribute.rs b/finance/token-fundraiser/anchor/programs/fundraiser/src/instructions/contribute.rs index da8ffa926..07ed4d623 100644 --- a/finance/token-fundraiser/anchor/programs/fundraiser/src/instructions/contribute.rs +++ b/finance/token-fundraiser/anchor/programs/fundraiser/src/instructions/contribute.rs @@ -42,7 +42,7 @@ pub struct ContributeAccountConstraints { #[account( mut, - associated_token::mint = fundraiser.mint_to_raise, + associated_token::mint = mint_to_raise, associated_token::authority = fundraiser, associated_token::token_program = token_program, )] diff --git a/finance/vault-strategy/anchor/programs/vault-strategy/src/instructions/add_asset.rs b/finance/vault-strategy/anchor/programs/vault-strategy/src/instructions/add_asset.rs index 8ff4688c4..6abd84b43 100644 --- a/finance/vault-strategy/anchor/programs/vault-strategy/src/instructions/add_asset.rs +++ b/finance/vault-strategy/anchor/programs/vault-strategy/src/instructions/add_asset.rs @@ -16,7 +16,7 @@ pub struct AddAssetAccountConstraints { mut, has_one = manager, has_one = registry @ VaultError::InvalidRegistry, - seeds = [b"strategy", strategy.index.to_le_bytes().as_ref()], + seeds = [b"strategy", strategy.index.to_le_bytes()], bump = strategy.bump )] pub strategy: Box>, diff --git a/finance/vault-strategy/anchor/programs/vault-strategy/src/instructions/collect_fees.rs b/finance/vault-strategy/anchor/programs/vault-strategy/src/instructions/collect_fees.rs index 280e64d23..f7299275b 100644 --- a/finance/vault-strategy/anchor/programs/vault-strategy/src/instructions/collect_fees.rs +++ b/finance/vault-strategy/anchor/programs/vault-strategy/src/instructions/collect_fees.rs @@ -17,7 +17,7 @@ pub struct CollectFeesAccountConstraints { #[account( mut, has_one = manager, - seeds = [b"strategy", strategy.index.to_le_bytes().as_ref()], + seeds = [b"strategy", strategy.index.to_le_bytes()], bump = strategy.bump )] pub strategy: BorshAccount, diff --git a/finance/vault-strategy/anchor/programs/vault-strategy/src/instructions/deposit.rs b/finance/vault-strategy/anchor/programs/vault-strategy/src/instructions/deposit.rs index 635496ce4..5ba36b1ac 100644 --- a/finance/vault-strategy/anchor/programs/vault-strategy/src/instructions/deposit.rs +++ b/finance/vault-strategy/anchor/programs/vault-strategy/src/instructions/deposit.rs @@ -19,7 +19,7 @@ pub struct DepositAccountConstraints { #[account( mut, has_one = usdc_mint @ VaultError::InvalidUsdcMint, - seeds = [b"strategy", strategy.index.to_le_bytes().as_ref()], + seeds = [b"strategy", strategy.index.to_le_bytes()], bump = strategy.bump )] pub strategy: Box>, diff --git a/finance/vault-strategy/anchor/programs/vault-strategy/src/instructions/initialize_strategy.rs b/finance/vault-strategy/anchor/programs/vault-strategy/src/instructions/initialize_strategy.rs index eb83c1dd5..1bc795062 100644 --- a/finance/vault-strategy/anchor/programs/vault-strategy/src/instructions/initialize_strategy.rs +++ b/finance/vault-strategy/anchor/programs/vault-strategy/src/instructions/initialize_strategy.rs @@ -35,7 +35,7 @@ pub struct InitializeStrategyAccountConstraints { init, payer = manager, space = Strategy::DISCRIMINATOR.len() + Strategy::INIT_SPACE, - seeds = [b"strategy", index.to_le_bytes().as_ref()], + seeds = [b"strategy", index.to_le_bytes()], bump )] pub strategy: Box>, diff --git a/finance/vault-strategy/anchor/programs/vault-strategy/src/instructions/rebalance.rs b/finance/vault-strategy/anchor/programs/vault-strategy/src/instructions/rebalance.rs index b19432fd7..5bf0b38af 100644 --- a/finance/vault-strategy/anchor/programs/vault-strategy/src/instructions/rebalance.rs +++ b/finance/vault-strategy/anchor/programs/vault-strategy/src/instructions/rebalance.rs @@ -20,7 +20,7 @@ pub struct RebalanceAccountConstraints { mut, has_one = manager, has_one = usdc_mint @ VaultError::InvalidUsdcMint, - seeds = [b"strategy", strategy.index.to_le_bytes().as_ref()], + seeds = [b"strategy", strategy.index.to_le_bytes()], bump = strategy.bump )] pub strategy: Box>, diff --git a/finance/vault-strategy/anchor/programs/vault-strategy/src/instructions/set_weight.rs b/finance/vault-strategy/anchor/programs/vault-strategy/src/instructions/set_weight.rs index 5460cfee8..7a8ce00d4 100644 --- a/finance/vault-strategy/anchor/programs/vault-strategy/src/instructions/set_weight.rs +++ b/finance/vault-strategy/anchor/programs/vault-strategy/src/instructions/set_weight.rs @@ -10,7 +10,7 @@ pub struct SetWeightAccountConstraints { #[account( mut, has_one = manager, - seeds = [b"strategy", strategy.index.to_le_bytes().as_ref()], + seeds = [b"strategy", strategy.index.to_le_bytes()], bump = strategy.bump )] pub strategy: Box>, diff --git a/finance/vault-strategy/anchor/programs/vault-strategy/src/instructions/withdraw.rs b/finance/vault-strategy/anchor/programs/vault-strategy/src/instructions/withdraw.rs index cc3c21697..a0153640c 100644 --- a/finance/vault-strategy/anchor/programs/vault-strategy/src/instructions/withdraw.rs +++ b/finance/vault-strategy/anchor/programs/vault-strategy/src/instructions/withdraw.rs @@ -18,7 +18,7 @@ pub struct WithdrawAccountConstraints { #[account( mut, has_one = usdc_mint @ VaultError::InvalidUsdcMint, - seeds = [b"strategy", strategy.index.to_le_bytes().as_ref()], + seeds = [b"strategy", strategy.index.to_le_bytes()], bump = strategy.bump )] pub strategy: Box>, From 5fc6a6b378e0e6ad2f47470d07581214078e1b79 Mon Sep 17 00:00:00 2001 From: Claude Date: Fri, 14 Aug 2026 14:57:30 +0000 Subject: [PATCH 13/59] finance: prop-amm, perpetual-futures, vault-strategy and betting-market compile Continuing the port. 22 of 39 compile now, up from 17. The oracle programs needed a `last_restart.rs`: anchor-lang v2 is built on pinocchio, which ships only the Clock and Rent sysvars, so `LastRestartSlot` is declared locally and read through the raw `sol_get_sysvar` syscall. This mirrors what the Quasar variants of these same examples already do, the repo hit the identical gap in quasar-lang. Other shapes found this pass: - `Context` takes one accounts type and no user lifetime in v2; the account model is static-scoped, so `Context<'info, T<'info>>` becomes `Context`. - `remaining_accounts()` is a fallible method (it walks the account cursor), and it takes `&mut self`, so anything holding a borrow of `context.accounts` across the call has to copy what it needs first. - `AccountView::owner` is a method, not a field; `Mint::supply` and `decimals` are accessors since the struct is Pod. - v2 only generates a `program` marker module from `declare_program!`, so a sibling program account is an `UncheckedAccount` validated by its constraint. - Accounts pulled out of `remaining_accounts` are plain `AccountView`s; the CPI slots want typed handles, so they are bound by value (`AccountView` is `Copy`) and wrapped with `CpiHandle::readonly` / `CpiHandleMut::writable` to match each slot's mutability. - `AssetConfig::load_checked` decodes with wincode under `BorshConfig` now, checking the discriminator explicitly first. betting-market and token-fundraiser build but still fail some tests, the runtime borrow rules need more work there, tracked in the next commit. --- .../cnft-burn/src/instructions/burn_cnft.rs | 10 +-- .../anchor/programs/cnft-burn/src/lib.rs | 4 +- .../src/instructions/withdraw_cnft.rs | 6 +- .../src/instructions/withdraw_two_cnfts.rs | 8 +- .../anchor/programs/cnft-vault/src/lib.rs | 8 +- .../programs/cutils/src/instructions/mint.rs | 4 +- .../cutils/src/instructions/verify.rs | 8 +- .../cutils/anchor/programs/cutils/src/lib.rs | 9 +-- .../src/instructions/claim_refund.rs | 14 +++- .../src/instructions/claim_winnings.rs | 14 +++- .../src/instructions/settle_event.rs | 11 ++- .../src/instructions/refresh_obligation.rs | 2 +- .../programs/lending/src/state/reserve.rs | 2 +- .../src/instructions/place_order.rs | 6 +- .../anchor/programs/order-book/src/lib.rs | 4 +- .../programs/perpetual-futures/Cargo.toml | 5 ++ .../src/instructions/add_liquidity.rs | 2 +- .../src/instructions/remove_liquidity.rs | 2 +- .../perpetual-futures/src/last_restart.rs | 69 ++++++++++++++++++ .../programs/perpetual-futures/src/lib.rs | 1 + .../perpetual-futures/src/state/oracle.rs | 6 +- .../tests/test_perpetual_futures.rs | 2 +- .../anchor/programs/prop-amm/Cargo.toml | 5 ++ .../programs/prop-amm/src/last_restart.rs | 69 ++++++++++++++++++ .../anchor/programs/prop-amm/src/lib.rs | 1 + .../programs/prop-amm/src/state/oracle.rs | 6 +- .../programs/prop-amm/tests/test_prop_amm.rs | 2 +- .../anchor/programs/fundraiser/Cargo.toml | 1 + .../fundraiser/tests/test_fundraiser.rs | 5 +- .../src/instructions/swap_asset_for_usdc.rs | 2 +- .../anchor/programs/vault-strategy/Cargo.toml | 1 + .../src/instructions/add_asset.rs | 8 +- .../src/instructions/approve_asset.rs | 4 +- .../src/instructions/collect_fees.rs | 2 +- .../src/instructions/deposit.rs | 47 ++++++------ .../src/instructions/initialize_registry.rs | 2 +- .../src/instructions/initialize_strategy.rs | 10 +-- .../src/instructions/rebalance.rs | 53 +++++++------- .../src/instructions/set_weight.rs | 2 +- .../src/instructions/withdraw.rs | 73 ++++++++++--------- .../anchor/programs/vault-strategy/src/lib.rs | 8 +- .../programs/vault-strategy/src/oracle.rs | 2 +- .../vault-strategy/src/state/strategy.rs | 14 +++- .../vault-strategy/tests/vault_strategy.rs | 1 + .../transfer-fee/src/instructions/harvest.rs | 4 +- .../anchor/programs/transfer-fee/src/lib.rs | 4 +- 46 files changed, 360 insertions(+), 163 deletions(-) create mode 100644 finance/perpetual-futures/anchor/programs/perpetual-futures/src/last_restart.rs create mode 100644 finance/prop-amm/anchor/programs/prop-amm/src/last_restart.rs diff --git a/compression/cnft-burn/anchor/programs/cnft-burn/src/instructions/burn_cnft.rs b/compression/cnft-burn/anchor/programs/cnft-burn/src/instructions/burn_cnft.rs index 03fada242..b88178835 100644 --- a/compression/cnft-burn/anchor/programs/cnft-burn/src/instructions/burn_cnft.rs +++ b/compression/cnft-burn/anchor/programs/cnft-burn/src/instructions/burn_cnft.rs @@ -49,8 +49,8 @@ pub struct BurnCnftAccountConstraints { pub system_program: Program, } -pub fn handle_burn_cnft<'info>( - context: &mut Context<'info, BurnCnftAccountConstraints<'info>>, +pub fn handle_burn_cnft( + context: &mut Context, root: [u8; 32], data_hash: [u8; 32], creator_hash: [u8; 32], @@ -69,7 +69,7 @@ pub fn handle_burn_cnft<'info>( args.serialize(&mut data)?; // Build account metas matching mpl-bubblegum Burn instruction layout - let mut accounts = Vec::with_capacity(7 + context.remaining_accounts().len()); + let mut accounts = Vec::with_capacity(7 + context.remaining_accounts()?.len()); accounts.push(AccountMeta::new_readonly( *context.accounts.tree_authority.address(), false, @@ -100,7 +100,7 @@ pub fn handle_burn_cnft<'info>( false, )); // Append remaining accounts (proof nodes) - for acc in context.remaining_accounts().iter() { + for acc in context.remaining_accounts()?.iter() { accounts.push(AccountMeta::new_readonly(acc.address(), false)); } @@ -120,7 +120,7 @@ pub fn handle_burn_cnft<'info>( context.accounts.compression_program.cpi_handle_mut(), context.accounts.system_program.cpi_handle_mut(), ]; - for acc in context.remaining_accounts().iter() { + for acc in context.remaining_accounts()?.iter() { account_infos.push(acc.cpi_handle_mut()); } diff --git a/compression/cnft-burn/anchor/programs/cnft-burn/src/lib.rs b/compression/cnft-burn/anchor/programs/cnft-burn/src/lib.rs index c0c3a4ffe..529f0673e 100644 --- a/compression/cnft-burn/anchor/programs/cnft-burn/src/lib.rs +++ b/compression/cnft-burn/anchor/programs/cnft-burn/src/lib.rs @@ -30,8 +30,8 @@ impl anchor_lang::Id for SPLCompression { pub mod cnft_burn { use super::*; - pub fn burn_cnft<'info>( - context: &mut Context<'info, BurnCnftAccountConstraints<'info>>, + pub fn burn_cnft( + context: &mut Context, root: [u8; 32], data_hash: [u8; 32], creator_hash: [u8; 32], diff --git a/compression/cnft-vault/anchor/programs/cnft-vault/src/instructions/withdraw_cnft.rs b/compression/cnft-vault/anchor/programs/cnft-vault/src/instructions/withdraw_cnft.rs index 0e334aef2..0d73a77f2 100644 --- a/compression/cnft-vault/anchor/programs/cnft-vault/src/instructions/withdraw_cnft.rs +++ b/compression/cnft-vault/anchor/programs/cnft-vault/src/instructions/withdraw_cnft.rs @@ -49,8 +49,8 @@ pub struct WithdrawCnftAccountConstraints { pub system_program: Program, } -pub fn handler<'info>( - context: &mut Context<'info, WithdrawCnftAccountConstraints<'info>>, +pub fn handler( + context: &mut Context, root: [u8; 32], data_hash: [u8; 32], creator_hash: [u8; 32], @@ -99,7 +99,7 @@ pub fn handler<'info>( context.accounts.compression_program.cpi_handle_mut(), context.accounts.system_program.cpi_handle_mut(), ]; - for acc in context.remaining_accounts().iter() { + for acc in context.remaining_accounts()?.iter() { account_infos.push(acc.cpi_handle_mut()); } diff --git a/compression/cnft-vault/anchor/programs/cnft-vault/src/instructions/withdraw_two_cnfts.rs b/compression/cnft-vault/anchor/programs/cnft-vault/src/instructions/withdraw_two_cnfts.rs index 4827b0231..792f46ef4 100644 --- a/compression/cnft-vault/anchor/programs/cnft-vault/src/instructions/withdraw_two_cnfts.rs +++ b/compression/cnft-vault/anchor/programs/cnft-vault/src/instructions/withdraw_two_cnfts.rs @@ -66,8 +66,8 @@ pub struct WithdrawTwoCnftsAccountConstraints { } #[allow(clippy::too_many_arguments)] -pub fn handler<'info>( - context: &mut Context<'info, WithdrawTwoCnftsAccountConstraints<'info>>, +pub fn handler( + context: &mut Context, root1: [u8; 32], data_hash1: [u8; 32], creator_hash1: [u8; 32], @@ -97,14 +97,14 @@ pub fn handler<'info>( require!( proof_1_length .checked_add(proof_2_length) - .is_some_and(|total| total == context.remaining_accounts().len()), + .is_some_and(|total| total == context.remaining_accounts()?.len()), VaultError::ProofLengthMismatch ); let signer_seeds: &[&[u8]] = &[VAULT_SEED, &[context.accounts.vault.bump]]; // Split remaining accounts into proof1 and proof2 - let (proof1_accounts, proof2_accounts) = context.remaining_accounts().split_at(proof_1_length); + let (proof1_accounts, proof2_accounts) = context.remaining_accounts()?.split_at(proof_1_length); let proof1_metas: Vec = proof1_accounts .iter() diff --git a/compression/cnft-vault/anchor/programs/cnft-vault/src/lib.rs b/compression/cnft-vault/anchor/programs/cnft-vault/src/lib.rs index d14a2dc0f..fd4d0a018 100644 --- a/compression/cnft-vault/anchor/programs/cnft-vault/src/lib.rs +++ b/compression/cnft-vault/anchor/programs/cnft-vault/src/lib.rs @@ -93,8 +93,8 @@ pub mod cnft_vault { instructions::initialize_vault::handler(context) } - pub fn withdraw_cnft<'info>( - context: &mut Context<'info, WithdrawCnftAccountConstraints<'info>>, + pub fn withdraw_cnft( + context: &mut Context, root: [u8; 32], data_hash: [u8; 32], creator_hash: [u8; 32], @@ -105,8 +105,8 @@ pub mod cnft_vault { } #[allow(clippy::too_many_arguments)] - pub fn withdraw_two_cnfts<'info>( - context: &mut Context<'info, WithdrawTwoCnftsAccountConstraints<'info>>, + pub fn withdraw_two_cnfts( + context: &mut Context, root1: [u8; 32], data_hash1: [u8; 32], creator_hash1: [u8; 32], diff --git a/compression/cutils/anchor/programs/cutils/src/instructions/mint.rs b/compression/cutils/anchor/programs/cutils/src/instructions/mint.rs index 05f9a7f88..34420d703 100644 --- a/compression/cutils/anchor/programs/cutils/src/instructions/mint.rs +++ b/compression/cutils/anchor/programs/cutils/src/instructions/mint.rs @@ -74,8 +74,8 @@ pub struct MintParams { // `with_capacity` + push is intentional here: it documents the exact 16-account // MintToCollectionV1 layout in CPI order, so allow clippy's vec_init_then_push. #[allow(clippy::vec_init_then_push)] -pub fn handle_mint<'info>( - context: &mut Context<'info, MintAccountConstraints<'info>>, +pub fn handle_mint( + context: &mut Context, params: MintParams, ) -> Result<()> { // Build MintToCollectionV1 instruction data diff --git a/compression/cutils/anchor/programs/cutils/src/instructions/verify.rs b/compression/cutils/anchor/programs/cutils/src/instructions/verify.rs index 7ff8cec3f..1f3d894ae 100644 --- a/compression/cutils/anchor/programs/cutils/src/instructions/verify.rs +++ b/compression/cutils/anchor/programs/cutils/src/instructions/verify.rs @@ -31,8 +31,8 @@ pub struct VerifyParams { /// at runtime burns compute for no benefit. const VERIFY_LEAF_DISCRIMINATOR: [u8; 8] = [124, 220, 22, 223, 104, 10, 250, 224]; -pub fn handle_verify<'info>( - context: &mut Context<'info, VerifyAccountConstraints<'info>>, +pub fn handle_verify( + context: &mut Context, params: &VerifyParams, ) -> Result<()> { let asset_id = get_asset_id(&context.accounts.merkle_tree.address(), params.nonce); @@ -52,7 +52,7 @@ pub fn handle_verify<'info>( *context.accounts.merkle_tree.address(), false, )]; - for acc in context.remaining_accounts().iter() { + for acc in context.remaining_accounts()?.iter() { accounts.push(AccountMeta::new_readonly(acc.address(), false)); } @@ -62,7 +62,7 @@ pub fn handle_verify<'info>( data.extend_from_slice(¶ms.index.to_le_bytes()); let mut account_infos = vec![context.accounts.merkle_tree.cpi_handle_mut()]; - for acc in context.remaining_accounts().iter() { + for acc in context.remaining_accounts()?.iter() { account_infos.push(acc.cpi_handle_mut()); } diff --git a/compression/cutils/anchor/programs/cutils/src/lib.rs b/compression/cutils/anchor/programs/cutils/src/lib.rs index 23e108f3c..2ae22c66a 100644 --- a/compression/cutils/anchor/programs/cutils/src/lib.rs +++ b/compression/cutils/anchor/programs/cutils/src/lib.rs @@ -36,15 +36,12 @@ declare_id!("BuFyrgRYzg2nPhqYrxZ7d9uYUs4VXtxH71U8EcoAfTQZ"); pub mod cutils { use super::*; - pub fn mint<'info>( - context: &mut Context<'info, MintAccountConstraints<'info>>, - params: MintParams, - ) -> Result<()> { + pub fn mint(context: &mut Context, params: MintParams) -> Result<()> { instructions::mint::handle_mint(context, params) } - pub fn verify<'info>( - context: &mut Context<'info, VerifyAccountConstraints<'info>>, + pub fn verify( + context: &mut Context, params: VerifyParams, ) -> Result<()> { instructions::verify::handle_verify(context, ¶ms) diff --git a/finance/betting-market/anchor/programs/betting-market/src/instructions/claim_refund.rs b/finance/betting-market/anchor/programs/betting-market/src/instructions/claim_refund.rs index 5fbf5fedf..9fc760bf4 100644 --- a/finance/betting-market/anchor/programs/betting-market/src/instructions/claim_refund.rs +++ b/finance/betting-market/anchor/programs/betting-market/src/instructions/claim_refund.rs @@ -16,7 +16,11 @@ pub struct ClaimRefundAccountConstraints { #[account(mint::token_program = token_program)] pub token_mint: InterfaceAccount, + // `mut` so the borrow released for the vault CPI below can be reacquired: + // v2 has no read-only reacquire, and the derive dereferences `event` again + // when it checks the constraints that name it. #[account( + mut, seeds = [b"event", event.event_id.to_le_bytes()], bump = event.bump, )] @@ -76,16 +80,24 @@ pub fn handle_claim_refund(context: &mut Context) let event_id = context.accounts.event.event_id; let event_bump = context.accounts.event.bump; + // `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, - *context.accounts.event.account(), + event_view, &context.accounts.token_program, event_id, event_bump, )?; + // Take the borrow back before the derive's exit path touches `event` again. + context.accounts.event.reacquire_borrow_mut()?; + Ok(()) } diff --git a/finance/betting-market/anchor/programs/betting-market/src/instructions/claim_winnings.rs b/finance/betting-market/anchor/programs/betting-market/src/instructions/claim_winnings.rs index 6e4fa3413..1764f0a50 100644 --- a/finance/betting-market/anchor/programs/betting-market/src/instructions/claim_winnings.rs +++ b/finance/betting-market/anchor/programs/betting-market/src/instructions/claim_winnings.rs @@ -16,7 +16,11 @@ pub struct ClaimWinningsAccountConstraints { #[account(mint::token_program = token_program)] pub token_mint: InterfaceAccount, + // `mut` so the borrow released for the vault CPI below can be reacquired: + // v2 has no read-only reacquire, and the derive dereferences `event` again + // when it checks the constraints that name it. #[account( + mut, seeds = [b"event", event.event_id.to_le_bytes()], bump = event.bump, )] @@ -101,16 +105,24 @@ pub fn handle_claim_winnings(context: &mut Context 0 { let event_id = context.accounts.event.event_id; let event_bump = context.accounts.event.bump; + // `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, - *context.accounts.event.account(), + event_view, &context.accounts.token_program, event_id, event_bump, )?; + + // Take the borrow back before writing the settled state through it. + // Only released on this branch, so only reacquired here. + context.accounts.event.reacquire_borrow_mut()?; } let event = &mut context.accounts.event; diff --git a/finance/lending/anchor/programs/lending/src/instructions/refresh_obligation.rs b/finance/lending/anchor/programs/lending/src/instructions/refresh_obligation.rs index a58bbb65a..b723865ba 100644 --- a/finance/lending/anchor/programs/lending/src/instructions/refresh_obligation.rs +++ b/finance/lending/anchor/programs/lending/src/instructions/refresh_obligation.rs @@ -19,7 +19,7 @@ pub fn handle_refresh_obligation(context: &mut Context) -> Re let slot = Clock::get()?.slot; let obligation = &mut context.accounts.obligation; let lending_market = obligation.lending_market; - let accounts = context.remaining_accounts(); + let accounts = context.remaining_accounts()?; let mut cursor = 0usize; let mut deposited_value: u128 = 0; diff --git a/finance/lending/anchor/programs/lending/src/state/reserve.rs b/finance/lending/anchor/programs/lending/src/state/reserve.rs index 867449597..73f2d58ad 100644 --- a/finance/lending/anchor/programs/lending/src/state/reserve.rs +++ b/finance/lending/anchor/programs/lending/src/state/reserve.rs @@ -6,7 +6,7 @@ use crate::math::{mul_div_ceil, mul_div_floor}; /// Signer seeds for a reserve PDA, which is the authority over its liquidity /// vault and the mint authority of its share token. -pub fn reserve_signer_seeds<'a>( +pub fn reserve_signer_seeds( lending_market: &'a Address, liquidity_mint: &'a Address, bump: &'a [u8; 1], diff --git a/finance/order-book/anchor/programs/order-book/src/instructions/place_order.rs b/finance/order-book/anchor/programs/order-book/src/instructions/place_order.rs index a9422e8b0..1a1508b3c 100644 --- a/finance/order-book/anchor/programs/order-book/src/instructions/place_order.rs +++ b/finance/order-book/anchor/programs/order-book/src/instructions/place_order.rs @@ -25,8 +25,8 @@ const BASIS_POINTS_DENOMINATOR: u128 = 10_000; // small. const ACCOUNTS_PER_MAKER: usize = 2; -pub fn handle_place_order<'info>( - context: &mut Context<'info, PlaceOrderAccountConstraints<'info>>, +pub fn handle_place_order( + context: &mut Context, side: OrderSide, price: u64, quantity: u64, @@ -111,7 +111,7 @@ pub fn handle_place_order<'info>( // transaction's remaining_accounts, in the same price-time-priority // order the book would walk. We plan fills against the resting tree, // then verify the caller's account list matches the plan, then apply. - let maker_accounts = &context.remaining_accounts(); + let maker_accounts = &context.remaining_accounts()?; require!( maker_accounts.len() % ACCOUNTS_PER_MAKER == 0, ErrorCode::MissingMakerAccounts diff --git a/finance/order-book/anchor/programs/order-book/src/lib.rs b/finance/order-book/anchor/programs/order-book/src/lib.rs index d8a621a79..0812df92e 100644 --- a/finance/order-book/anchor/programs/order-book/src/lib.rs +++ b/finance/order-book/anchor/programs/order-book/src/lib.rs @@ -52,8 +52,8 @@ pub mod order_book { /// `remaining_accounts`, in pairs of /// `(maker_order_pda, maker_user_account_pda)`, ordered by the /// book's price-time priority (i.e. best ask first for a taker bid). - pub fn place_order<'info>( - context: &mut Context<'info, PlaceOrderAccountConstraints<'info>>, + pub fn place_order( + context: &mut Context, side: state::OrderSide, price: u64, quantity: u64, diff --git a/finance/perpetual-futures/anchor/programs/perpetual-futures/Cargo.toml b/finance/perpetual-futures/anchor/programs/perpetual-futures/Cargo.toml index 78cc010cc..950500d3e 100644 --- a/finance/perpetual-futures/anchor/programs/perpetual-futures/Cargo.toml +++ b/finance/perpetual-futures/anchor/programs/perpetual-futures/Cargo.toml @@ -27,6 +27,10 @@ anchor-lang = "2.0.0-rc.1" # The `#[program]` macro expands to `wincode` paths for instruction-data # (de)serialization, so the crate has to be a direct dependency. wincode = { version = "0.5", features = ["derive"] } +# `last_restart.rs` reads the LastRestartSlot sysvar through the raw +# `sol_get_sysvar` syscall: pinocchio (and so anchor-lang v2) ships only the +# Clock and Rent sysvars. +solana-define-syscall = "5.0" # anchor-lang 2.0.0-rc.1 is built against wincode 0.5, but solana-address 2.7 # moved to wincode 0.6. With both in the graph, `Address`'s wincode impls # belong to the version the account derives are not using and every @@ -46,6 +50,7 @@ solana-sysvar = "3" [dev-dependencies] litesvm = "0.13.1" +solana-clock = "3.0.1" solana-signer = "3.0.0" solana-keypair = "3.0.1" solana-kite = "0.4.0" diff --git a/finance/perpetual-futures/anchor/programs/perpetual-futures/src/instructions/add_liquidity.rs b/finance/perpetual-futures/anchor/programs/perpetual-futures/src/instructions/add_liquidity.rs index 8330e8ef6..915f98b81 100644 --- a/finance/perpetual-futures/anchor/programs/perpetual-futures/src/instructions/add_liquidity.rs +++ b/finance/perpetual-futures/anchor/programs/perpetual-futures/src/instructions/add_liquidity.rs @@ -21,7 +21,7 @@ pub fn handle_add_liquidity( let pool = &mut context.accounts.pool; let price = refresh_price_and_funding(pool, &context.accounts.oracle_feed)?; - let lp_supply = context.accounts.lp_mint.supply; + let lp_supply = context.accounts.lp_mint.supply(); let shares: u64 = if lp_supply == 0 { // Bootstrap: shares track collateral one-for-one, less the withheld // minimum, so the share supply can never start at a dust amount. diff --git a/finance/perpetual-futures/anchor/programs/perpetual-futures/src/instructions/remove_liquidity.rs b/finance/perpetual-futures/anchor/programs/perpetual-futures/src/instructions/remove_liquidity.rs index 73ff3d07e..e7ccf2bf6 100644 --- a/finance/perpetual-futures/anchor/programs/perpetual-futures/src/instructions/remove_liquidity.rs +++ b/finance/perpetual-futures/anchor/programs/perpetual-futures/src/instructions/remove_liquidity.rs @@ -21,7 +21,7 @@ pub fn handle_remove_liquidity( let pool = &mut context.accounts.pool; let price = refresh_price_and_funding(pool, &context.accounts.oracle_feed)?; - let lp_supply = context.accounts.lp_mint.supply; + let lp_supply = context.accounts.lp_mint.supply(); let aum = liquidity_provider_aum(pool, price)?; require!(aum > 0, PerpError::PoolInsolvent); diff --git a/finance/perpetual-futures/anchor/programs/perpetual-futures/src/last_restart.rs b/finance/perpetual-futures/anchor/programs/perpetual-futures/src/last_restart.rs new file mode 100644 index 000000000..7c9f3647c --- /dev/null +++ b/finance/perpetual-futures/anchor/programs/perpetual-futures/src/last_restart.rs @@ -0,0 +1,69 @@ +//! The LastRestartSlot sysvar: the slot of the most recent cluster restart, +//! or 0 if the cluster has never restarted (SIMD-0047). anchor-lang v2 is +//! built on pinocchio, which ships only the Clock and Rent sysvars, so this +//! program declares the 8-byte layout itself and reads it through the same +//! `sol_get_sysvar` syscall pinocchio's own sysvars use. (`solana-sysvar`'s +//! `LastRestartSlot::get` is not usable here: it is bound to that crate's +//! `Sysvar` trait, not pinocchio's.) +//! +//! Why the program reads it: a halt stops the slot count but not the wall +//! clock, so after a restart an oracle price can look fresh in slots while +//! its value is hours old. `read_oracle_price` rejects any price stamped at +//! or before the restart slot, so the pool pauses valuation until the +//! publisher posts again. + +use anchor_lang::prelude::*; + +/// `SysvarLastRestartS1ot1111111111111111111111`, decoded at compile time. +const LAST_RESTART_SLOT_ID: Address = + anchor_lang::address!("SysvarLastRestartS1ot1111111111111111111111"); + +/// The sysvar's whole data: one little-endian u64. +#[repr(C)] +#[derive(Copy, Clone)] +pub struct LastRestartSlot { + pub last_restart_slot: [u8; 8], +} + +const _: () = assert!(core::mem::size_of::() == 8); +const _: () = assert!(core::mem::align_of::() == 1); + +impl LastRestartSlot { + /// Slot of the most recent cluster restart, 0 if there has never been one. + pub fn last_restart_slot(&self) -> u64 { + u64::from_le_bytes(self.last_restart_slot) + } + + pub fn get() -> Result { + let mut var = core::mem::MaybeUninit::::uninit(); + let var_addr = var.as_mut_ptr() as *mut u8; + + #[cfg(any(target_os = "solana", target_arch = "bpf"))] + // SAFETY: `var_addr` points at 8 writable bytes and the syscall + // writes exactly 8. + let result = unsafe { + solana_define_syscall::definitions::sol_get_sysvar( + &LAST_RESTART_SLOT_ID as *const _ as *const u8, + var_addr, + 0, + core::mem::size_of::() as u64, + ) + }; + + // Off-chain (IDL builds, client compilation) the sysvar reads as + // zero: the cluster has never restarted. + #[cfg(not(any(target_os = "solana", target_arch = "bpf")))] + let result: u64 = { + // SAFETY: `var_addr` points at 8 writable bytes. + unsafe { var_addr.write_bytes(0, core::mem::size_of::()) }; + 0 + }; + + match result { + // SAFETY: on success the syscall (or the zeroing above) has + // initialized all 8 bytes. + 0 => Ok(unsafe { var.assume_init() }), + _ => Err(ProgramError::UnsupportedSysvar.into()), + } + } +} diff --git a/finance/perpetual-futures/anchor/programs/perpetual-futures/src/lib.rs b/finance/perpetual-futures/anchor/programs/perpetual-futures/src/lib.rs index 0b0ce5678..b983697fc 100644 --- a/finance/perpetual-futures/anchor/programs/perpetual-futures/src/lib.rs +++ b/finance/perpetual-futures/anchor/programs/perpetual-futures/src/lib.rs @@ -1,6 +1,7 @@ use anchor_lang::prelude::*; mod constants; +mod last_restart; mod errors; // Public so the LiteSVM integration tests can build instruction arguments // (`PoolParameters`, `Side`) against the program's own types. diff --git a/finance/perpetual-futures/anchor/programs/perpetual-futures/src/state/oracle.rs b/finance/perpetual-futures/anchor/programs/perpetual-futures/src/state/oracle.rs index 127c7b667..ee1064a94 100644 --- a/finance/perpetual-futures/anchor/programs/perpetual-futures/src/state/oracle.rs +++ b/finance/perpetual-futures/anchor/programs/perpetual-futures/src/state/oracle.rs @@ -1,5 +1,5 @@ use anchor_lang::prelude::*; -use solana_sysvar::last_restart_slot::LastRestartSlot; +use crate::last_restart::LastRestartSlot; use crate::constants::{BASIS_POINTS_DENOMINATOR, MAX_PRICE_STALENESS_SLOTS}; use crate::errors::PerpError; @@ -44,7 +44,7 @@ pub fn read_oracle_price( expected_scale: u32, max_confidence_bps: u16, ) -> Result { - let data = feed.try_borrow_data()?; + let data = feed.try_borrow()?; require!( data.len() >= FEED_MINIMUM_LENGTH, PerpError::OracleDataTooShort @@ -88,7 +88,7 @@ pub fn read_oracle_price( // market-wide equity error, so reject any price stamped at or before the // restart slot; the pool pauses valuation until the publisher posts // again. Zero means the cluster has never restarted. - let last_restart_slot = LastRestartSlot::get()?.last_restart_slot; + let last_restart_slot = LastRestartSlot::get()?.last_restart_slot(); require!( last_restart_slot == 0 || last_update_slot > last_restart_slot, PerpError::PricePredatesRestart diff --git a/finance/perpetual-futures/anchor/programs/perpetual-futures/tests/test_perpetual_futures.rs b/finance/perpetual-futures/anchor/programs/perpetual-futures/tests/test_perpetual_futures.rs index d7dfe1de1..5d6c9237e 100644 --- a/finance/perpetual-futures/anchor/programs/perpetual-futures/tests/test_perpetual_futures.rs +++ b/finance/perpetual-futures/anchor/programs/perpetual-futures/tests/test_perpetual_futures.rs @@ -220,7 +220,7 @@ impl Market { } fn current_slot(&self) -> u64 { - self.svm.get_sysvar::().slot + self.svm.get_sysvar::().slot } /// Simulate a cluster restart at `slot`: prices stamped at or before it diff --git a/finance/prop-amm/anchor/programs/prop-amm/Cargo.toml b/finance/prop-amm/anchor/programs/prop-amm/Cargo.toml index 9bf25f995..0a3414190 100644 --- a/finance/prop-amm/anchor/programs/prop-amm/Cargo.toml +++ b/finance/prop-amm/anchor/programs/prop-amm/Cargo.toml @@ -26,6 +26,10 @@ anchor-lang = "2.0.0-rc.1" # The `#[program]` macro expands to `wincode` paths for instruction-data # (de)serialization, so the crate has to be a direct dependency. wincode = { version = "0.5", features = ["derive"] } +# `last_restart.rs` reads the LastRestartSlot sysvar through the raw +# `sol_get_sysvar` syscall: pinocchio (and so anchor-lang v2) ships only the +# Clock and Rent sysvars. +solana-define-syscall = "5.0" # anchor-lang 2.0.0-rc.1 is built against wincode 0.5, but solana-address 2.7 # moved to wincode 0.6. With both in the graph, `Address`'s wincode impls # belong to the version the account derives are not using and every @@ -43,6 +47,7 @@ spl-associated-token-account = { version = "8.0.0", features = ["no-entrypoint"] [dev-dependencies] litesvm = "0.13.1" +solana-clock = "3.0.1" solana-signer = "3.0.0" solana-keypair = "3.0.1" solana-kite = "0.4.0" diff --git a/finance/prop-amm/anchor/programs/prop-amm/src/last_restart.rs b/finance/prop-amm/anchor/programs/prop-amm/src/last_restart.rs new file mode 100644 index 000000000..7c9f3647c --- /dev/null +++ b/finance/prop-amm/anchor/programs/prop-amm/src/last_restart.rs @@ -0,0 +1,69 @@ +//! The LastRestartSlot sysvar: the slot of the most recent cluster restart, +//! or 0 if the cluster has never restarted (SIMD-0047). anchor-lang v2 is +//! built on pinocchio, which ships only the Clock and Rent sysvars, so this +//! program declares the 8-byte layout itself and reads it through the same +//! `sol_get_sysvar` syscall pinocchio's own sysvars use. (`solana-sysvar`'s +//! `LastRestartSlot::get` is not usable here: it is bound to that crate's +//! `Sysvar` trait, not pinocchio's.) +//! +//! Why the program reads it: a halt stops the slot count but not the wall +//! clock, so after a restart an oracle price can look fresh in slots while +//! its value is hours old. `read_oracle_price` rejects any price stamped at +//! or before the restart slot, so the pool pauses valuation until the +//! publisher posts again. + +use anchor_lang::prelude::*; + +/// `SysvarLastRestartS1ot1111111111111111111111`, decoded at compile time. +const LAST_RESTART_SLOT_ID: Address = + anchor_lang::address!("SysvarLastRestartS1ot1111111111111111111111"); + +/// The sysvar's whole data: one little-endian u64. +#[repr(C)] +#[derive(Copy, Clone)] +pub struct LastRestartSlot { + pub last_restart_slot: [u8; 8], +} + +const _: () = assert!(core::mem::size_of::() == 8); +const _: () = assert!(core::mem::align_of::() == 1); + +impl LastRestartSlot { + /// Slot of the most recent cluster restart, 0 if there has never been one. + pub fn last_restart_slot(&self) -> u64 { + u64::from_le_bytes(self.last_restart_slot) + } + + pub fn get() -> Result { + let mut var = core::mem::MaybeUninit::::uninit(); + let var_addr = var.as_mut_ptr() as *mut u8; + + #[cfg(any(target_os = "solana", target_arch = "bpf"))] + // SAFETY: `var_addr` points at 8 writable bytes and the syscall + // writes exactly 8. + let result = unsafe { + solana_define_syscall::definitions::sol_get_sysvar( + &LAST_RESTART_SLOT_ID as *const _ as *const u8, + var_addr, + 0, + core::mem::size_of::() as u64, + ) + }; + + // Off-chain (IDL builds, client compilation) the sysvar reads as + // zero: the cluster has never restarted. + #[cfg(not(any(target_os = "solana", target_arch = "bpf")))] + let result: u64 = { + // SAFETY: `var_addr` points at 8 writable bytes. + unsafe { var_addr.write_bytes(0, core::mem::size_of::()) }; + 0 + }; + + match result { + // SAFETY: on success the syscall (or the zeroing above) has + // initialized all 8 bytes. + 0 => Ok(unsafe { var.assume_init() }), + _ => Err(ProgramError::UnsupportedSysvar.into()), + } + } +} diff --git a/finance/prop-amm/anchor/programs/prop-amm/src/lib.rs b/finance/prop-amm/anchor/programs/prop-amm/src/lib.rs index ff0f5e72f..fa09fd200 100644 --- a/finance/prop-amm/anchor/programs/prop-amm/src/lib.rs +++ b/finance/prop-amm/anchor/programs/prop-amm/src/lib.rs @@ -1,6 +1,7 @@ use anchor_lang::prelude::*; mod constants; +mod last_restart; mod errors; // Public so the LiteSVM integration tests can build instruction arguments // (`MarketParameters`, `Direction`) against the program's own types. diff --git a/finance/prop-amm/anchor/programs/prop-amm/src/state/oracle.rs b/finance/prop-amm/anchor/programs/prop-amm/src/state/oracle.rs index d252f06d2..6c9f18882 100644 --- a/finance/prop-amm/anchor/programs/prop-amm/src/state/oracle.rs +++ b/finance/prop-amm/anchor/programs/prop-amm/src/state/oracle.rs @@ -1,5 +1,5 @@ use anchor_lang::prelude::*; -use solana_sysvar::last_restart_slot::LastRestartSlot; +use crate::last_restart::LastRestartSlot; use crate::constants::{BASIS_POINTS_DENOMINATOR, MAX_PRICE_STALENESS_SLOTS}; use crate::errors::PropAmmError; @@ -46,7 +46,7 @@ pub fn read_oracle_price( expected_scale: u32, max_confidence_bps: u16, ) -> Result { - let data = feed.try_borrow_data()?; + let data = feed.try_borrow()?; require!( data.len() >= FEED_MINIMUM_LENGTH, PropAmmError::OracleDataTooShort @@ -90,7 +90,7 @@ pub fn read_oracle_price( // whoever trades first, so reject any price stamped at or before the // restart slot; the market refuses to quote until the publisher posts // again. Zero means the cluster has never restarted. - let last_restart_slot = LastRestartSlot::get()?.last_restart_slot; + let last_restart_slot = LastRestartSlot::get()?.last_restart_slot(); require!( last_restart_slot == 0 || last_update_slot > last_restart_slot, PropAmmError::PricePredatesRestart diff --git a/finance/prop-amm/anchor/programs/prop-amm/tests/test_prop_amm.rs b/finance/prop-amm/anchor/programs/prop-amm/tests/test_prop_amm.rs index 9fc8220ed..fce4761c6 100644 --- a/finance/prop-amm/anchor/programs/prop-amm/tests/test_prop_amm.rs +++ b/finance/prop-amm/anchor/programs/prop-amm/tests/test_prop_amm.rs @@ -265,7 +265,7 @@ impl Market { } fn current_slot(&self) -> u64 { - self.svm.get_sysvar::().slot + self.svm.get_sysvar::().slot } /// Simulate a cluster restart at `slot`: prices stamped at or before it diff --git a/finance/token-fundraiser/anchor/programs/fundraiser/Cargo.toml b/finance/token-fundraiser/anchor/programs/fundraiser/Cargo.toml index 4e12e6c47..1294231ad 100644 --- a/finance/token-fundraiser/anchor/programs/fundraiser/Cargo.toml +++ b/finance/token-fundraiser/anchor/programs/fundraiser/Cargo.toml @@ -33,6 +33,7 @@ anchor-spl = "2.0.0-rc.1" [dev-dependencies] litesvm = "0.13.1" +solana-clock = "3.0.1" solana-signer = "3.0.0" solana-keypair = "3.0.1" solana-kite = "0.4.0" diff --git a/finance/token-fundraiser/anchor/programs/fundraiser/tests/test_fundraiser.rs b/finance/token-fundraiser/anchor/programs/fundraiser/tests/test_fundraiser.rs index 4bbbc4800..098a96c85 100644 --- a/finance/token-fundraiser/anchor/programs/fundraiser/tests/test_fundraiser.rs +++ b/finance/token-fundraiser/anchor/programs/fundraiser/tests/test_fundraiser.rs @@ -1,11 +1,12 @@ use { anchor_lang::{ - solana_program::{clock::Clock, instruction::Instruction}, - system_program, Address, InstructionData, ToAccountMetas, + solana_program::instruction::Instruction, system_program, Address, InstructionData, + ToAccountMetas, }, borsh::BorshDeserialize, fundraiser::SECONDS_TO_DAYS, litesvm::LiteSVM, + solana_clock::Clock, solana_keypair::Keypair, solana_kite::{ create_associated_token_account, create_token_mint, create_wallet, diff --git a/finance/vault-strategy/anchor/programs/mock-swap-router/src/instructions/swap_asset_for_usdc.rs b/finance/vault-strategy/anchor/programs/mock-swap-router/src/instructions/swap_asset_for_usdc.rs index 17f7f3313..0a84d8c44 100644 --- a/finance/vault-strategy/anchor/programs/mock-swap-router/src/instructions/swap_asset_for_usdc.rs +++ b/finance/vault-strategy/anchor/programs/mock-swap-router/src/instructions/swap_asset_for_usdc.rs @@ -24,7 +24,7 @@ pub struct SwapAssetForUsdcAccountConstraints { )] pub asset_rate: BorshAccount, - #[account(constraint = usdc_mint.address() == router_config.usdc_mint @ RouterError::WrongUsdcMint)] + #[account(constraint = *usdc_mint.address() == router_config.usdc_mint @ RouterError::WrongUsdcMint)] pub usdc_mint: Box>, #[account(mut)] diff --git a/finance/vault-strategy/anchor/programs/vault-strategy/Cargo.toml b/finance/vault-strategy/anchor/programs/vault-strategy/Cargo.toml index be180589f..573c1df5c 100644 --- a/finance/vault-strategy/anchor/programs/vault-strategy/Cargo.toml +++ b/finance/vault-strategy/anchor/programs/vault-strategy/Cargo.toml @@ -34,6 +34,7 @@ mock-swap-router = { path = "../mock-swap-router", features = ["cpi"] } [dev-dependencies] litesvm = "0.13.1" +solana-clock = "3.0.1" solana-account = "3.0.0" solana-signer = "3.0.0" solana-keypair = "3.0.1" diff --git a/finance/vault-strategy/anchor/programs/vault-strategy/src/instructions/add_asset.rs b/finance/vault-strategy/anchor/programs/vault-strategy/src/instructions/add_asset.rs index 6abd84b43..bf1b11b1a 100644 --- a/finance/vault-strategy/anchor/programs/vault-strategy/src/instructions/add_asset.rs +++ b/finance/vault-strategy/anchor/programs/vault-strategy/src/instructions/add_asset.rs @@ -72,13 +72,13 @@ pub fn handle_add_asset( let index = strategy.asset_count; - *context.accounts.asset_config = (AssetConfig { - strategy: strategy.address(), + **context.accounts.asset_config = (AssetConfig { + strategy: *strategy.address(), index, - mint: context.accounts.asset_mint.address(), + mint: *context.accounts.asset_mint.address(), // Copied from the registry entry, never supplied by the manager. price_feed: context.accounts.approved_asset.price_feed, - vault: context.accounts.vault_asset.address(), + vault: *context.accounts.vault_asset.address(), weight_bps, bump: context.bumps.asset_config, }); diff --git a/finance/vault-strategy/anchor/programs/vault-strategy/src/instructions/approve_asset.rs b/finance/vault-strategy/anchor/programs/vault-strategy/src/instructions/approve_asset.rs index e2af0d3c1..db858adb6 100644 --- a/finance/vault-strategy/anchor/programs/vault-strategy/src/instructions/approve_asset.rs +++ b/finance/vault-strategy/anchor/programs/vault-strategy/src/instructions/approve_asset.rs @@ -34,8 +34,8 @@ pub fn handle_approve_asset( price_feed: Address, ) -> Result<()> { *context.accounts.approved_asset = (ApprovedAsset { - registry: context.accounts.registry.address(), - mint: context.accounts.asset_mint.address(), + registry: *context.accounts.registry.address(), + mint: *context.accounts.asset_mint.address(), price_feed, bump: context.bumps.approved_asset, }); diff --git a/finance/vault-strategy/anchor/programs/vault-strategy/src/instructions/collect_fees.rs b/finance/vault-strategy/anchor/programs/vault-strategy/src/instructions/collect_fees.rs index f7299275b..67d807700 100644 --- a/finance/vault-strategy/anchor/programs/vault-strategy/src/instructions/collect_fees.rs +++ b/finance/vault-strategy/anchor/programs/vault-strategy/src/instructions/collect_fees.rs @@ -92,7 +92,7 @@ pub fn handle_collect_fees(context: &mut Context) let mint_accounts = MintTo { mint: context.accounts.share_mint.cpi_handle_mut(), to: context.accounts.manager_share_account.cpi_handle_mut(), - authority: context.accounts.strategy.cpi_handle_mut(), + authority: context.accounts.strategy.cpi_handle(), }; let cpi_ctx = CpiContext::new_with_signer( context.accounts.token_program.address(), diff --git a/finance/vault-strategy/anchor/programs/vault-strategy/src/instructions/deposit.rs b/finance/vault-strategy/anchor/programs/vault-strategy/src/instructions/deposit.rs index 5ba36b1ac..24515057d 100644 --- a/finance/vault-strategy/anchor/programs/vault-strategy/src/instructions/deposit.rs +++ b/finance/vault-strategy/anchor/programs/vault-strategy/src/instructions/deposit.rs @@ -71,9 +71,10 @@ pub struct DepositAccountConstraints { pub router_authority: UncheckedAccount, #[account( - constraint = swap_router_program.address() == strategy.swap_router @ VaultError::InvalidSwapRouter + constraint = *swap_router_program.address() == strategy.swap_router @ VaultError::InvalidSwapRouter )] - pub swap_router_program: Program, +/// CHECK: validated by the address constraint above + pub swap_router_program: UncheckedAccount, pub associated_token_program: Program, pub token_program: Interface<'static, TokenInterface>, @@ -88,8 +89,8 @@ pub struct DepositAccountConstraints { /// invested. For each asset the handler swaps `usdc_amount * weight_bps / 10000` /// through the registered router, so a depositor's money is invested in the same /// transaction they put it in (only sub-cent rounding dust can remain as USDC). -pub fn handle_deposit<'info>( - context: &mut Context<'info, DepositAccountConstraints<'info>>, +pub fn handle_deposit( + context: &mut Context, usdc_amount: u64, minimum_shares: u64, ) -> Result<()> { @@ -106,7 +107,7 @@ pub fn handle_deposit<'info>( let usdc_decimals = context.accounts.usdc_mint.decimals(); let strategy_index = context.accounts.strategy.index; let strategy_bump = context.accounts.strategy.bump; - let strategy_key = context.accounts.strategy.address(); + let strategy_key = *context.accounts.strategy.address(); let max_slippage_bps = context.accounts.strategy.max_slippage_bps; let asset_count = context.accounts.strategy.asset_count as usize; @@ -115,7 +116,7 @@ pub fn handle_deposit<'info>( // Net asset value over the complete asset set. The assets are exactly indices // 0..asset_count, so requiring five accounts per index, in order, each with a // matching index, makes it impossible to omit an asset and understate NAV. - let remaining = context.remaining_accounts(); + let remaining = context.remaining_accounts()?; require!( remaining.len() == asset_count * 5, VaultError::IncompleteAssetAccounts @@ -139,7 +140,7 @@ pub fn handle_deposit<'info>( VaultError::InvalidAssetAccount ); require_keys_eq!( - vault_account.address(), + *vault_account.address(), config.vault, VaultError::InvalidAssetAccount ); @@ -174,9 +175,9 @@ pub fn handle_deposit<'info>( // Pull the depositor's USDC into the strategy's USDC vault. let transfer_accounts = TransferChecked { from: context.accounts.depositor_usdc_account.cpi_handle_mut(), - mint: context.accounts.usdc_mint.cpi_handle_mut(), + mint: context.accounts.usdc_mint.cpi_handle(), to: context.accounts.vault_usdc.cpi_handle_mut(), - authority: context.accounts.depositor.cpi_handle_mut(), + authority: context.accounts.depositor.cpi_handle(), }; let cpi_ctx = CpiContext::new(context.accounts.token_program.address(), transfer_accounts); transfer_checked(cpi_ctx, usdc_amount, usdc_decimals)?; @@ -190,14 +191,14 @@ pub fn handle_deposit<'info>( // controls. for index in 0..asset_count { let config_account = &remaining[index * 5]; - let vault_account = &remaining[index * 5 + 1]; - let mint_account = &remaining[index * 5 + 2]; + let mut vault_account = remaining[index * 5 + 1]; + let mut mint_account = remaining[index * 5 + 2]; let rate_account = &remaining[index * 5 + 3]; let feed_account = &remaining[index * 5 + 4]; let config = AssetConfig::load_checked(config_account)?; require_keys_eq!( - mint_account.address(), + *mint_account.address(), config.mint, VaultError::InvalidAssetAccount ); @@ -233,18 +234,18 @@ pub fn handle_deposit<'info>( .map_err(|_| VaultError::MathOverflow)?; let cpi_accounts = RouterSwapAccounts { - caller: context.accounts.strategy.cpi_handle_mut(), - router_config: context.accounts.router_config.cpi_handle_mut(), - asset_rate: rate_account.clone(), - usdc_mint: context.accounts.usdc_mint.cpi_handle_mut(), - asset_mint: mint_account.clone(), + caller: context.accounts.strategy.cpi_handle(), + router_config: context.accounts.router_config.cpi_handle(), + asset_rate: CpiHandle::readonly(&rate_account), + usdc_mint: context.accounts.usdc_mint.cpi_handle(), + asset_mint: CpiHandleMut::writable(&mut mint_account), caller_usdc_account: context.accounts.vault_usdc.cpi_handle_mut(), - caller_asset_account: vault_account.clone(), + caller_asset_account: CpiHandleMut::writable(&mut vault_account), router_usdc_treasury: context.accounts.router_usdc_treasury.cpi_handle_mut(), - router_authority: context.accounts.router_authority.cpi_handle_mut(), - associated_token_program: context.accounts.associated_token_program.cpi_handle_mut(), - token_program: context.accounts.token_program.cpi_handle_mut(), - system_program: context.accounts.system_program.cpi_handle_mut(), + router_authority: context.accounts.router_authority.cpi_handle(), + associated_token_program: context.accounts.associated_token_program.cpi_handle(), + token_program: context.accounts.token_program.cpi_handle(), + system_program: context.accounts.system_program.cpi_handle(), }; let cpi_ctx = CpiContext::new_with_signer( context.accounts.swap_router_program.address(), @@ -258,7 +259,7 @@ pub fn handle_deposit<'info>( let mint_accounts = MintTo { mint: context.accounts.share_mint.cpi_handle_mut(), to: context.accounts.depositor_share_account.cpi_handle_mut(), - authority: context.accounts.strategy.cpi_handle_mut(), + authority: context.accounts.strategy.cpi_handle(), }; let cpi_ctx = CpiContext::new_with_signer( context.accounts.token_program.address(), diff --git a/finance/vault-strategy/anchor/programs/vault-strategy/src/instructions/initialize_registry.rs b/finance/vault-strategy/anchor/programs/vault-strategy/src/instructions/initialize_registry.rs index 03bbdf578..bf0bfce7d 100644 --- a/finance/vault-strategy/anchor/programs/vault-strategy/src/instructions/initialize_registry.rs +++ b/finance/vault-strategy/anchor/programs/vault-strategy/src/instructions/initialize_registry.rs @@ -23,7 +23,7 @@ pub fn handle_initialize_registry( context: &mut Context, ) -> Result<()> { *context.accounts.registry = (Registry { - authority: context.accounts.authority.address(), + authority: *context.accounts.authority.address(), bump: context.bumps.registry, }); Ok(()) diff --git a/finance/vault-strategy/anchor/programs/vault-strategy/src/instructions/initialize_strategy.rs b/finance/vault-strategy/anchor/programs/vault-strategy/src/instructions/initialize_strategy.rs index 1bc795062..712f23aae 100644 --- a/finance/vault-strategy/anchor/programs/vault-strategy/src/instructions/initialize_strategy.rs +++ b/finance/vault-strategy/anchor/programs/vault-strategy/src/instructions/initialize_strategy.rs @@ -82,12 +82,12 @@ pub fn handle_initialize_strategy( let clock = Clock::get()?; - *context.accounts.strategy = (Strategy { + **context.accounts.strategy = (Strategy { index, - manager: context.accounts.manager.address(), - registry: context.accounts.registry.address(), - share_mint: context.accounts.share_mint.address(), - usdc_mint: context.accounts.usdc_mint.address(), + manager: *context.accounts.manager.address(), + registry: *context.accounts.registry.address(), + share_mint: *context.accounts.share_mint.address(), + usdc_mint: *context.accounts.usdc_mint.address(), swap_router, fee_bps, max_slippage_bps, diff --git a/finance/vault-strategy/anchor/programs/vault-strategy/src/instructions/rebalance.rs b/finance/vault-strategy/anchor/programs/vault-strategy/src/instructions/rebalance.rs index 5bf0b38af..7a70af750 100644 --- a/finance/vault-strategy/anchor/programs/vault-strategy/src/instructions/rebalance.rs +++ b/finance/vault-strategy/anchor/programs/vault-strategy/src/instructions/rebalance.rs @@ -34,25 +34,25 @@ pub struct RebalanceAccountConstraints { pub buy_mint: Box>, #[account( - constraint = sell_config.strategy == strategy.address() @ VaultError::InvalidAssetAccount, - constraint = sell_config.mint == sell_mint.address() @ VaultError::AssetNotFound, - constraint = sell_config.vault == vault_sell.address() @ VaultError::InvalidAssetAccount, + constraint = sell_config.strategy == *strategy.address() @ VaultError::InvalidAssetAccount, + constraint = sell_config.mint == *sell_mint.address() @ VaultError::AssetNotFound, + constraint = sell_config.vault == *vault_sell.address() @ VaultError::InvalidAssetAccount, )] pub sell_config: Box>, #[account( - constraint = buy_config.strategy == strategy.address() @ VaultError::InvalidAssetAccount, - constraint = buy_config.mint == buy_mint.address() @ VaultError::AssetNotFound, - constraint = buy_config.vault == vault_buy.address() @ VaultError::InvalidAssetAccount, + constraint = buy_config.strategy == *strategy.address() @ VaultError::InvalidAssetAccount, + constraint = buy_config.mint == *buy_mint.address() @ VaultError::AssetNotFound, + constraint = buy_config.vault == *vault_buy.address() @ VaultError::InvalidAssetAccount, )] pub buy_config: Box>, /// CHECK: Pyth feed - validated against sell asset's registered feed - #[account(constraint = sell_price_feed.address() == sell_config.price_feed @ VaultError::InvalidPriceFeed)] + #[account(constraint = *sell_price_feed.address() == sell_config.price_feed @ VaultError::InvalidPriceFeed)] pub sell_price_feed: UncheckedAccount, /// CHECK: Pyth feed - validated against buy asset's registered feed - #[account(constraint = buy_price_feed.address() == buy_config.price_feed @ VaultError::InvalidPriceFeed)] + #[account(constraint = *buy_price_feed.address() == buy_config.price_feed @ VaultError::InvalidPriceFeed)] pub buy_price_feed: UncheckedAccount, #[account( @@ -96,9 +96,10 @@ pub struct RebalanceAccountConstraints { pub router_authority: UncheckedAccount, #[account( - constraint = swap_router_program.address() == strategy.swap_router @ VaultError::InvalidSwapRouter + constraint = *swap_router_program.address() == strategy.swap_router @ VaultError::InvalidSwapRouter )] - pub swap_router_program: Program, +/// CHECK: validated by the address constraint above + pub swap_router_program: UncheckedAccount, pub associated_token_program: Program, pub token_program: Interface<'static, TokenInterface>, @@ -165,18 +166,18 @@ pub fn handle_rebalance( // Step 1: sell basket token -> USDC let sell_cpi_accounts = RouterSellAccounts { - caller: context.accounts.strategy.cpi_handle_mut(), - router_config: context.accounts.router_config.cpi_handle_mut(), - asset_rate: context.accounts.sell_rate.cpi_handle_mut(), - usdc_mint: context.accounts.usdc_mint.cpi_handle_mut(), + caller: context.accounts.strategy.cpi_handle(), + router_config: context.accounts.router_config.cpi_handle(), + asset_rate: context.accounts.sell_rate.cpi_handle(), + usdc_mint: context.accounts.usdc_mint.cpi_handle(), asset_mint: context.accounts.sell_mint.cpi_handle_mut(), caller_asset_account: context.accounts.vault_sell.cpi_handle_mut(), caller_usdc_account: context.accounts.vault_usdc.cpi_handle_mut(), router_usdc_treasury: context.accounts.router_usdc_treasury.cpi_handle_mut(), - router_authority: context.accounts.router_authority.cpi_handle_mut(), - associated_token_program: context.accounts.associated_token_program.cpi_handle_mut(), - token_program: context.accounts.token_program.cpi_handle_mut(), - system_program: context.accounts.system_program.cpi_handle_mut(), + router_authority: context.accounts.router_authority.cpi_handle(), + associated_token_program: context.accounts.associated_token_program.cpi_handle(), + token_program: context.accounts.token_program.cpi_handle(), + system_program: context.accounts.system_program.cpi_handle(), }; mock_swap_router::cpi::swap_asset_for_usdc( CpiContext::new_with_signer( @@ -190,18 +191,18 @@ pub fn handle_rebalance( // Step 2: buy basket token with USDC let buy_cpi_accounts = RouterBuyAccounts { - caller: context.accounts.strategy.cpi_handle_mut(), - router_config: context.accounts.router_config.cpi_handle_mut(), - asset_rate: context.accounts.buy_rate.cpi_handle_mut(), - usdc_mint: context.accounts.usdc_mint.cpi_handle_mut(), + caller: context.accounts.strategy.cpi_handle(), + router_config: context.accounts.router_config.cpi_handle(), + asset_rate: context.accounts.buy_rate.cpi_handle(), + usdc_mint: context.accounts.usdc_mint.cpi_handle(), asset_mint: context.accounts.buy_mint.cpi_handle_mut(), caller_usdc_account: context.accounts.vault_usdc.cpi_handle_mut(), caller_asset_account: context.accounts.vault_buy.cpi_handle_mut(), router_usdc_treasury: context.accounts.router_usdc_treasury.cpi_handle_mut(), - router_authority: context.accounts.router_authority.cpi_handle_mut(), - associated_token_program: context.accounts.associated_token_program.cpi_handle_mut(), - token_program: context.accounts.token_program.cpi_handle_mut(), - system_program: context.accounts.system_program.cpi_handle_mut(), + router_authority: context.accounts.router_authority.cpi_handle(), + associated_token_program: context.accounts.associated_token_program.cpi_handle(), + token_program: context.accounts.token_program.cpi_handle(), + system_program: context.accounts.system_program.cpi_handle(), }; mock_swap_router::cpi::swap_usdc_for_asset( CpiContext::new_with_signer( diff --git a/finance/vault-strategy/anchor/programs/vault-strategy/src/instructions/set_weight.rs b/finance/vault-strategy/anchor/programs/vault-strategy/src/instructions/set_weight.rs index 7a8ce00d4..570ad2c91 100644 --- a/finance/vault-strategy/anchor/programs/vault-strategy/src/instructions/set_weight.rs +++ b/finance/vault-strategy/anchor/programs/vault-strategy/src/instructions/set_weight.rs @@ -17,7 +17,7 @@ pub struct SetWeightAccountConstraints { #[account( mut, - constraint = asset_config.strategy == strategy.address() @ VaultError::InvalidAssetAccount, + constraint = asset_config.strategy == *strategy.address() @ VaultError::InvalidAssetAccount, )] pub asset_config: Box>, } diff --git a/finance/vault-strategy/anchor/programs/vault-strategy/src/instructions/withdraw.rs b/finance/vault-strategy/anchor/programs/vault-strategy/src/instructions/withdraw.rs index a0153640c..702d21064 100644 --- a/finance/vault-strategy/anchor/programs/vault-strategy/src/instructions/withdraw.rs +++ b/finance/vault-strategy/anchor/programs/vault-strategy/src/instructions/withdraw.rs @@ -65,8 +65,8 @@ pub struct WithdrawAccountConstraints { // The user's asset token accounts must already exist. } -pub fn handle_withdraw<'info>( - context: &mut Context<'info, WithdrawAccountConstraints<'info>>, +pub fn handle_withdraw( + context: &mut Context, shares_to_burn: u64, min_usdc_out: u64, ) -> Result<()> { @@ -84,7 +84,7 @@ pub fn handle_withdraw<'info>( let asset_count = context.accounts.strategy.asset_count as usize; require!( - context.remaining_accounts().len() == asset_count * 4, + context.remaining_accounts()?.len() == asset_count * 4, VaultError::IncompleteAssetAccounts ); @@ -110,20 +110,27 @@ pub fn handle_withdraw<'info>( // Hoist owned account-info handles for every CPI up front, so the asset loop // can borrow remaining_accounts without also re-borrowing `context.accounts` // (Account is invariant over its lifetime, which otherwise fails to unify). - let strategy_info = context.accounts.strategy.cpi_handle_mut(); - let share_mint_info = context.accounts.share_mint.cpi_handle_mut(); - let usdc_mint_info = context.accounts.usdc_mint.cpi_handle_mut(); - let vault_usdc_info = context.accounts.vault_usdc.cpi_handle_mut(); - let user_info = context.accounts.user.cpi_handle_mut(); - let user_share_info = context.accounts.user_share_account.cpi_handle_mut(); - let user_usdc_info = context.accounts.user_usdc_account.cpi_handle_mut(); + // `strategy` signs the payouts below. It is a data account holding a live + // borrow on its buffer, so release it across the CPIs and take it back + // afterwards — the runtime rejects a CPI that borrows an account we hold. + context.accounts.strategy.release_borrow()?; + + // `AccountView` is Copy; each copy still points at the same account, and the + // typed handles below carry the borrow provenance v2 wants. + let strategy_view = *context.accounts.strategy.account(); + let mut share_mint_view = *context.accounts.share_mint.account(); + let usdc_mint_view = *context.accounts.usdc_mint.account(); + let mut vault_usdc_view = *context.accounts.vault_usdc.account(); + let user_view = *context.accounts.user.account(); + let mut user_share_view = *context.accounts.user_share_account.account(); + let mut user_usdc_view = *context.accounts.user_usdc_account.account(); let token_program_key = context.accounts.token_program.address(); // Burn the user's shares. let burn_accounts = Burn { - mint: share_mint_info, - from: user_share_info, - authority: user_info, + mint: CpiHandleMut::writable(&mut share_mint_view), + from: CpiHandleMut::writable(&mut user_share_view), + authority: CpiHandle::readonly(&user_view), }; burn( CpiContext::new(token_program_key, burn_accounts), @@ -133,10 +140,10 @@ pub fn handle_withdraw<'info>( // USDC payout. if amount_usdc > 0 { let transfer_accounts = TransferChecked { - from: vault_usdc_info, - mint: usdc_mint_info, - to: user_usdc_info, - authority: strategy_info.clone(), + from: CpiHandleMut::writable(&mut vault_usdc_view), + mint: CpiHandle::readonly(&usdc_mint_view), + to: CpiHandleMut::writable(&mut user_usdc_view), + authority: CpiHandle::readonly(&strategy_view), }; transfer_checked( CpiContext::new_with_signer(token_program_key, transfer_accounts, signer_seeds), @@ -146,36 +153,36 @@ pub fn handle_withdraw<'info>( } // Each basket asset, paid in kind, proportional to shares burned. - let remaining = context.remaining_accounts(); + let remaining = context.remaining_accounts()?; for i in 0..asset_count { let config_ai = &remaining[i * 4]; - let vault_ai = &remaining[i * 4 + 1]; - let mint_ai = &remaining[i * 4 + 2]; - let user_ata_ai = &remaining[i * 4 + 3]; + let mut vault_ai = remaining[i * 4 + 1]; + let mint_ai = remaining[i * 4 + 2]; + let mut user_ata_ai = remaining[i * 4 + 3]; - let config = AssetConfig::load_checked(config_ai)?; + let config = AssetConfig::load_checked(&config_ai)?; require_keys_eq!( config.strategy, - strategy_key, + *strategy_key, VaultError::InvalidAssetAccount ); require!(config.index as usize == i, VaultError::InvalidAssetAccount); require_keys_eq!( - vault_ai.address(), + *vault_ai.address(), config.vault, VaultError::InvalidAssetAccount ); require_keys_eq!( - mint_ai.address(), + *mint_ai.address(), config.mint, VaultError::InvalidAssetAccount ); - let (recipient_mint, recipient_owner) = read_token_mint_and_owner(user_ata_ai)?; - require_keys_eq!(recipient_owner, user_key, VaultError::InvalidRecipient); + let (recipient_mint, recipient_owner) = read_token_mint_and_owner(&user_ata_ai)?; + require_keys_eq!(recipient_owner, *user_key, VaultError::InvalidRecipient); require_keys_eq!(recipient_mint, config.mint, VaultError::InvalidRecipient); - let vault_balance = read_token_amount(vault_ai)?; + let vault_balance = read_token_amount(&vault_ai)?; let amount: u64 = (vault_balance as u128) .checked_mul(shares_u128) .ok_or(VaultError::MathOverflow)? @@ -183,12 +190,12 @@ pub fn handle_withdraw<'info>( .ok_or(VaultError::MathOverflow)? as u64; if amount > 0 { - let decimals = read_mint_decimals(mint_ai)?; + let decimals = read_mint_decimals(&mint_ai)?; let transfer_accounts = TransferChecked { - from: vault_ai.cpi_handle_mut(), - mint: mint_ai.cpi_handle_mut(), - to: user_ata_ai.cpi_handle_mut(), - authority: strategy_info.clone(), + from: CpiHandleMut::writable(&mut vault_ai), + mint: CpiHandle::readonly(&mint_ai), + to: CpiHandleMut::writable(&mut user_ata_ai), + authority: CpiHandle::readonly(&strategy_view), }; transfer_checked( CpiContext::new_with_signer(token_program_key, transfer_accounts, signer_seeds), diff --git a/finance/vault-strategy/anchor/programs/vault-strategy/src/lib.rs b/finance/vault-strategy/anchor/programs/vault-strategy/src/lib.rs index 550cbf771..36c2c785a 100644 --- a/finance/vault-strategy/anchor/programs/vault-strategy/src/lib.rs +++ b/finance/vault-strategy/anchor/programs/vault-strategy/src/lib.rs @@ -64,8 +64,8 @@ pub mod vault_strategy { instructions::set_weight::handle_set_weight(context, weight_bps) } - pub fn deposit<'info>( - context: &mut Context<'info, DepositAccountConstraints<'info>>, + pub fn deposit( + context: &mut Context, usdc_amount: u64, minimum_shares: u64, ) -> Result<()> { @@ -76,8 +76,8 @@ pub mod vault_strategy { instructions::collect_fees::handle_collect_fees(context) } - pub fn withdraw<'info>( - context: &mut Context<'info, WithdrawAccountConstraints<'info>>, + pub fn withdraw( + context: &mut Context, shares_to_burn: u64, min_usdc_out: u64, ) -> Result<()> { diff --git a/finance/vault-strategy/anchor/programs/vault-strategy/src/oracle.rs b/finance/vault-strategy/anchor/programs/vault-strategy/src/oracle.rs index 43eeb5fb5..8faf037ef 100644 --- a/finance/vault-strategy/anchor/programs/vault-strategy/src/oracle.rs +++ b/finance/vault-strategy/anchor/programs/vault-strategy/src/oracle.rs @@ -43,7 +43,7 @@ fn read_pyth_raw(account_data: &[u8]) -> Result<(i64, i64)> { /// return its positive, fresh price as u128. `now` is the current unix timestamp. pub fn load_price(price_feed: &AccountView, expected_key: &Address, now: i64) -> Result { require_keys_eq!( - price_feed.address(), + *price_feed.address(), *expected_key, VaultError::InvalidPriceFeed ); diff --git a/finance/vault-strategy/anchor/programs/vault-strategy/src/state/strategy.rs b/finance/vault-strategy/anchor/programs/vault-strategy/src/state/strategy.rs index c09f785cd..1d4d6db7c 100644 --- a/finance/vault-strategy/anchor/programs/vault-strategy/src/state/strategy.rs +++ b/finance/vault-strategy/anchor/programs/vault-strategy/src/state/strategy.rs @@ -70,12 +70,20 @@ impl AssetConfig { /// Avoids the lifetime invariance of `Account::try_from` on borrowed infos. pub fn load_checked(account: &AccountView) -> Result { require_keys_eq!( - *account.owner, + *account.owner(), crate::ID, crate::error::VaultError::InvalidAssetAccount ); - let data = account.try_borrow_data()?; - AssetConfig::try_deserialize(&mut &data[..]) + let data = account.try_borrow()?; + let disc_len = ::DISCRIMINATOR.len(); + require!( + data.len() > disc_len + && &data[..disc_len] + == ::DISCRIMINATOR, + crate::error::VaultError::InvalidAssetAccount + ); + let mut payload = &data[disc_len..]; + >::get(&mut payload) .map_err(|_| error!(crate::error::VaultError::InvalidAssetAccount)) } } diff --git a/finance/vault-strategy/anchor/programs/vault-strategy/tests/vault_strategy.rs b/finance/vault-strategy/anchor/programs/vault-strategy/tests/vault_strategy.rs index e77864f34..8a753c313 100644 --- a/finance/vault-strategy/anchor/programs/vault-strategy/tests/vault_strategy.rs +++ b/finance/vault-strategy/anchor/programs/vault-strategy/tests/vault_strategy.rs @@ -5,6 +5,7 @@ use { }, anchor_spl::token::spl_token, litesvm::LiteSVM, + solana_clock::Clock, solana_account::Account as SolanaAccount, solana_keypair::Keypair, solana_kite::{ diff --git a/tokens/token-extensions/transfer-fee/anchor/programs/transfer-fee/src/instructions/harvest.rs b/tokens/token-extensions/transfer-fee/anchor/programs/transfer-fee/src/instructions/harvest.rs index 73debe459..69a46d25c 100644 --- a/tokens/token-extensions/transfer-fee/anchor/programs/transfer-fee/src/instructions/harvest.rs +++ b/tokens/token-extensions/transfer-fee/anchor/programs/transfer-fee/src/instructions/harvest.rs @@ -12,9 +12,7 @@ pub struct HarvestAccountConstraints { // transfer fees are stored directly on the recipient token account and must be "harvested" // "harvesting" transfers fees accumulated on token accounts to the mint account -pub fn process_harvest<'info>( - context: &mut Context<'info, HarvestAccountConstraints<'info>>, -) -> Result<()> { +pub fn process_harvest(context: &mut Context) -> Result<()> { // Using remaining accounts to allow for passing in an unknown number of token accounts to harvest from // Check that remaining accounts are token accounts for the mint to harvest to let sources = context diff --git a/tokens/token-extensions/transfer-fee/anchor/programs/transfer-fee/src/lib.rs b/tokens/token-extensions/transfer-fee/anchor/programs/transfer-fee/src/lib.rs index 475ce91f5..628b32ff6 100644 --- a/tokens/token-extensions/transfer-fee/anchor/programs/transfer-fee/src/lib.rs +++ b/tokens/token-extensions/transfer-fee/anchor/programs/transfer-fee/src/lib.rs @@ -21,9 +21,7 @@ pub mod transfer_fee { handle_process_transfer(context, amount) } - pub fn harvest<'info>( - context: &mut Context<'info, HarvestAccountConstraints<'info>>, - ) -> Result<()> { + pub fn harvest(context: &mut Context) -> Result<()> { process_harvest(context) } From 075f64ec34c57f4387aa55413d92f9a3fb241a74 Mon Sep 17 00:00:00 2001 From: Claude Date: Fri, 14 Aug 2026 15:25:31 +0000 Subject: [PATCH 14/59] finance: token-fundraiser passes 17/17; vault-strategy builds More applications of the release/reacquire rule, now that its shape is clear. token-fundraiser's `fundraiser` PDA signs the vault transfer, close and refund CPIs. It is a data account, so its borrow is released across each CPI and taken back afterwards. refund.rs also asked `maker`, a read-only account there, for a writable handle purely to read its address, which panics in v2; it reads `*maker.address()` instead. vault-strategy: `.address()` returns a reference that keeps the account borrowed, which blocked the `&mut` uses further down, and `remaining_accounts()` takes `&mut Context` so it has to be collected before the per-account views borrow `context.accounts`. Measured status of the finance tier: escrow 5/5, betting-market 9/9, token-fundraiser 17/17 passing; prop-amm, perpetual-futures and vault-strategy build; token-swap, lending and order-book do not yet compile. --- .../fundraiser/src/instructions/checker.rs | 16 +++++++++--- .../fundraiser/src/instructions/close.rs | 16 +++++++++--- .../fundraiser/src/instructions/refund.rs | 26 +++++++++++++------ .../src/instructions/withdraw.rs | 16 +++++++----- 4 files changed, 53 insertions(+), 21 deletions(-) diff --git a/finance/token-fundraiser/anchor/programs/fundraiser/src/instructions/checker.rs b/finance/token-fundraiser/anchor/programs/fundraiser/src/instructions/checker.rs index 9517a53ba..2121be708 100644 --- a/finance/token-fundraiser/anchor/programs/fundraiser/src/instructions/checker.rs +++ b/finance/token-fundraiser/anchor/programs/fundraiser/src/instructions/checker.rs @@ -63,12 +63,19 @@ pub fn handle_check_contributions( let vault_amount = accounts.vault.amount(); let mint_decimals = accounts.mint_to_raise.decimals(); + // `fundraiser` signs both CPIs below. It is a data account holding a live + // borrow on its buffer, so release it across the CPIs — the runtime rejects + // a CPI that borrows an account we still hold — and take it back after. + let fundraiser_bump = accounts.fundraiser.bump; + accounts.fundraiser.release_borrow()?; + let fundraiser_view = *accounts.fundraiser.account(); + // The vault is owned by the fundraiser PDA, so both CPIs are signed with // its seeds. let signer_seeds: [&[&[u8]]; 1] = [&[ b"fundraiser".as_ref(), maker_address.as_ref(), - &[accounts.fundraiser.bump], + &[fundraiser_bump], ]]; // Drain the whole vault (including any direct donations) to the maker. @@ -76,7 +83,7 @@ pub fn handle_check_contributions( from: accounts.vault.cpi_handle_mut(), mint: accounts.mint_to_raise.cpi_handle(), to: accounts.maker_ata.cpi_handle_mut(), - authority: accounts.fundraiser.cpi_handle(), + authority: CpiHandle::readonly(&fundraiser_view), }; let transfer_context = CpiContext::new_with_signer( accounts.token_program.address(), @@ -89,7 +96,7 @@ pub fn handle_check_contributions( let close_accounts = CloseAccount { account: accounts.vault.cpi_handle_mut(), destination: accounts.maker.cpi_handle_mut(), - authority: accounts.fundraiser.cpi_handle(), + authority: CpiHandle::readonly(&fundraiser_view), }; let close_context = CpiContext::new_with_signer( accounts.token_program.address(), @@ -98,5 +105,8 @@ pub fn handle_check_contributions( ); close_account(close_context)?; + // Take the borrow back before the derive's exit path touches it again. + accounts.fundraiser.reacquire_borrow_mut()?; + Ok(()) } diff --git a/finance/token-fundraiser/anchor/programs/fundraiser/src/instructions/close.rs b/finance/token-fundraiser/anchor/programs/fundraiser/src/instructions/close.rs index a37aac4f8..9b28e1313 100644 --- a/finance/token-fundraiser/anchor/programs/fundraiser/src/instructions/close.rs +++ b/finance/token-fundraiser/anchor/programs/fundraiser/src/instructions/close.rs @@ -88,12 +88,19 @@ pub fn handle_close_fundraiser(accounts: &mut CloseFundraiserAccountConstraints) let vault_amount = accounts.vault.amount(); let mint_decimals = accounts.mint_to_raise.decimals(); + // `fundraiser` signs both CPIs below. It is a data account holding a live + // borrow on its buffer, so release it across the CPIs — the runtime rejects + // a CPI that borrows an account we still hold — and take it back after. + let fundraiser_bump = accounts.fundraiser.bump; + accounts.fundraiser.release_borrow()?; + let fundraiser_view = *accounts.fundraiser.account(); + // The vault is owned by the fundraiser PDA, so both CPIs are signed with // its seeds. let signer_seeds: [&[&[u8]]; 1] = [&[ b"fundraiser".as_ref(), maker_address.as_ref(), - &[accounts.fundraiser.bump], + &[fundraiser_bump], ]]; // Refunds have already drained every tracked contribution, so anything @@ -104,7 +111,7 @@ pub fn handle_close_fundraiser(accounts: &mut CloseFundraiserAccountConstraints) from: accounts.vault.cpi_handle_mut(), mint: accounts.mint_to_raise.cpi_handle(), to: accounts.maker_ata.cpi_handle_mut(), - authority: accounts.fundraiser.cpi_handle(), + authority: CpiHandle::readonly(&fundraiser_view), }; let transfer_context = CpiContext::new_with_signer( accounts.token_program.address(), @@ -119,7 +126,7 @@ pub fn handle_close_fundraiser(accounts: &mut CloseFundraiserAccountConstraints) let close_accounts = CloseAccount { account: accounts.vault.cpi_handle_mut(), destination: accounts.maker.cpi_handle_mut(), - authority: accounts.fundraiser.cpi_handle(), + authority: CpiHandle::readonly(&fundraiser_view), }; let close_context = CpiContext::new_with_signer( accounts.token_program.address(), @@ -128,5 +135,8 @@ pub fn handle_close_fundraiser(accounts: &mut CloseFundraiserAccountConstraints) ); close_account(close_context)?; + // Take the borrow back before the derive's exit path touches it again. + accounts.fundraiser.reacquire_borrow_mut()?; + Ok(()) } diff --git a/finance/token-fundraiser/anchor/programs/fundraiser/src/instructions/refund.rs b/finance/token-fundraiser/anchor/programs/fundraiser/src/instructions/refund.rs index db1702e81..e7610830e 100644 --- a/finance/token-fundraiser/anchor/programs/fundraiser/src/instructions/refund.rs +++ b/finance/token-fundraiser/anchor/programs/fundraiser/src/instructions/refund.rs @@ -85,29 +85,39 @@ pub fn handle_refund(accounts: &mut RefundAccountConstraints) -> Result<()> { .ok_or(FundraiserError::MathOverflow)?; accounts.contributor_account.amount = 0; + // Read these before any CPI handle below takes its borrow. `maker` is a + // read-only account here, so asking it for a writable handle would panic. + let maker_address = *accounts.maker.address(); + let mint_decimals = accounts.mint_to_raise.decimals(); + let fundraiser_bump = accounts.fundraiser.bump; + + // `fundraiser` signs the transfer. It is a data account holding a live + // borrow on its buffer, so release it across the CPI and take it back after. + accounts.fundraiser.release_borrow()?; + let fundraiser_view = *accounts.fundraiser.account(); + // Transfer the funds from the vault back to the contributor. The vault is // owned by the fundraiser PDA, so the CPI is signed with its seeds. let cpi_accounts = TransferChecked { from: accounts.vault.cpi_handle_mut(), mint: accounts.mint_to_raise.cpi_handle(), to: accounts.contributor_ata.cpi_handle_mut(), - authority: accounts.fundraiser.cpi_handle(), + authority: CpiHandle::readonly(&fundraiser_view), }; let signer_seeds: [&[&[u8]]; 1] = [&[ b"fundraiser".as_ref(), - accounts.maker.cpi_handle_mut().address().as_ref(), - &[accounts.fundraiser.bump], + maker_address.as_ref(), + &[fundraiser_bump], ]]; let cpi_context = CpiContext::new_with_signer( accounts.token_program.address(), cpi_accounts, &signer_seeds, ); - transfer_checked( - cpi_context, - refund_amount, - accounts.mint_to_raise.decimals(), - )?; + transfer_checked(cpi_context, refund_amount, mint_decimals)?; + + // Take the borrow back before the derive's exit path touches it again. + accounts.fundraiser.reacquire_borrow_mut()?; Ok(()) } diff --git a/finance/vault-strategy/anchor/programs/vault-strategy/src/instructions/withdraw.rs b/finance/vault-strategy/anchor/programs/vault-strategy/src/instructions/withdraw.rs index 702d21064..acd1f19e0 100644 --- a/finance/vault-strategy/anchor/programs/vault-strategy/src/instructions/withdraw.rs +++ b/finance/vault-strategy/anchor/programs/vault-strategy/src/instructions/withdraw.rs @@ -79,8 +79,8 @@ pub fn handle_withdraw( let usdc_decimals = context.accounts.usdc_mint.decimals(); let strategy_index = context.accounts.strategy.index; let strategy_bump = context.accounts.strategy.bump; - let strategy_key = context.accounts.strategy.address(); - let user_key = context.accounts.user.address(); + let strategy_key = *context.accounts.strategy.address(); + let user_key = *context.accounts.user.address(); let asset_count = context.accounts.strategy.asset_count as usize; require!( @@ -107,9 +107,12 @@ pub fn handle_withdraw( let index_bytes = strategy_index.to_le_bytes(); let signer_seeds: &[&[&[u8]]] = &[&[b"strategy", index_bytes.as_ref(), &[strategy_bump]]]; + // `remaining_accounts()` takes `&mut Context`, so collect it before the + // per-account views below borrow `context.accounts`. + let remaining = context.remaining_accounts()?; + // Hoist owned account-info handles for every CPI up front, so the asset loop - // can borrow remaining_accounts without also re-borrowing `context.accounts` - // (Account is invariant over its lifetime, which otherwise fails to unify). + // can borrow remaining_accounts without also re-borrowing `context.accounts`. // `strategy` signs the payouts below. It is a data account holding a live // borrow on its buffer, so release it across the CPIs and take it back // afterwards — the runtime rejects a CPI that borrows an account we hold. @@ -153,7 +156,6 @@ pub fn handle_withdraw( } // Each basket asset, paid in kind, proportional to shares burned. - let remaining = context.remaining_accounts()?; for i in 0..asset_count { let config_ai = &remaining[i * 4]; let mut vault_ai = remaining[i * 4 + 1]; @@ -163,7 +165,7 @@ pub fn handle_withdraw( let config = AssetConfig::load_checked(&config_ai)?; require_keys_eq!( config.strategy, - *strategy_key, + strategy_key, VaultError::InvalidAssetAccount ); require!(config.index as usize == i, VaultError::InvalidAssetAccount); @@ -179,7 +181,7 @@ pub fn handle_withdraw( ); let (recipient_mint, recipient_owner) = read_token_mint_and_owner(&user_ata_ai)?; - require_keys_eq!(recipient_owner, *user_key, VaultError::InvalidRecipient); + require_keys_eq!(recipient_owner, user_key, VaultError::InvalidRecipient); require_keys_eq!(recipient_mint, config.mint, VaultError::InvalidRecipient); let vault_balance = read_token_amount(&vault_ai)?; From 77bac771d3ac7cbce9a6514d46056b01b2bd6dea Mon Sep 17 00:00:00 2001 From: Claude Date: Fri, 14 Aug 2026 15:58:23 +0000 Subject: [PATCH 15/59] lending: restore a stripped lifetime and decode Reserve the v2 way Two fixes, one of them a regression of my own making. The codemod that dropped `<'info>` from handler signatures (v2's account model is static-scoped, so the lifetime is gone) also stripped `<'a>` from free functions that genuinely use it, `reserve_signer_seeds` borrows its arguments into the returned seed array. Restored. `refresh_obligation` loaded a reserve out of remaining_accounts with `Account::::try_from`. `Reserve` is borsh-backed in v2, so it now checks the discriminator explicitly and reads the payload with wincode under `BorshConfig`, the same shape vault-strategy's `AssetConfig::load_checked` uses. --- .../src/instructions/refresh_obligation.rs | 17 ++++++++++++++--- .../programs/lending/src/state/reserve.rs | 2 +- 2 files changed, 15 insertions(+), 4 deletions(-) diff --git a/finance/lending/anchor/programs/lending/src/instructions/refresh_obligation.rs b/finance/lending/anchor/programs/lending/src/instructions/refresh_obligation.rs index b723865ba..63f205924 100644 --- a/finance/lending/anchor/programs/lending/src/instructions/refresh_obligation.rs +++ b/finance/lending/anchor/programs/lending/src/instructions/refresh_obligation.rs @@ -124,11 +124,22 @@ where *cursor += 2; require_keys_eq!( - reserve_info.address(), + *reserve_info.address(), expected_reserve, LendingError::InvalidObligationAccount ); - let reserve = Account::::try_from(reserve_info)?; + let reserve = { + let data = reserve_info.try_borrow()?; + let disc_len = ::DISCRIMINATOR.len(); + require!( + data.len() > disc_len + && &data[..disc_len] == ::DISCRIMINATOR, + LendingError::InvalidObligationAccount + ); + let mut payload = &data[disc_len..]; + >::get(&mut payload) + .map_err(|_| error!(LendingError::InvalidObligationAccount))? + }; require_keys_eq!( reserve.lending_market, lending_market, @@ -137,7 +148,7 @@ where reserve.require_refreshed()?; require_keys_eq!( - price_info.address(), + *price_info.address(), reserve.price_feed, LendingError::InvalidObligationAccount ); diff --git a/finance/lending/anchor/programs/lending/src/state/reserve.rs b/finance/lending/anchor/programs/lending/src/state/reserve.rs index 73f2d58ad..867449597 100644 --- a/finance/lending/anchor/programs/lending/src/state/reserve.rs +++ b/finance/lending/anchor/programs/lending/src/state/reserve.rs @@ -6,7 +6,7 @@ use crate::math::{mul_div_ceil, mul_div_floor}; /// Signer seeds for a reserve PDA, which is the authority over its liquidity /// vault and the mint authority of its share token. -pub fn reserve_signer_seeds( +pub fn reserve_signer_seeds<'a>( lending_market: &'a Address, liquidity_mint: &'a Address, bump: &'a [u8; 1], From 3a53cdc4f93721c3c6c507ab2c8c9b435649fb89 Mon Sep 17 00:00:00 2001 From: Claude Date: Fri, 14 Aug 2026 16:00:48 +0000 Subject: [PATCH 16/59] ci: install Anchor 2.0.0-rc.1 rather than 1.1.2 A v2 CLI cannot build v1 programs and vice versa, so this pin can only move once every example has been ported, which is now the case for the source. The cache key moves with it so the old toolchain is not restored. 2.0.0-rc.1 is a pre-release, so avm needs it named explicitly rather than resolved as latest. The avm source also moves from coral-xyz/anchor to solana-foundation/anchor, matching the project's rename. --- .github/workflows/anchor.yml | 13 +++++++------ 1 file changed, 7 insertions(+), 6 deletions(-) diff --git a/.github/workflows/anchor.yml b/.github/workflows/anchor.yml index 829932cfd..24c1ab449 100644 --- a/.github/workflows/anchor.yml +++ b/.github/workflows/anchor.yml @@ -193,17 +193,18 @@ jobs: ~/.cargo/bin/avm ~/.avm # Pinned to match the anchor-lang/anchor-spl crate version the programs depend on. - key: anchor-toolchain-${{ runner.os }}-1.1.2 - - name: Install Anchor 1.1.2 + key: anchor-toolchain-${{ runner.os }}-2.0.0-rc.1 + - name: Install Anchor 2.0.0-rc.1 run: | export PATH="$HOME/.cargo/bin:$HOME/.avm/bin:$PATH" # `anchor --version` can emit more than one line; read the first line only so # a multi-line value never reaches downstream parsing. current="$(anchor --version 2>/dev/null | head -n1 | awk '{print $2}' || true)" - if [ "$current" != "1.1.2" ]; then - cargo install --git https://github.com/coral-xyz/anchor avm --force - avm install 1.1.2 - avm use 1.1.2 + if [ "$current" != "2.0.0-rc.1" ]; then + cargo install --git https://github.com/solana-foundation/anchor avm --force + # 2.0.0-rc.1 is a pre-release, so avm needs it named explicitly. + avm install 2.0.0-rc.1 + avm use 2.0.0-rc.1 fi echo "$HOME/.cargo/bin" >> "$GITHUB_PATH" echo "$HOME/.avm/bin" >> "$GITHUB_PATH" From a74f88fb0225c781c62d48cc7a827c4c86892e82 Mon Sep 17 00:00:00 2001 From: Claude Date: Fri, 14 Aug 2026 16:58:38 +0000 Subject: [PATCH 17/59] all: borsh derive feature, and replace the compat-only error! macro Two fixes the SBF sweep surfaced. The compression programs vendor Bubblegum types that derive `BorshSerialize`. borsh 1.x only exports its derive macros when the `derive` feature is named, so the three manifests ask for it explicitly. `error!` is only available under anchor-lang's `compat` feature, and my earlier edits had reintroduced it. The error enums convert into `Error` on their own, so the call sites use them directly, with an explicit `.into()` only where the `map_err` result is a tail expression, since `?` already performs the conversion. (The first pass added `.into()` everywhere, which made the target ambiguous at the `?` sites and broke allow-block-list-token; scoped back.) --- .../cnft-burn/anchor/programs/cnft-burn/Cargo.toml | 2 +- .../anchor/programs/cnft-vault/Cargo.toml | 2 +- .../cutils/anchor/programs/cutils/Cargo.toml | 2 +- .../lending/src/instructions/refresh_obligation.rs | 2 +- .../order-book/src/instructions/cancel_order.rs | 4 ++-- .../order-book/src/instructions/place_order.rs | 14 +++++++------- .../programs/order-book/src/state/order_book.rs | 8 ++++---- .../order-book/src/state/slab/ordertree.rs | 2 +- .../fundraiser/src/instructions/contribute.rs | 2 +- .../programs/vault-strategy/src/state/strategy.rs | 2 +- 10 files changed, 20 insertions(+), 20 deletions(-) diff --git a/compression/cnft-burn/anchor/programs/cnft-burn/Cargo.toml b/compression/cnft-burn/anchor/programs/cnft-burn/Cargo.toml index 0a296eccf..341a23b74 100644 --- a/compression/cnft-burn/anchor/programs/cnft-burn/Cargo.toml +++ b/compression/cnft-burn/anchor/programs/cnft-burn/Cargo.toml @@ -32,7 +32,7 @@ solana-address = ">=2.6, <2.7" # mpl-bubblegum and spl-account-compression removed: they depend on solana-program 2.x # which is incompatible with Anchor 1.0's solana 3.x types. CPI calls are built manually # using raw invoke() with hardcoded program IDs and discriminators. -borsh = "1" +borsh = { version = "1", features = ["derive"] } [dev-dependencies] litesvm = "0.13.1" diff --git a/compression/cnft-vault/anchor/programs/cnft-vault/Cargo.toml b/compression/cnft-vault/anchor/programs/cnft-vault/Cargo.toml index f100479e2..e4e3dbc44 100644 --- a/compression/cnft-vault/anchor/programs/cnft-vault/Cargo.toml +++ b/compression/cnft-vault/anchor/programs/cnft-vault/Cargo.toml @@ -32,7 +32,7 @@ solana-address = ">=2.6, <2.7" # mpl-bubblegum and spl-account-compression removed: they depend on solana-program 2.x # which is incompatible with Anchor 1.0's solana 3.x types. CPI calls are built manually # using raw invoke_signed() with hardcoded program IDs and discriminators. -borsh = "1" +borsh = { version = "1", features = ["derive"] } [lints.rust] unexpected_cfgs = { level = "warn", check-cfg = ['cfg(target_os, values("solana"))'] } diff --git a/compression/cutils/anchor/programs/cutils/Cargo.toml b/compression/cutils/anchor/programs/cutils/Cargo.toml index 64c049e52..5b9eaba69 100644 --- a/compression/cutils/anchor/programs/cutils/Cargo.toml +++ b/compression/cutils/anchor/programs/cutils/Cargo.toml @@ -33,7 +33,7 @@ solana-address = ">=2.6, <2.7" # which is incompatible with Anchor 1.0's solana 3.x types. CPI calls are built manually # using raw invoke() with hardcoded program IDs and discriminators. Bubblegum types # (MetadataArgs, LeafSchema, etc.) are re-implemented in bubblegum_types.rs. -borsh = "1" +borsh = { version = "1", features = ["derive"] } sha3 = "0.10" [lints.rust] diff --git a/finance/lending/anchor/programs/lending/src/instructions/refresh_obligation.rs b/finance/lending/anchor/programs/lending/src/instructions/refresh_obligation.rs index 63f205924..dae3c5458 100644 --- a/finance/lending/anchor/programs/lending/src/instructions/refresh_obligation.rs +++ b/finance/lending/anchor/programs/lending/src/instructions/refresh_obligation.rs @@ -138,7 +138,7 @@ where ); let mut payload = &data[disc_len..]; >::get(&mut payload) - .map_err(|_| error!(LendingError::InvalidObligationAccount))? + .map_err(|_| LendingError::InvalidObligationAccount)? }; require_keys_eq!( reserve.lending_market, diff --git a/finance/order-book/anchor/programs/order-book/src/instructions/cancel_order.rs b/finance/order-book/anchor/programs/order-book/src/instructions/cancel_order.rs index 8413f3fb0..89543438a 100644 --- a/finance/order-book/anchor/programs/order-book/src/instructions/cancel_order.rs +++ b/finance/order-book/anchor/programs/order-book/src/instructions/cancel_order.rs @@ -35,7 +35,7 @@ pub fn handle_cancel_order(context: &mut Context) .checked_mul(context.accounts.market.quote_lot_size as u128) .ok_or(ErrorCode::NumericalOverflow)? .try_into() - .map_err(|_| error!(ErrorCode::NumericalOverflow))?; + .map_err(|_| ErrorCode::NumericalOverflow)?; market_user.unsettled_quote = market_user .unsettled_quote .checked_add(quote_amount) @@ -46,7 +46,7 @@ pub fn handle_cancel_order(context: &mut Context) .checked_mul(context.accounts.market.base_lot_size as u128) .ok_or(ErrorCode::NumericalOverflow)? .try_into() - .map_err(|_| error!(ErrorCode::NumericalOverflow))?; + .map_err(|_| ErrorCode::NumericalOverflow)?; market_user.unsettled_base = market_user .unsettled_base .checked_add(base_amount) diff --git a/finance/order-book/anchor/programs/order-book/src/instructions/place_order.rs b/finance/order-book/anchor/programs/order-book/src/instructions/place_order.rs index 1a1508b3c..ae85349c6 100644 --- a/finance/order-book/anchor/programs/order-book/src/instructions/place_order.rs +++ b/finance/order-book/anchor/programs/order-book/src/instructions/place_order.rs @@ -73,7 +73,7 @@ pub fn handle_place_order( .checked_mul(market.quote_lot_size as u128) .ok_or(ErrorCode::NumericalOverflow)? .try_into() - .map_err(|_| error!(ErrorCode::NumericalOverflow))?, + .map_err(|_| ErrorCode::NumericalOverflow)?, context.accounts.quote_vault.cpi_handle_mut(), ), OrderSide::Ask => ( @@ -84,7 +84,7 @@ pub fn handle_place_order( .checked_mul(market.base_lot_size as u128) .ok_or(ErrorCode::NumericalOverflow)? .try_into() - .map_err(|_| error!(ErrorCode::NumericalOverflow))?, + .map_err(|_| ErrorCode::NumericalOverflow)?, context.accounts.base_vault.cpi_handle_mut(), ), }; @@ -202,7 +202,7 @@ pub fn handle_place_order( .checked_mul(market.quote_lot_size as u128) .ok_or(ErrorCode::NumericalOverflow)? .try_into() - .map_err(|_| error!(ErrorCode::NumericalOverflow))?; + .map_err(|_| ErrorCode::NumericalOverflow)?; // Ceiling division: round the fee in the protocol's favour. Flooring // would leak up to 1 minor unit of quote per fill to the maker, which @@ -215,7 +215,7 @@ pub fn handle_place_order( .checked_div(BASIS_POINTS_DENOMINATOR) .ok_or(ErrorCode::NumericalOverflow)? .try_into() - .map_err(|_| error!(ErrorCode::NumericalOverflow))?; + .map_err(|_| ErrorCode::NumericalOverflow)?; // Defensive invariant: fees are a fraction of gross, never more. // `fee_basis_points <= 10_000` is enforced at market init, so this @@ -238,7 +238,7 @@ pub fn handle_place_order( .checked_mul(market.base_lot_size as u128) .ok_or(ErrorCode::NumericalOverflow)? .try_into() - .map_err(|_| error!(ErrorCode::NumericalOverflow))?; + .map_err(|_| ErrorCode::NumericalOverflow)?; taker_base_received = taker_base_received .checked_add(base_from_fill) .ok_or(ErrorCode::NumericalOverflow)?; @@ -254,7 +254,7 @@ pub fn handle_place_order( .checked_mul(market.quote_lot_size as u128) .ok_or(ErrorCode::NumericalOverflow)? .try_into() - .map_err(|_| error!(ErrorCode::NumericalOverflow))?; + .map_err(|_| ErrorCode::NumericalOverflow)?; let rebate: u64 = locked_for_this_fill .checked_sub(gross_quote) .ok_or(ErrorCode::NumericalOverflow)?; @@ -268,7 +268,7 @@ pub fn handle_place_order( .checked_mul(market.base_lot_size as u128) .ok_or(ErrorCode::NumericalOverflow)? .try_into() - .map_err(|_| error!(ErrorCode::NumericalOverflow))?; + .map_err(|_| ErrorCode::NumericalOverflow)?; maker_market_user.unsettled_base = maker_market_user .unsettled_base .checked_add(base_from_fill) diff --git a/finance/order-book/anchor/programs/order-book/src/state/order_book.rs b/finance/order-book/anchor/programs/order-book/src/state/order_book.rs index bd91a5428..f6722675a 100644 --- a/finance/order-book/anchor/programs/order-book/src/state/order_book.rs +++ b/finance/order-book/anchor/programs/order-book/src/state/order_book.rs @@ -102,7 +102,7 @@ impl OrderBook { self.next_order_id = self .next_order_id .checked_add(1) - .ok_or_else(|| error!(ErrorCode::NumericalOverflow))?; + .ok_or_else(|| ErrorCode::NumericalOverflow)?; Ok(id) } @@ -221,11 +221,11 @@ impl OrderBook { let (handle, remaining_after) = { let (handle, leaf) = nodes .find_by_key(root, key) - .ok_or_else(|| error!(ErrorCode::OrderNotFound))?; + .ok_or_else(|| ErrorCode::OrderNotFound)?; let remaining = leaf .quantity .checked_sub(fill_quantity) - .ok_or_else(|| error!(ErrorCode::NumericalOverflow))?; + .ok_or_else(|| ErrorCode::NumericalOverflow)?; (handle, remaining) }; if remaining_after == 0 { @@ -237,7 +237,7 @@ impl OrderBook { let leaf = nodes .node_mut(handle) .and_then(AnyNode::as_leaf_mut) - .ok_or_else(|| error!(ErrorCode::OrderNotFound))?; + .ok_or_else(|| ErrorCode::OrderNotFound)?; leaf.quantity = remaining_after; } Ok(()) diff --git a/finance/order-book/anchor/programs/order-book/src/state/slab/ordertree.rs b/finance/order-book/anchor/programs/order-book/src/state/slab/ordertree.rs index 20b8e1be1..94957dee8 100644 --- a/finance/order-book/anchor/programs/order-book/src/state/slab/ordertree.rs +++ b/finance/order-book/anchor/programs/order-book/src/state/slab/ordertree.rs @@ -281,7 +281,7 @@ impl OrderTreeNodes { loop { let parent_contents = *self .node(parent_handle) - .ok_or_else(|| error!(ErrorCode::OrderBookFull))?; + .ok_or_else(|| ErrorCode::OrderBookFull)?; let parent_key = parent_contents.address().unwrap(); // Exact-key collision: only possible if the existing slot is a diff --git a/finance/token-fundraiser/anchor/programs/fundraiser/src/instructions/contribute.rs b/finance/token-fundraiser/anchor/programs/fundraiser/src/instructions/contribute.rs index 07ed4d623..09dedd209 100644 --- a/finance/token-fundraiser/anchor/programs/fundraiser/src/instructions/contribute.rs +++ b/finance/token-fundraiser/anchor/programs/fundraiser/src/instructions/contribute.rs @@ -62,7 +62,7 @@ fn calculate_max_contribution(amount_to_raise: u64) -> Result { .checked_div(PERCENTAGE_SCALER as u128) .ok_or(FundraiserError::MathOverflow)? .try_into() - .map_err(|_| error!(FundraiserError::MathOverflow)) + .map_err(|_| FundraiserError::MathOverflow.into()) } pub fn handle_contribute( diff --git a/finance/vault-strategy/anchor/programs/vault-strategy/src/state/strategy.rs b/finance/vault-strategy/anchor/programs/vault-strategy/src/state/strategy.rs index 1d4d6db7c..bda2b8267 100644 --- a/finance/vault-strategy/anchor/programs/vault-strategy/src/state/strategy.rs +++ b/finance/vault-strategy/anchor/programs/vault-strategy/src/state/strategy.rs @@ -84,6 +84,6 @@ impl AssetConfig { ); let mut payload = &data[disc_len..]; >::get(&mut payload) - .map_err(|_| error!(crate::error::VaultError::InvalidAssetAccount)) + .map_err(|_| crate::error::VaultError::InvalidAssetAccount.into()) } } From 8acdeddcda28e77310eb608b3393dc3201db5b3f Mon Sep 17 00:00:00 2001 From: Claude Date: Fri, 14 Aug 2026 17:03:11 +0000 Subject: [PATCH 18/59] order-book: v2 zero-copy mappings (65 -> 30 errors) Three renames v2 made to the zero-copy surface, applied repo-wide: - The `zero` account constraint is `zeroed`. - `#[account(zero_copy(unsafe))]` is just `#[account]`, v2's default account backing is already zero-copy, and the macro rejects any other argument. - v1's `AccountLoader<'info, T>` is gone. v2 has an `AccountLoader` but it is an internal account cursor, unrelated; the zero-copy account type is `Account`, which derefs straight to `T`, so `load()` / `load_mut()` become plain borrows. Also generalised the borsh decode helper that lending needed: v2 has no `Account::try_from(&AccountView)`, so loading a borsh-backed account out of remaining_accounts checks the discriminator and reads the payload with wincode under `BorshConfig`. order-book is not finished, 30 errors remain, mostly around `load_init` and state types my blanket borsh conversion moved off Pod that this program wants zero-copy. --- .../src/instructions/refresh_obligation.rs | 13 ++++++++++- .../src/instructions/cancel_order.rs | 4 ++-- .../src/instructions/initialize_market.rs | 6 ++--- .../src/instructions/place_order.rs | 23 ++++++++++++++----- .../order-book/src/state/order_book.rs | 4 ++-- 5 files changed, 36 insertions(+), 14 deletions(-) diff --git a/finance/lending/anchor/programs/lending/src/instructions/refresh_obligation.rs b/finance/lending/anchor/programs/lending/src/instructions/refresh_obligation.rs index dae3c5458..bf574aa59 100644 --- a/finance/lending/anchor/programs/lending/src/instructions/refresh_obligation.rs +++ b/finance/lending/anchor/programs/lending/src/instructions/refresh_obligation.rs @@ -152,7 +152,18 @@ where reserve.price_feed, LendingError::InvalidObligationAccount ); - let price_feed = Account::::try_from(price_info)?; + let price_feed = { + let data = price_info.try_borrow()?; + let disc_len = ::DISCRIMINATOR.len(); + require!( + data.len() > disc_len + && &data[..disc_len] == ::DISCRIMINATOR, + ErrorCode::MakerAccountMismatch + ); + let mut payload = &data[disc_len..]; + >::get(&mut payload) + .map_err(|_| ErrorCode::MakerAccountMismatch)? + }; let price_scaled = price_feed.price_scaled(slot)?; Ok((reserve.into_inner(), price_scaled)) diff --git a/finance/order-book/anchor/programs/order-book/src/instructions/cancel_order.rs b/finance/order-book/anchor/programs/order-book/src/instructions/cancel_order.rs index 89543438a..4e3a210c9 100644 --- a/finance/order-book/anchor/programs/order-book/src/instructions/cancel_order.rs +++ b/finance/order-book/anchor/programs/order-book/src/instructions/cancel_order.rs @@ -58,7 +58,7 @@ pub fn handle_cancel_order(context: &mut Context) // Remove the leaf from the slab. The current cancel API doesn't tell us // which side the order is on without reading the Order PDA - which we // already have, so use it. - let mut order_book = context.accounts.order_book.load_mut()?; + let mut order_book = (&mut *context.accounts.order_book); let removed = order_book.remove_from(order.side, order.order_id).is_some(); require!(removed, ErrorCode::OrderNotFound); drop(order_book); @@ -78,7 +78,7 @@ pub struct CancelOrderAccountConstraints { // Not a PDA (see initialize_market.rs); bound to `market` via has_one. #[account(mut)] - pub order_book: AccountLoader, + pub order_book: Account, #[account( mut, diff --git a/finance/order-book/anchor/programs/order-book/src/instructions/initialize_market.rs b/finance/order-book/anchor/programs/order-book/src/instructions/initialize_market.rs index 1cfcc1b53..a1ae52b0d 100644 --- a/finance/order-book/anchor/programs/order-book/src/instructions/initialize_market.rs +++ b/finance/order-book/anchor/programs/order-book/src/instructions/initialize_market.rs @@ -70,7 +70,7 @@ pub struct InitializeMarketAccountConstraints { // instruction, sizing the account to ORDER_BOOK_ACCOUNT_SIZE, owned by // this program, and zero-initialized. // - // `#[account(zero)]` verifies the account is owned by this program + // `#[account(zeroed)]` verifies the account is owned by this program // and has its discriminator unset, which is exactly what a freshly // create_account-d account looks like. The handler then stamps the // discriminator + struct via `load_init()`. @@ -80,8 +80,8 @@ pub struct InitializeMarketAccountConstraints { // client must generate a real keypair for it. The program ties this // account to its market via `has_one = order_book` on `market`, not via // seeds. - #[account(zero)] - pub order_book: AccountLoader, + #[account(zeroed)] + pub order_book: Account, pub base_mint: InterfaceAccount, diff --git a/finance/order-book/anchor/programs/order-book/src/instructions/place_order.rs b/finance/order-book/anchor/programs/order-book/src/instructions/place_order.rs index ae85349c6..482299063 100644 --- a/finance/order-book/anchor/programs/order-book/src/instructions/place_order.rs +++ b/finance/order-book/anchor/programs/order-book/src/instructions/place_order.rs @@ -124,7 +124,7 @@ pub fn handle_place_order( // RefCell-based runtime borrow, so the loaded ref must not outlive the // plan we copy out of it. let (fills, taker_remaining) = { - let order_book = order_book_loader.load()?; + let order_book = (&*order_book_loader); plan_fills(&order_book, side, price, quantity) }; @@ -139,7 +139,18 @@ pub fn handle_place_order( // market. for (fill_index, fill) in fills.iter().enumerate() { let maker_order_info = &maker_accounts[fill_index * ACCOUNTS_PER_MAKER]; - let maker_order = Account::::try_from(maker_order_info)?; + let maker_order = { + let data = maker_order_info.try_borrow()?; + let disc_len = ::DISCRIMINATOR.len(); + require!( + data.len() > disc_len + && &data[..disc_len] == ::DISCRIMINATOR, + ErrorCode::MakerAccountMismatch + ); + let mut payload = &data[disc_len..]; + >::get(&mut payload) + .map_err(|_| ErrorCode::MakerAccountMismatch)? + }; require!( maker_order.order_id == fill.maker_order_id, ErrorCode::MakerAccountMismatch @@ -317,7 +328,7 @@ pub fn handle_place_order( }; { - let mut order_book = order_book_loader.load_mut()?; + let mut order_book = (&mut *order_book_loader); for fill in &fills { order_book.apply_fill_to_maker( maker_side, @@ -379,7 +390,7 @@ pub fn handle_place_order( // --------------------------------------------------------------- let timestamp = Clock::get()?.unix_timestamp; let order_id = { - let mut order_book = order_book_loader.load_mut()?; + let mut order_book = (&mut *order_book_loader); let id = order_book.allocate_order_id()?; if taker_remaining > 0 { require!(!order_book.is_side_full(side), ErrorCode::OrderBookFull); @@ -453,7 +464,7 @@ pub struct PlaceOrderAccountConstraints { // `has_one = order_book` on `market` is what ties this specific account // to this specific market. #[account(mut)] - pub order_book: AccountLoader, + pub order_book: Account, // The order PDA seed uses the book's `next_order_id` *before* this // instruction increments it - i.e. the id this new order will receive. @@ -465,7 +476,7 @@ pub struct PlaceOrderAccountConstraints { seeds = [ ORDER_SEED, market.address().as_ref(), - order_book.load()?.next_order_id.to_le_bytes() + (&*order_book).next_order_id.to_le_bytes() ], bump )] diff --git a/finance/order-book/anchor/programs/order-book/src/state/order_book.rs b/finance/order-book/anchor/programs/order-book/src/state/order_book.rs index f6722675a..b1cade650 100644 --- a/finance/order-book/anchor/programs/order-book/src/state/order_book.rs +++ b/finance/order-book/anchor/programs/order-book/src/state/order_book.rs @@ -19,11 +19,11 @@ pub const MAX_ORDERS_PER_SIDE: usize = MAX_TREE_NODES; /// counter that gives every order a unique tie-break and acts as the public /// `order_id`. /// -/// Stored as one `AccountLoader` (zero-copy). The account is far +/// Stored as one `Account` (zero-copy). The account is far /// larger than Anchor's borsh `Account` would happily deserialize on every /// instruction - zero-copy gives us per-field memory access without paying /// the (de)serialization cost. -#[account(zero_copy(unsafe))] +#[account] #[repr(C)] pub struct OrderBook { /// Market PDA this book belongs to. Constrained onchain via the From 1a3b023717c97216bbbad43722486538832563f1 Mon Sep 17 00:00:00 2001 From: Claude Date: Fri, 14 Aug 2026 17:07:14 +0000 Subject: [PATCH 19/59] docs: write down the Anchor v1 -> v2 porting rules Everything learned porting the examples, in one place, so the next person (or the next session) does not rediscover it from compiler errors. The section that matters most is "Borrows across CPIs", it is the only rule in the document that the compiler will not catch. Copying an `AccountView` to get past v2's compile-time aliasing check is correct for `Signer` and `UncheckedAccount`, and silently wrong for data accounts, which hold a live borrow the runtime rejects with `AccountBorrowFailed`. That cost several programs a build-clean-then-fail-tests cycle each, along with its three corollaries: dereferencing after release panics (including the derive's own use after the handler returns), release and reacquire must sit on the same branch, and a read-only account cannot be reacquired at all. The rest is a rename table, the account-backing rules (default to `#[account(borsh)]`, it reproduces v1's on-chain layout byte for byte, and only keep zero-copy where a program deliberately wants it), the anchor-spl changes including the absent `extensions::*` constraints, and the handful of test-side imports that move. --- docs/anchor-v2-migration.md | 220 ++++++++++++++++++++++++++++++++++++ 1 file changed, 220 insertions(+) create mode 100644 docs/anchor-v2-migration.md diff --git a/docs/anchor-v2-migration.md b/docs/anchor-v2-migration.md new file mode 100644 index 000000000..97e951dbd --- /dev/null +++ b/docs/anchor-v2-migration.md @@ -0,0 +1,220 @@ +# Porting an Anchor program from v1 to v2.0.0-rc.1 + +Anchor v2 is a ground-up rewrite, not a version bump. The crate is `no_std` and +built on pinocchio, the account model is static-scoped, borsh is replaced by +wincode, and accounts are zero-copy by default. This is the list of every +difference that came up porting the examples in this repository, in rough order +of how often it bites. + +The single most important one is **[borrows across CPIs](#borrows-across-cpis)**: +it is the only rule here that the compiler will not catch for you. + +## Manifest + +| v1 | v2 | +|---|---| +| `anchor-lang = "1.1.2"` | `anchor-lang = "2.0.0-rc.1"` | +| `anchor-spl = "1.1.2"` | `anchor-spl = "2.0.0-rc.1"` | +| — | `wincode = { version = "0.5", features = ["derive"] }` | +| `features = ["init-if-needed"]` | no such feature; the constraint is always available | +| `idl-build = [... "anchor-spl/idl-build"]` | anchor-spl has no `idl-build`; drop it | +| `anchor-spl` features `metadata`, `spl-token-interface` | only `guardrails` (default) and `metadata` exist | + +Every program crate needs `wincode` as a **direct** dependency: the `#[program]` +macro expands to `wincode` paths for instruction-data (de)serialization. + +Programs that put an `Address` in serialized state also need: + +```toml +# anchor-lang 2.0.0-rc.1 is built against wincode 0.5, but solana-address 2.7 +# moved to wincode 0.6. With both in the graph, `Address`'s wincode impls belong +# to the version the account derives are not using, and every `SchemaRead` / +# `SchemaWrite` bound fails. 2.6.1 is the last release on the 0.5 line. +solana-address = ">=2.6, <2.7" +``` + +Note the range: a bare `<2.7` lets cargo satisfy the requirement by reusing an +older 1.x already in the graph, which does not fix anything. + +## Signatures and types + +| v1 | v2 | +|---|---| +| `Context` | `&mut Context` | +| `Context<'info, T<'info>>` | `Context` — no user lifetime | +| `Pubkey` | `Address` | +| `AccountInfo<'info>` | `AccountView` (or `UncheckedAccount` in an `Accounts` struct) | +| `Signer<'info>`, `Program<'info, T>`, … | `Signer`, `Program` — the `'info` lifetime is gone | +| `Interface<'info, TokenInterface>` | `Interface<'static, TokenInterface>` — the one wrapper that keeps a lifetime | +| `.key()` / `.key` | `.address()`, which returns `&Address` | +| `.to_account_info()` | `.cpi_handle()` / `.cpi_handle_mut()` | +| `ctx.remaining_accounts` (field) | `ctx.remaining_accounts()?` — fallible, and takes `&mut self` | +| `account.owner` | `account.owner()` | +| `Rent::minimum_balance` | `Rent::try_minimum_balance` | +| `#[instruction(discriminator = X)]` | `#[discrim = X]`, and it takes a literal | +| `#[account(zero)]` | `#[account(zeroed)]` | +| `#[account(zero_copy(unsafe))]` | `#[account]` — already zero-copy | +| `AccountLoader<'info, T>` + `load()` | `Account`, which derefs straight to `T` | +| `set_inner(X { .. })` | `*ctx.accounts.foo = X { .. }` | +| `error!(MyError::X)` | `MyError::X` (compat-only macro; `?` converts, tail position needs `.into()`) | + +Dropping the `'info` lifetime from handler signatures is mechanical, but do not +strip `<'a>` from free functions that genuinely use it — a helper returning +`[&'a [u8]; 4]` still needs its parameter. + +## Account backing + +v2's `#[account]` is **zero-copy** and requires a `Pod` layout: no implicit +padding, no `bool` (use `PodBool`), no `String` or `Vec`. + +- State holding `String` / `Vec` / enums → `#[account(borsh)]` + `BorshAccount`. +- Fixed-layout state can stay zero-copy but must carry **explicit padding** + (`u32` + `u8` leaves three bytes — name them). +- `#[derive(InitSpace)]` and `#[max_len(N)]` work as before on borsh accounts; + Pod accounts should size with `core::mem::size_of`. + +**Default to `#[account(borsh)]` when porting.** v1 accounts were borsh-encoded, +so it reproduces the on-chain layout byte-for-byte and existing clients and +tests keep working. Only keep zero-copy where the program deliberately wants it +(a large slab, say) — converting such a program to borsh defeats its purpose. + +v2 has no `Account::try_from(&AccountView)`. To load a borsh account out of +`remaining_accounts`, check the discriminator and read the payload yourself: + +```rust +let data = account.try_borrow()?; +let disc_len = ::DISCRIMINATOR.len(); +require!( + data.len() > disc_len + && &data[..disc_len] == ::DISCRIMINATOR, + MyError::BadAccount +); +let mut payload = &data[disc_len..]; +>::get(&mut payload) + .map_err(|_| MyError::BadAccount)? +``` + +`Owner` is a const in v2 (`const OWNER: Address`), which makes a foreign-owned +account — a vendored Pyth or Bubblegum type, say — straightforward to declare. + +## Borrows across CPIs + +**This is the rule tests catch and the compiler does not.** + +v2's typed CPI handles make aliasing a compile error: passing one account into +both a writable and a read-only CPI slot will not build. The obvious workaround +— copying the `AccountView`, which is `Copy` — satisfies the compiler, and is +correct **only for accounts that hold no data borrow** (`Signer`, +`UncheckedAccount`, `Program`): + +```rust +let payer_view = *ctx.accounts.payer.account(); +// ... CpiHandle::readonly(&payer_view) in the read-only slots +``` + +For a **data** account (`Account`, `BorshAccount`, `InterfaceAccount`) that +copy is not enough. The account holds a live borrow on its buffer, and the +runtime rejects the CPI's own borrow with `AccountBorrowFailed`. Release it +across the call instead: + +```rust +ctx.accounts.offer.release_borrow()?; +let offer_view = *ctx.accounts.offer.account(); +// ... CPI signed by `offer` ... +ctx.accounts.offer.reacquire_borrow_mut()?; +``` + +`reacquire_borrow_mut` re-runs the load-time owner and discriminator checks, +because a CPI in the release window could have mutated either. + +Three ways this goes wrong: + +1. **Dereferencing after release panics** (`account borrow released (closed)`). + That includes the derive's own use of the account after the handler returns — + `associated_token::authority = event`, `has_one = event` and friends all + deref it. So the reacquire has to happen before the handler ends. +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. +3. **A read-only account cannot be reacquired** — there is no read-only + reacquire. If the derive references the account after the handler, it has to + be declared `mut`. + +Also: asking a read-only account for a writable handle panics, so read +`*account.address()` rather than `account.cpi_handle_mut().address()`. + +## anchor-spl + +`Mint` moved from `anchor_spl::token` to `anchor_spl::mint`, and the namespaced +constraints (`mint::decimals`, `token::authority`, …) expand to paths rooted at +those modules, so `mint` / `token` must be nameable in the file that uses them. + +SPL account fields are behind accessors now that the structs are Pod: +`.amount()`, `.decimals()`, `.supply()`, `.mint()`, `.owner()`. + +Init constraints resolve a **sibling account field**, not a pubkey expression or +a field read off another account: `mint::authority = payer`, not +`mint::authority = payer.address()`; `associated_token::mint = mint_to_raise`, +not `= fundraiser.mint_to_raise`. + +CPI structs dropped their `token_program_id` / `program_id` slots — the program +comes from the `CpiContext`. `create_metadata_accounts_v3` takes four arguments +(the signer flag moved into the accounts struct, and the optional `rent` account +is gone). + +There are **no `extensions::*` constraints**, for init or validation. To create a +mint with a Token-2022 extension, do it by hand — which is what this repo's +`non-transferable` example always did: + +```rust +let mint_size = ExtensionType::try_calculate_account_len::(&[ + ExtensionType::MintCloseAuthority, +])?; +// create_account -> _initialize -> initialize_mint2, in that order +``` + +Extension initialization must come before `InitializeMint2`. + +## Seeds, sysvars, cross-program types + +`seeds` takes a byte array directly and binds it itself — `id.to_le_bytes()`, +not `id.to_le_bytes().as_ref()`, which produces a temporary that dies before the +derive uses it. + +pinocchio ships only the `Clock` and `Rent` sysvars. Anything else — this repo +needs `LastRestartSlot` — has to be declared locally and read through the +`sol_get_sysvar` syscall (`solana-define-syscall`). The Quasar variants of these +same examples carry the identical workaround. + +A sibling program's account is an `UncheckedAccount` validated by a constraint: +v2 only generates a `program` marker module from `declare_program!`, not from +`#[program]` in a dependency crate. + +Ambiguity to watch for: the prelude exports an `Event` trait, so a state struct +named `Event` that reaches the crate root via glob re-export becomes ambiguous. +Import it explicitly (`use crate::state::Event;`). + +## Tests + +The test-side surface barely changed, but three things move: + +- `anchor_lang::solana_program` has no `system_program` submodule. The real + module is at the crate root and exposes `ID`, not `id()`. +- `solana_program::pubkey::Pubkey` only exists under the `compat` feature; + `anchor_lang::Address` is the same 32-byte type. +- `anchor_lang::prelude::Clock` is pinocchio's on-chain type. LiteSVM's + `get_sysvar` / `set_sysvar` want the host-side `solana_clock::Clock`. + +Tests that decode account bytes with borsh keep working, because +`BorshConfig` makes wincode's wire format byte-identical — but a Pod account +that grew explicit padding needs that padding mirrored in the test's decode +struct, since `try_from_slice` rejects trailing bytes. + +## Toolchain + +CI installs the CLI with `avm install 2.0.0-rc.1`; it is a pre-release, so it +has to be named explicitly rather than resolved as latest. A v2 CLI cannot build +v1 programs, so that pin can only move once every example is ported. + +`anchor idl build` compiles the test targets too, so the `.so` has to exist +first — run `cargo-build-sbf` before regenerating an IDL. From 6a03e1fccd241b625a48b79380c9e2ce16fcb336 Mon Sep 17 00:00:00 2001 From: Claude Date: Fri, 14 Aug 2026 17:14:45 +0000 Subject: [PATCH 20/59] docs: has_one deprecation and the v2-only primitives From the v2 release notes, which the published changelog does not carry, its last entry is 1.0.0, and the tagged CHANGELOG.md lists a single CLI change under 2.0.0-rc.1. The consequential one: `has_one` is deprecated in favour of the more general `address` constraint, and the check moves off the owning account onto the sibling it names. It is only a deprecation, but `rust.yml` runs `cargo clippy -- -D warnings`, so every remaining use fails CI, escrow alone trips five. 269 uses across the repository. Also recorded: the v2-only primitives a mechanical port never produces but which are usually the right answer when a translation fights Pod, `Slab`, `PodVec`, the alignment-1 Pod integer wrappers, `#[pod_wrapper]` and `Nested`, plus the `guardrails` / `const-rent` / `compat` feature flags and the new debugger and profiling tooling. --- docs/anchor-v2-migration.md | 49 +++++++++++++++++++++++++++++++++++++ 1 file changed, 49 insertions(+) diff --git a/docs/anchor-v2-migration.md b/docs/anchor-v2-migration.md index 97e951dbd..a4890809f 100644 --- a/docs/anchor-v2-migration.md +++ b/docs/anchor-v2-migration.md @@ -57,6 +57,21 @@ older 1.x already in the graph, which does not fix anything. | `AccountLoader<'info, T>` + `load()` | `Account`, which derefs straight to `T` | | `set_inner(X { .. })` | `*ctx.accounts.foo = X { .. }` | | `error!(MyError::X)` | `MyError::X` (compat-only macro; `?` converts, tail position needs `.into()`) | +| `#[account(has_one = x)]` on the owner | `#[account(address = owner.x)]` on the **sibling** field | + +`has_one` is deprecated, not removed — but this repository's `rust.yml` runs +`cargo clippy -- -D warnings`, so every remaining use is a **hard CI failure**. +The check moves off the owning account and onto the sibling it names: + +```rust +// v1: on `offer` +#[account(mut, close = maker, has_one = maker)] +pub offer: BorshAccount, + +// v2: the constraint lives on `maker` +#[account(mut, address = offer.maker)] +pub maker: SystemAccount, +``` Dropping the `'info` lifetime from handler signatures is mechanical, but do not strip `<'a>` from free functions that genuinely use it — a helper returning @@ -175,6 +190,40 @@ let mint_size = ExtensionType::try_calculate_account_len::(&[ Extension initialization must come before `InitializeMint2`. +## v2-only primitives worth reaching for + +These have no v1 equivalent, so a mechanical port never produces them — but they +are often the right answer when a straight translation gets ugly: + +| Primitive | Use | +|---|---| +| `Slab` | Zero-copy header + dynamic item tail (ledgers, order books, event logs). `Account` is `Slab` underneath. | +| `PodVec` | Fixed-capacity vec with a `u16` length, stored inline — variable-length data without leaving Pod. | +| `PodU64`, `PodI128`, `PodBool`, … | Alignment-1 integer wrappers, so a `#[repr(C)]` struct packs with no padding. | +| `#[pod_wrapper]` | Safe enum-to-Pod conversion that validates the discriminant on equality and conversion. | +| `Nested` | Share one `#[derive(Accounts)]` validation block across instructions. | +| `#[event(bytemuck)]` | Fixed-size events: disc + one memcpy. Plain `#[event]` defaults to wincode (borsh-wire-compatible). | + +If a program was zero-copy in v1 and the port is fighting Pod's rules, the +answer is usually one of the first four rather than moving it to borsh. + +### Feature flags + +- `guardrails` (default on) — runtime safety nets. Dropping it saves ~300 bytes + and 1–2 CU per account, at the cost of diagnostic panics on misuse. +- `const-rent` (default off) — folds `Rent::get()` to a compile-time constant, + ~85 CU per `create_account`. Burns the rent formula into the binary, so a + formula change (SIMD-0194) needs a rebuild. +- `compat` (default off) — restores v1-shaped `error!`, `err!`, `pubkey!`, + `debug!`. Useful mid-port; `debug!` heap-allocates through `alloc::format!`. + +### Tooling + +- `anchor debugger` — TUI stepping through SBF instructions per test. +- `anchor test --profile` — per-test register-trace flamegraphs under + `target/anchor-v2-profile/`. +- `anchor-v2-testing` — wraps LiteSVM with optional register-trace capture. + ## Seeds, sysvars, cross-program types `seeds` takes a byte array directly and binds it itself — `id.to_le_bytes()`, From 832fb2dd6b8dcd1583acb164a1b95e320eda1884 Mon Sep 17 00:00:00 2001 From: Claude Date: Fri, 14 Aug 2026 18:55:25 +0000 Subject: [PATCH 21/59] tests: repair the Pubkey->Address rename inside import paths The repo-wide `Pubkey` -> `Address` rename also rewrote the *path* segment, so `solana_program::pubkey::Pubkey` became `solana_program::pubkey::Address`, a module that does not exist outside the `compat` feature. Twelve test files were importing it and failing to build even though their programs compiled. `Address` lives at the anchor_lang crate root in v2, so the import moves there. Also removed the duplicate `Clock`: the earlier pass added `solana_clock::Clock` alongside the compat `solana_program::clock::Clock` that was already there. --- .../anchor/programs/hand/tests/test_hand.rs | 2 +- .../pyth/anchor/programs/pythexample/tests/test_pyth.rs | 5 ++--- .../lending/anchor/programs/lending/tests/common/mod.rs | 7 ++----- .../anchor/programs/order-book/tests/test_order_book.rs | 1 - .../anchor/programs/fundraiser/tests/test_fundraiser.rs | 3 +-- .../programs/vault-strategy/tests/vault_strategy.rs | 5 ++--- .../anchor/programs/cpi-guard/tests/test_cpi_guard.rs | 8 ++------ .../tests/test_default_account_state.rs | 8 ++------ .../immutable-owner/tests/test_immutable_owner.rs | 8 ++------ .../programs/memo-transfer/tests/test_memo_transfer.rs | 8 ++------ .../programs/extension_nft/tests/test_extension_nft.rs | 6 +++--- .../permanent-delegate/tests/test_permanent_delegate.rs | 8 ++------ .../programs/transfer-fee/tests/test_transfer_fee.rs | 8 ++------ 13 files changed, 23 insertions(+), 54 deletions(-) diff --git a/basics/cross-program-invocation/anchor/programs/hand/tests/test_hand.rs b/basics/cross-program-invocation/anchor/programs/hand/tests/test_hand.rs index 5fe9cb0fb..9f79107c1 100644 --- a/basics/cross-program-invocation/anchor/programs/hand/tests/test_hand.rs +++ b/basics/cross-program-invocation/anchor/programs/hand/tests/test_hand.rs @@ -1,7 +1,7 @@ use { anchor_lang::{ solana_program::instruction::{AccountMeta, Instruction}, - system_program, InstructionData, ToAccountMetas, + Address, system_program, InstructionData, ToAccountMetas, }, litesvm::LiteSVM, solana_keypair::Keypair, diff --git a/basics/pyth/anchor/programs/pythexample/tests/test_pyth.rs b/basics/pyth/anchor/programs/pythexample/tests/test_pyth.rs index 92ee7e566..be6bd0d23 100644 --- a/basics/pyth/anchor/programs/pythexample/tests/test_pyth.rs +++ b/basics/pyth/anchor/programs/pythexample/tests/test_pyth.rs @@ -1,11 +1,10 @@ use { anchor_lang::{ solana_program::instruction::Instruction, - InstructionData, ToAccountMetas, + Address, InstructionData, ToAccountMetas, }, litesvm::LiteSVM, - solana_clock::Clock, - pythexample::MAXIMUM_PRICE_AGE_SECONDS, + solana_pythexample::MAXIMUM_PRICE_AGE_SECONDS, solana_keypair::Keypair, solana_kite::{create_wallet, send_transaction_from_instructions}, solana_signer::Signer, diff --git a/finance/lending/anchor/programs/lending/tests/common/mod.rs b/finance/lending/anchor/programs/lending/tests/common/mod.rs index 319316287..45579582d 100644 --- a/finance/lending/anchor/programs/lending/tests/common/mod.rs +++ b/finance/lending/anchor/programs/lending/tests/common/mod.rs @@ -7,11 +7,8 @@ //! instructions into the same transaction, exactly as a real client must. use anchor_lang::{ - solana_program::{ - instruction::{AccountMeta, Instruction}, - system_program, - }, - AccountDeserialize, InstructionData, ToAccountMetas, + solana_program::instruction::{AccountMeta, Instruction}, + system_program, AccountDeserialize, InstructionData, ToAccountMetas, }; use anchor_spl::token::ID as TOKEN_PROGRAM_ID; use litesvm::LiteSVM; diff --git a/finance/order-book/anchor/programs/order-book/tests/test_order_book.rs b/finance/order-book/anchor/programs/order-book/tests/test_order_book.rs index 165cbfe02..160725a25 100644 --- a/finance/order-book/anchor/programs/order-book/tests/test_order_book.rs +++ b/finance/order-book/anchor/programs/order-book/tests/test_order_book.rs @@ -13,7 +13,6 @@ use { anchor_lang::{ solana_program::{ instruction::{AccountMeta, Instruction}, - pubkey::Address, system_instruction, system_program, }, Discriminator, InstructionData, ToAccountMetas, diff --git a/finance/token-fundraiser/anchor/programs/fundraiser/tests/test_fundraiser.rs b/finance/token-fundraiser/anchor/programs/fundraiser/tests/test_fundraiser.rs index 098a96c85..ca8277ac5 100644 --- a/finance/token-fundraiser/anchor/programs/fundraiser/tests/test_fundraiser.rs +++ b/finance/token-fundraiser/anchor/programs/fundraiser/tests/test_fundraiser.rs @@ -6,14 +6,13 @@ use { borsh::BorshDeserialize, fundraiser::SECONDS_TO_DAYS, litesvm::LiteSVM, - solana_clock::Clock, - solana_keypair::Keypair, solana_kite::{ create_associated_token_account, create_token_mint, create_wallet, get_token_account_balance, mint_tokens_to_token_account, send_transaction_from_instructions, }, solana_signer::Signer, + solana_solana_keypair::Keypair, }; const MINT_DECIMALS: u8 = 6; diff --git a/finance/vault-strategy/anchor/programs/vault-strategy/tests/vault_strategy.rs b/finance/vault-strategy/anchor/programs/vault-strategy/tests/vault_strategy.rs index 8a753c313..0bcb98bbf 100644 --- a/finance/vault-strategy/anchor/programs/vault-strategy/tests/vault_strategy.rs +++ b/finance/vault-strategy/anchor/programs/vault-strategy/tests/vault_strategy.rs @@ -1,12 +1,11 @@ use { anchor_lang::{ - solana_program::{clock::Clock, instruction::AccountMeta, instruction::Instruction}, + solana_program::{instruction::AccountMeta, instruction::Instruction}, system_program, AccountDeserialize, Address, InstructionData, ToAccountMetas, }, anchor_spl::token::spl_token, litesvm::LiteSVM, - solana_clock::Clock, - solana_account::Account as SolanaAccount, + solana_solana_account::Account as SolanaAccount, solana_keypair::Keypair, solana_kite::{ create_associated_token_account, create_token_mint, create_wallet, diff --git a/tokens/token-extensions/cpi-guard/anchor/programs/cpi-guard/tests/test_cpi_guard.rs b/tokens/token-extensions/cpi-guard/anchor/programs/cpi-guard/tests/test_cpi_guard.rs index 8c8305fa7..7bb8c0c29 100644 --- a/tokens/token-extensions/cpi-guard/anchor/programs/cpi-guard/tests/test_cpi_guard.rs +++ b/tokens/token-extensions/cpi-guard/anchor/programs/cpi-guard/tests/test_cpi_guard.rs @@ -1,11 +1,7 @@ use { anchor_lang::{ - solana_program::{ - instruction::{AccountMeta, Instruction}, - pubkey::Address, - system_program, - }, - InstructionData, ToAccountMetas, + solana_program::instruction::{AccountMeta, Instruction}, + system_program, Address, InstructionData, ToAccountMetas, }, litesvm::LiteSVM, solana_keypair::Keypair, diff --git a/tokens/token-extensions/default-account-state/anchor/programs/default-account-state/tests/test_default_account_state.rs b/tokens/token-extensions/default-account-state/anchor/programs/default-account-state/tests/test_default_account_state.rs index a2b05e277..a14e03727 100644 --- a/tokens/token-extensions/default-account-state/anchor/programs/default-account-state/tests/test_default_account_state.rs +++ b/tokens/token-extensions/default-account-state/anchor/programs/default-account-state/tests/test_default_account_state.rs @@ -1,11 +1,7 @@ use { anchor_lang::{ - solana_program::{ - instruction::{AccountMeta, Instruction}, - pubkey::Address, - system_program, - }, - InstructionData, ToAccountMetas, + solana_program::instruction::{AccountMeta, Instruction}, + system_program, Address, InstructionData, ToAccountMetas, }, litesvm::LiteSVM, solana_keypair::Keypair, diff --git a/tokens/token-extensions/immutable-owner/anchor/programs/immutable-owner/tests/test_immutable_owner.rs b/tokens/token-extensions/immutable-owner/anchor/programs/immutable-owner/tests/test_immutable_owner.rs index 722d18f5a..1c4fb438c 100644 --- a/tokens/token-extensions/immutable-owner/anchor/programs/immutable-owner/tests/test_immutable_owner.rs +++ b/tokens/token-extensions/immutable-owner/anchor/programs/immutable-owner/tests/test_immutable_owner.rs @@ -1,11 +1,7 @@ use { anchor_lang::{ - solana_program::{ - instruction::{AccountMeta, Instruction}, - pubkey::Address, - system_program, - }, - InstructionData, ToAccountMetas, + solana_program::instruction::{AccountMeta, Instruction}, + system_program, Address, InstructionData, ToAccountMetas, }, litesvm::LiteSVM, solana_keypair::Keypair, diff --git a/tokens/token-extensions/memo-transfer/anchor/programs/memo-transfer/tests/test_memo_transfer.rs b/tokens/token-extensions/memo-transfer/anchor/programs/memo-transfer/tests/test_memo_transfer.rs index 005e328cd..666925b60 100644 --- a/tokens/token-extensions/memo-transfer/anchor/programs/memo-transfer/tests/test_memo_transfer.rs +++ b/tokens/token-extensions/memo-transfer/anchor/programs/memo-transfer/tests/test_memo_transfer.rs @@ -1,11 +1,7 @@ use { anchor_lang::{ - solana_program::{ - instruction::{AccountMeta, Instruction}, - pubkey::Address, - system_program, - }, - InstructionData, ToAccountMetas, + solana_program::instruction::{AccountMeta, Instruction}, + system_program, Address, InstructionData, ToAccountMetas, }, litesvm::LiteSVM, solana_keypair::Keypair, diff --git a/tokens/token-extensions/nft-meta-data-pointer/anchor-example/anchor/programs/extension_nft/tests/test_extension_nft.rs b/tokens/token-extensions/nft-meta-data-pointer/anchor-example/anchor/programs/extension_nft/tests/test_extension_nft.rs index ef982a143..747f3efb0 100644 --- a/tokens/token-extensions/nft-meta-data-pointer/anchor-example/anchor/programs/extension_nft/tests/test_extension_nft.rs +++ b/tokens/token-extensions/nft-meta-data-pointer/anchor-example/anchor/programs/extension_nft/tests/test_extension_nft.rs @@ -83,7 +83,7 @@ fn init_player_ix(program_id: &Pubkey, signer: &Pubkey) -> Instruction { player: player_pda(program_id, signer), game_data: game_data_pda(program_id, LEVEL_SEED), signer: *signer, - system_program: system_program::id(), + system_program: system_program::ID, } .to_account_metas(None), data: extension_nft::instruction::InitPlayer { @@ -98,7 +98,7 @@ fn mint_nft_ix(program_id: &Pubkey, signer: &Pubkey, mint: &Pubkey) -> Instructi program_id: *program_id, accounts: extension_nft::accounts::MintNftAccountConstraints { signer: *signer, - system_program: system_program::id(), + system_program: system_program::ID, token_program: TOKEN_2022_ID, token_account: associated_token_address(signer, mint), mint: *mint, @@ -121,7 +121,7 @@ fn chop_tree_ix(program_id: &Pubkey, signer: &Pubkey, mint: &Pubkey, counter: u1 player: player_pda(program_id, signer), game_data: game_data_pda(program_id, LEVEL_SEED), signer: *signer, - system_program: system_program::id(), + system_program: system_program::ID, mint: *mint, nft_authority: nft_authority_pda(program_id), token_program: TOKEN_2022_ID, diff --git a/tokens/token-extensions/permanent-delegate/anchor/programs/permanent-delegate/tests/test_permanent_delegate.rs b/tokens/token-extensions/permanent-delegate/anchor/programs/permanent-delegate/tests/test_permanent_delegate.rs index c8c47e3ff..372a0b5d0 100644 --- a/tokens/token-extensions/permanent-delegate/anchor/programs/permanent-delegate/tests/test_permanent_delegate.rs +++ b/tokens/token-extensions/permanent-delegate/anchor/programs/permanent-delegate/tests/test_permanent_delegate.rs @@ -1,11 +1,7 @@ use { anchor_lang::{ - solana_program::{ - instruction::{AccountMeta, Instruction}, - pubkey::Address, - system_program, - }, - InstructionData, ToAccountMetas, + solana_program::instruction::{AccountMeta, Instruction}, + system_program, Address, InstructionData, ToAccountMetas, }, litesvm::LiteSVM, solana_keypair::Keypair, diff --git a/tokens/token-extensions/transfer-fee/anchor/programs/transfer-fee/tests/test_transfer_fee.rs b/tokens/token-extensions/transfer-fee/anchor/programs/transfer-fee/tests/test_transfer_fee.rs index c88fcf2ba..bb9a2b8ae 100644 --- a/tokens/token-extensions/transfer-fee/anchor/programs/transfer-fee/tests/test_transfer_fee.rs +++ b/tokens/token-extensions/transfer-fee/anchor/programs/transfer-fee/tests/test_transfer_fee.rs @@ -1,11 +1,7 @@ use { anchor_lang::{ - solana_program::{ - instruction::{AccountMeta, Instruction}, - pubkey::Address, - system_program, - }, - InstructionData, ToAccountMetas, + solana_program::instruction::{AccountMeta, Instruction}, + system_program, Address, InstructionData, ToAccountMetas, }, litesvm::LiteSVM, solana_keypair::Keypair, From fb2f61141d5a8814d1d54c6d27e1d8617dd06982 Mon Sep 17 00:00:00 2001 From: Claude Date: Fri, 14 Aug 2026 19:12:03 +0000 Subject: [PATCH 22/59] compression: port cnft-burn, cnft-vault and cutils to Anchor v2 Enable solana-address's borsh feature so the vendored Bubblegum types keep their BorshSerialize derives, bind remaining_accounts() once per handler now that it returns an owned vec, and erase CpiHandleMut into CpiHandle for the raw invoke/invoke_signed calls. --- .../anchor/programs/cnft-burn/Cargo.toml | 4 +- .../cnft-burn/src/instructions/burn_cnft.rs | 28 +++++--- .../anchor/programs/cnft-vault/Cargo.toml | 4 +- .../src/instructions/withdraw_cnft.rs | 45 ++++++++----- .../src/instructions/withdraw_two_cnfts.rs | 66 +++++++++++-------- .../anchor/programs/cnft-vault/src/lib.rs | 3 +- .../cutils/anchor/programs/cutils/Cargo.toml | 4 +- .../programs/cutils/src/instructions/mint.rs | 14 ++-- .../cutils/src/instructions/verify.rs | 17 +++-- 9 files changed, 119 insertions(+), 66 deletions(-) diff --git a/compression/cnft-burn/anchor/programs/cnft-burn/Cargo.toml b/compression/cnft-burn/anchor/programs/cnft-burn/Cargo.toml index 341a23b74..ff89b4f26 100644 --- a/compression/cnft-burn/anchor/programs/cnft-burn/Cargo.toml +++ b/compression/cnft-burn/anchor/programs/cnft-burn/Cargo.toml @@ -28,7 +28,9 @@ wincode = { version = "0.5", features = ["derive"] } # moved to wincode 0.6. With both in the graph, `Address`'s wincode impls # belong to the version the account derives are not using and every # `SchemaRead`/`SchemaWrite` bound fails. 2.6.1 is the last 0.5-line release. -solana-address = ">=2.6, <2.7" +# `borsh` adds the BorshSerialize/BorshDeserialize impls for `Address` that the +# vendored Bubblegum types need; pinocchio re-exports this same type. +solana-address = { version = ">=2.6, <2.7", features = ["borsh"] } # mpl-bubblegum and spl-account-compression removed: they depend on solana-program 2.x # which is incompatible with Anchor 1.0's solana 3.x types. CPI calls are built manually # using raw invoke() with hardcoded program IDs and discriminators. diff --git a/compression/cnft-burn/anchor/programs/cnft-burn/src/instructions/burn_cnft.rs b/compression/cnft-burn/anchor/programs/cnft-burn/src/instructions/burn_cnft.rs index b88178835..231138b2a 100644 --- a/compression/cnft-burn/anchor/programs/cnft-burn/src/instructions/burn_cnft.rs +++ b/compression/cnft-burn/anchor/programs/cnft-burn/src/instructions/burn_cnft.rs @@ -57,6 +57,10 @@ pub fn handle_burn_cnft( nonce: u64, index: u32, ) -> Result<()> { + // `remaining_accounts()` walks the input cursor and returns an owned vec, + // so take it once up front and use the local everywhere below. + let proof_accounts = context.remaining_accounts()?; + // Build instruction data: discriminator + borsh-serialized args let args = BurnArgs { root, @@ -66,10 +70,11 @@ pub fn handle_burn_cnft( index, }; let mut data = BURN_DISCRIMINATOR.to_vec(); - args.serialize(&mut data)?; + args.serialize(&mut data) + .map_err(|_| ProgramError::InvalidInstructionData)?; // Build account metas matching mpl-bubblegum Burn instruction layout - let mut accounts = Vec::with_capacity(7 + context.remaining_accounts()?.len()); + let mut accounts = Vec::with_capacity(7 + proof_accounts.len()); accounts.push(AccountMeta::new_readonly( *context.accounts.tree_authority.address(), false, @@ -100,8 +105,8 @@ pub fn handle_burn_cnft( false, )); // Append remaining accounts (proof nodes) - for acc in context.remaining_accounts()?.iter() { - accounts.push(AccountMeta::new_readonly(acc.address(), false)); + for acc in proof_accounts.iter() { + accounts.push(AccountMeta::new_readonly(*acc.address(), false)); } let instruction = Instruction { @@ -110,8 +115,10 @@ pub fn handle_burn_cnft( data, }; - // Gather all account infos for the CPI - let mut account_infos = vec![ + // Gather all account infos for the CPI. `invoke` takes erased `CpiHandle`s, + // so the writable handles are converted on the way in and the proof nodes + // (bare `AccountView`s, read-only to Bubblegum) are wrapped directly. + let mut account_infos: Vec = vec![ context.accounts.bubblegum_program.cpi_handle_mut(), context.accounts.tree_authority.cpi_handle_mut(), context.accounts.leaf_owner.cpi_handle_mut(), @@ -119,9 +126,12 @@ pub fn handle_burn_cnft( context.accounts.log_wrapper.cpi_handle_mut(), context.accounts.compression_program.cpi_handle_mut(), context.accounts.system_program.cpi_handle_mut(), - ]; - for acc in context.remaining_accounts()?.iter() { - account_infos.push(acc.cpi_handle_mut()); + ] + .into_iter() + .map(CpiHandle::from) + .collect(); + for acc in proof_accounts.iter() { + account_infos.push(CpiHandle::readonly(acc)); } invoke(&instruction, &account_infos)?; diff --git a/compression/cnft-vault/anchor/programs/cnft-vault/Cargo.toml b/compression/cnft-vault/anchor/programs/cnft-vault/Cargo.toml index e4e3dbc44..56ccfe2a9 100644 --- a/compression/cnft-vault/anchor/programs/cnft-vault/Cargo.toml +++ b/compression/cnft-vault/anchor/programs/cnft-vault/Cargo.toml @@ -28,7 +28,9 @@ wincode = { version = "0.5", features = ["derive"] } # moved to wincode 0.6. With both in the graph, `Address`'s wincode impls # belong to the version the account derives are not using and every # `SchemaRead`/`SchemaWrite` bound fails. 2.6.1 is the last 0.5-line release. -solana-address = ">=2.6, <2.7" +# `borsh` adds the BorshSerialize/BorshDeserialize impls for `Address` that the +# vendored Bubblegum types need; pinocchio re-exports this same type. +solana-address = { version = ">=2.6, <2.7", features = ["borsh"] } # mpl-bubblegum and spl-account-compression removed: they depend on solana-program 2.x # which is incompatible with Anchor 1.0's solana 3.x types. CPI calls are built manually # using raw invoke_signed() with hardcoded program IDs and discriminators. diff --git a/compression/cnft-vault/anchor/programs/cnft-vault/src/instructions/withdraw_cnft.rs b/compression/cnft-vault/anchor/programs/cnft-vault/src/instructions/withdraw_cnft.rs index 0d73a77f2..c4e8e8ebd 100644 --- a/compression/cnft-vault/anchor/programs/cnft-vault/src/instructions/withdraw_cnft.rs +++ b/compression/cnft-vault/anchor/programs/cnft-vault/src/instructions/withdraw_cnft.rs @@ -63,21 +63,27 @@ pub fn handler( context.accounts.merkle_tree.address() ); - let proof_metas: Vec = context - .remaining_accounts + // `remaining_accounts()` returns an owned vec; take it once so the proof + // views stay alive for the CPI below. + let proof_accounts = context.remaining_accounts()?; + + // Read the bump before the CPI handles take a mutable borrow of `vault`. + let vault_bump = context.accounts.vault.bump; + + let proof_metas: Vec = proof_accounts .iter() - .map(|acc| AccountMeta::new_readonly(acc.address(), false)) + .map(|acc| AccountMeta::new_readonly(*acc.address(), false)) .collect(); let instruction = build_transfer_instruction( - context.accounts.tree_authority.address(), - context.accounts.vault.address(), - context.accounts.vault.address(), - context.accounts.new_leaf_owner.address(), - context.accounts.merkle_tree.address(), - context.accounts.log_wrapper.address(), - context.accounts.compression_program.address(), - context.accounts.system_program.address(), + *context.accounts.tree_authority.address(), + *context.accounts.vault.address(), + *context.accounts.vault.address(), + *context.accounts.new_leaf_owner.address(), + *context.accounts.merkle_tree.address(), + *context.accounts.log_wrapper.address(), + *context.accounts.compression_program.address(), + *context.accounts.system_program.address(), &proof_metas, TransferArgs { root, @@ -88,8 +94,10 @@ pub fn handler( }, )?; - // Gather all account infos for the CPI - let mut account_infos = vec![ + // Gather all account infos for the CPI. `invoke_signed` takes erased + // `CpiHandle`s, so the writable handles convert on the way in and the + // proof nodes (read-only to Bubblegum) are wrapped directly. + let mut account_infos: Vec = vec![ context.accounts.bubblegum_program.cpi_handle_mut(), context.accounts.tree_authority.cpi_handle_mut(), context.accounts.vault.cpi_handle_mut(), @@ -98,15 +106,18 @@ pub fn handler( context.accounts.log_wrapper.cpi_handle_mut(), context.accounts.compression_program.cpi_handle_mut(), context.accounts.system_program.cpi_handle_mut(), - ]; - for acc in context.remaining_accounts()?.iter() { - account_infos.push(acc.cpi_handle_mut()); + ] + .into_iter() + .map(CpiHandle::from) + .collect(); + for acc in proof_accounts.iter() { + account_infos.push(CpiHandle::readonly(acc)); } invoke_signed( &instruction, &account_infos, - &[&[VAULT_SEED, &[context.accounts.vault.bump]]], + &[&[VAULT_SEED, &[vault_bump]]], )?; Ok(()) diff --git a/compression/cnft-vault/anchor/programs/cnft-vault/src/instructions/withdraw_two_cnfts.rs b/compression/cnft-vault/anchor/programs/cnft-vault/src/instructions/withdraw_two_cnfts.rs index 792f46ef4..fc7476b63 100644 --- a/compression/cnft-vault/anchor/programs/cnft-vault/src/instructions/withdraw_two_cnfts.rs +++ b/compression/cnft-vault/anchor/programs/cnft-vault/src/instructions/withdraw_two_cnfts.rs @@ -92,41 +92,47 @@ pub fn handler( // The proof lengths are client-supplied: bounds-check them against the // accounts actually provided before slicing, so adversarial input gets a // clean named error instead of a panic. + // `remaining_accounts()` returns an owned vec; take it once so the proof + // views stay alive for both CPIs below. + let proof_accounts = context.remaining_accounts()?; + let proof_1_length = proof_1_length as usize; let proof_2_length = proof_2_length as usize; require!( proof_1_length .checked_add(proof_2_length) - .is_some_and(|total| total == context.remaining_accounts()?.len()), + .is_some_and(|total| total == proof_accounts.len()), VaultError::ProofLengthMismatch ); - let signer_seeds: &[&[u8]] = &[VAULT_SEED, &[context.accounts.vault.bump]]; + // Read the bump before the CPI handles take a mutable borrow of `vault`. + let vault_bump = context.accounts.vault.bump; + let signer_seeds: &[&[u8]] = &[VAULT_SEED, &[vault_bump]]; // Split remaining accounts into proof1 and proof2 - let (proof1_accounts, proof2_accounts) = context.remaining_accounts()?.split_at(proof_1_length); + let (proof1_accounts, proof2_accounts) = proof_accounts.split_at(proof_1_length); let proof1_metas: Vec = proof1_accounts .iter() - .map(|acc| AccountMeta::new_readonly(acc.address(), false)) + .map(|acc| AccountMeta::new_readonly(*acc.address(), false)) .collect(); let proof2_metas: Vec = proof2_accounts .iter() - .map(|acc| AccountMeta::new_readonly(acc.address(), false)) + .map(|acc| AccountMeta::new_readonly(*acc.address(), false)) .collect(); // Withdraw cNFT#1 msg!("withdrawing cNFT#1"); let instruction1 = build_transfer_instruction( - context.accounts.tree_authority1.address(), - context.accounts.vault.address(), - context.accounts.vault.address(), - context.accounts.new_leaf_owner1.address(), - context.accounts.merkle_tree1.address(), - context.accounts.log_wrapper.address(), - context.accounts.compression_program.address(), - context.accounts.system_program.address(), + *context.accounts.tree_authority1.address(), + *context.accounts.vault.address(), + *context.accounts.vault.address(), + *context.accounts.new_leaf_owner1.address(), + *context.accounts.merkle_tree1.address(), + *context.accounts.log_wrapper.address(), + *context.accounts.compression_program.address(), + *context.accounts.system_program.address(), &proof1_metas, TransferArgs { root: root1, @@ -137,7 +143,7 @@ pub fn handler( }, )?; - let mut account_infos1 = vec![ + let mut account_infos1: Vec = vec![ context.accounts.bubblegum_program.cpi_handle_mut(), context.accounts.tree_authority1.cpi_handle_mut(), context.accounts.vault.cpi_handle_mut(), @@ -146,9 +152,12 @@ pub fn handler( context.accounts.log_wrapper.cpi_handle_mut(), context.accounts.compression_program.cpi_handle_mut(), context.accounts.system_program.cpi_handle_mut(), - ]; + ] + .into_iter() + .map(CpiHandle::from) + .collect(); for acc in proof1_accounts.iter() { - account_infos1.push(acc.cpi_handle_mut()); + account_infos1.push(CpiHandle::readonly(acc)); } invoke_signed(&instruction1, &account_infos1, &[signer_seeds])?; @@ -156,14 +165,14 @@ pub fn handler( // Withdraw cNFT#2 msg!("withdrawing cNFT#2"); let instruction2 = build_transfer_instruction( - context.accounts.tree_authority2.address(), - context.accounts.vault.address(), - context.accounts.vault.address(), - context.accounts.new_leaf_owner2.address(), - context.accounts.merkle_tree2.address(), - context.accounts.log_wrapper.address(), - context.accounts.compression_program.address(), - context.accounts.system_program.address(), + *context.accounts.tree_authority2.address(), + *context.accounts.vault.address(), + *context.accounts.vault.address(), + *context.accounts.new_leaf_owner2.address(), + *context.accounts.merkle_tree2.address(), + *context.accounts.log_wrapper.address(), + *context.accounts.compression_program.address(), + *context.accounts.system_program.address(), &proof2_metas, TransferArgs { root: root2, @@ -174,7 +183,7 @@ pub fn handler( }, )?; - let mut account_infos2 = vec![ + let mut account_infos2: Vec = vec![ context.accounts.bubblegum_program.cpi_handle_mut(), context.accounts.tree_authority2.cpi_handle_mut(), context.accounts.vault.cpi_handle_mut(), @@ -183,9 +192,12 @@ pub fn handler( context.accounts.log_wrapper.cpi_handle_mut(), context.accounts.compression_program.cpi_handle_mut(), context.accounts.system_program.cpi_handle_mut(), - ]; + ] + .into_iter() + .map(CpiHandle::from) + .collect(); for acc in proof2_accounts.iter() { - account_infos2.push(acc.cpi_handle_mut()); + account_infos2.push(CpiHandle::readonly(acc)); } invoke_signed(&instruction2, &account_infos2, &[signer_seeds])?; diff --git a/compression/cnft-vault/anchor/programs/cnft-vault/src/lib.rs b/compression/cnft-vault/anchor/programs/cnft-vault/src/lib.rs index fd4d0a018..0763d752c 100644 --- a/compression/cnft-vault/anchor/programs/cnft-vault/src/lib.rs +++ b/compression/cnft-vault/anchor/programs/cnft-vault/src/lib.rs @@ -74,7 +74,8 @@ pub fn build_transfer_instruction( accounts.extend_from_slice(remaining_accounts); let mut data = TRANSFER_DISCRIMINATOR.to_vec(); - args.serialize(&mut data)?; + args.serialize(&mut data) + .map_err(|_| ProgramError::InvalidInstructionData)?; Ok(Instruction { program_id: MPL_BUBBLEGUM_ID, diff --git a/compression/cutils/anchor/programs/cutils/Cargo.toml b/compression/cutils/anchor/programs/cutils/Cargo.toml index 5b9eaba69..ce870ee74 100644 --- a/compression/cutils/anchor/programs/cutils/Cargo.toml +++ b/compression/cutils/anchor/programs/cutils/Cargo.toml @@ -28,7 +28,9 @@ wincode = { version = "0.5", features = ["derive"] } # moved to wincode 0.6. With both in the graph, `Address`'s wincode impls # belong to the version the account derives are not using and every # `SchemaRead`/`SchemaWrite` bound fails. 2.6.1 is the last 0.5-line release. -solana-address = ">=2.6, <2.7" +# `borsh` adds the BorshSerialize/BorshDeserialize impls for `Address` that the +# vendored Bubblegum types need; pinocchio re-exports this same type. +solana-address = { version = ">=2.6, <2.7", features = ["borsh"] } # mpl-bubblegum and spl-account-compression removed: they depend on solana-program 2.x # which is incompatible with Anchor 1.0's solana 3.x types. CPI calls are built manually # using raw invoke() with hardcoded program IDs and discriminators. Bubblegum types diff --git a/compression/cutils/anchor/programs/cutils/src/instructions/mint.rs b/compression/cutils/anchor/programs/cutils/src/instructions/mint.rs index 34420d703..45d217a3a 100644 --- a/compression/cutils/anchor/programs/cutils/src/instructions/mint.rs +++ b/compression/cutils/anchor/programs/cutils/src/instructions/mint.rs @@ -104,7 +104,8 @@ pub fn handle_mint( }; let mut data = MINT_TO_COLLECTION_V1_DISCRIMINATOR.to_vec(); - args.serialize(&mut data)?; + args.serialize(&mut data) + .map_err(|_| ProgramError::InvalidInstructionData)?; // Build account metas matching MintToCollectionV1 instruction layout let mut accounts = Vec::with_capacity(16); @@ -180,8 +181,9 @@ pub fn handle_mint( data, }; - // Gather all account infos for the CPI - let account_infos = vec![ + // Gather all account infos for the CPI. `invoke` takes erased `CpiHandle`s, + // so each writable handle is converted on the way in. + let account_infos: Vec = vec![ context.accounts.bubblegum_program.cpi_handle_mut(), context.accounts.tree_authority.cpi_handle_mut(), context.accounts.leaf_owner.cpi_handle_mut(), @@ -202,7 +204,11 @@ pub fn handle_mint( context.accounts.compression_program.cpi_handle_mut(), context.accounts.token_metadata_program.cpi_handle_mut(), context.accounts.system_program.cpi_handle_mut(), - ]; + ] + .into_iter() + .map(CpiHandle::from) + .collect(); + invoke(&instruction, &account_infos)?; diff --git a/compression/cutils/anchor/programs/cutils/src/instructions/verify.rs b/compression/cutils/anchor/programs/cutils/src/instructions/verify.rs index 1f3d894ae..323d8f529 100644 --- a/compression/cutils/anchor/programs/cutils/src/instructions/verify.rs +++ b/compression/cutils/anchor/programs/cutils/src/instructions/verify.rs @@ -35,6 +35,11 @@ pub fn handle_verify( context: &mut Context, params: &VerifyParams, ) -> Result<()> { + // `remaining_accounts()` walks the input cursor and hands back an owned + // vec, so take it once up front: the mutable borrow of `context` ends here + // 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 leaf_hash = leaf_schema_v1_hash( &asset_id, @@ -52,8 +57,8 @@ pub fn handle_verify( *context.accounts.merkle_tree.address(), false, )]; - for acc in context.remaining_accounts()?.iter() { - accounts.push(AccountMeta::new_readonly(acc.address(), false)); + for acc in proof_accounts.iter() { + accounts.push(AccountMeta::new_readonly(*acc.address(), false)); } let mut data = VERIFY_LEAF_DISCRIMINATOR.to_vec(); @@ -61,9 +66,11 @@ pub fn handle_verify( data.extend_from_slice(&leaf_hash); data.extend_from_slice(¶ms.index.to_le_bytes()); - let mut account_infos = vec![context.accounts.merkle_tree.cpi_handle_mut()]; - for acc in context.remaining_accounts()?.iter() { - account_infos.push(acc.cpi_handle_mut()); + // `verify_leaf` only reads, so every handle is readonly; the proof nodes + // arrive as bare `AccountView`s and are wrapped directly. + let mut account_infos = vec![context.accounts.merkle_tree.cpi_handle()]; + for acc in proof_accounts.iter() { + account_infos.push(CpiHandle::readonly(acc)); } anchor_lang::solana_program::program::invoke( From 12e1c5f1b17fec937d726a63705223e06923c8c4 Mon Sep 17 00:00:00 2001 From: Claude Date: Fri, 14 Aug 2026 19:25:15 +0000 Subject: [PATCH 23/59] compression: align raw CPI handle lists with v2's positional matching v2's invoke/invoke_signed match each instruction account meta to the next handle in order, so the program account is dropped from the list, an account that fills two meta slots supplies two handles, and read-only metas take read-only handles. Release the vault's data borrow before it signs, and read the v2 error code as discriminant + 6000 in the vault tests. All three compression programs now pass: cnft-burn 2/2, cnft-vault 5/5, cutils 2/2. --- .../cnft-burn/src/instructions/burn_cnft.rs | 28 +++++----- .../src/instructions/withdraw_cnft.rs | 34 ++++++------ .../src/instructions/withdraw_two_cnfts.rs | 53 ++++++++++--------- .../programs/cnft-vault/tests/test_vault.rs | 5 +- .../programs/cutils/src/instructions/mint.rs | 46 +++++++--------- 5 files changed, 86 insertions(+), 80 deletions(-) diff --git a/compression/cnft-burn/anchor/programs/cnft-burn/src/instructions/burn_cnft.rs b/compression/cnft-burn/anchor/programs/cnft-burn/src/instructions/burn_cnft.rs index 231138b2a..42592eab8 100644 --- a/compression/cnft-burn/anchor/programs/cnft-burn/src/instructions/burn_cnft.rs +++ b/compression/cnft-burn/anchor/programs/cnft-burn/src/instructions/burn_cnft.rs @@ -115,21 +115,21 @@ pub fn handle_burn_cnft( data, }; - // Gather all account infos for the CPI. `invoke` takes erased `CpiHandle`s, - // so the writable handles are converted on the way in and the proof nodes - // (bare `AccountView`s, read-only to Bubblegum) are wrapped directly. + // Account handles have to line up positionally with the instruction's + // account metas: v2's `invoke` matches each meta to the next handle in + // order, so the program account is not listed and an account that fills + // two slots supplies two handles. let mut account_infos: Vec = vec![ - context.accounts.bubblegum_program.cpi_handle_mut(), - context.accounts.tree_authority.cpi_handle_mut(), - context.accounts.leaf_owner.cpi_handle_mut(), - context.accounts.merkle_tree.cpi_handle_mut(), - context.accounts.log_wrapper.cpi_handle_mut(), - context.accounts.compression_program.cpi_handle_mut(), - context.accounts.system_program.cpi_handle_mut(), - ] - .into_iter() - .map(CpiHandle::from) - .collect(); + context.accounts.tree_authority.cpi_handle_mut().into(), + // leaf_owner also fills the leaf_delegate slot; both metas are + // read-only, so read-only handles satisfy them + context.accounts.leaf_owner.cpi_handle(), + context.accounts.leaf_owner.cpi_handle(), + context.accounts.merkle_tree.cpi_handle_mut().into(), + context.accounts.log_wrapper.cpi_handle(), + context.accounts.compression_program.cpi_handle(), + context.accounts.system_program.cpi_handle(), + ]; for acc in proof_accounts.iter() { account_infos.push(CpiHandle::readonly(acc)); } diff --git a/compression/cnft-vault/anchor/programs/cnft-vault/src/instructions/withdraw_cnft.rs b/compression/cnft-vault/anchor/programs/cnft-vault/src/instructions/withdraw_cnft.rs index c4e8e8ebd..40e2c70d7 100644 --- a/compression/cnft-vault/anchor/programs/cnft-vault/src/instructions/withdraw_cnft.rs +++ b/compression/cnft-vault/anchor/programs/cnft-vault/src/instructions/withdraw_cnft.rs @@ -94,22 +94,26 @@ pub fn handler( }, )?; - // Gather all account infos for the CPI. `invoke_signed` takes erased - // `CpiHandle`s, so the writable handles convert on the way in and the - // proof nodes (read-only to Bubblegum) are wrapped directly. + // `vault` signs the transfer, so its data borrow has to be handed back to + // the runtime before the CPI. It is read-only here and nothing reads it + // afterwards, so there is no reacquire. + context.accounts.vault.release_borrow()?; + + // Account handles have to line up positionally with the instruction's + // account metas: v2's `invoke` matches each meta to the next handle in + // order, so the program account is not listed and an account that fills + // two slots supplies two handles. let mut account_infos: Vec = vec![ - context.accounts.bubblegum_program.cpi_handle_mut(), - context.accounts.tree_authority.cpi_handle_mut(), - context.accounts.vault.cpi_handle_mut(), - context.accounts.new_leaf_owner.cpi_handle_mut(), - context.accounts.merkle_tree.cpi_handle_mut(), - context.accounts.log_wrapper.cpi_handle_mut(), - context.accounts.compression_program.cpi_handle_mut(), - context.accounts.system_program.cpi_handle_mut(), - ] - .into_iter() - .map(CpiHandle::from) - .collect(); + context.accounts.tree_authority.cpi_handle_mut().into(), + // leaf_owner and leaf_delegate are both the vault PDA + context.accounts.vault.cpi_handle(), + context.accounts.vault.cpi_handle(), + context.accounts.new_leaf_owner.cpi_handle(), + context.accounts.merkle_tree.cpi_handle_mut().into(), + context.accounts.log_wrapper.cpi_handle(), + context.accounts.compression_program.cpi_handle(), + context.accounts.system_program.cpi_handle(), + ]; for acc in proof_accounts.iter() { account_infos.push(CpiHandle::readonly(acc)); } diff --git a/compression/cnft-vault/anchor/programs/cnft-vault/src/instructions/withdraw_two_cnfts.rs b/compression/cnft-vault/anchor/programs/cnft-vault/src/instructions/withdraw_two_cnfts.rs index fc7476b63..5a1633855 100644 --- a/compression/cnft-vault/anchor/programs/cnft-vault/src/instructions/withdraw_two_cnfts.rs +++ b/compression/cnft-vault/anchor/programs/cnft-vault/src/instructions/withdraw_two_cnfts.rs @@ -122,6 +122,11 @@ pub fn handler( .map(|acc| AccountMeta::new_readonly(*acc.address(), false)) .collect(); + // `vault` signs both transfers, so its data borrow has to be handed back to + // the runtime before the CPIs. It is read-only here and nothing reads it + // afterwards, so there is no reacquire. + context.accounts.vault.release_borrow()?; + // Withdraw cNFT#1 msg!("withdrawing cNFT#1"); let instruction1 = build_transfer_instruction( @@ -143,19 +148,19 @@ pub fn handler( }, )?; + // Handles line up positionally with the instruction's metas: the program + // account is not listed, and the vault fills both the leaf_owner and + // leaf_delegate slots. let mut account_infos1: Vec = vec![ - context.accounts.bubblegum_program.cpi_handle_mut(), - context.accounts.tree_authority1.cpi_handle_mut(), - context.accounts.vault.cpi_handle_mut(), - context.accounts.new_leaf_owner1.cpi_handle_mut(), - context.accounts.merkle_tree1.cpi_handle_mut(), - context.accounts.log_wrapper.cpi_handle_mut(), - context.accounts.compression_program.cpi_handle_mut(), - context.accounts.system_program.cpi_handle_mut(), - ] - .into_iter() - .map(CpiHandle::from) - .collect(); + context.accounts.tree_authority1.cpi_handle_mut().into(), + context.accounts.vault.cpi_handle(), + context.accounts.vault.cpi_handle(), + context.accounts.new_leaf_owner1.cpi_handle(), + context.accounts.merkle_tree1.cpi_handle_mut().into(), + context.accounts.log_wrapper.cpi_handle(), + context.accounts.compression_program.cpi_handle(), + context.accounts.system_program.cpi_handle(), + ]; for acc in proof1_accounts.iter() { account_infos1.push(CpiHandle::readonly(acc)); } @@ -183,19 +188,19 @@ pub fn handler( }, )?; + // Handles line up positionally with the instruction's metas: the program + // account is not listed, and the vault fills both the leaf_owner and + // leaf_delegate slots. let mut account_infos2: Vec = vec![ - context.accounts.bubblegum_program.cpi_handle_mut(), - context.accounts.tree_authority2.cpi_handle_mut(), - context.accounts.vault.cpi_handle_mut(), - context.accounts.new_leaf_owner2.cpi_handle_mut(), - context.accounts.merkle_tree2.cpi_handle_mut(), - context.accounts.log_wrapper.cpi_handle_mut(), - context.accounts.compression_program.cpi_handle_mut(), - context.accounts.system_program.cpi_handle_mut(), - ] - .into_iter() - .map(CpiHandle::from) - .collect(); + context.accounts.tree_authority2.cpi_handle_mut().into(), + context.accounts.vault.cpi_handle(), + context.accounts.vault.cpi_handle(), + context.accounts.new_leaf_owner2.cpi_handle(), + context.accounts.merkle_tree2.cpi_handle_mut().into(), + context.accounts.log_wrapper.cpi_handle(), + context.accounts.compression_program.cpi_handle(), + context.accounts.system_program.cpi_handle(), + ]; for acc in proof2_accounts.iter() { account_infos2.push(CpiHandle::readonly(acc)); } diff --git a/compression/cnft-vault/anchor/programs/cnft-vault/tests/test_vault.rs b/compression/cnft-vault/anchor/programs/cnft-vault/tests/test_vault.rs index a57f22791..44183400a 100644 --- a/compression/cnft-vault/anchor/programs/cnft-vault/tests/test_vault.rs +++ b/compression/cnft-vault/anchor/programs/cnft-vault/tests/test_vault.rs @@ -268,7 +268,10 @@ fn assert_custom_error( expected: VaultError, ) { let failed = result.expect_err("transaction should fail"); - let expected_code = u32::from(expected); + // v2's `#[error_code]` makes the enum `#[repr(u32)]` and only generates + // `From for anchor_lang::Error`, so the on-wire custom code is + // the discriminant plus the default 6000 offset. + let expected_code = expected as u32 + 6000; let error_text = format!("{:?}", failed.err); assert!( error_text.contains(&format!("Custom({expected_code})")), diff --git a/compression/cutils/anchor/programs/cutils/src/instructions/mint.rs b/compression/cutils/anchor/programs/cutils/src/instructions/mint.rs index 45d217a3a..ae2364c72 100644 --- a/compression/cutils/anchor/programs/cutils/src/instructions/mint.rs +++ b/compression/cutils/anchor/programs/cutils/src/instructions/mint.rs @@ -181,33 +181,27 @@ pub fn handle_mint( data, }; - // Gather all account infos for the CPI. `invoke` takes erased `CpiHandle`s, - // so each writable handle is converted on the way in. + // Account handles have to line up positionally with the instruction's + // account metas above: v2's `invoke` matches each meta to the next handle + // in order, and the program account is not listed. let account_infos: Vec = vec![ - context.accounts.bubblegum_program.cpi_handle_mut(), - context.accounts.tree_authority.cpi_handle_mut(), - context.accounts.leaf_owner.cpi_handle_mut(), - context.accounts.leaf_delegate.cpi_handle_mut(), - context.accounts.merkle_tree.cpi_handle_mut(), - context.accounts.payer.cpi_handle_mut(), - context.accounts.tree_delegate.cpi_handle_mut(), - context.accounts.collection_authority.cpi_handle_mut(), - context - .accounts - .collection_authority_record_pda - .cpi_handle_mut(), - context.accounts.collection_mint.cpi_handle_mut(), - context.accounts.collection_metadata.cpi_handle_mut(), - context.accounts.edition_account.cpi_handle_mut(), - context.accounts.bubblegum_signer.cpi_handle_mut(), - context.accounts.log_wrapper.cpi_handle_mut(), - context.accounts.compression_program.cpi_handle_mut(), - context.accounts.token_metadata_program.cpi_handle_mut(), - context.accounts.system_program.cpi_handle_mut(), - ] - .into_iter() - .map(CpiHandle::from) - .collect(); + context.accounts.tree_authority.cpi_handle_mut().into(), + context.accounts.leaf_owner.cpi_handle(), + context.accounts.leaf_delegate.cpi_handle(), + context.accounts.merkle_tree.cpi_handle_mut().into(), + 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_mint.cpi_handle(), + context.accounts.collection_metadata.cpi_handle_mut().into(), + context.accounts.edition_account.cpi_handle(), + context.accounts.bubblegum_signer.cpi_handle(), + context.accounts.log_wrapper.cpi_handle(), + context.accounts.compression_program.cpi_handle(), + context.accounts.token_metadata_program.cpi_handle(), + context.accounts.system_program.cpi_handle(), + ]; invoke(&instruction, &account_infos)?; From d0ef9f393b9a073817144afacd651473e2ee72a2 Mon Sep 17 00:00:00 2001 From: Claude Date: Fri, 14 Aug 2026 19:25:45 +0000 Subject: [PATCH 24/59] docs: record the v2 rules for hand-built CPIs and error codes --- docs/anchor-v2-migration.md | 47 +++++++++++++++++++++++++++++++++++++ 1 file changed, 47 insertions(+) diff --git a/docs/anchor-v2-migration.md b/docs/anchor-v2-migration.md index a4890809f..56f8568a9 100644 --- a/docs/anchor-v2-migration.md +++ b/docs/anchor-v2-migration.md @@ -224,6 +224,49 @@ answer is usually one of the first four rather than moving it to borsh. `target/anchor-v2-profile/`. - `anchor-v2-testing` — wraps LiteSVM with optional register-trace capture. +## Hand-built CPIs (`invoke` / `invoke_signed`) + +An example that builds its own `Instruction` — because the callee's crate is +not on a compatible Solana version — hands `invoke` a slice of `CpiHandle` +rather than a slice of `AccountInfo`. Four rules, all of them enforced at +runtime rather than by the compiler: + +- **The handles are positional.** v2 walks the instruction's account metas and + binds each one to the next handle in the slice. A handle that does not match + the meta at its position fails the whole call with `InvalidArgument`, so the + list has to mirror the metas exactly. +- **The program account is not a handle.** v1 code habitually pushed the callee + program's `AccountInfo` into the infos vec; in v2 that extra leading entry is + what breaks the positional match. +- **An account filling two meta slots supplies two handles.** Bubblegum's + `Transfer` names the same PDA as both `leaf_owner` and `leaf_delegate`, so the + handle appears twice. Read-only handles make this trivial — `cpi_handle()` + takes `&self`. +- **Writability has to be at least as strong as the meta.** A writable meta + needs a writable handle; a read-only meta accepts either. Going the other way, + `cpi_handle_mut()` on an account not declared `mut` panics outright with + *"cpi_handle_mut called on a read-only account"*, which surfaces as + `ProgramFailedToComplete` and a `src/traits.rs` log line. + +Mixing the two handle kinds in one vec means converting element-wise — +`vec![a.cpi_handle_mut().into(), b.cpi_handle()]` — since a trailing +`.map(CpiHandle::from)` forces every element to be a `CpiHandleMut`. + +A `BorshAccount` that signs such a CPI still needs `release_borrow()` first, per +[Borrows across CPIs](#borrows-across-cpis). When the account is read-only and +nothing reads it after the CPI, there is no reacquire — `reacquire_borrow_mut` +asserts the account was loaded mutably. + +Vendored types that derive `BorshSerialize` over `Address` fields need the +`borsh` feature on `solana-address`; `Address` is pinocchio's re-export of that +crate's type and carries no borsh impls by default. Their `serialize` returns +`io::Error`, which no longer converts into `ProgramError` — map it: + +```rust +args.serialize(&mut data) + .map_err(|_| ProgramError::InvalidInstructionData)?; +``` + ## Seeds, sysvars, cross-program types `seeds` takes a byte array directly and binds it itself — `id.to_le_bytes()`, @@ -253,6 +296,10 @@ The test-side surface barely changed, but three things move: `anchor_lang::Address` is the same 32-byte type. - `anchor_lang::prelude::Clock` is pinocchio's on-chain type. LiteSVM's `get_sysvar` / `set_sysvar` want the host-side `solana_clock::Clock`. +- `#[error_code]` no longer generates `From for u32`. It makes the enum + `#[repr(u32)]` and generates only `From for anchor_lang::Error`, so a + test asserting on the wire code writes `my_error as u32 + 6000` (6000 being + the default offset, overridable with `#[error_code(offset = ...)]`). Tests that decode account bytes with borsh keep working, because `BorshConfig` makes wincode's wire format byte-identical — but a Pod account From e9ce409e318fe143e8b76404a2c2d14a8f66ef60 Mon Sep 17 00:00:00 2001 From: Claude Date: Fri, 14 Aug 2026 19:34:34 +0000 Subject: [PATCH 25/59] anchor: replace the deprecated has_one constraint with address v2 deprecates has_one, and rust.yml runs `cargo clippy -- -D warnings`, so every remaining use is a hard CI failure. The check moves off the owning account and onto the sibling it names: `has_one = maker` on `offer` becomes `address = offer.maker` on `maker`, carrying any `@ Error` across. Forward references are fine, the derive resolves the constraint after all accounts are loaded, so the sibling may be declared before its owner. 152 constraints across 59 files, plus the prose that named the old spelling. --- .../src/instructions/withdraw_cnft.rs | 8 ++---- .../src/instructions/withdraw_two_cnfts.rs | 8 ++---- .../src/instructions/add_outcome.rs | 9 ++---- .../src/instructions/cancel_event.rs | 8 ++---- .../src/instructions/claim_refund.rs | 5 ++-- .../src/instructions/claim_winnings.rs | 5 ++-- .../src/instructions/close_losing_bet.rs | 5 ++-- .../src/instructions/initialize_event.rs | 12 +++----- .../src/instructions/place_bet.rs | 11 +++----- .../src/instructions/settle_event.rs | 18 +++++------- .../escrow/src/instructions/cancel_offer.rs | 5 ++-- .../escrow/src/instructions/take_offer.rs | 9 +++--- .../programs/escrow/tests/test_escrow.rs | 2 +- .../admin/collect_protocol_fees.rs | 16 ++++------- .../admin/initialize_lending_market.rs | 2 +- .../instructions/admin/initialize_reserve.rs | 3 +- .../src/instructions/admin/set_price.rs | 3 +- .../admin/update_reserve_config.rs | 10 +++---- .../borrow_obligation_liquidity.rs | 10 +++---- .../deposit_obligation_collateral.rs | 6 ++-- .../instructions/deposit_reserve_liquidity.rs | 12 +++----- .../instructions/redeem_reserve_collateral.rs | 12 +++----- .../repay_obligation_liquidity.rs | 5 ++-- .../withdraw_obligation_collateral.rs | 7 +++-- .../programs/lending/tests/test_security.rs | 2 +- .../src/instructions/admin/withdraw_fees.rs | 7 ++--- .../src/instructions/cancel_order.rs | 5 ++-- .../src/instructions/initialize_market.rs | 2 +- .../src/instructions/place_order.rs | 28 ++++++++----------- .../src/instructions/settle_funds.rs | 16 ++++------- .../order-book/src/state/order_book.rs | 2 +- .../order-book/tests/test_order_book.rs | 6 ++-- .../programs/mock-switchboard/src/lib.rs | 6 ++-- .../src/instructions/add_liquidity.rs | 11 ++++---- .../src/instructions/close_position.rs | 13 ++++----- .../src/instructions/collect_fees.rs | 7 ++--- .../src/instructions/liquidate_position.rs | 15 +++++----- .../src/instructions/open_position.rs | 8 +++--- .../src/instructions/remove_liquidity.rs | 11 ++++---- .../programs/mock-switchboard/src/lib.rs | 6 ++-- .../src/instructions/deposit_inventory.rs | 13 ++++----- .../prop-amm/src/instructions/set_quote.rs | 2 +- .../prop-amm/src/instructions/swap.rs | 12 ++++---- .../src/instructions/withdraw_inventory.rs | 11 ++++---- .../fundraiser/src/instructions/close.rs | 2 +- .../fundraiser/src/instructions/contribute.rs | 2 +- .../fundraiser/src/instructions/refund.rs | 2 +- .../instructions/admin/claim_admin_fees.rs | 19 ++++++------- .../src/instructions/deposit_liquidity.rs | 4 +-- .../src/instructions/swap_tokens.rs | 11 +++----- .../src/instructions/withdraw_liquidity.rs | 6 ++-- .../programs/token-swap/tests/test_swap.rs | 2 +- .../src/instructions/set_rate.rs | 9 ++---- .../src/instructions/add_asset.rs | 7 ++--- .../src/instructions/approve_asset.rs | 5 ++-- .../src/instructions/collect_fees.rs | 4 +-- .../src/instructions/deposit.rs | 4 +-- .../src/instructions/rebalance.rs | 6 ++-- .../src/instructions/set_weight.rs | 4 +-- .../src/instructions/withdraw.rs | 4 +-- .../src/instructions/authority_transfer.rs | 2 +- .../src/instructions/set_ethereum_address.rs | 3 +- .../src/instructions/transfer_tokens.rs | 3 +- .../abl-token/src/instructions/init_wallet.rs | 9 ++---- .../src/instructions/remove_wallet.rs | 9 ++---- .../src/instructions/switch.rs | 9 ++---- 66 files changed, 207 insertions(+), 293 deletions(-) diff --git a/compression/cnft-vault/anchor/programs/cnft-vault/src/instructions/withdraw_cnft.rs b/compression/cnft-vault/anchor/programs/cnft-vault/src/instructions/withdraw_cnft.rs index 40e2c70d7..9040244f8 100644 --- a/compression/cnft-vault/anchor/programs/cnft-vault/src/instructions/withdraw_cnft.rs +++ b/compression/cnft-vault/anchor/programs/cnft-vault/src/instructions/withdraw_cnft.rs @@ -8,15 +8,13 @@ use crate::{build_transfer_instruction, SPLCompression, TransferArgs, MPL_BUBBLE #[derive(Accounts)] pub struct WithdrawCnftAccountConstraints { /// The stored vault authority. Only this signer may withdraw. + #[account(address = vault.authority @ VaultError::InvalidWithdrawAuthority)] pub authority: Signer, // The vault PDA owns the cNFTs (as Bubblegum leaf owner) and signs the // transfer CPI via invoke_signed. - #[account( - seeds = [VAULT_SEED], - bump = vault.bump, - has_one = authority @ VaultError::InvalidWithdrawAuthority, - )] + #[account(seeds = [VAULT_SEED], + bump = vault.bump)] pub vault: BorshAccount, #[account(mut)] diff --git a/compression/cnft-vault/anchor/programs/cnft-vault/src/instructions/withdraw_two_cnfts.rs b/compression/cnft-vault/anchor/programs/cnft-vault/src/instructions/withdraw_two_cnfts.rs index 5a1633855..18a82d112 100644 --- a/compression/cnft-vault/anchor/programs/cnft-vault/src/instructions/withdraw_two_cnfts.rs +++ b/compression/cnft-vault/anchor/programs/cnft-vault/src/instructions/withdraw_two_cnfts.rs @@ -8,15 +8,13 @@ use crate::{build_transfer_instruction, SPLCompression, TransferArgs, MPL_BUBBLE #[derive(Accounts)] pub struct WithdrawTwoCnftsAccountConstraints { /// The stored vault authority. Only this signer may withdraw. + #[account(address = vault.authority @ VaultError::InvalidWithdrawAuthority)] pub authority: Signer, // The vault PDA owns the cNFTs (as Bubblegum leaf owner) and signs both // transfer CPIs via invoke_signed. - #[account( - seeds = [VAULT_SEED], - bump = vault.bump, - has_one = authority @ VaultError::InvalidWithdrawAuthority, - )] + #[account(seeds = [VAULT_SEED], + bump = vault.bump)] pub vault: BorshAccount, #[account(mut)] diff --git a/finance/betting-market/anchor/programs/betting-market/src/instructions/add_outcome.rs b/finance/betting-market/anchor/programs/betting-market/src/instructions/add_outcome.rs index 23a3aae4d..a97b5fcb1 100644 --- a/finance/betting-market/anchor/programs/betting-market/src/instructions/add_outcome.rs +++ b/finance/betting-market/anchor/programs/betting-market/src/instructions/add_outcome.rs @@ -8,14 +8,11 @@ pub const MAX_LABEL_LEN: usize = 64; #[derive(Accounts)] pub struct AddOutcomeAccountConstraints { - #[account(mut)] + #[account(mut, address = config.admin @ BettingError::Unauthorized)] pub admin: Signer, - #[account( - seeds = [b"config"], - bump = config.bump, - has_one = admin @ BettingError::Unauthorized, - )] + #[account(seeds = [b"config"], + bump = config.bump)] pub config: BorshAccount, #[account( diff --git a/finance/betting-market/anchor/programs/betting-market/src/instructions/cancel_event.rs b/finance/betting-market/anchor/programs/betting-market/src/instructions/cancel_event.rs index fee309937..fbd31f33a 100644 --- a/finance/betting-market/anchor/programs/betting-market/src/instructions/cancel_event.rs +++ b/finance/betting-market/anchor/programs/betting-market/src/instructions/cancel_event.rs @@ -8,13 +8,11 @@ use crate::{error::BettingError, Config, EventStatus}; // Bettors then reclaim their exact stakes via `claim_refund`; no fee is taken. #[derive(Accounts)] pub struct CancelEventAccountConstraints { + #[account(address = config.admin @ BettingError::Unauthorized)] pub admin: Signer, - #[account( - seeds = [b"config"], - bump = config.bump, - has_one = admin @ BettingError::Unauthorized, - )] + #[account(seeds = [b"config"], + bump = config.bump)] pub config: BorshAccount, #[account( diff --git a/finance/betting-market/anchor/programs/betting-market/src/instructions/claim_refund.rs b/finance/betting-market/anchor/programs/betting-market/src/instructions/claim_refund.rs index 9fc760bf4..f4917c8f5 100644 --- a/finance/betting-market/anchor/programs/betting-market/src/instructions/claim_refund.rs +++ b/finance/betting-market/anchor/programs/betting-market/src/instructions/claim_refund.rs @@ -10,7 +10,7 @@ use super::transfer_tokens_from_vault; #[derive(Accounts)] pub struct ClaimRefundAccountConstraints { - #[account(mut)] + #[account(mut, address = bet.bettor)] pub bettor: Signer, #[account(mint::token_program = token_program)] @@ -23,6 +23,7 @@ pub struct ClaimRefundAccountConstraints { mut, seeds = [b"event", event.event_id.to_le_bytes()], bump = event.bump, + address = bet.event, )] pub event: BorshAccount, @@ -31,8 +32,6 @@ pub struct ClaimRefundAccountConstraints { #[account( mut, close = bettor, - has_one = bettor, - has_one = event, seeds = [b"bet", bet.outcome.as_ref(), bettor.address().as_ref()], bump = bet.bump, )] diff --git a/finance/betting-market/anchor/programs/betting-market/src/instructions/claim_winnings.rs b/finance/betting-market/anchor/programs/betting-market/src/instructions/claim_winnings.rs index 1764f0a50..bda74a5e9 100644 --- a/finance/betting-market/anchor/programs/betting-market/src/instructions/claim_winnings.rs +++ b/finance/betting-market/anchor/programs/betting-market/src/instructions/claim_winnings.rs @@ -10,7 +10,7 @@ use super::transfer_tokens_from_vault; #[derive(Accounts)] pub struct ClaimWinningsAccountConstraints { - #[account(mut)] + #[account(mut, address = bet.bettor)] pub bettor: Signer, #[account(mint::token_program = token_program)] @@ -23,6 +23,7 @@ pub struct ClaimWinningsAccountConstraints { mut, seeds = [b"event", event.event_id.to_le_bytes()], bump = event.bump, + address = bet.event, )] pub event: BorshAccount, @@ -31,8 +32,6 @@ pub struct ClaimWinningsAccountConstraints { #[account( mut, close = bettor, - has_one = bettor, - has_one = event, seeds = [b"bet", bet.outcome.as_ref(), bettor.address().as_ref()], bump = bet.bump, )] diff --git a/finance/betting-market/anchor/programs/betting-market/src/instructions/close_losing_bet.rs b/finance/betting-market/anchor/programs/betting-market/src/instructions/close_losing_bet.rs index 301e440f8..5fcb37b68 100644 --- a/finance/betting-market/anchor/programs/betting-market/src/instructions/close_losing_bet.rs +++ b/finance/betting-market/anchor/programs/betting-market/src/instructions/close_losing_bet.rs @@ -10,20 +10,19 @@ use crate::{error::BettingError, Bet, EventStatus, User}; // claim_winnings instead, which also pays out the stake and winnings. #[derive(Accounts)] pub struct CloseLosingBetAccountConstraints { - #[account(mut)] + #[account(mut, address = bet.bettor)] pub bettor: Signer, #[account( seeds = [b"event", event.event_id.to_le_bytes()], bump = event.bump, + address = bet.event, )] pub event: BorshAccount, #[account( mut, close = bettor, - has_one = bettor, - has_one = event, seeds = [b"bet", bet.outcome.as_ref(), bettor.address().as_ref()], bump = bet.bump, )] diff --git a/finance/betting-market/anchor/programs/betting-market/src/instructions/initialize_event.rs b/finance/betting-market/anchor/programs/betting-market/src/instructions/initialize_event.rs index 77ce9064a..3964a0fe7 100644 --- a/finance/betting-market/anchor/programs/betting-market/src/instructions/initialize_event.rs +++ b/finance/betting-market/anchor/programs/betting-market/src/instructions/initialize_event.rs @@ -14,19 +14,15 @@ pub const MAX_DESCRIPTION_LEN: usize = 200; #[derive(Accounts)] #[instruction(event_id: u64)] pub struct InitializeEventAccountConstraints { - #[account(mut)] + #[account(mut, address = config.admin @ BettingError::Unauthorized)] pub admin: Signer, - #[account( - mut, + #[account(mut, seeds = [b"config"], - bump = config.bump, - has_one = admin @ BettingError::Unauthorized, - has_one = token_mint, - )] + bump = config.bump)] pub config: BorshAccount, - #[account(mint::token_program = token_program)] + #[account(mint::token_program = token_program, address = config.token_mint)] pub token_mint: InterfaceAccount, #[account( diff --git a/finance/betting-market/anchor/programs/betting-market/src/instructions/place_bet.rs b/finance/betting-market/anchor/programs/betting-market/src/instructions/place_bet.rs index 803194cc7..2af5be0c4 100644 --- a/finance/betting-market/anchor/programs/betting-market/src/instructions/place_bet.rs +++ b/finance/betting-market/anchor/programs/betting-market/src/instructions/place_bet.rs @@ -16,26 +16,23 @@ pub struct PlaceBetAccountConstraints { #[account(mut)] pub bettor: Signer, - #[account( - seeds = [b"config"], - bump = config.bump, - has_one = token_mint, - )] + #[account(seeds = [b"config"], + bump = config.bump)] pub config: BorshAccount, - #[account(mint::token_program = token_program)] + #[account(mint::token_program = token_program, address = config.token_mint)] pub token_mint: Box>, #[account( mut, seeds = [b"event", event.event_id.to_le_bytes()], bump = event.bump, + address = outcome.event, )] pub event: Box>, #[account( mut, - has_one = event, seeds = [b"outcome", event.address().as_ref(), &[outcome.index]], bump = outcome.bump, )] diff --git a/finance/betting-market/anchor/programs/betting-market/src/instructions/settle_event.rs b/finance/betting-market/anchor/programs/betting-market/src/instructions/settle_event.rs index 48eb4712e..12f019348 100644 --- a/finance/betting-market/anchor/programs/betting-market/src/instructions/settle_event.rs +++ b/finance/betting-market/anchor/programs/betting-market/src/instructions/settle_event.rs @@ -16,30 +16,25 @@ const BPS_DENOMINATOR: u128 = 10_000; #[derive(Accounts)] #[instruction(winning_outcome_index: u8)] pub struct SettleEventAccountConstraints { - #[account(mut)] + #[account(mut, address = config.admin @ BettingError::Unauthorized)] pub admin: Signer, - #[account( - seeds = [b"config"], - bump = config.bump, - has_one = admin @ BettingError::Unauthorized, - has_one = token_mint, - has_one = fee_recipient, - )] + #[account(seeds = [b"config"], + bump = config.bump)] pub config: BorshAccount, - #[account(mint::token_program = token_program)] + #[account(mint::token_program = token_program, address = config.token_mint)] pub token_mint: InterfaceAccount, #[account( mut, seeds = [b"event", event.event_id.to_le_bytes()], bump = event.bump, + address = winning_outcome.event, )] pub event: BorshAccount, #[account( - has_one = event, seeds = [b"outcome", event.address().as_ref(), &[winning_outcome_index]], bump = winning_outcome.bump, )] @@ -53,7 +48,8 @@ pub struct SettleEventAccountConstraints { )] pub vault: InterfaceAccount, - /// CHECK: validated against config.fee_recipient by the `has_one` above. + /// CHECK: validated against config.fee_recipient by the `address` constraint. + #[account(address = config.fee_recipient)] pub fee_recipient: UncheckedAccount, #[account( diff --git a/finance/escrow/anchor/programs/escrow/src/instructions/cancel_offer.rs b/finance/escrow/anchor/programs/escrow/src/instructions/cancel_offer.rs index af376ad4c..e62dbe7e8 100644 --- a/finance/escrow/anchor/programs/escrow/src/instructions/cancel_offer.rs +++ b/finance/escrow/anchor/programs/escrow/src/instructions/cancel_offer.rs @@ -15,9 +15,10 @@ use super::{close_token_account, transfer_tokens}; // the maker, and both the vault and the offer accounts are closed. #[derive(Accounts)] pub struct CancelOfferAccountConstraints { - #[account(mut)] + #[account(mut, address = offer.maker)] pub maker: Signer, + #[account(address = offer.token_mint_a)] pub token_mint_a: InterfaceAccount, #[account( @@ -31,8 +32,6 @@ pub struct CancelOfferAccountConstraints { #[account( mut, close = maker, - has_one = maker, - has_one = token_mint_a, seeds = [b"offer", maker.address().as_ref(), offer.id.to_le_bytes()], bump = offer.bump, )] diff --git a/finance/escrow/anchor/programs/escrow/src/instructions/take_offer.rs b/finance/escrow/anchor/programs/escrow/src/instructions/take_offer.rs index 3a9e8ec4c..f1d6f58a2 100644 --- a/finance/escrow/anchor/programs/escrow/src/instructions/take_offer.rs +++ b/finance/escrow/anchor/programs/escrow/src/instructions/take_offer.rs @@ -14,11 +14,13 @@ pub struct TakeOfferAccountConstraints { #[account(mut)] pub taker: Signer, - #[account(mut)] + #[account(mut, address = offer.maker)] pub maker: SystemAccount, + #[account(address = offer.token_mint_a)] pub token_mint_a: InterfaceAccount, + #[account(address = offer.token_mint_b)] pub token_mint_b: InterfaceAccount, #[account( @@ -50,11 +52,8 @@ pub struct TakeOfferAccountConstraints { #[account( mut, close = maker, - has_one = maker, - has_one = token_mint_a, - has_one = token_mint_b, seeds = [b"offer", maker.address().as_ref(), offer.id.to_le_bytes()], - bump = offer.bump + bump = offer.bump, )] offer: BorshAccount, diff --git a/finance/escrow/anchor/programs/escrow/tests/test_escrow.rs b/finance/escrow/anchor/programs/escrow/tests/test_escrow.rs index 62379bcd4..837a099f8 100644 --- a/finance/escrow/anchor/programs/escrow/tests/test_escrow.rs +++ b/finance/escrow/anchor/programs/escrow/tests/test_escrow.rs @@ -459,7 +459,7 @@ fn test_cancel_offer_rejects_non_maker() { ) .unwrap(); - // Bob tries to cancel Alice's offer - the has_one = maker / signer + seeds + // Bob tries to cancel Alice's offer - the address = offer.maker / signer + seeds // constraints should reject this. let bob_ata_a = create_associated_token_account(&mut es.svm, &es.bob.pubkey(), &es.mint_a, &es.payer) diff --git a/finance/lending/anchor/programs/lending/src/instructions/admin/collect_protocol_fees.rs b/finance/lending/anchor/programs/lending/src/instructions/admin/collect_protocol_fees.rs index 485b24e41..8ebc9bb4c 100644 --- a/finance/lending/anchor/programs/lending/src/instructions/admin/collect_protocol_fees.rs +++ b/finance/lending/anchor/programs/lending/src/instructions/admin/collect_protocol_fees.rs @@ -52,25 +52,21 @@ pub fn handle_collect_protocol_fees(context: &mut Context) #[derive(Accounts)] pub struct CollectProtocolFees { - // Identified by the reserve's `has_one = lending_market`; we only prove the + // Identified by `address = reserve.lending_market`; we only prove the // signer owns it. - #[account(has_one = owner)] + #[account(address = reserve.lending_market)] pub lending_market: BorshAccount, - #[account(mut)] + #[account(mut, address = lending_market.owner)] pub owner: Signer, - #[account( - mut, - has_one = lending_market, - has_one = liquidity_mint, - has_one = liquidity_vault, - )] + #[account(mut)] pub reserve: BorshAccount, + #[account(address = reserve.liquidity_mint)] pub liquidity_mint: InterfaceAccount, - #[account(mut)] + #[account(mut, address = reserve.liquidity_vault)] pub liquidity_vault: InterfaceAccount, #[account(mut)] diff --git a/finance/lending/anchor/programs/lending/src/instructions/admin/initialize_lending_market.rs b/finance/lending/anchor/programs/lending/src/instructions/admin/initialize_lending_market.rs index acb8e03c0..0eff12db4 100644 --- a/finance/lending/anchor/programs/lending/src/instructions/admin/initialize_lending_market.rs +++ b/finance/lending/anchor/programs/lending/src/instructions/admin/initialize_lending_market.rs @@ -21,7 +21,7 @@ pub fn handle_initialize_lending_market( pub struct InitializeLendingMarket { // Seeded by `market_id` alone — the market is not identified by any // individual's address. `owner` is stored as a field and used only for - // authorization (`has_one = owner`) on admin instructions. + // authorization (`address = lending_market.owner`) on admin instructions. #[account( init, payer = owner, diff --git a/finance/lending/anchor/programs/lending/src/instructions/admin/initialize_reserve.rs b/finance/lending/anchor/programs/lending/src/instructions/admin/initialize_reserve.rs index 650a32829..e0e2be7a6 100644 --- a/finance/lending/anchor/programs/lending/src/instructions/admin/initialize_reserve.rs +++ b/finance/lending/anchor/programs/lending/src/instructions/admin/initialize_reserve.rs @@ -36,10 +36,9 @@ pub fn handle_initialize_reserve( pub struct InitializeReserve { // The reserve PDA below is seeded by this market's address, so the market is // pinned by that seed; we only need to prove the signer owns it. - #[account(has_one = owner)] pub lending_market: BorshAccount, - #[account(mut)] + #[account(mut, address = lending_market.owner)] pub owner: Signer, #[account( diff --git a/finance/lending/anchor/programs/lending/src/instructions/admin/set_price.rs b/finance/lending/anchor/programs/lending/src/instructions/admin/set_price.rs index 6f4ff159f..7918e0193 100644 --- a/finance/lending/anchor/programs/lending/src/instructions/admin/set_price.rs +++ b/finance/lending/anchor/programs/lending/src/instructions/admin/set_price.rs @@ -29,10 +29,9 @@ pub fn handle_set_price( #[derive(Accounts)] pub struct SetPrice { // Only the market's owner may publish its prices. - #[account(has_one = owner)] pub lending_market: BorshAccount, - #[account(mut)] + #[account(mut, address = lending_market.owner)] pub owner: Signer, #[account( diff --git a/finance/lending/anchor/programs/lending/src/instructions/admin/update_reserve_config.rs b/finance/lending/anchor/programs/lending/src/instructions/admin/update_reserve_config.rs index 9e68b917b..ddcf53dd0 100644 --- a/finance/lending/anchor/programs/lending/src/instructions/admin/update_reserve_config.rs +++ b/finance/lending/anchor/programs/lending/src/instructions/admin/update_reserve_config.rs @@ -13,16 +13,14 @@ pub fn handle_update_reserve_config( #[derive(Accounts)] pub struct UpdateReserveConfig { - // The market is identified by the reserve's `has_one = lending_market`; we + // The market is identified by `address = reserve.lending_market`; we // only need to prove the signer owns it, not re-derive its address. - #[account(has_one = owner)] + #[account(address = reserve.lending_market)] pub lending_market: BorshAccount, + #[account(address = lending_market.owner)] pub owner: Signer, - #[account( - mut, - has_one = lending_market, - )] + #[account(mut)] pub reserve: BorshAccount, } diff --git a/finance/lending/anchor/programs/lending/src/instructions/borrow_obligation_liquidity.rs b/finance/lending/anchor/programs/lending/src/instructions/borrow_obligation_liquidity.rs index 213517a19..5a83f4a9f 100644 --- a/finance/lending/anchor/programs/lending/src/instructions/borrow_obligation_liquidity.rs +++ b/finance/lending/anchor/programs/lending/src/instructions/borrow_obligation_liquidity.rs @@ -93,25 +93,25 @@ pub fn handle_borrow_obligation_liquidity( #[derive(Accounts)] pub struct BorrowObligationLiquidity { - #[account(mut, has_one = owner)] + #[account(mut)] pub obligation: BorshAccount, + #[account(address = obligation.owner)] pub owner: Signer, #[account( mut, - has_one = liquidity_mint, - has_one = liquidity_vault, - has_one = price_feed, constraint = reserve.lending_market == obligation.lending_market @ LendingError::MarketMismatch, )] pub reserve: BorshAccount, + #[account(address = reserve.price_feed)] pub price_feed: BorshAccount, + #[account(address = reserve.liquidity_mint)] pub liquidity_mint: InterfaceAccount, - #[account(mut)] + #[account(mut, address = reserve.liquidity_vault)] pub liquidity_vault: InterfaceAccount, #[account(mut)] diff --git a/finance/lending/anchor/programs/lending/src/instructions/deposit_obligation_collateral.rs b/finance/lending/anchor/programs/lending/src/instructions/deposit_obligation_collateral.rs index 4905a64fd..60c8e3cd0 100644 --- a/finance/lending/anchor/programs/lending/src/instructions/deposit_obligation_collateral.rs +++ b/finance/lending/anchor/programs/lending/src/instructions/deposit_obligation_collateral.rs @@ -46,18 +46,18 @@ pub fn handle_deposit_obligation_collateral( #[derive(Accounts)] pub struct DepositObligationCollateral { - #[account(mut, has_one = owner)] + #[account(mut)] pub obligation: BorshAccount, - #[account(mut)] + #[account(mut, address = obligation.owner)] pub owner: Signer, #[account( - has_one = share_mint, constraint = reserve.lending_market == obligation.lending_market @ LendingError::MarketMismatch, )] pub reserve: BorshAccount, + #[account(address = reserve.share_mint)] pub share_mint: InterfaceAccount, #[account( diff --git a/finance/lending/anchor/programs/lending/src/instructions/deposit_reserve_liquidity.rs b/finance/lending/anchor/programs/lending/src/instructions/deposit_reserve_liquidity.rs index 4c8831004..4fc84d02d 100644 --- a/finance/lending/anchor/programs/lending/src/instructions/deposit_reserve_liquidity.rs +++ b/finance/lending/anchor/programs/lending/src/instructions/deposit_reserve_liquidity.rs @@ -76,20 +76,16 @@ pub fn handle_deposit_reserve_liquidity( #[derive(Accounts)] pub struct DepositReserveLiquidity { - #[account( - mut, - has_one = liquidity_mint, - has_one = liquidity_vault, - has_one = share_mint, - )] + #[account(mut)] pub reserve: BorshAccount, + #[account(address = reserve.liquidity_mint)] pub liquidity_mint: InterfaceAccount, - #[account(mut)] + #[account(mut, address = reserve.liquidity_vault)] pub liquidity_vault: InterfaceAccount, - #[account(mut)] + #[account(mut, address = reserve.share_mint)] pub share_mint: InterfaceAccount, #[account(mut)] diff --git a/finance/lending/anchor/programs/lending/src/instructions/redeem_reserve_collateral.rs b/finance/lending/anchor/programs/lending/src/instructions/redeem_reserve_collateral.rs index 0a45cd757..a0e910b8e 100644 --- a/finance/lending/anchor/programs/lending/src/instructions/redeem_reserve_collateral.rs +++ b/finance/lending/anchor/programs/lending/src/instructions/redeem_reserve_collateral.rs @@ -76,20 +76,16 @@ pub fn handle_redeem_reserve_collateral( #[derive(Accounts)] pub struct RedeemReserveCollateral { - #[account( - mut, - has_one = liquidity_mint, - has_one = liquidity_vault, - has_one = share_mint, - )] + #[account(mut)] pub reserve: BorshAccount, + #[account(address = reserve.liquidity_mint)] pub liquidity_mint: InterfaceAccount, - #[account(mut)] + #[account(mut, address = reserve.liquidity_vault)] pub liquidity_vault: InterfaceAccount, - #[account(mut)] + #[account(mut, address = reserve.share_mint)] pub share_mint: InterfaceAccount, #[account(mut)] diff --git a/finance/lending/anchor/programs/lending/src/instructions/repay_obligation_liquidity.rs b/finance/lending/anchor/programs/lending/src/instructions/repay_obligation_liquidity.rs index 163cf2a1f..522a77eb1 100644 --- a/finance/lending/anchor/programs/lending/src/instructions/repay_obligation_liquidity.rs +++ b/finance/lending/anchor/programs/lending/src/instructions/repay_obligation_liquidity.rs @@ -81,15 +81,14 @@ pub struct RepayObligationLiquidity { #[account( mut, - has_one = liquidity_mint, - has_one = liquidity_vault, constraint = reserve.lending_market == obligation.lending_market @ LendingError::MarketMismatch, )] pub reserve: BorshAccount, + #[account(address = reserve.liquidity_mint)] pub liquidity_mint: InterfaceAccount, - #[account(mut)] + #[account(mut, address = reserve.liquidity_vault)] pub liquidity_vault: InterfaceAccount, #[account(mut)] diff --git a/finance/lending/anchor/programs/lending/src/instructions/withdraw_obligation_collateral.rs b/finance/lending/anchor/programs/lending/src/instructions/withdraw_obligation_collateral.rs index 00c6f13c6..a2e75df0b 100644 --- a/finance/lending/anchor/programs/lending/src/instructions/withdraw_obligation_collateral.rs +++ b/finance/lending/anchor/programs/lending/src/instructions/withdraw_obligation_collateral.rs @@ -105,20 +105,21 @@ pub fn handle_withdraw_obligation_collateral( #[derive(Accounts)] pub struct WithdrawObligationCollateral { - #[account(mut, has_one = owner)] + #[account(mut)] pub obligation: BorshAccount, + #[account(address = obligation.owner)] pub owner: Signer, #[account( - has_one = share_mint, - has_one = price_feed, constraint = reserve.lending_market == obligation.lending_market @ LendingError::MarketMismatch, )] pub reserve: BorshAccount, + #[account(address = reserve.price_feed)] pub price_feed: BorshAccount, + #[account(address = reserve.share_mint)] pub share_mint: InterfaceAccount, #[account( diff --git a/finance/lending/anchor/programs/lending/tests/test_security.rs b/finance/lending/anchor/programs/lending/tests/test_security.rs index da65879ca..e298ded1a 100644 --- a/finance/lending/anchor/programs/lending/tests/test_security.rs +++ b/finance/lending/anchor/programs/lending/tests/test_security.rs @@ -46,7 +46,7 @@ fn non_owner_cannot_write_market_price_feed() { // The market's feed for this mint (seeds ["price_feed", market, mint]). let market_feed = env.price_feed_address(env.market, usdc.mint); - // The attacker passes the real market but signs as themself; `has_one = owner` + // The attacker passes the real market but signs as themself; `address = lending_market.owner` // on the market rejects them before any write. let instruction = Instruction { program_id: lending::id(), diff --git a/finance/order-book/anchor/programs/order-book/src/instructions/admin/withdraw_fees.rs b/finance/order-book/anchor/programs/order-book/src/instructions/admin/withdraw_fees.rs index 8b4757484..0a5932709 100644 --- a/finance/order-book/anchor/programs/order-book/src/instructions/admin/withdraw_fees.rs +++ b/finance/order-book/anchor/programs/order-book/src/instructions/admin/withdraw_fees.rs @@ -56,14 +56,11 @@ pub fn handle_withdraw_fees(context: &mut Context, // Boxed to keep the struct under the BPF stack limit (see PlaceOrderAccountConstraints). - #[account(mut)] + #[account(mut, address = market.fee_vault @ ErrorCode::InvalidFeeVault)] pub fee_vault: Box>, #[account(mut)] diff --git a/finance/order-book/anchor/programs/order-book/src/instructions/cancel_order.rs b/finance/order-book/anchor/programs/order-book/src/instructions/cancel_order.rs index 4e3a210c9..3f09e6d89 100644 --- a/finance/order-book/anchor/programs/order-book/src/instructions/cancel_order.rs +++ b/finance/order-book/anchor/programs/order-book/src/instructions/cancel_order.rs @@ -73,11 +73,10 @@ pub fn handle_cancel_order(context: &mut Context) #[derive(Accounts)] pub struct CancelOrderAccountConstraints { - #[account(has_one = order_book @ ErrorCode::InvalidOrderBook)] pub market: BorshAccount, - // Not a PDA (see initialize_market.rs); bound to `market` via has_one. - #[account(mut)] + // Not a PDA (see initialize_market.rs); bound to `market` via `address`. + #[account(mut, address = market.order_book @ ErrorCode::InvalidOrderBook)] pub order_book: Account, #[account( diff --git a/finance/order-book/anchor/programs/order-book/src/instructions/initialize_market.rs b/finance/order-book/anchor/programs/order-book/src/instructions/initialize_market.rs index a1ae52b0d..c76b7661d 100644 --- a/finance/order-book/anchor/programs/order-book/src/instructions/initialize_market.rs +++ b/finance/order-book/anchor/programs/order-book/src/instructions/initialize_market.rs @@ -78,7 +78,7 @@ pub struct InitializeMarketAccountConstraints { // This is not a PDA. create_account requires the new account to sign // its own creation, and a PDA has no private key to sign with, so the // client must generate a real keypair for it. The program ties this - // account to its market via `has_one = order_book` on `market`, not via + // account to its market via `address = market.order_book`, not via // seeds. #[account(zeroed)] pub order_book: Account, diff --git a/finance/order-book/anchor/programs/order-book/src/instructions/place_order.rs b/finance/order-book/anchor/programs/order-book/src/instructions/place_order.rs index 482299063..5a25048cc 100644 --- a/finance/order-book/anchor/programs/order-book/src/instructions/place_order.rs +++ b/finance/order-book/anchor/programs/order-book/src/instructions/place_order.rs @@ -440,30 +440,22 @@ pub fn handle_place_order( #[derive(Accounts)] #[instruction(side: OrderSide, price: u64, quantity: u64)] pub struct PlaceOrderAccountConstraints { - // `has_one` ties every market-owned account on this struct to the + // `address` ties every market-owned account on this struct to the // addresses recorded on the Market PDA. Crucially, without - // has_one on base_vault / quote_vault / base_mint / quote_mint a caller + // address constraints on base_vault / quote_vault / base_mint / quote_mint a caller // could swap fee_vault in for quote_vault (same mint, same authority) // and steer the per-fill fee transfer to drain real fees instead of // routing them in. - #[account( - mut, - has_one = fee_vault @ ErrorCode::InvalidFeeVault, - has_one = base_vault @ ErrorCode::InvalidBaseVault, - has_one = quote_vault @ ErrorCode::InvalidQuoteVault, - has_one = base_mint @ ErrorCode::InvalidBaseMint, - has_one = quote_mint @ ErrorCode::InvalidQuoteMint, - has_one = order_book @ ErrorCode::InvalidOrderBook, - )] + #[account(mut)] pub market: BorshAccount, // Zero-copy: AccountLoader streams the slab in/out without paying // borsh (de)serialization on every instruction. See order_book.rs for // the layout. Not a PDA - the client created it directly via // system_program::create_account (see initialize_market.rs for why); - // `has_one = order_book` on `market` is what ties this specific account + // `address = market.order_book` is what ties this specific account // to this specific market. - #[account(mut)] + #[account(mut, address = market.order_book @ ErrorCode::InvalidOrderBook)] pub order_book: Account, // The order PDA seed uses the book's `next_order_id` *before* this @@ -491,15 +483,15 @@ pub struct PlaceOrderAccountConstraints { // InterfaceAccount on the stack is ~1 KB each; with 7 of them this struct // blows the 4 KB stack-offset limit on BPF. Boxing moves each to the heap. - #[account(mut)] + #[account(mut, address = market.base_vault @ ErrorCode::InvalidBaseVault)] pub base_vault: Box>, - #[account(mut)] + #[account(mut, address = market.quote_vault @ ErrorCode::InvalidQuoteVault)] pub quote_vault: Box>, - // Taker fees are routed here. Constrained via `has_one = fee_vault` on + // Taker fees are routed here. Constrained via `address = market.fee_vault` on // `market` above so the program can trust it without re-checking. - #[account(mut)] + #[account(mut, address = market.fee_vault @ ErrorCode::InvalidFeeVault)] pub fee_vault: Box>, #[account(mut)] @@ -508,8 +500,10 @@ pub struct PlaceOrderAccountConstraints { #[account(mut)] pub user_quote_account: Box>, + #[account(address = market.base_mint @ ErrorCode::InvalidBaseMint)] pub base_mint: Box>, + #[account(address = market.quote_mint @ ErrorCode::InvalidQuoteMint)] pub quote_mint: Box>, #[account(mut)] diff --git a/finance/order-book/anchor/programs/order-book/src/instructions/settle_funds.rs b/finance/order-book/anchor/programs/order-book/src/instructions/settle_funds.rs index c5b8b8c80..90752ddb5 100644 --- a/finance/order-book/anchor/programs/order-book/src/instructions/settle_funds.rs +++ b/finance/order-book/anchor/programs/order-book/src/instructions/settle_funds.rs @@ -72,19 +72,13 @@ pub fn handle_settle_funds(context: &mut Context) #[derive(Accounts)] pub struct SettleFundsAccountConstraints { - // `has_one` constraints bind these vaults/mints to the addresses stored + // `address` constraints bind these vaults/mints to the addresses stored // on the Market PDA at initialise_market time. Without them a caller // could substitute the fee_vault (same mint + same authority as // quote_vault) for `quote_vault` and drain accumulated taker fees, // since transfer_checked only verifies mint + authority on the source // account, not its identity. - #[account( - mut, - has_one = base_vault @ ErrorCode::InvalidBaseVault, - has_one = quote_vault @ ErrorCode::InvalidQuoteVault, - has_one = base_mint @ ErrorCode::InvalidBaseMint, - has_one = quote_mint @ ErrorCode::InvalidQuoteMint, - )] + #[account(mut)] pub market: BorshAccount, #[account( @@ -96,10 +90,10 @@ pub struct SettleFundsAccountConstraints { // Boxed for the same reason as in PlaceOrderAccountConstraints - // InterfaceAccount is too large to keep on the BPF stack in bulk. - #[account(mut)] + #[account(mut, address = market.base_vault @ ErrorCode::InvalidBaseVault)] pub base_vault: Box>, - #[account(mut)] + #[account(mut, address = market.quote_vault @ ErrorCode::InvalidQuoteVault)] pub quote_vault: Box>, #[account(mut)] @@ -108,8 +102,10 @@ pub struct SettleFundsAccountConstraints { #[account(mut)] pub user_quote_account: Box>, + #[account(address = market.base_mint @ ErrorCode::InvalidBaseMint)] pub base_mint: Box>, + #[account(address = market.quote_mint @ ErrorCode::InvalidQuoteMint)] pub quote_mint: Box>, pub owner: Signer, diff --git a/finance/order-book/anchor/programs/order-book/src/state/order_book.rs b/finance/order-book/anchor/programs/order-book/src/state/order_book.rs index b1cade650..35438c13b 100644 --- a/finance/order-book/anchor/programs/order-book/src/state/order_book.rs +++ b/finance/order-book/anchor/programs/order-book/src/state/order_book.rs @@ -27,7 +27,7 @@ pub const MAX_ORDERS_PER_SIDE: usize = MAX_TREE_NODES; #[repr(C)] pub struct OrderBook { /// Market PDA this book belongs to. Constrained onchain via the - /// `market` `has_one = order_book` (and vice-versa) bindings. + /// `market` / `order_book` address bindings. pub market: Address, /// Tree roots for the two sides. Kept on this struct (rather than inside diff --git a/finance/order-book/anchor/programs/order-book/tests/test_order_book.rs b/finance/order-book/anchor/programs/order-book/tests/test_order_book.rs index 160725a25..01c2da90c 100644 --- a/finance/order-book/anchor/programs/order-book/tests/test_order_book.rs +++ b/finance/order-book/anchor/programs/order-book/tests/test_order_book.rs @@ -1027,10 +1027,10 @@ fn cancel_and_settle_bid_refunds_full_quote() { // Regression test for the fee-drain attack on settle_funds. Pre-fix, // `SettleFundsAccountConstraints` did not bind `quote_vault` to `market.quote_vault` via -// `has_one`, so a caller could pass `market.fee_vault` (same mint and +// an address constraint, so a caller could pass `market.fee_vault` (same mint and // same authority) where `quote_vault` was expected and drain accumulated // taker fees while spending their own unsettled_quote credit. The -// has_one constraint now bound on the `market` field must surface this +// address constraint now bound on the vault field must surface this // as `ConstraintHasOne` (anchor error 2001) before any transfer runs. #[test] fn settle_funds_rejects_fee_vault_substituted_for_quote_vault() { @@ -1066,7 +1066,7 @@ fn settle_funds_rejects_fee_vault_substituted_for_quote_vault() { // Build a settle_funds ix but swap fee_vault in for quote_vault. // Everything else (base_vault, mints, user accounts, owner) stays // correct, so the only thing that should reject this is the new - // has_one constraint on the market PDA. + // address constraint tying the vault to the market PDA. let attack_ix = Instruction::new_with_bytes( sc.program_id, &order_book::instruction::SettleFunds {}.data(), diff --git a/finance/perpetual-futures/anchor/programs/mock-switchboard/src/lib.rs b/finance/perpetual-futures/anchor/programs/mock-switchboard/src/lib.rs index a8026d835..f2f638fc5 100644 --- a/finance/perpetual-futures/anchor/programs/mock-switchboard/src/lib.rs +++ b/finance/perpetual-futures/anchor/programs/mock-switchboard/src/lib.rs @@ -72,12 +72,10 @@ pub struct InitializeFeedAccountConstraints { #[derive(Accounts)] pub struct SetPriceAccountConstraints { - #[account( - mut, - has_one = authority, - )] + #[account(mut)] pub feed: BorshAccount, + #[account(address = feed.authority)] pub authority: Signer, } diff --git a/finance/perpetual-futures/anchor/programs/perpetual-futures/src/instructions/add_liquidity.rs b/finance/perpetual-futures/anchor/programs/perpetual-futures/src/instructions/add_liquidity.rs index 915f98b81..c5f64d9c6 100644 --- a/finance/perpetual-futures/anchor/programs/perpetual-futures/src/instructions/add_liquidity.rs +++ b/finance/perpetual-futures/anchor/programs/perpetual-futures/src/instructions/add_liquidity.rs @@ -92,10 +92,6 @@ pub struct AddLiquidityAccountConstraints { mut, seeds = [POOL_SEED, pool.collateral_mint.as_ref(), pool.oracle_feed.as_ref()], bump = pool.bump, - has_one = collateral_mint, - has_one = lp_mint, - has_one = custody_vault, - has_one = oracle_feed, )] pub pool: Box>, @@ -106,18 +102,21 @@ pub struct AddLiquidityAccountConstraints { )] pub pool_authority: UncheckedAccount, - /// CHECK: validated by the `has_one = oracle_feed` constraint on the pool. + /// CHECK: validated by the `address = pool.oracle_feed` constraint below. + #[account(address = pool.oracle_feed)] pub oracle_feed: UncheckedAccount, + #[account(address = pool.collateral_mint)] pub collateral_mint: Box>, - #[account(mut)] + #[account(mut, address = pool.lp_mint)] pub lp_mint: Box>, #[account( mut, seeds = [VAULT_SEED, pool.address().as_ref()], bump, + address = pool.custody_vault, )] pub custody_vault: Box>, diff --git a/finance/perpetual-futures/anchor/programs/perpetual-futures/src/instructions/close_position.rs b/finance/perpetual-futures/anchor/programs/perpetual-futures/src/instructions/close_position.rs index c748f7e22..1b51778d3 100644 --- a/finance/perpetual-futures/anchor/programs/perpetual-futures/src/instructions/close_position.rs +++ b/finance/perpetual-futures/anchor/programs/perpetual-futures/src/instructions/close_position.rs @@ -88,16 +88,14 @@ pub fn handle_close_position( #[derive(Accounts)] pub struct ClosePositionAccountConstraints { - #[account(mut)] + #[account(mut, address = position.owner)] pub owner: Signer, #[account( mut, seeds = [POOL_SEED, pool.collateral_mint.as_ref(), pool.oracle_feed.as_ref()], bump = pool.bump, - has_one = collateral_mint, - has_one = custody_vault, - has_one = oracle_feed, + address = position.pool, )] pub pool: Box>, @@ -106,8 +104,6 @@ pub struct ClosePositionAccountConstraints { close = owner, seeds = [POSITION_SEED, pool.address().as_ref(), owner.address().as_ref(), position.side.as_seed()], bump = position.bump, - has_one = owner, - has_one = pool, )] pub position: Box>, @@ -118,15 +114,18 @@ pub struct ClosePositionAccountConstraints { )] pub pool_authority: UncheckedAccount, - /// CHECK: validated by the `has_one = oracle_feed` constraint on the pool. + /// CHECK: validated by the `address = pool.oracle_feed` constraint below. + #[account(address = pool.oracle_feed)] pub oracle_feed: UncheckedAccount, + #[account(address = pool.collateral_mint)] pub collateral_mint: Box>, #[account( mut, seeds = [VAULT_SEED, pool.address().as_ref()], bump, + address = pool.custody_vault, )] pub custody_vault: Box>, diff --git a/finance/perpetual-futures/anchor/programs/perpetual-futures/src/instructions/collect_fees.rs b/finance/perpetual-futures/anchor/programs/perpetual-futures/src/instructions/collect_fees.rs index 1eb23c309..4a30485fc 100644 --- a/finance/perpetual-futures/anchor/programs/perpetual-futures/src/instructions/collect_fees.rs +++ b/finance/perpetual-futures/anchor/programs/perpetual-futures/src/instructions/collect_fees.rs @@ -38,16 +38,13 @@ pub fn handle_collect_fees(context: &mut Context) #[derive(Accounts)] pub struct CollectFeesAccountConstraints { - #[account(mut)] + #[account(mut, address = pool.authority)] pub authority: Signer, #[account( mut, seeds = [POOL_SEED, pool.collateral_mint.as_ref(), pool.oracle_feed.as_ref()], bump = pool.bump, - has_one = authority, - has_one = collateral_mint, - has_one = custody_vault, )] pub pool: Box>, @@ -58,12 +55,14 @@ pub struct CollectFeesAccountConstraints { )] pub pool_authority: UncheckedAccount, + #[account(address = pool.collateral_mint)] pub collateral_mint: Box>, #[account( mut, seeds = [VAULT_SEED, pool.address().as_ref()], bump, + address = pool.custody_vault, )] pub custody_vault: Box>, diff --git a/finance/perpetual-futures/anchor/programs/perpetual-futures/src/instructions/liquidate_position.rs b/finance/perpetual-futures/anchor/programs/perpetual-futures/src/instructions/liquidate_position.rs index d8d4aaa09..2e88553ae 100644 --- a/finance/perpetual-futures/anchor/programs/perpetual-futures/src/instructions/liquidate_position.rs +++ b/finance/perpetual-futures/anchor/programs/perpetual-futures/src/instructions/liquidate_position.rs @@ -105,18 +105,16 @@ pub struct LiquidatePositionAccountConstraints { #[account(mut)] pub liquidator: Signer, - /// CHECK: the position owner, validated by the position's `has_one = owner`. + /// CHECK: the position owner, validated by `address = position.owner` below. /// Receives the position account's rent and any equity refund. - #[account(mut)] + #[account(mut, address = position.owner)] pub owner: UncheckedAccount, #[account( mut, seeds = [POOL_SEED, pool.collateral_mint.as_ref(), pool.oracle_feed.as_ref()], bump = pool.bump, - has_one = collateral_mint, - has_one = custody_vault, - has_one = oracle_feed, + address = position.pool, )] pub pool: Box>, @@ -125,8 +123,6 @@ pub struct LiquidatePositionAccountConstraints { close = owner, seeds = [POSITION_SEED, pool.address().as_ref(), owner.address().as_ref(), position.side.as_seed()], bump = position.bump, - has_one = owner, - has_one = pool, )] pub position: Box>, @@ -137,15 +133,18 @@ pub struct LiquidatePositionAccountConstraints { )] pub pool_authority: UncheckedAccount, - /// CHECK: validated by the `has_one = oracle_feed` constraint on the pool. + /// CHECK: validated by the `address = pool.oracle_feed` constraint below. + #[account(address = pool.oracle_feed)] pub oracle_feed: UncheckedAccount, + #[account(address = pool.collateral_mint)] pub collateral_mint: Box>, #[account( mut, seeds = [VAULT_SEED, pool.address().as_ref()], bump, + address = pool.custody_vault, )] pub custody_vault: Box>, diff --git a/finance/perpetual-futures/anchor/programs/perpetual-futures/src/instructions/open_position.rs b/finance/perpetual-futures/anchor/programs/perpetual-futures/src/instructions/open_position.rs index 003fb970f..3a7ac4917 100644 --- a/finance/perpetual-futures/anchor/programs/perpetual-futures/src/instructions/open_position.rs +++ b/finance/perpetual-futures/anchor/programs/perpetual-futures/src/instructions/open_position.rs @@ -135,9 +135,6 @@ pub struct OpenPositionAccountConstraints { mut, seeds = [POOL_SEED, pool.collateral_mint.as_ref(), pool.oracle_feed.as_ref()], bump = pool.bump, - has_one = collateral_mint, - has_one = custody_vault, - has_one = oracle_feed, )] pub pool: Box>, @@ -150,15 +147,18 @@ pub struct OpenPositionAccountConstraints { )] pub position: Box>, - /// CHECK: validated by the `has_one = oracle_feed` constraint on the pool. + /// CHECK: validated by the `address = pool.oracle_feed` constraint below. + #[account(address = pool.oracle_feed)] pub oracle_feed: UncheckedAccount, + #[account(address = pool.collateral_mint)] pub collateral_mint: Box>, #[account( mut, seeds = [VAULT_SEED, pool.address().as_ref()], bump, + address = pool.custody_vault, )] pub custody_vault: Box>, diff --git a/finance/perpetual-futures/anchor/programs/perpetual-futures/src/instructions/remove_liquidity.rs b/finance/perpetual-futures/anchor/programs/perpetual-futures/src/instructions/remove_liquidity.rs index e7ccf2bf6..c15f61610 100644 --- a/finance/perpetual-futures/anchor/programs/perpetual-futures/src/instructions/remove_liquidity.rs +++ b/finance/perpetual-futures/anchor/programs/perpetual-futures/src/instructions/remove_liquidity.rs @@ -97,10 +97,6 @@ pub struct RemoveLiquidityAccountConstraints { mut, seeds = [POOL_SEED, pool.collateral_mint.as_ref(), pool.oracle_feed.as_ref()], bump = pool.bump, - has_one = collateral_mint, - has_one = lp_mint, - has_one = custody_vault, - has_one = oracle_feed, )] pub pool: Box>, @@ -111,18 +107,21 @@ pub struct RemoveLiquidityAccountConstraints { )] pub pool_authority: UncheckedAccount, - /// CHECK: validated by the `has_one = oracle_feed` constraint on the pool. + /// CHECK: validated by the `address = pool.oracle_feed` constraint below. + #[account(address = pool.oracle_feed)] pub oracle_feed: UncheckedAccount, + #[account(address = pool.collateral_mint)] pub collateral_mint: Box>, - #[account(mut)] + #[account(mut, address = pool.lp_mint)] pub lp_mint: Box>, #[account( mut, seeds = [VAULT_SEED, pool.address().as_ref()], bump, + address = pool.custody_vault, )] pub custody_vault: Box>, diff --git a/finance/prop-amm/anchor/programs/mock-switchboard/src/lib.rs b/finance/prop-amm/anchor/programs/mock-switchboard/src/lib.rs index 8c314c624..03ae2006a 100644 --- a/finance/prop-amm/anchor/programs/mock-switchboard/src/lib.rs +++ b/finance/prop-amm/anchor/programs/mock-switchboard/src/lib.rs @@ -72,12 +72,10 @@ pub struct InitializeFeedAccountConstraints { #[derive(Accounts)] pub struct SetPriceAccountConstraints { - #[account( - mut, - has_one = authority, - )] + #[account(mut)] pub feed: BorshAccount, + #[account(address = feed.authority)] pub authority: Signer, } diff --git a/finance/prop-amm/anchor/programs/prop-amm/src/instructions/deposit_inventory.rs b/finance/prop-amm/anchor/programs/prop-amm/src/instructions/deposit_inventory.rs index 67bec9f4c..883dd718a 100644 --- a/finance/prop-amm/anchor/programs/prop-amm/src/instructions/deposit_inventory.rs +++ b/finance/prop-amm/anchor/programs/prop-amm/src/instructions/deposit_inventory.rs @@ -54,30 +54,28 @@ pub fn handle_deposit_inventory( #[derive(Accounts)] pub struct DepositInventoryAccountConstraints { - // `has_one = operator` on the market is the whole access control: only the + // `address = market.operator` on the operator is the whole access control: only the // firm's key can stock the market. - #[account(mut)] + #[account(mut, address = market.operator)] pub operator: Signer, #[account( seeds = [MARKET_SEED, market.base_mint.as_ref(), market.quote_mint.as_ref()], bump = market.bump, - has_one = operator, - has_one = base_mint, - has_one = quote_mint, - has_one = base_vault, - has_one = quote_vault, )] pub market: Box>, + #[account(address = market.base_mint)] pub base_mint: Box>, + #[account(address = market.quote_mint)] pub quote_mint: Box>, #[account( mut, seeds = [BASE_VAULT_SEED, market.address().as_ref()], bump, + address = market.base_vault, )] pub base_vault: Box>, @@ -85,6 +83,7 @@ pub struct DepositInventoryAccountConstraints { mut, seeds = [QUOTE_VAULT_SEED, market.address().as_ref()], bump, + address = market.quote_vault, )] pub quote_vault: Box>, diff --git a/finance/prop-amm/anchor/programs/prop-amm/src/instructions/set_quote.rs b/finance/prop-amm/anchor/programs/prop-amm/src/instructions/set_quote.rs index b571fb062..265bfb31e 100644 --- a/finance/prop-amm/anchor/programs/prop-amm/src/instructions/set_quote.rs +++ b/finance/prop-amm/anchor/programs/prop-amm/src/instructions/set_quote.rs @@ -32,13 +32,13 @@ pub fn handle_set_quote( #[derive(Accounts)] pub struct SetQuoteAccountConstraints { + #[account(address = market.operator)] pub operator: Signer, #[account( mut, seeds = [MARKET_SEED, market.base_mint.as_ref(), market.quote_mint.as_ref()], bump = market.bump, - has_one = operator, )] pub market: Box>, } diff --git a/finance/prop-amm/anchor/programs/prop-amm/src/instructions/swap.rs b/finance/prop-amm/anchor/programs/prop-amm/src/instructions/swap.rs index c20b10380..122bebb71 100644 --- a/finance/prop-amm/anchor/programs/prop-amm/src/instructions/swap.rs +++ b/finance/prop-amm/anchor/programs/prop-amm/src/instructions/swap.rs @@ -191,11 +191,6 @@ pub struct SwapAccountConstraints { #[account( seeds = [MARKET_SEED, market.base_mint.as_ref(), market.quote_mint.as_ref()], bump = market.bump, - has_one = base_mint, - has_one = quote_mint, - has_one = oracle_feed, - has_one = base_vault, - has_one = quote_vault, )] pub market: Box>, @@ -206,17 +201,21 @@ pub struct SwapAccountConstraints { )] pub market_authority: UncheckedAccount, - /// CHECK: validated by the `has_one = oracle_feed` constraint on the market. + /// CHECK: validated by the `address = market.oracle_feed` constraint below. + #[account(address = market.oracle_feed)] pub oracle_feed: UncheckedAccount, + #[account(address = market.base_mint)] pub base_mint: Box>, + #[account(address = market.quote_mint)] pub quote_mint: Box>, #[account( mut, seeds = [BASE_VAULT_SEED, market.address().as_ref()], bump, + address = market.base_vault, )] pub base_vault: Box>, @@ -224,6 +223,7 @@ pub struct SwapAccountConstraints { mut, seeds = [QUOTE_VAULT_SEED, market.address().as_ref()], bump, + address = market.quote_vault, )] pub quote_vault: Box>, diff --git a/finance/prop-amm/anchor/programs/prop-amm/src/instructions/withdraw_inventory.rs b/finance/prop-amm/anchor/programs/prop-amm/src/instructions/withdraw_inventory.rs index 06a56e19f..f4d117980 100644 --- a/finance/prop-amm/anchor/programs/prop-amm/src/instructions/withdraw_inventory.rs +++ b/finance/prop-amm/anchor/programs/prop-amm/src/instructions/withdraw_inventory.rs @@ -78,17 +78,12 @@ pub fn handle_withdraw_inventory( #[derive(Accounts)] pub struct WithdrawInventoryAccountConstraints { - #[account(mut)] + #[account(mut, address = market.operator)] pub operator: Signer, #[account( seeds = [MARKET_SEED, market.base_mint.as_ref(), market.quote_mint.as_ref()], bump = market.bump, - has_one = operator, - has_one = base_mint, - has_one = quote_mint, - has_one = base_vault, - has_one = quote_vault, )] pub market: Box>, @@ -99,14 +94,17 @@ pub struct WithdrawInventoryAccountConstraints { )] pub market_authority: UncheckedAccount, + #[account(address = market.base_mint)] pub base_mint: Box>, + #[account(address = market.quote_mint)] pub quote_mint: Box>, #[account( mut, seeds = [BASE_VAULT_SEED, market.address().as_ref()], bump, + address = market.base_vault, )] pub base_vault: Box>, @@ -114,6 +112,7 @@ pub struct WithdrawInventoryAccountConstraints { mut, seeds = [QUOTE_VAULT_SEED, market.address().as_ref()], bump, + address = market.quote_vault, )] pub quote_vault: Box>, diff --git a/finance/token-fundraiser/anchor/programs/fundraiser/src/instructions/close.rs b/finance/token-fundraiser/anchor/programs/fundraiser/src/instructions/close.rs index 9b28e1313..fd20a3a48 100644 --- a/finance/token-fundraiser/anchor/programs/fundraiser/src/instructions/close.rs +++ b/finance/token-fundraiser/anchor/programs/fundraiser/src/instructions/close.rs @@ -14,11 +14,11 @@ pub struct CloseFundraiserAccountConstraints { #[account(mut)] pub maker: Signer, + #[account(address = fundraiser.mint_to_raise)] pub mint_to_raise: InterfaceAccount, #[account( mut, - has_one = mint_to_raise, seeds = [b"fundraiser".as_ref(), maker.address().as_ref()], bump = fundraiser.bump, close = maker, diff --git a/finance/token-fundraiser/anchor/programs/fundraiser/src/instructions/contribute.rs b/finance/token-fundraiser/anchor/programs/fundraiser/src/instructions/contribute.rs index 09dedd209..d6b7e306f 100644 --- a/finance/token-fundraiser/anchor/programs/fundraiser/src/instructions/contribute.rs +++ b/finance/token-fundraiser/anchor/programs/fundraiser/src/instructions/contribute.rs @@ -13,11 +13,11 @@ pub struct ContributeAccountConstraints { #[account(mut)] pub contributor: Signer, + #[account(address = fundraiser.mint_to_raise)] pub mint_to_raise: InterfaceAccount, #[account( mut, - has_one = mint_to_raise, seeds = [b"fundraiser".as_ref(), fundraiser.maker.as_ref()], bump = fundraiser.bump, )] diff --git a/finance/token-fundraiser/anchor/programs/fundraiser/src/instructions/refund.rs b/finance/token-fundraiser/anchor/programs/fundraiser/src/instructions/refund.rs index e7610830e..7e54c7a00 100644 --- a/finance/token-fundraiser/anchor/programs/fundraiser/src/instructions/refund.rs +++ b/finance/token-fundraiser/anchor/programs/fundraiser/src/instructions/refund.rs @@ -15,11 +15,11 @@ pub struct RefundAccountConstraints { pub maker: SystemAccount, + #[account(address = fundraiser.mint_to_raise)] pub mint_to_raise: InterfaceAccount, #[account( mut, - has_one = mint_to_raise, seeds = [b"fundraiser", maker.address().as_ref()], bump = fundraiser.bump, )] diff --git a/finance/token-swap/anchor/programs/token-swap/src/instructions/admin/claim_admin_fees.rs b/finance/token-swap/anchor/programs/token-swap/src/instructions/admin/claim_admin_fees.rs index ea9303ecb..b226349be 100644 --- a/finance/token-swap/anchor/programs/token-swap/src/instructions/admin/claim_admin_fees.rs +++ b/finance/token-swap/anchor/programs/token-swap/src/instructions/admin/claim_admin_fees.rs @@ -16,10 +16,10 @@ use crate::{ /// `admin_fees_owed_b`). This handler transfers those amounts out of the /// pool reserves into the admin's ATAs and resets the accumulators to zero. /// -/// Authorisation: the `has_one = admin` constraint on `config` plus the +/// Authorisation: the `address = config.admin` constraint on `admin` plus the /// `Signer` constraint on `admin` together mean only the address stored in /// `Config.admin` can call this. Any other signer will be rejected by -/// Anchor's built-in `has_one` check. +/// Anchor's built-in `address` check. pub fn handle_claim_admin_fees( context: &mut Context, ) -> Result<()> { @@ -106,11 +106,8 @@ pub fn handle_claim_admin_fees( #[derive(Accounts)] pub struct ClaimAdminFeesAccountConstraints { - #[account( - seeds = [CONFIG_SEED], - bump, - has_one = admin, - )] + #[account(seeds = [CONFIG_SEED], + bump, address = pool_config.config)] pub config: BorshAccount, #[account( @@ -121,9 +118,6 @@ pub struct ClaimAdminFeesAccountConstraints { pool_config.mint_b.address().as_ref(), ], bump, - has_one = config, - has_one = mint_a, - has_one = mint_b, )] pub pool_config: BorshAccount, @@ -139,8 +133,10 @@ pub struct ClaimAdminFeesAccountConstraints { )] pub pool_authority: UncheckedAccount, + #[account(address = pool_config.mint_a)] pub mint_a: Box>, + #[account(address = pool_config.mint_b)] pub mint_b: Box>, /// The pool's token-A reserve. The admin's owed token-A fees are paid out @@ -164,7 +160,8 @@ pub struct ClaimAdminFeesAccountConstraints { pub pool_b: Box>, /// Must match the address stored in `Config.admin` (enforced by - /// `has_one = admin` above). + /// `address = config.admin` above). + #[account(address = config.admin)] pub admin: Signer, /// Admin's token-A receiving account. Must already exist; the admin is diff --git a/finance/token-swap/anchor/programs/token-swap/src/instructions/deposit_liquidity.rs b/finance/token-swap/anchor/programs/token-swap/src/instructions/deposit_liquidity.rs index 89f9f6db4..c906055e0 100644 --- a/finance/token-swap/anchor/programs/token-swap/src/instructions/deposit_liquidity.rs +++ b/finance/token-swap/anchor/programs/token-swap/src/instructions/deposit_liquidity.rs @@ -256,8 +256,6 @@ pub struct DepositLiquidityAccountConstraints { pool_config.mint_b.address().as_ref(), ], bump, - has_one = mint_a, - has_one = mint_b, )] pub pool_config: Box>, @@ -288,8 +286,10 @@ pub struct DepositLiquidityAccountConstraints { )] pub liquidity_provider_mint: Box>, + #[account(address = pool_config.mint_a)] pub mint_a: Box>, + #[account(address = pool_config.mint_b)] pub mint_b: Box>, #[account( diff --git a/finance/token-swap/anchor/programs/token-swap/src/instructions/swap_tokens.rs b/finance/token-swap/anchor/programs/token-swap/src/instructions/swap_tokens.rs index 0d6fda093..e74e88066 100644 --- a/finance/token-swap/anchor/programs/token-swap/src/instructions/swap_tokens.rs +++ b/finance/token-swap/anchor/programs/token-swap/src/instructions/swap_tokens.rs @@ -274,10 +274,8 @@ pub fn handle_swap_tokens( #[derive(Accounts)] pub struct SwapTokensAccountConstraints { - #[account( - seeds = [CONFIG_SEED], - bump, - )] + #[account(seeds = [CONFIG_SEED], + bump, address = pool_config.config)] pub config: BorshAccount, #[account( @@ -288,9 +286,6 @@ pub struct SwapTokensAccountConstraints { pool_config.mint_b.address().as_ref(), ], bump, - has_one = config, - has_one = mint_a, - has_one = mint_b, )] pub pool_config: BorshAccount, @@ -309,8 +304,10 @@ pub struct SwapTokensAccountConstraints { /// The account doing the swap pub trader: Signer, + #[account(address = pool_config.mint_a)] pub mint_a: Box>, + #[account(address = pool_config.mint_b)] pub mint_b: Box>, #[account( diff --git a/finance/token-swap/anchor/programs/token-swap/src/instructions/withdraw_liquidity.rs b/finance/token-swap/anchor/programs/token-swap/src/instructions/withdraw_liquidity.rs index 896a2955b..61087a1ec 100644 --- a/finance/token-swap/anchor/programs/token-swap/src/instructions/withdraw_liquidity.rs +++ b/finance/token-swap/anchor/programs/token-swap/src/instructions/withdraw_liquidity.rs @@ -154,8 +154,6 @@ pub struct WithdrawLiquidityAccountConstraints { pool_config.mint_b.address().as_ref(), ], bump, - has_one = mint_a, - has_one = mint_b, )] pub pool_config: BorshAccount, @@ -185,10 +183,10 @@ pub struct WithdrawLiquidityAccountConstraints { )] pub liquidity_provider_mint: Box>, - #[account(mut)] + #[account(mut, address = pool_config.mint_a)] pub mint_a: Box>, - #[account(mut)] + #[account(mut, address = pool_config.mint_b)] pub mint_b: Box>, #[account( diff --git a/finance/token-swap/anchor/programs/token-swap/tests/test_swap.rs b/finance/token-swap/anchor/programs/token-swap/tests/test_swap.rs index 178d1f418..61ef57e99 100644 --- a/finance/token-swap/anchor/programs/token-swap/tests/test_swap.rs +++ b/finance/token-swap/anchor/programs/token-swap/tests/test_swap.rs @@ -762,7 +762,7 @@ fn test_claim_admin_fees_rejects_non_admin() { ); // Should fail because the signer (attacker) does not match Config.admin - // (enforced by Anchor's `has_one = admin` constraint). + // (enforced by Anchor's `address = config.admin` constraint). let result = send_transaction_from_instructions( &mut ts.svm, vec![claim_ix], diff --git a/finance/vault-strategy/anchor/programs/mock-swap-router/src/instructions/set_rate.rs b/finance/vault-strategy/anchor/programs/mock-swap-router/src/instructions/set_rate.rs index d0eb3d8fa..49e7914f1 100644 --- a/finance/vault-strategy/anchor/programs/mock-swap-router/src/instructions/set_rate.rs +++ b/finance/vault-strategy/anchor/programs/mock-swap-router/src/instructions/set_rate.rs @@ -8,14 +8,11 @@ use crate::state::{AssetRate, RouterConfig}; #[derive(Accounts)] pub struct SetRateAccountConstraints { - #[account(mut)] + #[account(mut, address = router_config.authority)] pub authority: Signer, - #[account( - has_one = authority, - seeds = [b"router_config"], - bump = router_config.bump - )] + #[account(seeds = [b"router_config"], + bump = router_config.bump)] pub router_config: BorshAccount, pub asset_mint: InterfaceAccount, diff --git a/finance/vault-strategy/anchor/programs/vault-strategy/src/instructions/add_asset.rs b/finance/vault-strategy/anchor/programs/vault-strategy/src/instructions/add_asset.rs index bf1b11b1a..5edad14ee 100644 --- a/finance/vault-strategy/anchor/programs/vault-strategy/src/instructions/add_asset.rs +++ b/finance/vault-strategy/anchor/programs/vault-strategy/src/instructions/add_asset.rs @@ -9,18 +9,17 @@ use crate::state::{ApprovedAsset, AssetConfig, Registry, Strategy, MAX_ASSETS}; #[derive(Accounts)] pub struct AddAssetAccountConstraints { - #[account(mut)] + #[account(mut, address = strategy.manager)] pub manager: Signer, #[account( mut, - has_one = manager, - has_one = registry @ VaultError::InvalidRegistry, seeds = [b"strategy", strategy.index.to_le_bytes()], - bump = strategy.bump + bump = strategy.bump, )] pub strategy: Box>, + #[account(address = strategy.registry @ VaultError::InvalidRegistry)] pub registry: Box>, pub asset_mint: Box>, diff --git a/finance/vault-strategy/anchor/programs/vault-strategy/src/instructions/approve_asset.rs b/finance/vault-strategy/anchor/programs/vault-strategy/src/instructions/approve_asset.rs index db858adb6..4aab249b6 100644 --- a/finance/vault-strategy/anchor/programs/vault-strategy/src/instructions/approve_asset.rs +++ b/finance/vault-strategy/anchor/programs/vault-strategy/src/instructions/approve_asset.rs @@ -5,13 +5,12 @@ use crate::state::{ApprovedAsset, Registry}; #[derive(Accounts)] pub struct ApproveAssetAccountConstraints { - #[account(mut)] + #[account(mut, address = registry.authority)] pub authority: Signer, #[account( - has_one = authority, seeds = [b"registry", authority.address().as_ref()], - bump = registry.bump + bump = registry.bump, )] pub registry: BorshAccount, diff --git a/finance/vault-strategy/anchor/programs/vault-strategy/src/instructions/collect_fees.rs b/finance/vault-strategy/anchor/programs/vault-strategy/src/instructions/collect_fees.rs index 67d807700..bec362e61 100644 --- a/finance/vault-strategy/anchor/programs/vault-strategy/src/instructions/collect_fees.rs +++ b/finance/vault-strategy/anchor/programs/vault-strategy/src/instructions/collect_fees.rs @@ -12,13 +12,13 @@ const SECONDS_PER_YEAR: u64 = 31_536_000; #[derive(Accounts)] pub struct CollectFeesAccountConstraints { /// CHECK: manager is stored in strategy; we only read their pubkey for derivation + #[account(address = strategy.manager)] pub manager: UncheckedAccount, #[account( mut, - has_one = manager, seeds = [b"strategy", strategy.index.to_le_bytes()], - bump = strategy.bump + bump = strategy.bump, )] pub strategy: BorshAccount, diff --git a/finance/vault-strategy/anchor/programs/vault-strategy/src/instructions/deposit.rs b/finance/vault-strategy/anchor/programs/vault-strategy/src/instructions/deposit.rs index 24515057d..74c1372e6 100644 --- a/finance/vault-strategy/anchor/programs/vault-strategy/src/instructions/deposit.rs +++ b/finance/vault-strategy/anchor/programs/vault-strategy/src/instructions/deposit.rs @@ -18,9 +18,8 @@ pub struct DepositAccountConstraints { #[account( mut, - has_one = usdc_mint @ VaultError::InvalidUsdcMint, seeds = [b"strategy", strategy.index.to_le_bytes()], - bump = strategy.bump + bump = strategy.bump, )] pub strategy: Box>, @@ -31,6 +30,7 @@ pub struct DepositAccountConstraints { )] pub share_mint: Box>, + #[account(address = strategy.usdc_mint @ VaultError::InvalidUsdcMint)] pub usdc_mint: Box>, #[account( diff --git a/finance/vault-strategy/anchor/programs/vault-strategy/src/instructions/rebalance.rs b/finance/vault-strategy/anchor/programs/vault-strategy/src/instructions/rebalance.rs index 7a70af750..2234403ae 100644 --- a/finance/vault-strategy/anchor/programs/vault-strategy/src/instructions/rebalance.rs +++ b/finance/vault-strategy/anchor/programs/vault-strategy/src/instructions/rebalance.rs @@ -14,17 +14,17 @@ use crate::state::{AssetConfig, Strategy}; #[derive(Accounts)] pub struct RebalanceAccountConstraints { + #[account(address = strategy.manager)] pub manager: Signer, #[account( mut, - has_one = manager, - has_one = usdc_mint @ VaultError::InvalidUsdcMint, seeds = [b"strategy", strategy.index.to_le_bytes()], - bump = strategy.bump + bump = strategy.bump, )] pub strategy: Box>, + #[account(address = strategy.usdc_mint @ VaultError::InvalidUsdcMint)] pub usdc_mint: Box>, #[account(mut)] diff --git a/finance/vault-strategy/anchor/programs/vault-strategy/src/instructions/set_weight.rs b/finance/vault-strategy/anchor/programs/vault-strategy/src/instructions/set_weight.rs index 570ad2c91..d9549712c 100644 --- a/finance/vault-strategy/anchor/programs/vault-strategy/src/instructions/set_weight.rs +++ b/finance/vault-strategy/anchor/programs/vault-strategy/src/instructions/set_weight.rs @@ -5,13 +5,13 @@ use crate::state::{AssetConfig, Strategy}; #[derive(Accounts)] pub struct SetWeightAccountConstraints { + #[account(address = strategy.manager)] pub manager: Signer, #[account( mut, - has_one = manager, seeds = [b"strategy", strategy.index.to_le_bytes()], - bump = strategy.bump + bump = strategy.bump, )] pub strategy: Box>, diff --git a/finance/vault-strategy/anchor/programs/vault-strategy/src/instructions/withdraw.rs b/finance/vault-strategy/anchor/programs/vault-strategy/src/instructions/withdraw.rs index acd1f19e0..86c474c87 100644 --- a/finance/vault-strategy/anchor/programs/vault-strategy/src/instructions/withdraw.rs +++ b/finance/vault-strategy/anchor/programs/vault-strategy/src/instructions/withdraw.rs @@ -17,9 +17,8 @@ pub struct WithdrawAccountConstraints { #[account( mut, - has_one = usdc_mint @ VaultError::InvalidUsdcMint, seeds = [b"strategy", strategy.index.to_le_bytes()], - bump = strategy.bump + bump = strategy.bump, )] pub strategy: Box>, @@ -30,6 +29,7 @@ pub struct WithdrawAccountConstraints { )] pub share_mint: Box>, + #[account(address = strategy.usdc_mint @ VaultError::InvalidUsdcMint)] pub usdc_mint: Box>, #[account( diff --git a/tokens/external-delegate-token-master/anchor/programs/external-delegate-token-master/src/instructions/authority_transfer.rs b/tokens/external-delegate-token-master/anchor/programs/external-delegate-token-master/src/instructions/authority_transfer.rs index dd08df8d7..e9c3d0073 100644 --- a/tokens/external-delegate-token-master/anchor/programs/external-delegate-token-master/src/instructions/authority_transfer.rs +++ b/tokens/external-delegate-token-master/anchor/programs/external-delegate-token-master/src/instructions/authority_transfer.rs @@ -7,9 +7,9 @@ use crate::UserAccount; #[derive(Accounts)] pub struct AuthorityTransferAccountConstraints { - #[account(has_one = authority)] pub user_account: BorshAccount, + #[account(address = user_account.authority)] pub authority: Signer, pub mint: InterfaceAccount, diff --git a/tokens/external-delegate-token-master/anchor/programs/external-delegate-token-master/src/instructions/set_ethereum_address.rs b/tokens/external-delegate-token-master/anchor/programs/external-delegate-token-master/src/instructions/set_ethereum_address.rs index cb65388df..7272c1ea3 100644 --- a/tokens/external-delegate-token-master/anchor/programs/external-delegate-token-master/src/instructions/set_ethereum_address.rs +++ b/tokens/external-delegate-token-master/anchor/programs/external-delegate-token-master/src/instructions/set_ethereum_address.rs @@ -4,9 +4,10 @@ use crate::UserAccount; #[derive(Accounts)] pub struct SetEthereumAddressAccountConstraints { - #[account(mut, has_one = authority)] + #[account(mut)] pub user_account: BorshAccount, + #[account(address = user_account.authority)] pub authority: Signer, } diff --git a/tokens/external-delegate-token-master/anchor/programs/external-delegate-token-master/src/instructions/transfer_tokens.rs b/tokens/external-delegate-token-master/anchor/programs/external-delegate-token-master/src/instructions/transfer_tokens.rs index c9a87950a..62667d9bc 100644 --- a/tokens/external-delegate-token-master/anchor/programs/external-delegate-token-master/src/instructions/transfer_tokens.rs +++ b/tokens/external-delegate-token-master/anchor/programs/external-delegate-token-master/src/instructions/transfer_tokens.rs @@ -9,9 +9,10 @@ use crate::{ #[derive(Accounts)] pub struct TransferTokensAccountConstraints { - #[account(mut, has_one = authority)] + #[account(mut)] pub user_account: BorshAccount, + #[account(address = user_account.authority)] pub authority: Signer, pub mint: InterfaceAccount, diff --git a/tokens/token-extensions/transfer-hook/allow-block-list-token/anchor/programs/abl-token/src/instructions/init_wallet.rs b/tokens/token-extensions/transfer-hook/allow-block-list-token/anchor/programs/abl-token/src/instructions/init_wallet.rs index 0900bbcef..d95d1eaa1 100644 --- a/tokens/token-extensions/transfer-hook/allow-block-list-token/anchor/programs/abl-token/src/instructions/init_wallet.rs +++ b/tokens/token-extensions/transfer-hook/allow-block-list-token/anchor/programs/abl-token/src/instructions/init_wallet.rs @@ -4,14 +4,11 @@ use crate::{ABWallet, Config, AB_WALLET_SEED, CONFIG_SEED}; #[derive(Accounts)] pub struct InitWalletAccountConstraints { - #[account(mut)] + #[account(mut, address = config.authority)] pub authority: Signer, - #[account( - seeds = [CONFIG_SEED], - bump = config.bump, - has_one = authority, - )] + #[account(seeds = [CONFIG_SEED], + bump = config.bump)] pub config: Box>, pub wallet: SystemAccount, diff --git a/tokens/token-extensions/transfer-hook/allow-block-list-token/anchor/programs/abl-token/src/instructions/remove_wallet.rs b/tokens/token-extensions/transfer-hook/allow-block-list-token/anchor/programs/abl-token/src/instructions/remove_wallet.rs index b2c540e2e..adc1d5278 100644 --- a/tokens/token-extensions/transfer-hook/allow-block-list-token/anchor/programs/abl-token/src/instructions/remove_wallet.rs +++ b/tokens/token-extensions/transfer-hook/allow-block-list-token/anchor/programs/abl-token/src/instructions/remove_wallet.rs @@ -4,14 +4,11 @@ use crate::{ABWallet, Config}; #[derive(Accounts)] pub struct RemoveWalletAccountConstraints { - #[account(mut)] + #[account(mut, address = config.authority)] pub authority: Signer, - #[account( - seeds = [b"config"], - bump = config.bump, - has_one = authority, - )] + #[account(seeds = [b"config"], + bump = config.bump)] pub config: Box>, #[account( diff --git a/tokens/token-extensions/transfer-hook/transfer-switch/anchor/programs/transfer-switch/src/instructions/switch.rs b/tokens/token-extensions/transfer-hook/transfer-switch/anchor/programs/transfer-switch/src/instructions/switch.rs index d9a0fde54..e3b4a586a 100644 --- a/tokens/token-extensions/transfer-hook/transfer-switch/anchor/programs/transfer-switch/src/instructions/switch.rs +++ b/tokens/token-extensions/transfer-hook/transfer-switch/anchor/programs/transfer-switch/src/instructions/switch.rs @@ -6,7 +6,7 @@ use { #[derive(Accounts)] pub struct SwitchAccountConstraints { /// admin that controls the switch - #[account(mut)] + #[account(mut, address = admin_config.admin)] pub admin: Signer, /// CHECK: wallet - transfer sender @@ -14,11 +14,8 @@ pub struct SwitchAccountConstraints { pub wallet: UncheckedAccount, /// admin config - #[account( - has_one=admin, - seeds=[b"admin-config"], - bump, - )] + #[account(seeds=[b"admin-config"], + bump)] pub admin_config: BorshAccount, /// the wallet (sender) transfer switch From 503b9f4a392e7dd2dfa0cda87d2198e291d175b9 Mon Sep 17 00:00:00 2001 From: Claude Date: Fri, 14 Aug 2026 19:44:17 +0000 Subject: [PATCH 26/59] transfer-hook: give the hook programs a real entrypoint v2's `#[program(interface, ...)]` only generates a CPI client, no dispatch, so a program declared that way builds to a ~900-byte object with no entrypoint and fails to load. An executable `#[program]` does dispatch, but its custom discriminators are limited to one byte, which the transfer-hook interface's eight-byte `Execute` value cannot use. So the hook programs build with `no-entrypoint`, anchor then exports its dispatch as `__anchor_dispatch`, and supply an entrypoint that swaps `Execute` for the handler's own discriminator before delegating. The payload behind it is identical. Also ports allow-block-list-token the rest of the way: anchor-spl v2 has no token_2022 features, TokenMetadataUpdateField lost its program_id field, and the wallet account is decoded by hand now that Account::try_from is gone. Its tests pass. --- .../src/instructions/transfer_tokens.rs | 4 +- .../anchor/programs/transfer-hook/src/lib.rs | 6 ++- .../anchor/programs/abl-token/Cargo.toml | 13 +++-- .../programs/abl-token/src/entrypoint.rs | 49 +++++++++++++++++++ .../src/instructions/attach_to_mint.rs | 10 ++-- .../abl-token/src/instructions/change_mode.rs | 41 +++++++--------- .../abl-token/src/instructions/init_config.rs | 8 +-- .../abl-token/src/instructions/init_wallet.rs | 4 +- .../src/instructions/remove_wallet.rs | 2 +- .../abl-token/src/instructions/tx_hook.rs | 22 ++++++--- .../anchor/programs/abl-token/src/lib.rs | 11 +++-- .../anchor/programs/transfer-hook/src/lib.rs | 6 ++- .../anchor/programs/transfer-hook/src/lib.rs | 6 ++- .../anchor/programs/transfer-hook/src/lib.rs | 6 ++- .../programs/transfer-switch/src/lib.rs | 6 ++- .../anchor/programs/transfer-hook/src/lib.rs | 6 ++- 16 files changed, 145 insertions(+), 55 deletions(-) create mode 100644 tokens/token-extensions/transfer-hook/allow-block-list-token/anchor/programs/abl-token/src/entrypoint.rs diff --git a/tokens/external-delegate-token-master/anchor/programs/external-delegate-token-master/src/instructions/transfer_tokens.rs b/tokens/external-delegate-token-master/anchor/programs/external-delegate-token-master/src/instructions/transfer_tokens.rs index 62667d9bc..2b6b2eeaa 100644 --- a/tokens/external-delegate-token-master/anchor/programs/external-delegate-token-master/src/instructions/transfer_tokens.rs +++ b/tokens/external-delegate-token-master/anchor/programs/external-delegate-token-master/src/instructions/transfer_tokens.rs @@ -37,8 +37,10 @@ pub fn handler( amount: u64, signature: [u8; 65], ) -> Result<()> { + // Copy out what the message needs, so the shared borrow ends before the + // nonce bump below takes a mutable one. + let user_account_key = *context.accounts.user_account.address(); let user_account = &context.accounts.user_account; - let user_account_key = user_account.address(); // Rebuild the authorized message onchain so the signature commits to // this exact transfer (amount, recipient, and the current nonce). diff --git a/tokens/token-extensions/transfer-hook/account-data-as-seed/anchor/programs/transfer-hook/src/lib.rs b/tokens/token-extensions/transfer-hook/account-data-as-seed/anchor/programs/transfer-hook/src/lib.rs index 60b76e728..877cceb7c 100644 --- a/tokens/token-extensions/transfer-hook/account-data-as-seed/anchor/programs/transfer-hook/src/lib.rs +++ b/tokens/token-extensions/transfer-hook/account-data-as-seed/anchor/programs/transfer-hook/src/lib.rs @@ -26,7 +26,11 @@ pub enum TransferError { IsNotCurrentlyTransferring, } -#[program(interface, program_id = ID)] +// v2's `#[program(interface)]` declares an interface for CPI and emits no +// entrypoint. This is a real deployable program that also implements the +// transfer-hook interface, so it stays a plain `#[program]`; the interface +// instruction gets its discriminator from `#[discrim = ...]` below. +#[program] pub mod transfer_hook { use super::*; diff --git a/tokens/token-extensions/transfer-hook/allow-block-list-token/anchor/programs/abl-token/Cargo.toml b/tokens/token-extensions/transfer-hook/allow-block-list-token/anchor/programs/abl-token/Cargo.toml index 1914163bf..1a341a025 100644 --- a/tokens/token-extensions/transfer-hook/allow-block-list-token/anchor/programs/abl-token/Cargo.toml +++ b/tokens/token-extensions/transfer-hook/allow-block-list-token/anchor/programs/abl-token/Cargo.toml @@ -9,7 +9,11 @@ crate-type = ["cdylib", "lib"] name = "abl_token" [features] -default = [] +# `no-entrypoint` is always on: anchor then exports its dispatch as +# `__anchor_dispatch` rather than claiming the `entrypoint` symbol, leaving +# src/entrypoint.rs free to claim it and route the transfer-hook interface's +# `Execute` discriminator to `tx_hook`. +default = ["no-entrypoint"] cpi = ["no-entrypoint"] no-entrypoint = [] no-idl = [] @@ -31,10 +35,9 @@ wincode = { version = "0.5", features = ["derive"] } # belong to the version the account derives are not using and every # `SchemaRead`/`SchemaWrite` bound fails. 2.6.1 is the last 0.5-line release. solana-address = ">=2.6, <2.7" -anchor-spl = { version = "2.0.0-rc.1", features = [ - "token_2022_extensions", - "token_2022", -] } +# v2's anchor-spl has only `guardrails` (default) and `metadata`; the +# Token-2022 modules are unconditional now. +anchor-spl = "2.0.0-rc.1" spl-tlv-account-resolution = "0.11.1" spl-transfer-hook-interface = { version = "2.1.0" } diff --git a/tokens/token-extensions/transfer-hook/allow-block-list-token/anchor/programs/abl-token/src/entrypoint.rs b/tokens/token-extensions/transfer-hook/allow-block-list-token/anchor/programs/abl-token/src/entrypoint.rs new file mode 100644 index 000000000..4c5098602 --- /dev/null +++ b/tokens/token-extensions/transfer-hook/allow-block-list-token/anchor/programs/abl-token/src/entrypoint.rs @@ -0,0 +1,49 @@ +//! Program entrypoint, hand-written so the SPL transfer-hook interface reaches +//! `tx_hook`. +//! +//! Anchor v2 gives every instruction in an executable `#[program]` the eight-byte +//! `sha256("global:")` discriminator, and `#[discrim = N]` there is limited +//! to a single byte. The transfer-hook interface calls `Execute` under its own +//! eight-byte value, which leaves no way to declare that handler directly. +//! `#[program(interface, ...)]` accepts arbitrary discriminator bytes but only +//! generates a CPI client — no dispatch, and so no deployable program. +//! +//! So the crate builds with `no-entrypoint` (which makes anchor export its +//! dispatch as `__anchor_dispatch` instead of claiming the `entrypoint` symbol) +//! and this module claims `entrypoint` itself. All it does is swap the +//! interface's discriminator for `tx_hook`'s before delegating; the payload +//! behind it — a single `u64` amount — is identical either way. + +use anchor_lang::pinocchio; + +pinocchio::default_allocator!(); +pinocchio::default_panic_handler!(); + +/// `sha256("spl-transfer-hook-interface:execute")[..8]`, the discriminator +/// Token-2022 uses when it calls a mint's transfer hook. +const EXECUTE_DISCRIMINATOR: [u8; 8] = [105, 37, 101, 197, 75, 251, 102, 26]; + +/// `sha256("global:tx_hook")[..8]`, what anchor's dispatch matches on. +const TX_HOOK_DISCRIMINATOR: [u8; 8] = [55, 222, 121, 59, 26, 10, 108, 168]; + +/// # Safety +/// +/// Called only by the SBF loader, with the register convention anchor's own +/// entrypoint documents: `r1` is the start of the serialized parameter region +/// and `r2` points at the instruction data, whose length sits in the eight +/// bytes below it. +#[cfg(target_os = "solana")] +#[no_mangle] +pub unsafe extern "C" fn entrypoint(input: *mut u8, ix_data_ptr: *const u8) -> u64 { + let len = *(ix_data_ptr.sub(8) as *const u64) as usize; + if len >= EXECUTE_DISCRIMINATOR.len() { + let discriminator = core::slice::from_raw_parts_mut( + ix_data_ptr as *mut u8, + EXECUTE_DISCRIMINATOR.len(), + ); + if discriminator == EXECUTE_DISCRIMINATOR { + discriminator.copy_from_slice(&TX_HOOK_DISCRIMINATOR); + } + } + crate::__anchor_dispatch(input, ix_data_ptr) +} diff --git a/tokens/token-extensions/transfer-hook/allow-block-list-token/anchor/programs/abl-token/src/instructions/attach_to_mint.rs b/tokens/token-extensions/transfer-hook/allow-block-list-token/anchor/programs/abl-token/src/instructions/attach_to_mint.rs index 943c86719..6ed4a5761 100644 --- a/tokens/token-extensions/transfer-hook/allow-block-list-token/anchor/programs/abl-token/src/instructions/attach_to_mint.rs +++ b/tokens/token-extensions/transfer-hook/allow-block-list-token/anchor/programs/abl-token/src/instructions/attach_to_mint.rs @@ -36,21 +36,21 @@ pub struct AttachToMintAccountConstraints { pub token_program: Program, } -impl AttachToMintAccountConstraints<'_> { +impl AttachToMintAccountConstraints { pub fn attach_to_mint(&mut self) -> Result<()> { let tx_hook_accs = TransferHookUpdate { mint: self.mint.cpi_handle_mut(), - authority: self.payer.cpi_handle_mut(), + authority: self.payer.cpi_handle(), }; let context = CpiContext::new(self.token_program.address(), tx_hook_accs); - transfer_hook_update(context, Some(crate::ID_CONST))?; + transfer_hook_update(context, Some(&crate::ID))?; // initialize the extra metas account - let extra_metas_account = &self.extra_metas_account; let metas = get_extra_account_metas()?; - let mut data = extra_metas_account.try_borrow_mut_data()?; + let mut extra_metas_view = *self.extra_metas_account.account(); + let mut data = extra_metas_view.try_borrow_mut()?; ExtraAccountMetaList::init::(&mut data, &metas) .map_err(|_| ProgramError::InvalidAccountData)?; diff --git a/tokens/token-extensions/transfer-hook/allow-block-list-token/anchor/programs/abl-token/src/instructions/change_mode.rs b/tokens/token-extensions/transfer-hook/allow-block-list-token/anchor/programs/abl-token/src/instructions/change_mode.rs index 10bf008d8..1b0a1ac0c 100644 --- a/tokens/token-extensions/transfer-hook/allow-block-list-token/anchor/programs/abl-token/src/instructions/change_mode.rs +++ b/tokens/token-extensions/transfer-hook/allow-block-list-token/anchor/programs/abl-token/src/instructions/change_mode.rs @@ -1,5 +1,5 @@ -use anchor_lang::solana_program::program::invoke; -use anchor_lang::{prelude::*, solana_program::system_instruction::transfer}; +use anchor_lang::prelude::*; +use anchor_lang::system_program::{transfer, Transfer}; use anchor_spl::token_interface::spl_token_metadata_interface::state::TokenMetadata; use anchor_spl::{ mint, @@ -38,12 +38,11 @@ pub struct ChangeModeArgs { pub threshold: u64, } -impl ChangeModeAccountConstraints<'_> { +impl ChangeModeAccountConstraints { pub fn change_mode(&mut self, args: ChangeModeArgs) -> Result<()> { let cpi_accounts = TokenMetadataUpdateField { metadata: self.mint.cpi_handle_mut(), - update_authority: self.authority.cpi_handle_mut(), - program_id: self.token_program.cpi_handle_mut(), + update_authority: self.authority.cpi_handle(), }; let cpi_program = self.token_program.address(); let cpi_ctx = CpiContext::new(cpi_program, cpi_accounts); @@ -59,8 +58,7 @@ impl ChangeModeAccountConstraints<'_> { let cpi_accounts = TokenMetadataUpdateField { metadata: self.mint.cpi_handle_mut(), - update_authority: self.authority.cpi_handle_mut(), - program_id: self.token_program.cpi_handle_mut(), + update_authority: self.authority.cpi_handle(), }; let cpi_program = self.token_program.address(); let cpi_ctx = CpiContext::new(cpi_program, cpi_accounts); @@ -72,20 +70,20 @@ impl ChangeModeAccountConstraints<'_> { )?; } - let data = self.mint.cpi_handle_mut().data_len(); - let min_balance = Rent::get()?.try_minimum_balance(data)?; - if min_balance > self.mint.cpi_handle_mut().get_lamports() { - invoke( - &transfer( - &self.authority.address(), - &self.mint.cpi_handle_mut().address(), - min_balance - self.mint.cpi_handle_mut().get_lamports(), + // Writing the metadata grew the mint, so top it back up to rent exemption. + let data_len = self.mint.account().data_len(); + let min_balance = Rent::get()?.try_minimum_balance(data_len)?; + let current = self.mint.account().lamports(); + if min_balance > current { + transfer( + CpiContext::new( + self.system_program.address(), + Transfer { + from: self.authority.cpi_handle_mut(), + to: self.mint.cpi_handle_mut(), + }, ), - &[ - self.authority.cpi_handle_mut(), - self.mint.cpi_handle_mut(), - self.system_program.cpi_handle_mut(), - ], + min_balance - current, )?; } @@ -93,8 +91,7 @@ impl ChangeModeAccountConstraints<'_> { } fn has_threshold(&self) -> Result { - let mint_info = self.mint.cpi_handle_mut(); - let mint_data = mint_info.data.borrow(); + let mint_data = self.mint.account().try_borrow()?; let mint = StateWithExtensions::::unpack(&mint_data)?; let metadata = mint.get_variable_len_extension::(); Ok(metadata.is_ok() diff --git a/tokens/token-extensions/transfer-hook/allow-block-list-token/anchor/programs/abl-token/src/instructions/init_config.rs b/tokens/token-extensions/transfer-hook/allow-block-list-token/anchor/programs/abl-token/src/instructions/init_config.rs index 2f26f5cae..d9608870c 100644 --- a/tokens/token-extensions/transfer-hook/allow-block-list-token/anchor/programs/abl-token/src/instructions/init_config.rs +++ b/tokens/token-extensions/transfer-hook/allow-block-list-token/anchor/programs/abl-token/src/instructions/init_config.rs @@ -18,12 +18,12 @@ pub struct InitConfigAccountConstraints { pub system_program: Program, } -impl InitConfigAccountConstraints<'_> { +impl InitConfigAccountConstraints { pub fn init_config(&mut self, config_bump: u8) -> Result<()> { - *self.config = (Config { - authority: self.payer.address(), + **self.config = Config { + authority: *self.payer.address(), bump: config_bump, - }); + }; Ok(()) } diff --git a/tokens/token-extensions/transfer-hook/allow-block-list-token/anchor/programs/abl-token/src/instructions/init_wallet.rs b/tokens/token-extensions/transfer-hook/allow-block-list-token/anchor/programs/abl-token/src/instructions/init_wallet.rs index d95d1eaa1..71a77a382 100644 --- a/tokens/token-extensions/transfer-hook/allow-block-list-token/anchor/programs/abl-token/src/instructions/init_wallet.rs +++ b/tokens/token-extensions/transfer-hook/allow-block-list-token/anchor/programs/abl-token/src/instructions/init_wallet.rs @@ -25,10 +25,10 @@ pub struct InitWalletAccountConstraints { pub system_program: Program, } -impl InitWalletAccountConstraints<'_> { +impl InitWalletAccountConstraints { pub fn init_wallet(&mut self, args: InitWalletArgs, bump: u8) -> Result<()> { let ab_wallet = &mut self.ab_wallet; - ab_wallet.wallet = self.wallet.address(); + ab_wallet.wallet = *self.wallet.address(); ab_wallet.allowed = args.allowed; ab_wallet.bump = bump; Ok(()) diff --git a/tokens/token-extensions/transfer-hook/allow-block-list-token/anchor/programs/abl-token/src/instructions/remove_wallet.rs b/tokens/token-extensions/transfer-hook/allow-block-list-token/anchor/programs/abl-token/src/instructions/remove_wallet.rs index adc1d5278..d8630b19e 100644 --- a/tokens/token-extensions/transfer-hook/allow-block-list-token/anchor/programs/abl-token/src/instructions/remove_wallet.rs +++ b/tokens/token-extensions/transfer-hook/allow-block-list-token/anchor/programs/abl-token/src/instructions/remove_wallet.rs @@ -20,7 +20,7 @@ pub struct RemoveWalletAccountConstraints { pub system_program: Program, } -impl RemoveWalletAccountConstraints<'_> { +impl RemoveWalletAccountConstraints { pub fn remove_wallet(&mut self) -> Result<()> { Ok(()) } diff --git a/tokens/token-extensions/transfer-hook/allow-block-list-token/anchor/programs/abl-token/src/instructions/tx_hook.rs b/tokens/token-extensions/transfer-hook/allow-block-list-token/anchor/programs/abl-token/src/instructions/tx_hook.rs index f967471b0..29739da0e 100644 --- a/tokens/token-extensions/transfer-hook/allow-block-list-token/anchor/programs/abl-token/src/instructions/tx_hook.rs +++ b/tokens/token-extensions/transfer-hook/allow-block-list-token/anchor/programs/abl-token/src/instructions/tx_hook.rs @@ -27,10 +27,9 @@ pub struct TxHookAccountConstraints { pub ab_wallet: UncheckedAccount, } -impl TxHookAccountConstraints<'_> { +impl TxHookAccountConstraints { pub fn tx_hook(&self, amount: u64) -> Result<()> { - let mint_info = self.mint.cpi_handle_mut(); - let mint_data = mint_info.data.borrow(); + let mint_data = self.mint.account().try_borrow()?; let mint = StateWithExtensions::::unpack(&mint_data)?; let metadata = mint.get_variable_len_extension::()?; @@ -55,12 +54,23 @@ impl TxHookAccountConstraints<'_> { } fn decode_wallet_mode(&self) -> Result { - if self.ab_wallet.data_is_empty() { + let wallet_data = self.ab_wallet.account().try_borrow()?; + if wallet_data.is_empty() { return Ok(DecodedWalletMode::None); } - let wallet_data = &mut self.ab_wallet.data.borrow(); - let wallet = ABWallet::try_deserialize(&mut &wallet_data[..])?; + // v2 has no `Account::try_from`, so the discriminator is checked and + // the payload read by hand. + let disc_len = ::DISCRIMINATOR.len(); + require!( + wallet_data.len() > disc_len + && &wallet_data[..disc_len] + == ::DISCRIMINATOR, + ABListError::InvalidMetadata + ); + let mut payload = &wallet_data[disc_len..]; + let wallet = >::get(&mut payload) + .map_err(|_| ABListError::InvalidMetadata)?; if wallet.allowed { Ok(DecodedWalletMode::Allow) diff --git a/tokens/token-extensions/transfer-hook/allow-block-list-token/anchor/programs/abl-token/src/lib.rs b/tokens/token-extensions/transfer-hook/allow-block-list-token/anchor/programs/abl-token/src/lib.rs index c5b987c6b..e3be4b6d7 100644 --- a/tokens/token-extensions/transfer-hook/allow-block-list-token/anchor/programs/abl-token/src/lib.rs +++ b/tokens/token-extensions/transfer-hook/allow-block-list-token/anchor/programs/abl-token/src/lib.rs @@ -15,7 +15,14 @@ pub use utils::*; declare_id!("3ku1ZEGvBEEfhaYsAzBZuecTPEa58ZRhoVqHVGpGxVGi"); -#[program(interface, program_id = ID)] +pub mod entrypoint; + +// v2's `#[program(interface, ...)]` declares an interface for other programs to +// CPI into and emits no entrypoint, and an executable `#[program]` only accepts +// one-byte custom discriminators — so the transfer-hook interface's eight-byte +// `Execute` discriminator has no direct spelling. `entrypoint` bridges the gap: +// it routes `Execute` to `tx_hook` and hands everything else to anchor. +#[program] pub mod abl_token { use super::*; @@ -35,8 +42,6 @@ pub mod abl_token { context.accounts.attach_to_mint() } - // sha256("spl-transfer-hook-interface:execute")[..8] - #[discrim = [105, 37, 101, 197, 75, 251, 102, 26]] pub fn tx_hook(context: &mut Context, amount: u64) -> Result<()> { context.accounts.tx_hook(amount) } diff --git a/tokens/token-extensions/transfer-hook/counter/anchor/programs/transfer-hook/src/lib.rs b/tokens/token-extensions/transfer-hook/counter/anchor/programs/transfer-hook/src/lib.rs index b0397b1fe..deb82fe48 100644 --- a/tokens/token-extensions/transfer-hook/counter/anchor/programs/transfer-hook/src/lib.rs +++ b/tokens/token-extensions/transfer-hook/counter/anchor/programs/transfer-hook/src/lib.rs @@ -26,7 +26,11 @@ pub enum TransferError { IsNotCurrentlyTransferring, } -#[program(interface, program_id = ID)] +// v2's `#[program(interface)]` declares an interface for CPI and emits no +// entrypoint. This is a real deployable program that also implements the +// transfer-hook interface, so it stays a plain `#[program]`; the interface +// instruction gets its discriminator from `#[discrim = ...]` below. +#[program] pub mod transfer_hook { use super::*; diff --git a/tokens/token-extensions/transfer-hook/hello-world/anchor/programs/transfer-hook/src/lib.rs b/tokens/token-extensions/transfer-hook/hello-world/anchor/programs/transfer-hook/src/lib.rs index e26e7b59a..82c47644d 100644 --- a/tokens/token-extensions/transfer-hook/hello-world/anchor/programs/transfer-hook/src/lib.rs +++ b/tokens/token-extensions/transfer-hook/hello-world/anchor/programs/transfer-hook/src/lib.rs @@ -24,7 +24,11 @@ pub enum TransferError { IsNotCurrentlyTransferring, } -#[program(interface, program_id = ID)] +// v2's `#[program(interface)]` declares an interface for CPI and emits no +// entrypoint. This is a real deployable program that also implements the +// transfer-hook interface, so it stays a plain `#[program]`; the interface +// instruction gets its discriminator from `#[discrim = ...]` below. +#[program] pub mod transfer_hook { use super::*; diff --git a/tokens/token-extensions/transfer-hook/transfer-cost/anchor/programs/transfer-hook/src/lib.rs b/tokens/token-extensions/transfer-hook/transfer-cost/anchor/programs/transfer-hook/src/lib.rs index 218247283..c8ed7deba 100644 --- a/tokens/token-extensions/transfer-hook/transfer-cost/anchor/programs/transfer-hook/src/lib.rs +++ b/tokens/token-extensions/transfer-hook/transfer-cost/anchor/programs/transfer-hook/src/lib.rs @@ -36,7 +36,11 @@ pub enum TransferError { IsNotCurrentlyTransferring, } -#[program(interface, program_id = ID)] +// v2's `#[program(interface)]` declares an interface for CPI and emits no +// entrypoint. This is a real deployable program that also implements the +// transfer-hook interface, so it stays a plain `#[program]`; the interface +// instruction gets its discriminator from `#[discrim = ...]` below. +#[program] pub mod transfer_hook { use super::*; diff --git a/tokens/token-extensions/transfer-hook/transfer-switch/anchor/programs/transfer-switch/src/lib.rs b/tokens/token-extensions/transfer-hook/transfer-switch/anchor/programs/transfer-switch/src/lib.rs index 3ca716a74..0755a6035 100644 --- a/tokens/token-extensions/transfer-hook/transfer-switch/anchor/programs/transfer-switch/src/lib.rs +++ b/tokens/token-extensions/transfer-hook/transfer-switch/anchor/programs/transfer-switch/src/lib.rs @@ -11,7 +11,11 @@ use spl_transfer_hook_interface::instruction::{ declare_id!("FjcHckEgXcBhFmSGai3FRpDLiT6hbpV893n8iTxVd81g"); -#[program(interface, program_id = ID)] +// v2's `#[program(interface)]` declares an interface for CPI and emits no +// entrypoint. This is a real deployable program that also implements the +// transfer-hook interface, so it stays a plain `#[program]`; the interface +// instruction gets its discriminator from `#[discrim = ...]` below. +#[program] pub mod transfer_switch { use super::*; diff --git a/tokens/token-extensions/transfer-hook/whitelist/anchor/programs/transfer-hook/src/lib.rs b/tokens/token-extensions/transfer-hook/whitelist/anchor/programs/transfer-hook/src/lib.rs index 7ae332eb5..9ce465aba 100644 --- a/tokens/token-extensions/transfer-hook/whitelist/anchor/programs/transfer-hook/src/lib.rs +++ b/tokens/token-extensions/transfer-hook/whitelist/anchor/programs/transfer-hook/src/lib.rs @@ -24,7 +24,11 @@ pub enum TransferError { IsNotCurrentlyTransferring, } -#[program(interface, program_id = ID)] +// v2's `#[program(interface)]` declares an interface for CPI and emits no +// entrypoint. This is a real deployable program that also implements the +// transfer-hook interface, so it stays a plain `#[program]`; the interface +// instruction gets its discriminator from `#[discrim = ...]` below. +#[program] pub mod transfer_hook { use super::*; From e46658384c49a96cf3ab78448c2058fd7d52ceea Mon Sep 17 00:00:00 2001 From: Claude Date: Fri, 14 Aug 2026 20:10:59 +0000 Subject: [PATCH 27/59] transfer-hook: wire the entrypoint shim into the remaining hook programs Same treatment as allow-block-list-token, the interface discriminators are mapped onto handlers by a hand-written entrypoint instead of `#[discrim]`, which an executable `#[program]` limits to one byte. check_is_transferring also drops to a shared borrow: the account already holds one, so try_borrow_mut on a copy of the view was rejected at runtime with AccountBorrowFailed. Reading `transferring` never needed write access. --- .../anchor/programs/transfer-hook/Cargo.toml | 6 ++- .../programs/transfer-hook/src/entrypoint.rs | 50 +++++++++++++++++++ .../anchor/programs/transfer-hook/src/lib.rs | 26 +++++----- .../anchor/programs/transfer-hook/Cargo.toml | 6 ++- .../programs/transfer-hook/src/entrypoint.rs | 50 +++++++++++++++++++ .../anchor/programs/transfer-hook/src/lib.rs | 29 +++++++---- .../anchor/programs/transfer-hook/Cargo.toml | 6 ++- .../programs/transfer-hook/src/entrypoint.rs | 50 +++++++++++++++++++ .../anchor/programs/transfer-hook/src/lib.rs | 26 +++++----- .../anchor/programs/transfer-hook/Cargo.toml | 6 ++- .../programs/transfer-hook/src/entrypoint.rs | 50 +++++++++++++++++++ .../anchor/programs/transfer-hook/src/lib.rs | 24 +++++---- .../programs/transfer-switch/Cargo.toml | 6 ++- .../transfer-switch/src/entrypoint.rs | 50 +++++++++++++++++++ .../programs/transfer-switch/src/lib.rs | 13 ++--- .../anchor/programs/transfer-hook/Cargo.toml | 6 ++- .../programs/transfer-hook/src/entrypoint.rs | 50 +++++++++++++++++++ .../anchor/programs/transfer-hook/src/lib.rs | 26 +++++----- 18 files changed, 411 insertions(+), 69 deletions(-) create mode 100644 tokens/token-extensions/transfer-hook/account-data-as-seed/anchor/programs/transfer-hook/src/entrypoint.rs create mode 100644 tokens/token-extensions/transfer-hook/counter/anchor/programs/transfer-hook/src/entrypoint.rs create mode 100644 tokens/token-extensions/transfer-hook/hello-world/anchor/programs/transfer-hook/src/entrypoint.rs create mode 100644 tokens/token-extensions/transfer-hook/transfer-cost/anchor/programs/transfer-hook/src/entrypoint.rs create mode 100644 tokens/token-extensions/transfer-hook/transfer-switch/anchor/programs/transfer-switch/src/entrypoint.rs create mode 100644 tokens/token-extensions/transfer-hook/whitelist/anchor/programs/transfer-hook/src/entrypoint.rs diff --git a/tokens/token-extensions/transfer-hook/account-data-as-seed/anchor/programs/transfer-hook/Cargo.toml b/tokens/token-extensions/transfer-hook/account-data-as-seed/anchor/programs/transfer-hook/Cargo.toml index 3b769ebc2..098b26a15 100644 --- a/tokens/token-extensions/transfer-hook/account-data-as-seed/anchor/programs/transfer-hook/Cargo.toml +++ b/tokens/token-extensions/transfer-hook/account-data-as-seed/anchor/programs/transfer-hook/Cargo.toml @@ -9,7 +9,11 @@ crate-type = ["cdylib", "lib"] name = "transfer_hook" [features] -default = [] +# `no-entrypoint` is always on: anchor then exports its dispatch as +# `__anchor_dispatch` rather than claiming the `entrypoint` symbol, leaving +# src/entrypoint.rs free to claim it and map the transfer-hook interface's +# discriminators onto this program's handlers. +default = ["no-entrypoint"] cpi = ["no-entrypoint"] no-entrypoint = [] no-idl = [] diff --git a/tokens/token-extensions/transfer-hook/account-data-as-seed/anchor/programs/transfer-hook/src/entrypoint.rs b/tokens/token-extensions/transfer-hook/account-data-as-seed/anchor/programs/transfer-hook/src/entrypoint.rs new file mode 100644 index 000000000..26ef91dbb --- /dev/null +++ b/tokens/token-extensions/transfer-hook/account-data-as-seed/anchor/programs/transfer-hook/src/entrypoint.rs @@ -0,0 +1,50 @@ +//! Program entrypoint, hand-written so the SPL transfer-hook interface reaches +//! this program's handlers. +//! +//! Anchor v2 gives every instruction in an executable `#[program]` the eight-byte +//! `sha256("global:")` discriminator, and `#[discrim = N]` there is limited +//! to a single byte. The transfer-hook interface calls its instructions under +//! their own eight-byte values, which leaves no way to declare those handlers +//! directly. `#[program(interface, ...)]` accepts arbitrary discriminator bytes +//! but only generates a CPI client — no dispatch, and so no deployable program. +//! +//! So the crate builds with `no-entrypoint` (which makes anchor export its +//! dispatch as `__anchor_dispatch` instead of claiming the `entrypoint` symbol) +//! and this module claims `entrypoint` itself. All it does is swap an interface +//! discriminator for the matching handler's before delegating; the payload +//! behind it is identical either way. + +use anchor_lang::pinocchio; + +pinocchio::default_allocator!(); +pinocchio::default_panic_handler!(); + +/// Interface discriminator paired with the handler's own, in declaration order. +const DISCRIMINATOR_MAP: [([u8; 8], [u8; 8]); 2] = [ + // initialize_extra_account_meta_list + ([43, 34, 13, 49, 167, 88, 235, 235], [92, 197, 174, 197, 41, 124, 19, 3]), + // transfer_hook + ([105, 37, 101, 197, 75, 251, 102, 26], [220, 57, 220, 152, 126, 125, 97, 168]), +]; + +/// # Safety +/// +/// Called only by the SBF loader, with the register convention anchor's own +/// entrypoint documents: `r1` is the start of the serialized parameter region +/// and `r2` points at the instruction data, whose length sits in the eight +/// bytes below it. +#[cfg(target_os = "solana")] +#[no_mangle] +pub unsafe extern "C" fn entrypoint(input: *mut u8, ix_data_ptr: *const u8) -> u64 { + let len = *(ix_data_ptr.sub(8) as *const u64) as usize; + if len >= 8 { + let discriminator = core::slice::from_raw_parts_mut(ix_data_ptr as *mut u8, 8); + for (interface, handler) in DISCRIMINATOR_MAP { + if discriminator == interface { + discriminator.copy_from_slice(&handler); + break; + } + } + } + crate::__anchor_dispatch(input, ix_data_ptr) +} diff --git a/tokens/token-extensions/transfer-hook/account-data-as-seed/anchor/programs/transfer-hook/src/lib.rs b/tokens/token-extensions/transfer-hook/account-data-as-seed/anchor/programs/transfer-hook/src/lib.rs index 877cceb7c..35401c3b3 100644 --- a/tokens/token-extensions/transfer-hook/account-data-as-seed/anchor/programs/transfer-hook/src/lib.rs +++ b/tokens/token-extensions/transfer-hook/account-data-as-seed/anchor/programs/transfer-hook/src/lib.rs @@ -3,7 +3,7 @@ use std::cell::RefMut; use anchor_lang::prelude::*; use anchor_spl::token_2022::spl_token_2022::{ extension::{ - transfer_hook::TransferHookAccount, BaseStateWithExtensionsMut, PodStateWithExtensionsMut, + transfer_hook::TransferHookAccount, BaseStateWithExtensions, PodStateWithExtensions, }, pod::PodAccount, }; @@ -26,16 +26,18 @@ pub enum TransferError { IsNotCurrentlyTransferring, } -// v2's `#[program(interface)]` declares an interface for CPI and emits no -// entrypoint. This is a real deployable program that also implements the -// transfer-hook interface, so it stays a plain `#[program]`; the interface -// instruction gets its discriminator from `#[discrim = ...]` below. +pub mod entrypoint; + +// v2's `#[program(interface, ...)]` declares an interface for other programs to +// CPI into and emits no entrypoint, and an executable `#[program]` only accepts +// one-byte custom discriminators — so the transfer-hook interface's eight-byte +// discriminators have no direct spelling. `entrypoint` bridges the gap: it maps +// each of them onto a handler before anchor's dispatch runs. #[program] pub mod transfer_hook { use super::*; // sha256("spl-transfer-hook-interface:initialize-extra-account-metas")[..8] - #[discrim = [43, 34, 13, 49, 167, 88, 235, 235]] pub fn initialize_extra_account_meta_list( context: &mut Context, ) -> Result<()> { @@ -43,7 +45,6 @@ pub mod transfer_hook { } // sha256("spl-transfer-hook-interface:execute")[..8] - #[discrim = [105, 37, 101, 197, 75, 251, 102, 26]] pub fn transfer_hook( context: &mut Context, amount: u64, @@ -53,14 +54,15 @@ pub mod transfer_hook { } pub fn check_is_transferring(context: &Context) -> Result<()> { - let mut source_token_info = *context.accounts.source_token.account(); - let mut account_data_ref = source_token_info.try_borrow_mut()?; + // Read-only: the account already holds a shared borrow of its buffer, and a + // second shared borrow is fine where `try_borrow_mut` would be rejected. + let account_data_ref = context.accounts.source_token.account().try_borrow()?; // .map_err() needed because spl-token-2022 uses solana-program-error 2.x - // while anchor-lang 1.0 uses 3.x - structurally identical but different semver types - let mut account = PodStateWithExtensionsMut::::unpack(&mut account_data_ref) + // while anchor-lang uses 3.x - structurally identical but different semver types + let account = PodStateWithExtensions::::unpack(&account_data_ref) .map_err(|_| ProgramError::InvalidAccountData)?; let account_extension = account - .get_extension_mut::() + .get_extension::() .map_err(|_| ProgramError::InvalidAccountData)?; if !bool::from(account_extension.transferring) { diff --git a/tokens/token-extensions/transfer-hook/counter/anchor/programs/transfer-hook/Cargo.toml b/tokens/token-extensions/transfer-hook/counter/anchor/programs/transfer-hook/Cargo.toml index 3b769ebc2..098b26a15 100644 --- a/tokens/token-extensions/transfer-hook/counter/anchor/programs/transfer-hook/Cargo.toml +++ b/tokens/token-extensions/transfer-hook/counter/anchor/programs/transfer-hook/Cargo.toml @@ -9,7 +9,11 @@ crate-type = ["cdylib", "lib"] name = "transfer_hook" [features] -default = [] +# `no-entrypoint` is always on: anchor then exports its dispatch as +# `__anchor_dispatch` rather than claiming the `entrypoint` symbol, leaving +# src/entrypoint.rs free to claim it and map the transfer-hook interface's +# discriminators onto this program's handlers. +default = ["no-entrypoint"] cpi = ["no-entrypoint"] no-entrypoint = [] no-idl = [] diff --git a/tokens/token-extensions/transfer-hook/counter/anchor/programs/transfer-hook/src/entrypoint.rs b/tokens/token-extensions/transfer-hook/counter/anchor/programs/transfer-hook/src/entrypoint.rs new file mode 100644 index 000000000..26ef91dbb --- /dev/null +++ b/tokens/token-extensions/transfer-hook/counter/anchor/programs/transfer-hook/src/entrypoint.rs @@ -0,0 +1,50 @@ +//! Program entrypoint, hand-written so the SPL transfer-hook interface reaches +//! this program's handlers. +//! +//! Anchor v2 gives every instruction in an executable `#[program]` the eight-byte +//! `sha256("global:")` discriminator, and `#[discrim = N]` there is limited +//! to a single byte. The transfer-hook interface calls its instructions under +//! their own eight-byte values, which leaves no way to declare those handlers +//! directly. `#[program(interface, ...)]` accepts arbitrary discriminator bytes +//! but only generates a CPI client — no dispatch, and so no deployable program. +//! +//! So the crate builds with `no-entrypoint` (which makes anchor export its +//! dispatch as `__anchor_dispatch` instead of claiming the `entrypoint` symbol) +//! and this module claims `entrypoint` itself. All it does is swap an interface +//! discriminator for the matching handler's before delegating; the payload +//! behind it is identical either way. + +use anchor_lang::pinocchio; + +pinocchio::default_allocator!(); +pinocchio::default_panic_handler!(); + +/// Interface discriminator paired with the handler's own, in declaration order. +const DISCRIMINATOR_MAP: [([u8; 8], [u8; 8]); 2] = [ + // initialize_extra_account_meta_list + ([43, 34, 13, 49, 167, 88, 235, 235], [92, 197, 174, 197, 41, 124, 19, 3]), + // transfer_hook + ([105, 37, 101, 197, 75, 251, 102, 26], [220, 57, 220, 152, 126, 125, 97, 168]), +]; + +/// # Safety +/// +/// Called only by the SBF loader, with the register convention anchor's own +/// entrypoint documents: `r1` is the start of the serialized parameter region +/// and `r2` points at the instruction data, whose length sits in the eight +/// bytes below it. +#[cfg(target_os = "solana")] +#[no_mangle] +pub unsafe extern "C" fn entrypoint(input: *mut u8, ix_data_ptr: *const u8) -> u64 { + let len = *(ix_data_ptr.sub(8) as *const u64) as usize; + if len >= 8 { + let discriminator = core::slice::from_raw_parts_mut(ix_data_ptr as *mut u8, 8); + for (interface, handler) in DISCRIMINATOR_MAP { + if discriminator == interface { + discriminator.copy_from_slice(&handler); + break; + } + } + } + crate::__anchor_dispatch(input, ix_data_ptr) +} diff --git a/tokens/token-extensions/transfer-hook/counter/anchor/programs/transfer-hook/src/lib.rs b/tokens/token-extensions/transfer-hook/counter/anchor/programs/transfer-hook/src/lib.rs index deb82fe48..79c91f6c8 100644 --- a/tokens/token-extensions/transfer-hook/counter/anchor/programs/transfer-hook/src/lib.rs +++ b/tokens/token-extensions/transfer-hook/counter/anchor/programs/transfer-hook/src/lib.rs @@ -3,7 +3,7 @@ use std::cell::RefMut; use anchor_lang::prelude::*; use anchor_spl::token_2022::spl_token_2022::{ extension::{ - transfer_hook::TransferHookAccount, BaseStateWithExtensionsMut, PodStateWithExtensionsMut, + transfer_hook::TransferHookAccount, BaseStateWithExtensions, PodStateWithExtensions, }, pod::PodAccount, }; @@ -26,16 +26,18 @@ pub enum TransferError { IsNotCurrentlyTransferring, } -// v2's `#[program(interface)]` declares an interface for CPI and emits no -// entrypoint. This is a real deployable program that also implements the -// transfer-hook interface, so it stays a plain `#[program]`; the interface -// instruction gets its discriminator from `#[discrim = ...]` below. +pub mod entrypoint; + +// v2's `#[program(interface, ...)]` declares an interface for other programs to +// CPI into and emits no entrypoint, and an executable `#[program]` only accepts +// one-byte custom discriminators — so the transfer-hook interface's eight-byte +// discriminators have no direct spelling. `entrypoint` bridges the gap: it maps +// each of them onto a handler before anchor's dispatch runs. #[program] pub mod transfer_hook { use super::*; // sha256("spl-transfer-hook-interface:initialize-extra-account-metas")[..8] - #[discrim = [43, 34, 13, 49, 167, 88, 235, 235]] pub fn initialize_extra_account_meta_list( context: &mut Context, ) -> Result<()> { @@ -43,7 +45,6 @@ pub mod transfer_hook { } // sha256("spl-transfer-hook-interface:execute")[..8] - #[discrim = [105, 37, 101, 197, 75, 251, 102, 26]] pub fn transfer_hook( context: &mut Context, amount: u64, @@ -53,10 +54,16 @@ pub mod transfer_hook { } pub fn check_is_transferring(context: &Context) -> Result<()> { - let mut source_token_info = *context.accounts.source_token.account(); - let mut account_data_ref = source_token_info.try_borrow_mut()?; - let mut account = PodStateWithExtensionsMut::::unpack(&mut account_data_ref)?; - let account_extension = account.get_extension_mut::()?; + // Read-only: the account already holds a shared borrow of its buffer, and a + // second shared borrow is fine where `try_borrow_mut` would be rejected. + let account_data_ref = context.accounts.source_token.account().try_borrow()?; + // .map_err() needed because spl-token-2022 uses solana-program-error 2.x + // while anchor-lang uses 3.x - structurally identical but different semver types + let account = PodStateWithExtensions::::unpack(&account_data_ref) + .map_err(|_| ProgramError::InvalidAccountData)?; + let account_extension = account + .get_extension::() + .map_err(|_| ProgramError::InvalidAccountData)?; if !bool::from(account_extension.transferring) { return err!(TransferError::IsNotCurrentlyTransferring); diff --git a/tokens/token-extensions/transfer-hook/hello-world/anchor/programs/transfer-hook/Cargo.toml b/tokens/token-extensions/transfer-hook/hello-world/anchor/programs/transfer-hook/Cargo.toml index 3b769ebc2..098b26a15 100644 --- a/tokens/token-extensions/transfer-hook/hello-world/anchor/programs/transfer-hook/Cargo.toml +++ b/tokens/token-extensions/transfer-hook/hello-world/anchor/programs/transfer-hook/Cargo.toml @@ -9,7 +9,11 @@ crate-type = ["cdylib", "lib"] name = "transfer_hook" [features] -default = [] +# `no-entrypoint` is always on: anchor then exports its dispatch as +# `__anchor_dispatch` rather than claiming the `entrypoint` symbol, leaving +# src/entrypoint.rs free to claim it and map the transfer-hook interface's +# discriminators onto this program's handlers. +default = ["no-entrypoint"] cpi = ["no-entrypoint"] no-entrypoint = [] no-idl = [] diff --git a/tokens/token-extensions/transfer-hook/hello-world/anchor/programs/transfer-hook/src/entrypoint.rs b/tokens/token-extensions/transfer-hook/hello-world/anchor/programs/transfer-hook/src/entrypoint.rs new file mode 100644 index 000000000..26ef91dbb --- /dev/null +++ b/tokens/token-extensions/transfer-hook/hello-world/anchor/programs/transfer-hook/src/entrypoint.rs @@ -0,0 +1,50 @@ +//! Program entrypoint, hand-written so the SPL transfer-hook interface reaches +//! this program's handlers. +//! +//! Anchor v2 gives every instruction in an executable `#[program]` the eight-byte +//! `sha256("global:")` discriminator, and `#[discrim = N]` there is limited +//! to a single byte. The transfer-hook interface calls its instructions under +//! their own eight-byte values, which leaves no way to declare those handlers +//! directly. `#[program(interface, ...)]` accepts arbitrary discriminator bytes +//! but only generates a CPI client — no dispatch, and so no deployable program. +//! +//! So the crate builds with `no-entrypoint` (which makes anchor export its +//! dispatch as `__anchor_dispatch` instead of claiming the `entrypoint` symbol) +//! and this module claims `entrypoint` itself. All it does is swap an interface +//! discriminator for the matching handler's before delegating; the payload +//! behind it is identical either way. + +use anchor_lang::pinocchio; + +pinocchio::default_allocator!(); +pinocchio::default_panic_handler!(); + +/// Interface discriminator paired with the handler's own, in declaration order. +const DISCRIMINATOR_MAP: [([u8; 8], [u8; 8]); 2] = [ + // initialize_extra_account_meta_list + ([43, 34, 13, 49, 167, 88, 235, 235], [92, 197, 174, 197, 41, 124, 19, 3]), + // transfer_hook + ([105, 37, 101, 197, 75, 251, 102, 26], [220, 57, 220, 152, 126, 125, 97, 168]), +]; + +/// # Safety +/// +/// Called only by the SBF loader, with the register convention anchor's own +/// entrypoint documents: `r1` is the start of the serialized parameter region +/// and `r2` points at the instruction data, whose length sits in the eight +/// bytes below it. +#[cfg(target_os = "solana")] +#[no_mangle] +pub unsafe extern "C" fn entrypoint(input: *mut u8, ix_data_ptr: *const u8) -> u64 { + let len = *(ix_data_ptr.sub(8) as *const u64) as usize; + if len >= 8 { + let discriminator = core::slice::from_raw_parts_mut(ix_data_ptr as *mut u8, 8); + for (interface, handler) in DISCRIMINATOR_MAP { + if discriminator == interface { + discriminator.copy_from_slice(&handler); + break; + } + } + } + crate::__anchor_dispatch(input, ix_data_ptr) +} diff --git a/tokens/token-extensions/transfer-hook/hello-world/anchor/programs/transfer-hook/src/lib.rs b/tokens/token-extensions/transfer-hook/hello-world/anchor/programs/transfer-hook/src/lib.rs index 82c47644d..7a8acc0f2 100644 --- a/tokens/token-extensions/transfer-hook/hello-world/anchor/programs/transfer-hook/src/lib.rs +++ b/tokens/token-extensions/transfer-hook/hello-world/anchor/programs/transfer-hook/src/lib.rs @@ -3,7 +3,7 @@ use std::cell::RefMut; use anchor_lang::prelude::*; use anchor_spl::token_2022::spl_token_2022::{ extension::{ - transfer_hook::TransferHookAccount, BaseStateWithExtensionsMut, PodStateWithExtensionsMut, + transfer_hook::TransferHookAccount, BaseStateWithExtensions, PodStateWithExtensions, }, pod::PodAccount, }; @@ -24,10 +24,13 @@ pub enum TransferError { IsNotCurrentlyTransferring, } -// v2's `#[program(interface)]` declares an interface for CPI and emits no -// entrypoint. This is a real deployable program that also implements the -// transfer-hook interface, so it stays a plain `#[program]`; the interface -// instruction gets its discriminator from `#[discrim = ...]` below. +pub mod entrypoint; + +// v2's `#[program(interface, ...)]` declares an interface for other programs to +// CPI into and emits no entrypoint, and an executable `#[program]` only accepts +// one-byte custom discriminators — so the transfer-hook interface's eight-byte +// discriminators have no direct spelling. `entrypoint` bridges the gap: it maps +// each of them onto a handler before anchor's dispatch runs. #[program] pub mod transfer_hook { use super::*; @@ -40,7 +43,6 @@ pub mod transfer_hook { } // sha256("spl-transfer-hook-interface:initialize-extra-account-metas")[..8] - #[discrim = [43, 34, 13, 49, 167, 88, 235, 235]] pub fn initialize_extra_account_meta_list( context: &mut Context, ) -> Result<()> { @@ -48,7 +50,6 @@ pub mod transfer_hook { } // sha256("spl-transfer-hook-interface:execute")[..8] - #[discrim = [105, 37, 101, 197, 75, 251, 102, 26]] pub fn transfer_hook( context: &mut Context, amount: u64, @@ -58,14 +59,15 @@ pub mod transfer_hook { } pub fn check_is_transferring(context: &Context) -> Result<()> { - let mut source_token_info = *context.accounts.source_token.account(); - let mut account_data_ref = source_token_info.try_borrow_mut()?; + // Read-only: the account already holds a shared borrow of its buffer, and a + // second shared borrow is fine where `try_borrow_mut` would be rejected. + let account_data_ref = context.accounts.source_token.account().try_borrow()?; // .map_err() needed because spl-token-2022 uses solana-program-error 2.x - // while anchor-lang 1.0 uses 3.x - structurally identical but different semver types - let mut account = PodStateWithExtensionsMut::::unpack(&mut account_data_ref) + // while anchor-lang uses 3.x - structurally identical but different semver types + let account = PodStateWithExtensions::::unpack(&account_data_ref) .map_err(|_| ProgramError::InvalidAccountData)?; let account_extension = account - .get_extension_mut::() + .get_extension::() .map_err(|_| ProgramError::InvalidAccountData)?; if !bool::from(account_extension.transferring) { diff --git a/tokens/token-extensions/transfer-hook/transfer-cost/anchor/programs/transfer-hook/Cargo.toml b/tokens/token-extensions/transfer-hook/transfer-cost/anchor/programs/transfer-hook/Cargo.toml index 8f1d2d09c..9f340987c 100644 --- a/tokens/token-extensions/transfer-hook/transfer-cost/anchor/programs/transfer-hook/Cargo.toml +++ b/tokens/token-extensions/transfer-hook/transfer-cost/anchor/programs/transfer-hook/Cargo.toml @@ -9,7 +9,11 @@ crate-type = ["cdylib", "lib"] name = "transfer_hook" [features] -default = [] +# `no-entrypoint` is always on: anchor then exports its dispatch as +# `__anchor_dispatch` rather than claiming the `entrypoint` symbol, leaving +# src/entrypoint.rs free to claim it and map the transfer-hook interface's +# discriminators onto this program's handlers. +default = ["no-entrypoint"] cpi = ["no-entrypoint"] no-entrypoint = [] no-idl = [] diff --git a/tokens/token-extensions/transfer-hook/transfer-cost/anchor/programs/transfer-hook/src/entrypoint.rs b/tokens/token-extensions/transfer-hook/transfer-cost/anchor/programs/transfer-hook/src/entrypoint.rs new file mode 100644 index 000000000..26ef91dbb --- /dev/null +++ b/tokens/token-extensions/transfer-hook/transfer-cost/anchor/programs/transfer-hook/src/entrypoint.rs @@ -0,0 +1,50 @@ +//! Program entrypoint, hand-written so the SPL transfer-hook interface reaches +//! this program's handlers. +//! +//! Anchor v2 gives every instruction in an executable `#[program]` the eight-byte +//! `sha256("global:")` discriminator, and `#[discrim = N]` there is limited +//! to a single byte. The transfer-hook interface calls its instructions under +//! their own eight-byte values, which leaves no way to declare those handlers +//! directly. `#[program(interface, ...)]` accepts arbitrary discriminator bytes +//! but only generates a CPI client — no dispatch, and so no deployable program. +//! +//! So the crate builds with `no-entrypoint` (which makes anchor export its +//! dispatch as `__anchor_dispatch` instead of claiming the `entrypoint` symbol) +//! and this module claims `entrypoint` itself. All it does is swap an interface +//! discriminator for the matching handler's before delegating; the payload +//! behind it is identical either way. + +use anchor_lang::pinocchio; + +pinocchio::default_allocator!(); +pinocchio::default_panic_handler!(); + +/// Interface discriminator paired with the handler's own, in declaration order. +const DISCRIMINATOR_MAP: [([u8; 8], [u8; 8]); 2] = [ + // initialize_extra_account_meta_list + ([43, 34, 13, 49, 167, 88, 235, 235], [92, 197, 174, 197, 41, 124, 19, 3]), + // transfer_hook + ([105, 37, 101, 197, 75, 251, 102, 26], [220, 57, 220, 152, 126, 125, 97, 168]), +]; + +/// # Safety +/// +/// Called only by the SBF loader, with the register convention anchor's own +/// entrypoint documents: `r1` is the start of the serialized parameter region +/// and `r2` points at the instruction data, whose length sits in the eight +/// bytes below it. +#[cfg(target_os = "solana")] +#[no_mangle] +pub unsafe extern "C" fn entrypoint(input: *mut u8, ix_data_ptr: *const u8) -> u64 { + let len = *(ix_data_ptr.sub(8) as *const u64) as usize; + if len >= 8 { + let discriminator = core::slice::from_raw_parts_mut(ix_data_ptr as *mut u8, 8); + for (interface, handler) in DISCRIMINATOR_MAP { + if discriminator == interface { + discriminator.copy_from_slice(&handler); + break; + } + } + } + crate::__anchor_dispatch(input, ix_data_ptr) +} diff --git a/tokens/token-extensions/transfer-hook/transfer-cost/anchor/programs/transfer-hook/src/lib.rs b/tokens/token-extensions/transfer-hook/transfer-cost/anchor/programs/transfer-hook/src/lib.rs index c8ed7deba..8731dd5a9 100644 --- a/tokens/token-extensions/transfer-hook/transfer-cost/anchor/programs/transfer-hook/src/lib.rs +++ b/tokens/token-extensions/transfer-hook/transfer-cost/anchor/programs/transfer-hook/src/lib.rs @@ -36,16 +36,18 @@ pub enum TransferError { IsNotCurrentlyTransferring, } -// v2's `#[program(interface)]` declares an interface for CPI and emits no -// entrypoint. This is a real deployable program that also implements the -// transfer-hook interface, so it stays a plain `#[program]`; the interface -// instruction gets its discriminator from `#[discrim = ...]` below. +pub mod entrypoint; + +// v2's `#[program(interface, ...)]` declares an interface for other programs to +// CPI into and emits no entrypoint, and an executable `#[program]` only accepts +// one-byte custom discriminators — so the transfer-hook interface's eight-byte +// discriminators have no direct spelling. `entrypoint` bridges the gap: it maps +// each of them onto a handler before anchor's dispatch runs. #[program] pub mod transfer_hook { use super::*; // sha256("spl-transfer-hook-interface:initialize-extra-account-metas")[..8] - #[discrim = [43, 34, 13, 49, 167, 88, 235, 235]] pub fn initialize_extra_account_meta_list( context: &mut Context, ) -> Result<()> { @@ -53,7 +55,6 @@ pub mod transfer_hook { } // sha256("spl-transfer-hook-interface:execute")[..8] - #[discrim = [105, 37, 101, 197, 75, 251, 102, 26]] pub fn transfer_hook( context: &mut Context, amount: u64, @@ -63,12 +64,15 @@ pub mod transfer_hook { } pub fn check_is_transferring(context: &Context) -> Result<()> { - let mut source_token_info = *context.accounts.source_token.account(); - let mut account_data_ref = source_token_info.try_borrow_mut()?; - let mut account = PodStateWithExtensionsMut::::unpack(&mut account_data_ref) + // Read-only: the account already holds a shared borrow of its buffer, and a + // second shared borrow is fine where `try_borrow_mut` would be rejected. + let account_data_ref = context.accounts.source_token.account().try_borrow()?; + // .map_err() needed because spl-token-2022 uses solana-program-error 2.x + // while anchor-lang uses 3.x - structurally identical but different semver types + let account = PodStateWithExtensions::::unpack(&account_data_ref) .map_err(|_| ProgramError::InvalidAccountData)?; let account_extension = account - .get_extension_mut::() + .get_extension::() .map_err(|_| ProgramError::InvalidAccountData)?; if !bool::from(account_extension.transferring) { diff --git a/tokens/token-extensions/transfer-hook/transfer-switch/anchor/programs/transfer-switch/Cargo.toml b/tokens/token-extensions/transfer-hook/transfer-switch/anchor/programs/transfer-switch/Cargo.toml index 17c4b0a2d..363c1f6d8 100644 --- a/tokens/token-extensions/transfer-hook/transfer-switch/anchor/programs/transfer-switch/Cargo.toml +++ b/tokens/token-extensions/transfer-hook/transfer-switch/anchor/programs/transfer-switch/Cargo.toml @@ -9,7 +9,11 @@ crate-type = ["cdylib", "lib"] name = "transfer_switch" [features] -default = [] +# `no-entrypoint` is always on: anchor then exports its dispatch as +# `__anchor_dispatch` rather than claiming the `entrypoint` symbol, leaving +# src/entrypoint.rs free to claim it and map the transfer-hook interface's +# discriminators onto this program's handlers. +default = ["no-entrypoint"] cpi = ["no-entrypoint"] no-entrypoint = [] no-idl = [] diff --git a/tokens/token-extensions/transfer-hook/transfer-switch/anchor/programs/transfer-switch/src/entrypoint.rs b/tokens/token-extensions/transfer-hook/transfer-switch/anchor/programs/transfer-switch/src/entrypoint.rs new file mode 100644 index 000000000..d99c8748b --- /dev/null +++ b/tokens/token-extensions/transfer-hook/transfer-switch/anchor/programs/transfer-switch/src/entrypoint.rs @@ -0,0 +1,50 @@ +//! Program entrypoint, hand-written so the SPL transfer-hook interface reaches +//! this program's handlers. +//! +//! Anchor v2 gives every instruction in an executable `#[program]` the eight-byte +//! `sha256("global:")` discriminator, and `#[discrim = N]` there is limited +//! to a single byte. The transfer-hook interface calls its instructions under +//! their own eight-byte values, which leaves no way to declare those handlers +//! directly. `#[program(interface, ...)]` accepts arbitrary discriminator bytes +//! but only generates a CPI client — no dispatch, and so no deployable program. +//! +//! So the crate builds with `no-entrypoint` (which makes anchor export its +//! dispatch as `__anchor_dispatch` instead of claiming the `entrypoint` symbol) +//! and this module claims `entrypoint` itself. All it does is swap an interface +//! discriminator for the matching handler's before delegating; the payload +//! behind it is identical either way. + +use anchor_lang::pinocchio; + +pinocchio::default_allocator!(); +pinocchio::default_panic_handler!(); + +/// Interface discriminator paired with the handler's own, in declaration order. +const DISCRIMINATOR_MAP: [([u8; 8], [u8; 8]); 2] = [ + // initialize_extra_account_metas_list + ([43, 34, 13, 49, 167, 88, 235, 235], [253, 251, 47, 0, 182, 68, 159, 62]), + // transfer_hook + ([105, 37, 101, 197, 75, 251, 102, 26], [220, 57, 220, 152, 126, 125, 97, 168]), +]; + +/// # Safety +/// +/// Called only by the SBF loader, with the register convention anchor's own +/// entrypoint documents: `r1` is the start of the serialized parameter region +/// and `r2` points at the instruction data, whose length sits in the eight +/// bytes below it. +#[cfg(target_os = "solana")] +#[no_mangle] +pub unsafe extern "C" fn entrypoint(input: *mut u8, ix_data_ptr: *const u8) -> u64 { + let len = *(ix_data_ptr.sub(8) as *const u64) as usize; + if len >= 8 { + let discriminator = core::slice::from_raw_parts_mut(ix_data_ptr as *mut u8, 8); + for (interface, handler) in DISCRIMINATOR_MAP { + if discriminator == interface { + discriminator.copy_from_slice(&handler); + break; + } + } + } + crate::__anchor_dispatch(input, ix_data_ptr) +} diff --git a/tokens/token-extensions/transfer-hook/transfer-switch/anchor/programs/transfer-switch/src/lib.rs b/tokens/token-extensions/transfer-hook/transfer-switch/anchor/programs/transfer-switch/src/lib.rs index 0755a6035..eff3270dc 100644 --- a/tokens/token-extensions/transfer-hook/transfer-switch/anchor/programs/transfer-switch/src/lib.rs +++ b/tokens/token-extensions/transfer-hook/transfer-switch/anchor/programs/transfer-switch/src/lib.rs @@ -11,10 +11,13 @@ use spl_transfer_hook_interface::instruction::{ declare_id!("FjcHckEgXcBhFmSGai3FRpDLiT6hbpV893n8iTxVd81g"); -// v2's `#[program(interface)]` declares an interface for CPI and emits no -// entrypoint. This is a real deployable program that also implements the -// transfer-hook interface, so it stays a plain `#[program]`; the interface -// instruction gets its discriminator from `#[discrim = ...]` below. +pub mod entrypoint; + +// v2's `#[program(interface, ...)]` declares an interface for other programs to +// CPI into and emits no entrypoint, and an executable `#[program]` only accepts +// one-byte custom discriminators — so the transfer-hook interface's eight-byte +// discriminators have no direct spelling. `entrypoint` bridges the gap: it maps +// each of them onto a handler before anchor's dispatch runs. #[program] pub mod transfer_switch { use super::*; @@ -28,7 +31,6 @@ pub mod transfer_switch { } // sha256("spl-transfer-hook-interface:initialize-extra-account-metas")[..8] - #[discrim = [43, 34, 13, 49, 167, 88, 235, 235]] pub fn initialize_extra_account_metas_list( mut context: &mut Context, ) -> Result<()> { @@ -41,7 +43,6 @@ pub mod transfer_switch { } // sha256("spl-transfer-hook-interface:execute")[..8] - #[discrim = [105, 37, 101, 197, 75, 251, 102, 26]] pub fn transfer_hook( mut context: &mut Context, _amount: u64, diff --git a/tokens/token-extensions/transfer-hook/whitelist/anchor/programs/transfer-hook/Cargo.toml b/tokens/token-extensions/transfer-hook/whitelist/anchor/programs/transfer-hook/Cargo.toml index 3b769ebc2..098b26a15 100644 --- a/tokens/token-extensions/transfer-hook/whitelist/anchor/programs/transfer-hook/Cargo.toml +++ b/tokens/token-extensions/transfer-hook/whitelist/anchor/programs/transfer-hook/Cargo.toml @@ -9,7 +9,11 @@ crate-type = ["cdylib", "lib"] name = "transfer_hook" [features] -default = [] +# `no-entrypoint` is always on: anchor then exports its dispatch as +# `__anchor_dispatch` rather than claiming the `entrypoint` symbol, leaving +# src/entrypoint.rs free to claim it and map the transfer-hook interface's +# discriminators onto this program's handlers. +default = ["no-entrypoint"] cpi = ["no-entrypoint"] no-entrypoint = [] no-idl = [] diff --git a/tokens/token-extensions/transfer-hook/whitelist/anchor/programs/transfer-hook/src/entrypoint.rs b/tokens/token-extensions/transfer-hook/whitelist/anchor/programs/transfer-hook/src/entrypoint.rs new file mode 100644 index 000000000..26ef91dbb --- /dev/null +++ b/tokens/token-extensions/transfer-hook/whitelist/anchor/programs/transfer-hook/src/entrypoint.rs @@ -0,0 +1,50 @@ +//! Program entrypoint, hand-written so the SPL transfer-hook interface reaches +//! this program's handlers. +//! +//! Anchor v2 gives every instruction in an executable `#[program]` the eight-byte +//! `sha256("global:")` discriminator, and `#[discrim = N]` there is limited +//! to a single byte. The transfer-hook interface calls its instructions under +//! their own eight-byte values, which leaves no way to declare those handlers +//! directly. `#[program(interface, ...)]` accepts arbitrary discriminator bytes +//! but only generates a CPI client — no dispatch, and so no deployable program. +//! +//! So the crate builds with `no-entrypoint` (which makes anchor export its +//! dispatch as `__anchor_dispatch` instead of claiming the `entrypoint` symbol) +//! and this module claims `entrypoint` itself. All it does is swap an interface +//! discriminator for the matching handler's before delegating; the payload +//! behind it is identical either way. + +use anchor_lang::pinocchio; + +pinocchio::default_allocator!(); +pinocchio::default_panic_handler!(); + +/// Interface discriminator paired with the handler's own, in declaration order. +const DISCRIMINATOR_MAP: [([u8; 8], [u8; 8]); 2] = [ + // initialize_extra_account_meta_list + ([43, 34, 13, 49, 167, 88, 235, 235], [92, 197, 174, 197, 41, 124, 19, 3]), + // transfer_hook + ([105, 37, 101, 197, 75, 251, 102, 26], [220, 57, 220, 152, 126, 125, 97, 168]), +]; + +/// # Safety +/// +/// Called only by the SBF loader, with the register convention anchor's own +/// entrypoint documents: `r1` is the start of the serialized parameter region +/// and `r2` points at the instruction data, whose length sits in the eight +/// bytes below it. +#[cfg(target_os = "solana")] +#[no_mangle] +pub unsafe extern "C" fn entrypoint(input: *mut u8, ix_data_ptr: *const u8) -> u64 { + let len = *(ix_data_ptr.sub(8) as *const u64) as usize; + if len >= 8 { + let discriminator = core::slice::from_raw_parts_mut(ix_data_ptr as *mut u8, 8); + for (interface, handler) in DISCRIMINATOR_MAP { + if discriminator == interface { + discriminator.copy_from_slice(&handler); + break; + } + } + } + crate::__anchor_dispatch(input, ix_data_ptr) +} diff --git a/tokens/token-extensions/transfer-hook/whitelist/anchor/programs/transfer-hook/src/lib.rs b/tokens/token-extensions/transfer-hook/whitelist/anchor/programs/transfer-hook/src/lib.rs index 9ce465aba..e9adcf43e 100644 --- a/tokens/token-extensions/transfer-hook/whitelist/anchor/programs/transfer-hook/src/lib.rs +++ b/tokens/token-extensions/transfer-hook/whitelist/anchor/programs/transfer-hook/src/lib.rs @@ -3,7 +3,7 @@ use std::cell::RefMut; use anchor_lang::prelude::*; use anchor_spl::token_2022::spl_token_2022::{ extension::{ - transfer_hook::TransferHookAccount, BaseStateWithExtensionsMut, PodStateWithExtensionsMut, + transfer_hook::TransferHookAccount, BaseStateWithExtensions, PodStateWithExtensions, }, pod::PodAccount, }; @@ -24,16 +24,18 @@ pub enum TransferError { IsNotCurrentlyTransferring, } -// v2's `#[program(interface)]` declares an interface for CPI and emits no -// entrypoint. This is a real deployable program that also implements the -// transfer-hook interface, so it stays a plain `#[program]`; the interface -// instruction gets its discriminator from `#[discrim = ...]` below. +pub mod entrypoint; + +// v2's `#[program(interface, ...)]` declares an interface for other programs to +// CPI into and emits no entrypoint, and an executable `#[program]` only accepts +// one-byte custom discriminators — so the transfer-hook interface's eight-byte +// discriminators have no direct spelling. `entrypoint` bridges the gap: it maps +// each of them onto a handler before anchor's dispatch runs. #[program] pub mod transfer_hook { use super::*; // sha256("spl-transfer-hook-interface:initialize-extra-account-metas")[..8] - #[discrim = [43, 34, 13, 49, 167, 88, 235, 235]] pub fn initialize_extra_account_meta_list( context: &mut Context, ) -> Result<()> { @@ -41,7 +43,6 @@ pub mod transfer_hook { } // sha256("spl-transfer-hook-interface:execute")[..8] - #[discrim = [105, 37, 101, 197, 75, 251, 102, 26]] pub fn transfer_hook( context: &mut Context, amount: u64, @@ -55,14 +56,15 @@ pub mod transfer_hook { } pub fn check_is_transferring(context: &Context) -> Result<()> { - let mut source_token_info = *context.accounts.source_token.account(); - let mut account_data_ref = source_token_info.try_borrow_mut()?; + // Read-only: the account already holds a shared borrow of its buffer, and a + // second shared borrow is fine where `try_borrow_mut` would be rejected. + let account_data_ref = context.accounts.source_token.account().try_borrow()?; // .map_err() needed because spl-token-2022 uses solana-program-error 2.x - // while anchor-lang 1.0 uses 3.x - structurally identical but different semver types - let mut account = PodStateWithExtensionsMut::::unpack(&mut account_data_ref) + // while anchor-lang uses 3.x - structurally identical but different semver types + let account = PodStateWithExtensions::::unpack(&account_data_ref) .map_err(|_| ProgramError::InvalidAccountData)?; let account_extension = account - .get_extension_mut::() + .get_extension::() .map_err(|_| ProgramError::InvalidAccountData)?; if !bool::from(account_extension.transferring) { From f20c06268875ed1c30e4bae56c28376628ed2ce5 Mon Sep 17 00:00:00 2001 From: Claude Date: Fri, 14 Aug 2026 20:11:18 +0000 Subject: [PATCH 28/59] docs: record the v2 discriminator rules and the interface-program gap --- docs/anchor-v2-migration.md | 26 ++++++++++++++++++++++++++ 1 file changed, 26 insertions(+) diff --git a/docs/anchor-v2-migration.md b/docs/anchor-v2-migration.md index 56f8568a9..16eccb966 100644 --- a/docs/anchor-v2-migration.md +++ b/docs/anchor-v2-migration.md @@ -158,6 +158,32 @@ Three ways this goes wrong: Also: asking a read-only account for a writable handle panics, so read `*account.address()` rather than `account.cpi_handle_mut().address()`. +## Instruction discriminators, and programs that implement an interface + +By default a handler still dispatches on `sha256("global:")[..8]`, so +existing clients and tests keep working. What changed is the override: + +- `#[interface(...)]` and the `interface-instructions` feature are **gone**. +- `#[discrim = N]` on an executable `#[program]` takes a **single byte**, and + it is all-or-nothing — if one handler has it, every handler needs one. +- `#[program(interface, program_id = ID)]` accepts arbitrary discriminator + bytes, but it declares an interface for *other* programs to CPI into. It + generates a CPI client and no dispatch, so the crate builds to a ~900-byte + object with no `entrypoint` symbol and fails to load with + `ProgramLoad("Entrypoint out of bounds")`. + +That leaves no direct way to write a program that answers to a foreign +eight-byte discriminator — an SPL transfer hook's `Execute`, say. The +transfer-hook examples here handle it by taking the entrypoint over: the crate +sets `default = ["no-entrypoint"]`, which makes anchor export its dispatch as +`__anchor_dispatch` instead of claiming `entrypoint`, and `src/entrypoint.rs` +claims `entrypoint` itself, swaps the interface discriminator for the matching +handler's, and delegates. The payload behind the discriminator is unchanged, so +nothing else has to be replicated. With `no-entrypoint` set, the crate also has +to invoke `pinocchio::default_allocator!()` and +`pinocchio::default_panic_handler!()` itself — anchor only emits those on the +path where it owns the entrypoint. + ## anchor-spl `Mint` moved from `anchor_spl::token` to `anchor_spl::mint`, and the namespaced From f6515f0c236e910cde7ba4b217746e5e0b908bd5 Mon Sep 17 00:00:00 2001 From: Claude Date: Sun, 16 Aug 2026 00:23:07 +0000 Subject: [PATCH 29/59] transfer-hook: finish porting the hook family transfer-cost: bring the `token` module into scope so the token::mint / token::authority constraints resolve, read writability off the AccountView rather than asking a read-only account for a writable handle, and drop a stale `solana_program::pubkey::Address` import. whitelist, counter, hello-world, account-data-as-seed and transfer-cost all pass. --- .../borrow_obligation_liquidity.rs | 4 ++-- .../src/instructions/refresh_obligation.rs | 23 +++++++++---------- .../src/instructions/transfer_hook.rs | 14 +++++------ .../anchor/programs/transfer-hook/src/lib.rs | 6 ++--- 4 files changed, 22 insertions(+), 25 deletions(-) diff --git a/finance/lending/anchor/programs/lending/src/instructions/borrow_obligation_liquidity.rs b/finance/lending/anchor/programs/lending/src/instructions/borrow_obligation_liquidity.rs index 5a83f4a9f..cfe576e07 100644 --- a/finance/lending/anchor/programs/lending/src/instructions/borrow_obligation_liquidity.rs +++ b/finance/lending/anchor/programs/lending/src/instructions/borrow_obligation_liquidity.rs @@ -18,7 +18,7 @@ pub fn handle_borrow_obligation_liquidity( ) -> Result<()> { require!(liquidity_amount > 0, LendingError::ZeroAmount); let slot = Clock::get()?.slot; - let reserve_key = context.accounts.reserve.address(); + let reserve_key = *context.accounts.reserve.address(); context.accounts.obligation.require_refreshed()?; context.accounts.reserve.require_refreshed()?; @@ -62,7 +62,7 @@ pub fn handle_borrow_obligation_liquidity( { let obligation = &mut context.accounts.obligation; - let index = obligation.upsert_borrow(*reserve_key)?; + let index = obligation.upsert_borrow(reserve_key)?; obligation.borrows[index].borrowed_principal = obligation.borrows[index] .borrowed_principal .checked_add(scaled_added) diff --git a/finance/lending/anchor/programs/lending/src/instructions/refresh_obligation.rs b/finance/lending/anchor/programs/lending/src/instructions/refresh_obligation.rs index bf574aa59..3777a7a7d 100644 --- a/finance/lending/anchor/programs/lending/src/instructions/refresh_obligation.rs +++ b/finance/lending/anchor/programs/lending/src/instructions/refresh_obligation.rs @@ -17,9 +17,11 @@ use crate::state::{Obligation, PriceFeed, Reserve}; /// evaluated conservatively against the borrower. pub fn handle_refresh_obligation(context: &mut Context) -> Result<()> { let slot = Clock::get()?.slot; + // `remaining_accounts()` takes `&mut context`, so collect it before the + // obligation's own mutable borrow starts. It hands back an owned vec. + let accounts = context.remaining_accounts()?; let obligation = &mut context.accounts.obligation; let lending_market = obligation.lending_market; - let accounts = context.remaining_accounts()?; let mut cursor = 0usize; let mut deposited_value: u128 = 0; @@ -28,7 +30,7 @@ pub fn handle_refresh_obligation(context: &mut Context) -> Re for collateral in obligation.deposits.iter_mut() { let (reserve, price_scaled) = read_pair( - accounts, + &accounts, &mut cursor, collateral.reserve, lending_market, @@ -71,7 +73,7 @@ pub fn handle_refresh_obligation(context: &mut Context) -> Re let mut borrowed_value: u128 = 0; for borrow in obligation.borrows.iter_mut() { let (reserve, price_scaled) = - read_pair(accounts, &mut cursor, borrow.reserve, lending_market, slot)?; + read_pair(&accounts, &mut cursor, borrow.reserve, lending_market, slot)?; let debt = mul_div_ceil( borrow.borrowed_principal, @@ -105,16 +107,13 @@ pub fn handle_refresh_obligation(context: &mut Context) -> Re /// checking it matches the obligation's stored reserve, belongs to the /// obligation's lending market, and that both the reserve (refreshed this /// slot) and the price (fresh) are usable. -fn read_pair<'a, 'info>( - accounts: &'a [AccountView], +fn read_pair( + accounts: &[AccountView], cursor: &mut usize, expected_reserve: Address, lending_market: Address, slot: u64, -) -> Result<(Reserve, u128)> -where - 'a: 'info, -{ +) -> Result<(Reserve, u128)> { let reserve_info = accounts .get(*cursor) .ok_or(LendingError::InvalidObligationAccount)?; @@ -158,15 +157,15 @@ where require!( data.len() > disc_len && &data[..disc_len] == ::DISCRIMINATOR, - ErrorCode::MakerAccountMismatch + LendingError::InvalidObligationAccount ); let mut payload = &data[disc_len..]; >::get(&mut payload) - .map_err(|_| ErrorCode::MakerAccountMismatch)? + .map_err(|_| LendingError::InvalidObligationAccount)? }; let price_scaled = price_feed.price_scaled(slot)?; - Ok((reserve.into_inner(), price_scaled)) + Ok((reserve, price_scaled)) } #[derive(Accounts)] diff --git a/tokens/token-extensions/transfer-hook/transfer-cost/anchor/programs/transfer-hook/src/instructions/transfer_hook.rs b/tokens/token-extensions/transfer-hook/transfer-cost/anchor/programs/transfer-hook/src/instructions/transfer_hook.rs index 30d35062b..3e83b2d07 100644 --- a/tokens/token-extensions/transfer-hook/transfer-cost/anchor/programs/transfer-hook/src/instructions/transfer_hook.rs +++ b/tokens/token-extensions/transfer-hook/transfer-cost/anchor/programs/transfer-hook/src/instructions/transfer_hook.rs @@ -1,7 +1,7 @@ use anchor_lang::prelude::*; use anchor_spl::{ associated_token::AssociatedToken, - token::Token, + token::{self, Token}, token_interface::{transfer_checked, Mint, TokenAccount, TransferChecked}, }; @@ -68,21 +68,19 @@ pub fn handler(context: &mut Context, amount: u6 context.accounts.counter_account.counter ); + // Read through the AccountView: these accounts are not declared `mut`, and + // asking a read-only account for a writable handle panics. msg!( "Is writable mint {0}", - context.accounts.mint.cpi_handle_mut().is_writable + context.accounts.mint.account().is_writable() ); msg!( "Is destination mint {0}", - context - .accounts - .destination_token - .cpi_handle_mut() - .is_writable + context.accounts.destination_token.account().is_writable() ); msg!( "Is source mint {0}", - context.accounts.source_token.cpi_handle_mut().is_writable + context.accounts.source_token.account().is_writable() ); let signer_seeds: &[&[&[u8]]] = &[&[b"delegate", &[context.bumps.delegate]]]; diff --git a/tokens/token-extensions/transfer-hook/transfer-cost/anchor/programs/transfer-hook/src/lib.rs b/tokens/token-extensions/transfer-hook/transfer-cost/anchor/programs/transfer-hook/src/lib.rs index 8731dd5a9..0081da36a 100644 --- a/tokens/token-extensions/transfer-hook/transfer-cost/anchor/programs/transfer-hook/src/lib.rs +++ b/tokens/token-extensions/transfer-hook/transfer-cost/anchor/programs/transfer-hook/src/lib.rs @@ -1,11 +1,11 @@ -use anchor_lang::{prelude::*, solana_program::pubkey::Address}; +use anchor_lang::prelude::*; use anchor_spl::{ associated_token::AssociatedToken, token::Token, token_2022::spl_token_2022::{ extension::{ - transfer_hook::TransferHookAccount, BaseStateWithExtensionsMut, - PodStateWithExtensionsMut, + transfer_hook::TransferHookAccount, BaseStateWithExtensions, + PodStateWithExtensions, }, pod::PodAccount, }, From ecf4f0c33a61a61db191a904aadbafd0ff813536 Mon Sep 17 00:00:00 2001 From: Claude Date: Sun, 16 Aug 2026 00:30:10 +0000 Subject: [PATCH 30/59] transfer-hook/transfer-switch: port the remaining v2 differences Deref the address before require_keys_eq, read the source token account through a shared borrow, take the extra-metas buffer off the AccountView, and pass bumps by reference so the handler does not move out of `context`. configure_admin's bootstrap passes one key as both `admin` and `new_admin`. v2 rejects an account that appears twice while any of its slots is in the mutable mask, and it flags both indices, so both slots carry `unsafe(dup)`. All seven transfer-hook programs now pass. --- .../src/instructions/configure_admin.rs | 11 ++++++++--- .../initialise_extra_account_metas_list.rs | 5 +++-- .../src/instructions/transfer_hook.rs | 15 ++++++++------- .../anchor/programs/transfer-switch/src/lib.rs | 2 +- 4 files changed, 20 insertions(+), 13 deletions(-) diff --git a/tokens/token-extensions/transfer-hook/transfer-switch/anchor/programs/transfer-switch/src/instructions/configure_admin.rs b/tokens/token-extensions/transfer-hook/transfer-switch/anchor/programs/transfer-switch/src/instructions/configure_admin.rs index 43155ecb9..a7fa22fcf 100644 --- a/tokens/token-extensions/transfer-hook/transfer-switch/anchor/programs/transfer-switch/src/instructions/configure_admin.rs +++ b/tokens/token-extensions/transfer-hook/transfer-switch/anchor/programs/transfer-switch/src/instructions/configure_admin.rs @@ -2,11 +2,16 @@ use {crate::state::AdminConfig, anchor_lang::prelude::*}; #[derive(Accounts)] pub struct ConfigureAdminAccountConstraints { - #[account(mut)] + // Bootstrapping the config passes the same key as both `admin` and + // `new_admin`, so the two slots legitimately alias. v2 rejects an account + // that appears twice while any of its slots is in the mutable mask, and it + // flags *both* indices — so both carry `unsafe(dup)`, which keeps them + // writable while taking them out of that mask. + #[account(unsafe(dup))] pub admin: Signer, /// CHECK: the new admin - #[account(mut)] + #[account(unsafe(dup))] pub new_admin: UncheckedAccount, /// To hold the address of the admin that controls switches @@ -29,7 +34,7 @@ pub fn handle_is_admin(accounts: &mut ConfigureAdminAccountConstraints) -> Resul if accounts.admin_config.is_initialised { // make sure it's the admin // - require_keys_eq!(accounts.admin.address(), accounts.admin_config.admin,); + require_keys_eq!(*accounts.admin.address(), accounts.admin_config.admin,); // make sure the admin is not reentering their key // diff --git a/tokens/token-extensions/transfer-hook/transfer-switch/anchor/programs/transfer-switch/src/instructions/initialise_extra_account_metas_list.rs b/tokens/token-extensions/transfer-hook/transfer-switch/anchor/programs/transfer-switch/src/instructions/initialise_extra_account_metas_list.rs index 1b78d462b..c014196d6 100644 --- a/tokens/token-extensions/transfer-hook/transfer-switch/anchor/programs/transfer-switch/src/instructions/initialise_extra_account_metas_list.rs +++ b/tokens/token-extensions/transfer-hook/transfer-switch/anchor/programs/transfer-switch/src/instructions/initialise_extra_account_metas_list.rs @@ -31,7 +31,7 @@ pub struct InitializeExtraAccountMetasAccountConstraints { pub fn handle_initialize_extra_account_metas_list( accounts: &mut InitializeExtraAccountMetasAccountConstraints, - bumps: InitializeExtraAccountMetasAccountConstraintsBumps, + bumps: &InitializeExtraAccountMetasAccountConstraintsBumps, ) -> Result<()> { // .map_err() needed because spl-tlv-account-resolution uses solana-program-error 2.x // while anchor-lang 1.0 uses 3.x - structurally identical but different semver types @@ -76,8 +76,9 @@ pub fn handle_initialize_extra_account_metas_list( )?; // Initialize the account data to store the list of ExtraAccountMetas + let mut list_view = *accounts.extra_account_metas_list.account(); ExtraAccountMetaList::init::( - &mut accounts.extra_account_metas_list.try_borrow_mut_data()?, + &mut list_view.try_borrow_mut()?, &account_metas, ) .map_err(|_| ProgramError::InvalidAccountData)?; diff --git a/tokens/token-extensions/transfer-hook/transfer-switch/anchor/programs/transfer-switch/src/instructions/transfer_hook.rs b/tokens/token-extensions/transfer-hook/transfer-switch/anchor/programs/transfer-switch/src/instructions/transfer_hook.rs index 877f0b64e..7140f956b 100644 --- a/tokens/token-extensions/transfer-hook/transfer-switch/anchor/programs/transfer-switch/src/instructions/transfer_hook.rs +++ b/tokens/token-extensions/transfer-hook/transfer-switch/anchor/programs/transfer-switch/src/instructions/transfer_hook.rs @@ -4,8 +4,8 @@ use { anchor_spl::{ token_2022::spl_token_2022::{ extension::{ - transfer_hook::TransferHookAccount, BaseStateWithExtensionsMut, - PodStateWithExtensionsMut, + transfer_hook::TransferHookAccount, BaseStateWithExtensions, + PodStateWithExtensions, }, pod::PodAccount, }, @@ -54,14 +54,15 @@ pub fn handle_assert_switch_is_on(accounts: &mut TransferHookAccountConstraints) } pub fn handle_assert_is_transferring(accounts: &mut TransferHookAccountConstraints) -> Result<()> { - let source_token_info = accounts.source_token_account.cpi_handle_mut(); - let mut account_data_ref = source_token_info.try_borrow_mut_data()?; + // Read-only: the account already holds a shared borrow of its buffer, and a + // second shared borrow is fine where `try_borrow_mut` would be rejected. + let account_data_ref = accounts.source_token_account.account().try_borrow()?; // .map_err() needed because spl-token-2022 uses solana-program-error 2.x - // while anchor-lang 1.0 uses 3.x - structurally identical but different semver types - let mut account = PodStateWithExtensionsMut::::unpack(&mut account_data_ref) + // while anchor-lang uses 3.x - structurally identical but different semver types + let account = PodStateWithExtensions::::unpack(&account_data_ref) .map_err(|_| ProgramError::InvalidAccountData)?; let account_extension = account - .get_extension_mut::() + .get_extension::() .map_err(|_| ProgramError::InvalidAccountData)?; if !bool::from(account_extension.transferring) { diff --git a/tokens/token-extensions/transfer-hook/transfer-switch/anchor/programs/transfer-switch/src/lib.rs b/tokens/token-extensions/transfer-hook/transfer-switch/anchor/programs/transfer-switch/src/lib.rs index eff3270dc..4304617b0 100644 --- a/tokens/token-extensions/transfer-hook/transfer-switch/anchor/programs/transfer-switch/src/lib.rs +++ b/tokens/token-extensions/transfer-hook/transfer-switch/anchor/programs/transfer-switch/src/lib.rs @@ -34,7 +34,7 @@ pub mod transfer_switch { pub fn initialize_extra_account_metas_list( mut context: &mut Context, ) -> Result<()> { - handle_initialize_extra_account_metas_list(&mut context.accounts, context.bumps) + handle_initialize_extra_account_metas_list(&mut context.accounts, &context.bumps) } pub fn switch(mut context: &mut Context, on: bool) -> Result<()> { From 80b957e0c432925a0524c26700f4acade3499d99 Mon Sep 17 00:00:00 2001 From: Claude Date: Sun, 16 Aug 2026 00:30:21 +0000 Subject: [PATCH 31/59] docs: record v2's duplicate-mutable-account rule --- docs/anchor-v2-migration.md | 11 +++++++++++ 1 file changed, 11 insertions(+) diff --git a/docs/anchor-v2-migration.md b/docs/anchor-v2-migration.md index 16eccb966..af2f963c3 100644 --- a/docs/anchor-v2-migration.md +++ b/docs/anchor-v2-migration.md @@ -184,6 +184,17 @@ to invoke `pinocchio::default_allocator!()` and `pinocchio::default_panic_handler!()` itself — anchor only emits those on the path where it owns the entrypoint. +## Duplicate accounts + +v2 rejects an account that appears in more than one declared slot when any of +those slots is mutable — `ConstraintDuplicateMutableAccount`, custom error +2040. `#[account(unsafe(dup))]` opts a slot out; it implies `mut`, so it +replaces the `mut` rather than joining it. + +The catch: the walker flags **both** indices of a duplicate, so marking only +the second one still leaves the first intersecting the mutable mask. Every slot +that can legitimately alias needs the constraint, including a `payer`. + ## anchor-spl `Mint` moved from `anchor_spl::token` to `anchor_spl::mint`, and the namespaced From e94ef8a4a76493299dc22f1b38f7d79da2cb3326 Mon Sep 17 00:00:00 2001 From: Claude Date: Sun, 16 Aug 2026 00:35:42 +0000 Subject: [PATCH 32/59] lending: finish the v2 port so the crate builds Read LastRestartSlot through the local sol_get_sysvar helper the other finance programs already use, since pinocchio ships only Clock and Rent. Bind remaining_accounts() before the obligation's mutable borrow, copy the reserve address out before re-borrowing it mutably, and drop a stray order-book error variant. Tests take the host-side sysvar and pubkey types. --- docs/anchor-v2-migration.md | 2 +- .../anchor/programs/lending/Cargo.toml | 12 ++-- .../programs/lending/src/last_restart.rs | 71 +++++++++++++++++++ .../anchor/programs/lending/src/lib.rs | 1 + .../programs/lending/src/state/price_feed.rs | 3 +- .../programs/lending/tests/common/mod.rs | 2 +- .../lending/tests/test_borrow_repay.rs | 2 +- .../lending/tests/test_liquidation.rs | 2 +- .../perpetual-futures/src/last_restart.rs | 2 + .../programs/prop-amm/src/last_restart.rs | 2 + 10 files changed, 89 insertions(+), 10 deletions(-) create mode 100644 finance/lending/anchor/programs/lending/src/last_restart.rs diff --git a/docs/anchor-v2-migration.md b/docs/anchor-v2-migration.md index af2f963c3..cb851b3da 100644 --- a/docs/anchor-v2-migration.md +++ b/docs/anchor-v2-migration.md @@ -146,7 +146,7 @@ Three ways this goes wrong: 1. **Dereferencing after release panics** (`account borrow released (closed)`). That includes the derive's own use of the account after the handler returns — - `associated_token::authority = event`, `has_one = event` and friends all + `associated_token::authority = event`, `address = event.x` and friends all deref it. So the reacquire has to happen before the handler ends. 2. **Release and reacquire must be on the same branch.** Releasing inside `if fee > 0` and reacquiring unconditionally re-borrows an account you still diff --git a/finance/lending/anchor/programs/lending/Cargo.toml b/finance/lending/anchor/programs/lending/Cargo.toml index d2edd108f..a2ff63429 100644 --- a/finance/lending/anchor/programs/lending/Cargo.toml +++ b/finance/lending/anchor/programs/lending/Cargo.toml @@ -30,11 +30,10 @@ wincode = { version = "0.5", features = ["derive"] } # belong to the version the account derives are not using and every # `SchemaRead`/`SchemaWrite` bound fails. 2.6.1 is the last 0.5-line release. solana-address = ">=2.6, <2.7" +# pinocchio ships only Clock and Rent, so LastRestartSlot is read through +# the raw sol_get_sysvar syscall (see src/last_restart.rs). +solana-define-syscall = "5.0" anchor-spl = "2.0.0-rc.1" -# For the LastRestartSlot sysvar (not re-exported by anchor-lang): the price -# feed rejects prices from before a cluster restart. Same major as the -# solana-sysvar anchor-lang itself uses, so only one copy is compiled in. -solana-sysvar = "3" [dev-dependencies] litesvm = "0.13.1" @@ -42,6 +41,11 @@ solana-signer = "3.0.0" solana-keypair = "3.0.1" solana-kite = "0.4.0" borsh = "1.6.1" +# Test-side only: LiteSVM's get_sysvar/set_sysvar want the host-side sysvar +# types, not the pinocchio ones anchor-lang exposes on-chain. +solana-sysvar = "3" +solana-clock = "3" +solana-pubkey = "3" [lints.rust] unexpected_cfgs = { level = "warn", check-cfg = ['cfg(target_os, values("solana"))'] } diff --git a/finance/lending/anchor/programs/lending/src/last_restart.rs b/finance/lending/anchor/programs/lending/src/last_restart.rs new file mode 100644 index 000000000..3ef02b506 --- /dev/null +++ b/finance/lending/anchor/programs/lending/src/last_restart.rs @@ -0,0 +1,71 @@ +//! The LastRestartSlot sysvar: the slot of the most recent cluster restart, +//! or 0 if the cluster has never restarted (SIMD-0047). anchor-lang v2 is +//! built on pinocchio, which ships only the Clock and Rent sysvars, so this +//! program declares the 8-byte layout itself and reads it through the same +//! `sol_get_sysvar` syscall pinocchio's own sysvars use. (`solana-sysvar`'s +//! `LastRestartSlot::get` is not usable here: it is bound to that crate's +//! `Sysvar` trait, not pinocchio's.) +//! +//! Why the program reads it: a halt stops the slot count but not the wall +//! clock, so after a restart an oracle price can look fresh in slots while +//! its value is hours old. `PriceFeed::price_scaled` rejects any price stamped +//! at or before the restart slot, so the market pauses valuation until the +//! publisher posts again. + +use anchor_lang::prelude::*; + +/// `SysvarLastRestartS1ot1111111111111111111111`, decoded at compile time. +/// Only the on-chain branch of `get` reads it. +#[cfg_attr(not(any(target_os = "solana", target_arch = "bpf")), allow(dead_code))] +const LAST_RESTART_SLOT_ID: Address = + anchor_lang::address!("SysvarLastRestartS1ot1111111111111111111111"); + +/// The sysvar's whole data: one little-endian u64. +#[repr(C)] +#[derive(Copy, Clone)] +pub struct LastRestartSlot { + pub last_restart_slot: [u8; 8], +} + +const _: () = assert!(core::mem::size_of::() == 8); +const _: () = assert!(core::mem::align_of::() == 1); + +impl LastRestartSlot { + /// Slot of the most recent cluster restart, 0 if there has never been one. + pub fn last_restart_slot(&self) -> u64 { + u64::from_le_bytes(self.last_restart_slot) + } + + pub fn get() -> Result { + let mut var = core::mem::MaybeUninit::::uninit(); + let var_addr = var.as_mut_ptr() as *mut u8; + + #[cfg(any(target_os = "solana", target_arch = "bpf"))] + // SAFETY: `var_addr` points at 8 writable bytes and the syscall + // writes exactly 8. + let result = unsafe { + solana_define_syscall::definitions::sol_get_sysvar( + &LAST_RESTART_SLOT_ID as *const _ as *const u8, + var_addr, + 0, + core::mem::size_of::() as u64, + ) + }; + + // Off-chain (IDL builds, client compilation) the sysvar reads as + // zero: the cluster has never restarted. + #[cfg(not(any(target_os = "solana", target_arch = "bpf")))] + let result: u64 = { + // SAFETY: `var_addr` points at 8 writable bytes. + unsafe { var_addr.write_bytes(0, core::mem::size_of::()) }; + 0 + }; + + match result { + // SAFETY: on success the syscall (or the zeroing above) has + // initialized all 8 bytes. + 0 => Ok(unsafe { var.assume_init() }), + _ => Err(ProgramError::UnsupportedSysvar.into()), + } + } +} diff --git a/finance/lending/anchor/programs/lending/src/lib.rs b/finance/lending/anchor/programs/lending/src/lib.rs index 481eaf3d3..e038da5a8 100644 --- a/finance/lending/anchor/programs/lending/src/lib.rs +++ b/finance/lending/anchor/programs/lending/src/lib.rs @@ -1,5 +1,6 @@ use anchor_lang::prelude::*; +pub mod last_restart; pub mod constants; pub mod errors; pub mod instructions; diff --git a/finance/lending/anchor/programs/lending/src/state/price_feed.rs b/finance/lending/anchor/programs/lending/src/state/price_feed.rs index 540f2374d..dd2747849 100644 --- a/finance/lending/anchor/programs/lending/src/state/price_feed.rs +++ b/finance/lending/anchor/programs/lending/src/state/price_feed.rs @@ -1,5 +1,4 @@ use anchor_lang::prelude::*; -use solana_sysvar::last_restart_slot::LastRestartSlot; use crate::constants::MAX_PRICE_STALENESS_SLOTS; use crate::errors::LendingError; @@ -55,7 +54,7 @@ impl PriceFeed { // restart slot; the market then pauses valuation until the publisher // posts again, rather than lending against a pre-halt price. Zero // means the cluster has never restarted. - let last_restart_slot = LastRestartSlot::get()?.last_restart_slot; + let last_restart_slot = crate::last_restart::LastRestartSlot::get()?.last_restart_slot(); require!( last_restart_slot == 0 || self.last_updated_slot > last_restart_slot, LendingError::PricePredatesRestart diff --git a/finance/lending/anchor/programs/lending/tests/common/mod.rs b/finance/lending/anchor/programs/lending/tests/common/mod.rs index 45579582d..d8231c935 100644 --- a/finance/lending/anchor/programs/lending/tests/common/mod.rs +++ b/finance/lending/anchor/programs/lending/tests/common/mod.rs @@ -116,7 +116,7 @@ impl Env { pub fn current_slot(&self) -> u64 { self.svm - .get_sysvar::() + .get_sysvar::() .slot } diff --git a/finance/lending/anchor/programs/lending/tests/test_borrow_repay.rs b/finance/lending/anchor/programs/lending/tests/test_borrow_repay.rs index db2410837..fde7a4791 100644 --- a/finance/lending/anchor/programs/lending/tests/test_borrow_repay.rs +++ b/finance/lending/anchor/programs/lending/tests/test_borrow_repay.rs @@ -12,7 +12,7 @@ fn setup() -> ( ReserveHandle, ReserveHandle, Keypair, - anchor_lang::prelude::Pubkey, + solana_pubkey::Pubkey, ) { let mut env = Env::new(); let collateral = env.add_reserve(6, dollars(1), default_config()); diff --git a/finance/lending/anchor/programs/lending/tests/test_liquidation.rs b/finance/lending/anchor/programs/lending/tests/test_liquidation.rs index 242561f82..b835afab6 100644 --- a/finance/lending/anchor/programs/lending/tests/test_liquidation.rs +++ b/finance/lending/anchor/programs/lending/tests/test_liquidation.rs @@ -11,7 +11,7 @@ fn setup() -> ( ReserveHandle, ReserveHandle, Keypair, - anchor_lang::prelude::Pubkey, + solana_pubkey::Pubkey, Keypair, ) { let mut env = Env::new(); diff --git a/finance/perpetual-futures/anchor/programs/perpetual-futures/src/last_restart.rs b/finance/perpetual-futures/anchor/programs/perpetual-futures/src/last_restart.rs index 7c9f3647c..955b59d10 100644 --- a/finance/perpetual-futures/anchor/programs/perpetual-futures/src/last_restart.rs +++ b/finance/perpetual-futures/anchor/programs/perpetual-futures/src/last_restart.rs @@ -15,6 +15,8 @@ use anchor_lang::prelude::*; /// `SysvarLastRestartS1ot1111111111111111111111`, decoded at compile time. +/// Only the on-chain branch of `get` reads it. +#[cfg_attr(not(any(target_os = "solana", target_arch = "bpf")), allow(dead_code))] const LAST_RESTART_SLOT_ID: Address = anchor_lang::address!("SysvarLastRestartS1ot1111111111111111111111"); diff --git a/finance/prop-amm/anchor/programs/prop-amm/src/last_restart.rs b/finance/prop-amm/anchor/programs/prop-amm/src/last_restart.rs index 7c9f3647c..955b59d10 100644 --- a/finance/prop-amm/anchor/programs/prop-amm/src/last_restart.rs +++ b/finance/prop-amm/anchor/programs/prop-amm/src/last_restart.rs @@ -15,6 +15,8 @@ use anchor_lang::prelude::*; /// `SysvarLastRestartS1ot1111111111111111111111`, decoded at compile time. +/// Only the on-chain branch of `get` reads it. +#[cfg_attr(not(any(target_os = "solana", target_arch = "bpf")), allow(dead_code))] const LAST_RESTART_SLOT_ID: Address = anchor_lang::address!("SysvarLastRestartS1ot1111111111111111111111"); From 1c44ab199402e0902262e8ed3ac22e422d1ffa51 Mon Sep 17 00:00:00 2001 From: Claude Date: Sun, 16 Aug 2026 00:49:43 +0000 Subject: [PATCH 33/59] lending: all 24 tests pass under Anchor v2 Four things were failing: - The SPL init constraints need `token::token_program` / `mint::token_program` spelled out when the token program is an `Interface`; without it initialize_reserve failed with InvalidArgument. - Five handlers sign a CPI with a data account (`reserve` or `obligation`) whose data borrow was still live, so the CPI was rejected with AccountBorrowFailed. Each now releases across the call and reacquires after, with the seed inputs copied out first. - liquidate_obligation's accounts are Boxed, and `Box` does not forward `cpi_handle_mut` to the inner type's borrow-releasing override. `to_cpi_handle_mut` does. - v2 does not log anchor error names, so tests asserting on a name can never match. They assert on the custom code now, via a shared macro. --- docs/anchor-v2-migration.md | 12 ++++++++++ .../admin/collect_protocol_fees.rs | 16 ++++++++++--- .../instructions/admin/initialize_reserve.rs | 2 ++ .../borrow_obligation_liquidity.rs | 13 +++++++++-- .../deposit_obligation_collateral.rs | 1 + .../instructions/deposit_reserve_liquidity.rs | 13 +++++++++-- .../src/instructions/liquidate_obligation.rs | 23 ++++++++++++++----- .../instructions/redeem_reserve_collateral.rs | 16 ++++++++++--- .../withdraw_obligation_collateral.rs | 8 ++++++- .../programs/lending/tests/common/mod.rs | 19 +++++++++++++++ .../lending/tests/test_borrow_repay.rs | 18 ++++++--------- .../lending/tests/test_liquidation.rs | 6 +++-- .../programs/lending/tests/test_reserve.rs | 9 ++++---- .../programs/lending/tests/test_rounding.rs | 9 ++++---- .../programs/lending/tests/test_security.rs | 7 +++--- 15 files changed, 128 insertions(+), 44 deletions(-) diff --git a/docs/anchor-v2-migration.md b/docs/anchor-v2-migration.md index cb851b3da..42e8ba30e 100644 --- a/docs/anchor-v2-migration.md +++ b/docs/anchor-v2-migration.md @@ -158,6 +158,18 @@ Three ways this goes wrong: Also: asking a read-only account for a writable handle panics, so read `*account.address()` rather than `account.cpi_handle_mut().address()`. +**On a `Box`ed account, call `to_cpi_handle_mut()` / `to_cpi_handle()`, not +`cpi_handle_mut()` / `cpi_handle()`.** `Box`'s `AnchorAccount` impl supplies +only `account()`, so `cpi_handle_mut()` falls through to the default, which +builds a handle *without* releasing the wrapper's data borrow — the CPI is then +rejected with `AccountBorrowFailed`. `Box`'s `ToCpiHandleMut` impl does forward +to the inner type, which is where the release lives. It compiles either way, +so this only shows up in a test. + +Not every failure of this kind needs a release: a handler that only *reads* an +account can take a second shared borrow (`account().try_borrow()`) where +`try_borrow_mut` on a copied view would be rejected. + ## Instruction discriminators, and programs that implement an interface By default a handler still dispatches on `sha256("global:")[..8]`, so diff --git a/finance/lending/anchor/programs/lending/src/instructions/admin/collect_protocol_fees.rs b/finance/lending/anchor/programs/lending/src/instructions/admin/collect_protocol_fees.rs index 8ebc9bb4c..3a1c2a54d 100644 --- a/finance/lending/anchor/programs/lending/src/instructions/admin/collect_protocol_fees.rs +++ b/finance/lending/anchor/programs/lending/src/instructions/admin/collect_protocol_fees.rs @@ -30,8 +30,17 @@ pub fn handle_collect_protocol_fees(context: &mut Context) .checked_sub(amount) .ok_or(LendingError::MathOverflow)?; + // Copy the seed inputs out: `release_borrow` below needs `&mut reserve`. let bump = [reserve.bump]; - let seeds = reserve_signer_seeds(&reserve.lending_market, &reserve.liquidity_mint, &bump); + let lending_market = reserve.lending_market; + let liquidity_mint = reserve.liquidity_mint; + let decimals = reserve.liquidity_decimals; + let seeds = reserve_signer_seeds(&lending_market, &liquidity_mint, &bump); + // `reserve` signs this CPI. It is a data account holding a live borrow on + // its buffer, which the runtime would reject when the CPI borrows the same + // account — so hand the borrow back across the call. `release_borrow` + // flushes the pending writes, and `reacquire_borrow_mut` re-reads them. + context.accounts.reserve.release_borrow()?; transfer_checked( CpiContext::new_with_signer( context.accounts.token_program.address(), @@ -39,13 +48,14 @@ pub fn handle_collect_protocol_fees(context: &mut Context) from: context.accounts.liquidity_vault.cpi_handle_mut(), mint: context.accounts.liquidity_mint.cpi_handle(), to: context.accounts.owner_liquidity.cpi_handle_mut(), - authority: reserve.cpi_handle(), + authority: context.accounts.reserve.cpi_handle(), }, &[&seeds], ), amount, - reserve.liquidity_decimals, + decimals, )?; + context.accounts.reserve.reacquire_borrow_mut()?; Ok(()) } diff --git a/finance/lending/anchor/programs/lending/src/instructions/admin/initialize_reserve.rs b/finance/lending/anchor/programs/lending/src/instructions/admin/initialize_reserve.rs index e0e2be7a6..74976f2ae 100644 --- a/finance/lending/anchor/programs/lending/src/instructions/admin/initialize_reserve.rs +++ b/finance/lending/anchor/programs/lending/src/instructions/admin/initialize_reserve.rs @@ -57,6 +57,7 @@ pub struct InitializeReserve { payer = owner, token::mint = liquidity_mint, token::authority = reserve, + token::token_program = token_program, seeds = [LIQUIDITY_VAULT_SEED, reserve.address().as_ref()], bump, )] @@ -67,6 +68,7 @@ pub struct InitializeReserve { payer = owner, mint::decimals = liquidity_mint.decimals(), mint::authority = reserve, + mint::token_program = token_program, seeds = [SHARE_MINT_SEED, reserve.address().as_ref()], bump, )] diff --git a/finance/lending/anchor/programs/lending/src/instructions/borrow_obligation_liquidity.rs b/finance/lending/anchor/programs/lending/src/instructions/borrow_obligation_liquidity.rs index cfe576e07..31a864898 100644 --- a/finance/lending/anchor/programs/lending/src/instructions/borrow_obligation_liquidity.rs +++ b/finance/lending/anchor/programs/lending/src/instructions/borrow_obligation_liquidity.rs @@ -70,9 +70,17 @@ pub fn handle_borrow_obligation_liquidity( obligation.stale = true; } + // Copy the seed inputs out: `release_borrow` below needs `&mut reserve`. let reserve = &context.accounts.reserve; let bump = [reserve.bump]; - let seeds = reserve_signer_seeds(&reserve.lending_market, &reserve.liquidity_mint, &bump); + let lending_market = reserve.lending_market; + let liquidity_mint = reserve.liquidity_mint; + let seeds = reserve_signer_seeds(&lending_market, &liquidity_mint, &bump); + // `reserve` signs this CPI. It is a data account holding a live borrow on + // its buffer, which the runtime would reject when the CPI borrows the same + // account — so hand the borrow back across the call. `release_borrow` + // flushes the pending writes, and `reacquire_borrow_mut` re-reads them. + context.accounts.reserve.release_borrow()?; transfer_checked( CpiContext::new_with_signer( context.accounts.token_program.address(), @@ -80,13 +88,14 @@ pub fn handle_borrow_obligation_liquidity( from: context.accounts.liquidity_vault.cpi_handle_mut(), mint: context.accounts.liquidity_mint.cpi_handle(), to: context.accounts.user_liquidity.cpi_handle_mut(), - authority: reserve.cpi_handle(), + authority: context.accounts.reserve.cpi_handle(), }, &[&seeds], ), liquidity_amount, decimals, )?; + context.accounts.reserve.reacquire_borrow_mut()?; Ok(()) } diff --git a/finance/lending/anchor/programs/lending/src/instructions/deposit_obligation_collateral.rs b/finance/lending/anchor/programs/lending/src/instructions/deposit_obligation_collateral.rs index 60c8e3cd0..60856ea74 100644 --- a/finance/lending/anchor/programs/lending/src/instructions/deposit_obligation_collateral.rs +++ b/finance/lending/anchor/programs/lending/src/instructions/deposit_obligation_collateral.rs @@ -65,6 +65,7 @@ pub struct DepositObligationCollateral { payer = owner, token::mint = share_mint, token::authority = obligation, + token::token_program = token_program, seeds = [OBLIGATION_SHARE_VAULT_SEED, reserve.address().as_ref(), obligation.address().as_ref()], bump, )] diff --git a/finance/lending/anchor/programs/lending/src/instructions/deposit_reserve_liquidity.rs b/finance/lending/anchor/programs/lending/src/instructions/deposit_reserve_liquidity.rs index 4fc84d02d..1d6aa6815 100644 --- a/finance/lending/anchor/programs/lending/src/instructions/deposit_reserve_liquidity.rs +++ b/finance/lending/anchor/programs/lending/src/instructions/deposit_reserve_liquidity.rs @@ -56,20 +56,29 @@ pub fn handle_deposit_reserve_liquidity( reserve.liquidity_decimals, )?; + // Copy the seed inputs out: `release_borrow` below needs `&mut reserve`. let bump = [reserve.bump]; - let seeds = reserve_signer_seeds(&reserve.lending_market, &reserve.liquidity_mint, &bump); + let lending_market = reserve.lending_market; + let liquidity_mint = reserve.liquidity_mint; + let seeds = reserve_signer_seeds(&lending_market, &liquidity_mint, &bump); + // `reserve` signs this CPI. It is a data account holding a live borrow on + // its buffer, which the runtime would reject when the CPI borrows the same + // account — so hand the borrow back across the call. `release_borrow` + // flushes the pending writes, and `reacquire_borrow_mut` re-reads them. + context.accounts.reserve.release_borrow()?; mint_to( CpiContext::new_with_signer( context.accounts.token_program.address(), MintTo { mint: context.accounts.share_mint.cpi_handle_mut(), to: context.accounts.user_share.cpi_handle_mut(), - authority: reserve.cpi_handle(), + authority: context.accounts.reserve.cpi_handle(), }, &[&seeds], ), share_amount, )?; + context.accounts.reserve.reacquire_borrow_mut()?; Ok(()) } diff --git a/finance/lending/anchor/programs/lending/src/instructions/liquidate_obligation.rs b/finance/lending/anchor/programs/lending/src/instructions/liquidate_obligation.rs index c3152bf37..bac49a996 100644 --- a/finance/lending/anchor/programs/lending/src/instructions/liquidate_obligation.rs +++ b/finance/lending/anchor/programs/lending/src/instructions/liquidate_obligation.rs @@ -137,13 +137,18 @@ pub fn handle_liquidate_obligation( }; // Interactions: liquidator repays, then receives the seized share tokens. + // + // These accounts are `Box`ed, and `Box`'s `AnchorAccount` impl does not + // override `cpi_handle_mut`, so that call would build a handle without + // releasing the wrapper's data borrow and the CPI would be rejected with + // `AccountBorrowFailed`. `to_cpi_handle_mut` forwards to the inner type. transfer_checked( CpiContext::new( context.accounts.token_program.address(), TransferChecked { - from: context.accounts.liquidator_repay_source.cpi_handle_mut(), - mint: context.accounts.repay_liquidity_mint.cpi_handle(), - to: context.accounts.repay_liquidity_vault.cpi_handle_mut(), + from: context.accounts.liquidator_repay_source.to_cpi_handle_mut(), + mint: context.accounts.repay_liquidity_mint.to_cpi_handle(), + to: context.accounts.repay_liquidity_vault.to_cpi_handle_mut(), authority: context.accounts.liquidator.cpi_handle(), }, ), @@ -158,6 +163,11 @@ pub fn handle_liquidate_obligation( owner.as_ref(), &bump, ]; + // `obligation` signs this CPI. It is a data account holding a live borrow on + // its buffer, which the runtime would reject when the CPI borrows the same + // account — so hand the borrow back across the call. `release_borrow` + // flushes the pending writes, and `reacquire_borrow_mut` re-reads them. + context.accounts.obligation.release_borrow()?; transfer_checked( CpiContext::new_with_signer( context.accounts.token_program.address(), @@ -165,9 +175,9 @@ pub fn handle_liquidate_obligation( from: context .accounts .obligation_collateral_vault - .cpi_handle_mut(), - mint: context.accounts.collateral_share_mint.cpi_handle(), - to: context.accounts.liquidator_collateral_dest.cpi_handle_mut(), + .to_cpi_handle_mut(), + mint: context.accounts.collateral_share_mint.to_cpi_handle(), + to: context.accounts.liquidator_collateral_dest.to_cpi_handle_mut(), authority: context.accounts.obligation.cpi_handle(), }, &[&seeds], @@ -175,6 +185,7 @@ pub fn handle_liquidate_obligation( seize_shares, context.accounts.collateral_share_mint.decimals(), )?; + context.accounts.obligation.reacquire_borrow_mut()?; Ok(()) } diff --git a/finance/lending/anchor/programs/lending/src/instructions/redeem_reserve_collateral.rs b/finance/lending/anchor/programs/lending/src/instructions/redeem_reserve_collateral.rs index a0e910b8e..464eda399 100644 --- a/finance/lending/anchor/programs/lending/src/instructions/redeem_reserve_collateral.rs +++ b/finance/lending/anchor/programs/lending/src/instructions/redeem_reserve_collateral.rs @@ -54,8 +54,17 @@ pub fn handle_redeem_reserve_collateral( share_amount, )?; + // Copy the seed inputs out: `release_borrow` below needs `&mut reserve`. let bump = [reserve.bump]; - let seeds = reserve_signer_seeds(&reserve.lending_market, &reserve.liquidity_mint, &bump); + let lending_market = reserve.lending_market; + let liquidity_mint = reserve.liquidity_mint; + let decimals = reserve.liquidity_decimals; + let seeds = reserve_signer_seeds(&lending_market, &liquidity_mint, &bump); + // `reserve` signs this CPI. It is a data account holding a live borrow on + // its buffer, which the runtime would reject when the CPI borrows the same + // account — so hand the borrow back across the call. `release_borrow` + // flushes the pending writes, and `reacquire_borrow_mut` re-reads them. + context.accounts.reserve.release_borrow()?; transfer_checked( CpiContext::new_with_signer( context.accounts.token_program.address(), @@ -63,13 +72,14 @@ pub fn handle_redeem_reserve_collateral( from: context.accounts.liquidity_vault.cpi_handle_mut(), mint: context.accounts.liquidity_mint.cpi_handle(), to: context.accounts.user_liquidity.cpi_handle_mut(), - authority: reserve.cpi_handle(), + authority: context.accounts.reserve.cpi_handle(), }, &[&seeds], ), liquidity_amount, - reserve.liquidity_decimals, + decimals, )?; + context.accounts.reserve.reacquire_borrow_mut()?; Ok(()) } diff --git a/finance/lending/anchor/programs/lending/src/instructions/withdraw_obligation_collateral.rs b/finance/lending/anchor/programs/lending/src/instructions/withdraw_obligation_collateral.rs index a2e75df0b..24fbb966b 100644 --- a/finance/lending/anchor/programs/lending/src/instructions/withdraw_obligation_collateral.rs +++ b/finance/lending/anchor/programs/lending/src/instructions/withdraw_obligation_collateral.rs @@ -85,6 +85,11 @@ pub fn handle_withdraw_obligation_collateral( owner.as_ref(), &bump, ]; + // `obligation` signs this CPI. It is a data account holding a live borrow on + // its buffer, which the runtime would reject when the CPI borrows the same + // account — so hand the borrow back across the call. `release_borrow` + // flushes the pending writes, and `reacquire_borrow_mut` re-reads them. + context.accounts.obligation.release_borrow()?; transfer_checked( CpiContext::new_with_signer( context.accounts.token_program.address(), @@ -92,13 +97,14 @@ pub fn handle_withdraw_obligation_collateral( from: context.accounts.obligation_share_vault.cpi_handle_mut(), mint: context.accounts.share_mint.cpi_handle(), to: context.accounts.user_share.cpi_handle_mut(), - authority: obligation.cpi_handle(), + authority: context.accounts.obligation.cpi_handle(), }, &[&seeds], ), share_amount, context.accounts.share_mint.decimals(), )?; + context.accounts.obligation.reacquire_borrow_mut()?; Ok(()) } diff --git a/finance/lending/anchor/programs/lending/tests/common/mod.rs b/finance/lending/anchor/programs/lending/tests/common/mod.rs index d8231c935..4edd78ff1 100644 --- a/finance/lending/anchor/programs/lending/tests/common/mod.rs +++ b/finance/lending/anchor/programs/lending/tests/common/mod.rs @@ -67,6 +67,25 @@ fn send( .map_err(|thrown| format!("{thrown:?}")) } +/// v2's `#[error_code]` does not log the variant name, so a failed transaction +/// carries only the numeric custom code: the enum discriminant plus anchor's +/// default 6000 offset. Assert on that rather than on a name that is no longer +/// in the logs. +pub const ANCHOR_ERROR_OFFSET: u32 = 6000; + +macro_rules! assert_program_error { + ($result:expr, $variant:path) => {{ + let message = $result.expect_err("transaction should have failed"); + let code = $variant as u32 + $crate::common::ANCHOR_ERROR_OFFSET; + assert!( + message.contains(&format!("Custom({code})")), + "expected {} (Custom({code})), got: {message}", + stringify!($variant), + ); + }}; +} +pub(crate) use assert_program_error; + /// Handle to one reserve and its associated PDAs. #[derive(Clone, Copy)] pub struct ReserveHandle { diff --git a/finance/lending/anchor/programs/lending/tests/test_borrow_repay.rs b/finance/lending/anchor/programs/lending/tests/test_borrow_repay.rs index fde7a4791..a31083604 100644 --- a/finance/lending/anchor/programs/lending/tests/test_borrow_repay.rs +++ b/finance/lending/anchor/programs/lending/tests/test_borrow_repay.rs @@ -1,5 +1,7 @@ mod common; +use lending::errors::LendingError; + use common::{ata, default_config, dollars, Env, ReserveHandle}; use solana_keypair::Keypair; use solana_signer::Signer; @@ -61,10 +63,7 @@ fn borrow_up_to_max_ltv_then_one_more_fails() { &borrow, 1, ); - assert!( - result.unwrap_err().contains("BorrowTooLarge"), - "borrowing past the LTV limit must be rejected" - ); + common::assert_program_error!(result, LendingError::BorrowTooLarge); } #[test] @@ -77,7 +76,7 @@ fn borrow_without_obligation_refresh_is_rejected() { &borrow, 100_000_000, ); - assert!(result.unwrap_err().contains("ObligationStale")); + common::assert_program_error!(result, LendingError::ObligationStale); } #[test] @@ -93,7 +92,7 @@ fn borrow_with_stale_price_feed_is_rejected() { &borrow, 100_000_000, ); - assert!(result.unwrap_err().contains("StalePriceFeed")); + common::assert_program_error!(result, LendingError::StalePriceFeed); } /// A cluster restart passes hours of wall-clock time in zero slots, so a price @@ -118,10 +117,7 @@ fn borrow_with_price_from_before_a_restart_is_rejected() { &borrow, 100_000_000, ); - assert!( - result.unwrap_err().contains("PricePredatesRestart"), - "a pre-restart price must be rejected even inside the staleness window" - ); + common::assert_program_error!(result, LendingError::PricePredatesRestart); // Publishing after the restart reopens the market. Warp first: the retry is // otherwise byte-identical to the rejected borrow, so it would carry the @@ -187,7 +183,7 @@ fn withdraw_blocked_while_borrowed_then_allowed_after_repay() { &collateral, 100_000_000, ); - assert!(blocked.unwrap_err().contains("WithdrawTooLarge")); + common::assert_program_error!(blocked, LendingError::WithdrawTooLarge); // Repay everything, then the collateral is free to withdraw. env.repay(&borrower, obligation, &borrow, 750_000_000); diff --git a/finance/lending/anchor/programs/lending/tests/test_liquidation.rs b/finance/lending/anchor/programs/lending/tests/test_liquidation.rs index b835afab6..2fc35216f 100644 --- a/finance/lending/anchor/programs/lending/tests/test_liquidation.rs +++ b/finance/lending/anchor/programs/lending/tests/test_liquidation.rs @@ -1,5 +1,7 @@ mod common; +use lending::errors::LendingError; + use common::{ata, cents, default_config, dollars, Env, ReserveHandle}; use solana_keypair::Keypair; use solana_signer::Signer; @@ -56,7 +58,7 @@ fn healthy_obligation_cannot_be_liquidated() { &collateral, 100_000_000, ); - assert!(result.unwrap_err().contains("ObligationHealthy")); + common::assert_program_error!(result, LendingError::ObligationHealthy); } #[test] @@ -130,7 +132,7 @@ fn over_seizing_liquidation_rejected_smaller_succeeds() { &collateral, 350_000_000, ); - assert!(over_seize.unwrap_err().contains("LiquidationTooLarge")); + common::assert_program_error!(over_seize, LendingError::LiquidationTooLarge); // Repaying $50 seizes $52.50 of collateral = 525 units at $0.10 — fits. env.try_liquidate( diff --git a/finance/lending/anchor/programs/lending/tests/test_reserve.rs b/finance/lending/anchor/programs/lending/tests/test_reserve.rs index a0e494acc..7043ca46f 100644 --- a/finance/lending/anchor/programs/lending/tests/test_reserve.rs +++ b/finance/lending/anchor/programs/lending/tests/test_reserve.rs @@ -1,5 +1,7 @@ mod common; +use lending::errors::LendingError; + use common::{default_config, Env}; use lending::constants::FIXED_POINT_SCALE; @@ -28,10 +30,7 @@ fn rejects_ltv_above_liquidation_threshold() { bad.loan_to_value_bps = 9_000; bad.liquidation_threshold_bps = 8_000; let result = env.try_update_config(&usdc, bad); - assert!( - result.unwrap_err().contains("InvalidConfig"), - "LTV above the liquidation threshold must be rejected" - ); + common::assert_program_error!(result, LendingError::InvalidConfig); } #[test] @@ -44,7 +43,7 @@ fn rejects_misordered_interest_rate_curve() { bad.optimal_borrow_rate_bps = 2_000; // optimal below min bad.max_borrow_rate_bps = 15_000; let result = env.try_update_config(&usdc, bad); - assert!(result.unwrap_err().contains("InvalidConfig")); + common::assert_program_error!(result, LendingError::InvalidConfig); } #[test] diff --git a/finance/lending/anchor/programs/lending/tests/test_rounding.rs b/finance/lending/anchor/programs/lending/tests/test_rounding.rs index 06fa3179a..223acae55 100644 --- a/finance/lending/anchor/programs/lending/tests/test_rounding.rs +++ b/finance/lending/anchor/programs/lending/tests/test_rounding.rs @@ -1,5 +1,7 @@ mod common; +use lending::errors::LendingError; + use common::{ata, default_config, dollars, Env}; use solana_signer::Signer; @@ -42,10 +44,7 @@ fn deposit_that_would_mint_zero_shares_is_rejected() { let dust_depositor = env.create_user(); env.fund(&dust_depositor, borrow.mint, 1); let result = env.try_supply(&dust_depositor, &borrow, 1); - assert!( - result.unwrap_err().contains("DepositTooSmall"), - "a 1-unit deposit into an appreciated pool mints zero shares and must be rejected" - ); + common::assert_program_error!(result, LendingError::DepositTooSmall); } #[test] @@ -118,5 +117,5 @@ fn withdraw_at_health_boundary_then_one_more_unit_fails() { &collateral, 1, ); - assert!(result.unwrap_err().contains("WithdrawTooLarge")); + common::assert_program_error!(result, LendingError::WithdrawTooLarge); } diff --git a/finance/lending/anchor/programs/lending/tests/test_security.rs b/finance/lending/anchor/programs/lending/tests/test_security.rs index e298ded1a..88be6eaab 100644 --- a/finance/lending/anchor/programs/lending/tests/test_security.rs +++ b/finance/lending/anchor/programs/lending/tests/test_security.rs @@ -1,5 +1,7 @@ mod common; +use lending::errors::LendingError; + use anchor_lang::{ solana_program::instruction::Instruction, system_program, InstructionData, ToAccountMetas, }; @@ -29,10 +31,7 @@ fn cross_market_reserve_is_rejected() { // token movement. env.fund(&borrower, foreign_reserve.share_mint, 0); // create the share ATA let result = env.try_post_collateral(&borrower, obligation, &foreign_reserve, 1); - assert!( - result.unwrap_err().contains("MarketMismatch"), - "a reserve from another lending market must be rejected" - ); + common::assert_program_error!(result, LendingError::MarketMismatch); } /// A market's price feed can only be written by that market's owner: an From e394c4ecf24de7dcc2d254c494634a36d5aadab6 Mon Sep 17 00:00:00 2001 From: Claude Date: Sun, 16 Aug 2026 01:00:24 +0000 Subject: [PATCH 34/59] token-swap: 20/20 tests pass under Anchor v2 The Accounts client modules are generated beside their structs and `#[program]` resolves them at the crate root, so the instructions re-export moves out of the program module. `mint::token_program` has to be spelled out for the init constraint when the token program is an Interface. Every account here is Boxed, and `Box` does not forward `cpi_handle_mut` to the inner type's borrow-releasing override, `to_cpi_handle_mut` does. `reload()` is gone: zero-copy accounts read live, so the post-CPI call becomes `revalidate_after_cpi()`. Tests assert on error codes, not names. --- .../instructions/admin/claim_admin_fees.rs | 16 +++---- .../src/instructions/deposit_liquidity.rs | 22 +++++----- .../src/instructions/initialize_pool.rs | 3 ++ .../src/instructions/swap_tokens.rs | 35 ++++++++------- .../src/instructions/withdraw_liquidity.rs | 22 +++++----- .../anchor/programs/token-swap/src/lib.rs | 10 +++-- .../programs/token-swap/tests/test_swap.rs | 43 +++++++++---------- 7 files changed, 80 insertions(+), 71 deletions(-) diff --git a/finance/token-swap/anchor/programs/token-swap/src/instructions/admin/claim_admin_fees.rs b/finance/token-swap/anchor/programs/token-swap/src/instructions/admin/claim_admin_fees.rs index b226349be..85db74579 100644 --- a/finance/token-swap/anchor/programs/token-swap/src/instructions/admin/claim_admin_fees.rs +++ b/finance/token-swap/anchor/programs/token-swap/src/instructions/admin/claim_admin_fees.rs @@ -66,9 +66,9 @@ pub fn handle_claim_admin_fees( CpiContext::new_with_signer( context.accounts.token_program.address(), TransferChecked { - from: context.accounts.pool_a.cpi_handle_mut(), - mint: context.accounts.mint_a.cpi_handle(), - to: context.accounts.admin_token_a.cpi_handle_mut(), + from: context.accounts.pool_a.to_cpi_handle_mut(), + mint: context.accounts.mint_a.to_cpi_handle(), + to: context.accounts.admin_token_a.to_cpi_handle_mut(), authority: context.accounts.pool_authority.cpi_handle(), }, signer_seeds, @@ -83,9 +83,9 @@ pub fn handle_claim_admin_fees( CpiContext::new_with_signer( context.accounts.token_program.address(), TransferChecked { - from: context.accounts.pool_b.cpi_handle_mut(), - mint: context.accounts.mint_b.cpi_handle(), - to: context.accounts.admin_token_b.cpi_handle_mut(), + from: context.accounts.pool_b.to_cpi_handle_mut(), + mint: context.accounts.mint_b.to_cpi_handle(), + to: context.accounts.admin_token_b.to_cpi_handle_mut(), authority: context.accounts.pool_authority.cpi_handle(), }, signer_seeds, @@ -114,8 +114,8 @@ pub struct ClaimAdminFeesAccountConstraints { mut, seeds = [ pool_config.config.as_ref(), - pool_config.mint_a.address().as_ref(), - pool_config.mint_b.address().as_ref(), + pool_config.mint_a.as_ref(), + pool_config.mint_b.as_ref(), ], bump, )] diff --git a/finance/token-swap/anchor/programs/token-swap/src/instructions/deposit_liquidity.rs b/finance/token-swap/anchor/programs/token-swap/src/instructions/deposit_liquidity.rs index c906055e0..2b1191729 100644 --- a/finance/token-swap/anchor/programs/token-swap/src/instructions/deposit_liquidity.rs +++ b/finance/token-swap/anchor/programs/token-swap/src/instructions/deposit_liquidity.rs @@ -157,7 +157,7 @@ pub fn handle_deposit_liquidity( .checked_sub(MINIMUM_LIQUIDITY) .ok_or(AmmError::MathOverflow)? } else { - let total_supply = context.accounts.liquidity_provider_mint.supply as u128; + let total_supply = context.accounts.liquidity_provider_mint.supply() as u128; let liquidity_from_a = (amount_a as u128) .checked_mul(total_supply) .ok_or(AmmError::MathOverflow)? @@ -198,9 +198,9 @@ pub fn handle_deposit_liquidity( CpiContext::new( context.accounts.token_program.address(), TransferChecked { - from: context.accounts.token_a.cpi_handle_mut(), - mint: context.accounts.mint_a.cpi_handle(), - to: context.accounts.pool_a.cpi_handle_mut(), + from: context.accounts.token_a.to_cpi_handle_mut(), + mint: context.accounts.mint_a.to_cpi_handle(), + to: context.accounts.pool_a.to_cpi_handle_mut(), authority: context.accounts.depositor.cpi_handle(), }, ), @@ -211,9 +211,9 @@ pub fn handle_deposit_liquidity( CpiContext::new( context.accounts.token_program.address(), TransferChecked { - from: context.accounts.token_b.cpi_handle_mut(), - mint: context.accounts.mint_b.cpi_handle(), - to: context.accounts.pool_b.cpi_handle_mut(), + from: context.accounts.token_b.to_cpi_handle_mut(), + mint: context.accounts.mint_b.to_cpi_handle(), + to: context.accounts.pool_b.to_cpi_handle_mut(), authority: context.accounts.depositor.cpi_handle(), }, ), @@ -235,8 +235,8 @@ pub fn handle_deposit_liquidity( CpiContext::new_with_signer( context.accounts.token_program.address(), MintTo { - mint: context.accounts.liquidity_provider_mint.cpi_handle_mut(), - to: context.accounts.liquidity_provider_token.cpi_handle_mut(), + mint: context.accounts.liquidity_provider_mint.to_cpi_handle_mut(), + to: context.accounts.liquidity_provider_token.to_cpi_handle_mut(), authority: context.accounts.pool_authority.cpi_handle(), }, signer_seeds, @@ -252,8 +252,8 @@ pub struct DepositLiquidityAccountConstraints { #[account( seeds = [ pool_config.config.as_ref(), - pool_config.mint_a.address().as_ref(), - pool_config.mint_b.address().as_ref(), + pool_config.mint_a.as_ref(), + pool_config.mint_b.as_ref(), ], bump, )] diff --git a/finance/token-swap/anchor/programs/token-swap/src/instructions/initialize_pool.rs b/finance/token-swap/anchor/programs/token-swap/src/instructions/initialize_pool.rs index eac7bdbc7..462b24f5f 100644 --- a/finance/token-swap/anchor/programs/token-swap/src/instructions/initialize_pool.rs +++ b/finance/token-swap/anchor/programs/token-swap/src/instructions/initialize_pool.rs @@ -70,6 +70,9 @@ pub struct InitializePoolAccountConstraints { bump, mint::decimals = 6, mint::authority = pool_authority, + // Required when the token program is an `Interface`: without it the + // init CPI is rejected with InvalidArgument. + mint::token_program = token_program, )] pub liquidity_provider_mint: Box>, diff --git a/finance/token-swap/anchor/programs/token-swap/src/instructions/swap_tokens.rs b/finance/token-swap/anchor/programs/token-swap/src/instructions/swap_tokens.rs index e74e88066..27bca8a91 100644 --- a/finance/token-swap/anchor/programs/token-swap/src/instructions/swap_tokens.rs +++ b/finance/token-swap/anchor/programs/token-swap/src/instructions/swap_tokens.rs @@ -177,9 +177,9 @@ pub fn handle_swap_tokens( CpiContext::new( context.accounts.token_program.address(), TransferChecked { - from: context.accounts.token_a.cpi_handle_mut(), - mint: context.accounts.mint_a.cpi_handle(), - to: context.accounts.pool_a.cpi_handle_mut(), + from: context.accounts.token_a.to_cpi_handle_mut(), + mint: context.accounts.mint_a.to_cpi_handle(), + to: context.accounts.pool_a.to_cpi_handle_mut(), authority: context.accounts.trader.cpi_handle(), }, ), @@ -190,9 +190,9 @@ pub fn handle_swap_tokens( CpiContext::new_with_signer( context.accounts.token_program.address(), TransferChecked { - from: context.accounts.pool_b.cpi_handle_mut(), - mint: context.accounts.mint_b.cpi_handle(), - to: context.accounts.token_b.cpi_handle_mut(), + from: context.accounts.pool_b.to_cpi_handle_mut(), + mint: context.accounts.mint_b.to_cpi_handle(), + to: context.accounts.token_b.to_cpi_handle_mut(), authority: context.accounts.pool_authority.cpi_handle(), }, signer_seeds, @@ -205,9 +205,9 @@ pub fn handle_swap_tokens( CpiContext::new_with_signer( context.accounts.token_program.address(), TransferChecked { - from: context.accounts.pool_a.cpi_handle_mut(), - mint: context.accounts.mint_a.cpi_handle(), - to: context.accounts.token_a.cpi_handle_mut(), + from: context.accounts.pool_a.to_cpi_handle_mut(), + mint: context.accounts.mint_a.to_cpi_handle(), + to: context.accounts.token_a.to_cpi_handle_mut(), authority: context.accounts.pool_authority.cpi_handle(), }, signer_seeds, @@ -219,9 +219,9 @@ pub fn handle_swap_tokens( CpiContext::new( context.accounts.token_program.address(), TransferChecked { - from: context.accounts.token_b.cpi_handle_mut(), - mint: context.accounts.mint_b.cpi_handle(), - to: context.accounts.pool_b.cpi_handle_mut(), + from: context.accounts.token_b.to_cpi_handle_mut(), + mint: context.accounts.mint_b.to_cpi_handle(), + to: context.accounts.pool_b.to_cpi_handle_mut(), authority: context.accounts.trader.cpi_handle(), }, ), @@ -249,8 +249,11 @@ pub fn handle_swap_tokens( // the pool). // // u128 + checked: same overflow concern as the pre-trade invariant. - context.accounts.pool_a.reload()?; - context.accounts.pool_b.reload()?; + // v2's token accounts are zero-copy, so the balances below are read live + // from the runtime buffer — there is nothing to reload. What the CPI can + // change is the schema, so re-run the load-time checks instead. + context.accounts.pool_a.revalidate_after_cpi()?; + context.accounts.pool_b.revalidate_after_cpi()?; let pool_config = &context.accounts.pool_config; let effective_pool_a_after = context .accounts @@ -282,8 +285,8 @@ pub struct SwapTokensAccountConstraints { mut, seeds = [ pool_config.config.as_ref(), - pool_config.mint_a.address().as_ref(), - pool_config.mint_b.address().as_ref(), + pool_config.mint_a.as_ref(), + pool_config.mint_b.as_ref(), ], bump, )] diff --git a/finance/token-swap/anchor/programs/token-swap/src/instructions/withdraw_liquidity.rs b/finance/token-swap/anchor/programs/token-swap/src/instructions/withdraw_liquidity.rs index 61087a1ec..a6fac0e91 100644 --- a/finance/token-swap/anchor/programs/token-swap/src/instructions/withdraw_liquidity.rs +++ b/finance/token-swap/anchor/programs/token-swap/src/instructions/withdraw_liquidity.rs @@ -62,7 +62,7 @@ pub fn handle_withdraw_liquidity( // Both amounts are computed up-front (before the slippage checks) so // the LP gets a consistent error regardless of which side trips first, // and so we don't transfer one side then revert. - let divisor = (context.accounts.liquidity_provider_mint.supply as u128) + let divisor = (context.accounts.liquidity_provider_mint.supply() as u128) .checked_add(MINIMUM_LIQUIDITY as u128) .ok_or(AmmError::MathOverflow)?; let amount_a_u128 = (amount as u128) @@ -96,9 +96,9 @@ pub fn handle_withdraw_liquidity( CpiContext::new_with_signer( context.accounts.token_program.address(), TransferChecked { - from: context.accounts.pool_a.cpi_handle_mut(), - mint: context.accounts.mint_a.cpi_handle(), - to: context.accounts.token_a.cpi_handle_mut(), + from: context.accounts.pool_a.to_cpi_handle_mut(), + mint: context.accounts.mint_a.to_cpi_handle(), + to: context.accounts.token_a.to_cpi_handle_mut(), authority: context.accounts.pool_authority.cpi_handle(), }, signer_seeds, @@ -111,9 +111,9 @@ pub fn handle_withdraw_liquidity( CpiContext::new_with_signer( context.accounts.token_program.address(), TransferChecked { - from: context.accounts.pool_b.cpi_handle_mut(), - mint: context.accounts.mint_b.cpi_handle(), - to: context.accounts.token_b.cpi_handle_mut(), + from: context.accounts.pool_b.to_cpi_handle_mut(), + mint: context.accounts.mint_b.to_cpi_handle(), + to: context.accounts.token_b.to_cpi_handle_mut(), authority: context.accounts.pool_authority.cpi_handle(), }, signer_seeds, @@ -128,8 +128,8 @@ pub fn handle_withdraw_liquidity( CpiContext::new( context.accounts.token_program.address(), Burn { - mint: context.accounts.liquidity_provider_mint.cpi_handle_mut(), - from: context.accounts.liquidity_provider_token.cpi_handle_mut(), + mint: context.accounts.liquidity_provider_mint.to_cpi_handle_mut(), + from: context.accounts.liquidity_provider_token.to_cpi_handle_mut(), authority: context.accounts.withdrawer.cpi_handle(), }, ), @@ -150,8 +150,8 @@ pub struct WithdrawLiquidityAccountConstraints { #[account( seeds = [ pool_config.config.as_ref(), - pool_config.mint_a.address().as_ref(), - pool_config.mint_b.address().as_ref(), + pool_config.mint_a.as_ref(), + pool_config.mint_b.as_ref(), ], bump, )] diff --git a/finance/token-swap/anchor/programs/token-swap/src/lib.rs b/finance/token-swap/anchor/programs/token-swap/src/lib.rs index c0b7d5088..1ab23c2b7 100644 --- a/finance/token-swap/anchor/programs/token-swap/src/lib.rs +++ b/finance/token-swap/anchor/programs/token-swap/src/lib.rs @@ -1,15 +1,19 @@ use anchor_lang::prelude::*; mod constants; -mod errors; -mod instructions; +pub mod errors; +pub mod instructions; mod state; +// The `#[derive(Accounts)]` client modules are generated beside their structs, +// and `#[program]` resolves them at `super::` — the crate root. Re-exporting +// here rather than inside the module puts them where it looks. +use instructions::*; + declare_id!("GahM6PrXesrBkHiGJ5no4EskLNnVBCaSwVKbM4UtzyK6"); #[program] pub mod swap_example { - pub use super::instructions::*; use super::*; pub fn initialize_config( diff --git a/finance/token-swap/anchor/programs/token-swap/tests/test_swap.rs b/finance/token-swap/anchor/programs/token-swap/tests/test_swap.rs index 61ef57e99..8b2539e3b 100644 --- a/finance/token-swap/anchor/programs/token-swap/tests/test_swap.rs +++ b/finance/token-swap/anchor/programs/token-swap/tests/test_swap.rs @@ -1,3 +1,5 @@ +use swap_example::errors::AmmError; + use { anchor_lang::{ solana_program::instruction::Instruction, system_program, Address, InstructionData, @@ -198,6 +200,20 @@ fn full_setup() -> TestSetup { } } +/// v2's `#[error_code]` does not log the variant name, so a failed transaction +/// carries only the numeric custom code: the enum discriminant plus anchor's +/// default 6000 offset. Assert on that rather than on a name that is no longer +/// in the logs. +const ANCHOR_ERROR_OFFSET: u32 = 6000; + +fn assert_program_error(message: &str, expected: AmmError, name: &str) { + let code = expected as u32 + ANCHOR_ERROR_OFFSET; + assert!( + message.contains(&format!("Custom({code})")), + "expected {name} (Custom({code})), got: {message}" + ); +} + #[test] fn test_initialize_config() { let (mut svm, program_id, payer) = setup(); @@ -710,12 +726,7 @@ fn test_claim_admin_fees() { "claim with both accumulators at zero must revert" ); let err_msg = format!("{:?}", result.unwrap_err()); - assert!( - err_msg.contains("NothingToClaim") - || err_msg.contains("0x1777") - || err_msg.contains("6007"), - "expected NothingToClaim error, got: {err_msg}" - ); + assert_program_error(&err_msg, AmmError::NothingToClaim, "NothingToClaim"); // Balance unchanged - the revert rolled back any partial state. let balance_a_after_third_claim = @@ -1309,10 +1320,7 @@ fn test_swap_reverts_when_output_below_min() { &ts.payer.pubkey(), ); let err = format!("{:?}", result.expect_err("must revert")); - assert!( - err.contains("SlippageExceeded"), - "expected SlippageExceeded, got: {err}" - ); + assert_program_error(&err, AmmError::SlippageExceeded, "SlippageExceeded"); } /// Slippage test: a deposit with `minimum_lp_tokens_out` strictly higher @@ -1344,10 +1352,7 @@ fn test_deposit_reverts_when_lp_below_min() { &ts.payer.pubkey(), ); let err = format!("{:?}", result.expect_err("must revert")); - assert!( - err.contains("DepositBelowMinimum"), - "expected DepositBelowMinimum, got: {err}" - ); + assert_program_error(&err, AmmError::DepositBelowMinimum, "DepositBelowMinimum"); // Sanity: the same deposit with `achievable_lp` as the floor succeeds. let ok_ix = deposit_ix_with_min_lp(&ts, 4_000_000, 1_000_000, achievable_lp); @@ -1380,10 +1385,7 @@ fn test_withdraw_reverts_when_below_min() { &ts.payer.pubkey(), ); let err = format!("{:?}", result.expect_err("must revert (A side)")); - assert!( - err.contains("WithdrawalBelowMinimum"), - "expected WithdrawalBelowMinimum (A side), got: {err}" - ); + assert_program_error(&err, AmmError::WithdrawalBelowMinimum, "WithdrawalBelowMinimum"); // Same on the B side. let strict_ix_b = withdraw_ix_with_min(&ts, lp / 2, 0, 4_000_000); @@ -1394,10 +1396,7 @@ fn test_withdraw_reverts_when_below_min() { &ts.payer.pubkey(), ); let err_b = format!("{:?}", result_b.expect_err("must revert (B side)")); - assert!( - err_b.contains("WithdrawalBelowMinimum"), - "expected WithdrawalBelowMinimum (B side), got: {err_b}" - ); + assert_program_error(&err_b, AmmError::WithdrawalBelowMinimum, "WithdrawalBelowMinimum"); } /// Slippage test: passing `min_output_amount = 0` is the explicit From bb2c0f5b25b85bd4a7fa0e99089eb35d20056e97 Mon Sep 17 00:00:00 2001 From: Claude Date: Sun, 16 Aug 2026 01:11:32 +0000 Subject: [PATCH 35/59] order-book: 28/28 tests pass under Anchor v2 The zero-copy book stays zero-copy: v2's `Account` derefs straight to T, so `load_init` / `load_mut` go away rather than the state converting to borsh. Maker accounts reached through remaining_accounts load via `AnchorAccount::load_mut`, which replaces `Account::try_from` and still writes back through `exit()`. The rest is the two borrow rules: Boxed accounts need `to_cpi_handle_mut`, and the market PDA has to release its data borrow while it signs. Also repairs a `key()` that an earlier rename had turned into `address()` on the critbit node type. --- .../src/instructions/admin/withdraw_fees.rs | 24 ++++--- .../src/instructions/initialize_market.rs | 14 ++-- .../src/instructions/place_order.rs | 64 ++++++++++++------- .../src/instructions/settle_funds.rs | 42 ++++++++---- .../order-book/src/state/slab/ordertree.rs | 2 +- .../order-book/tests/test_order_book.rs | 6 +- 6 files changed, 97 insertions(+), 55 deletions(-) diff --git a/finance/order-book/anchor/programs/order-book/src/instructions/admin/withdraw_fees.rs b/finance/order-book/anchor/programs/order-book/src/instructions/admin/withdraw_fees.rs index 0a5932709..89b6758ac 100644 --- a/finance/order-book/anchor/programs/order-book/src/instructions/admin/withdraw_fees.rs +++ b/finance/order-book/anchor/programs/order-book/src/instructions/admin/withdraw_fees.rs @@ -15,7 +15,7 @@ pub fn handle_withdraw_fees(context: &mut Context` derefs straight to `T`, so the slab + // is written in place with no `load_init` / `load_mut` step. The order book + // is not a PDA (see the comment on the `order_book` account below), so + // `bump` is unused and stored as 0. + let market_address = *context.accounts.market.address(); + context.accounts.order_book.initialize(market_address, 0); Ok(()) } diff --git a/finance/order-book/anchor/programs/order-book/src/instructions/place_order.rs b/finance/order-book/anchor/programs/order-book/src/instructions/place_order.rs index 5a25048cc..eafbd147e 100644 --- a/finance/order-book/anchor/programs/order-book/src/instructions/place_order.rs +++ b/finance/order-book/anchor/programs/order-book/src/instructions/place_order.rs @@ -31,9 +31,16 @@ pub fn handle_place_order( price: u64, quantity: u64, ) -> Result<()> { + // `remaining_accounts()` takes `&mut context` and returns an owned vec, so + // collect it before anything borrows `context.accounts`. + let maker_accounts = context.remaining_accounts()?; + // `AccountView` is Copy, and a copy still points at the same // account — v2's typed handles make the aliasing a compile error. let quote_mint_view = *context.accounts.quote_mint.account(); + // Read the decimals up front: the CPI handles below borrow the mints. + let quote_decimals = context.accounts.quote_mint.decimals(); + let base_decimals = context.accounts.base_mint.decimals(); let market = &context.accounts.market; require!(market.is_active, ErrorCode::MarketPaused); @@ -64,9 +71,9 @@ pub fn handle_place_order( let (source_account, mint_account_info, decimals, transfer_amount, destination_vault) = match side { OrderSide::Bid => ( - context.accounts.user_quote_account.cpi_handle_mut(), - context.accounts.quote_mint.cpi_handle_mut(), - context.accounts.quote_mint.decimals(), + context.accounts.user_quote_account.to_cpi_handle_mut(), + context.accounts.quote_mint.to_cpi_handle(), + quote_decimals, (price as u128) .checked_mul(quantity as u128) .ok_or(ErrorCode::NumericalOverflow)? @@ -74,18 +81,18 @@ pub fn handle_place_order( .ok_or(ErrorCode::NumericalOverflow)? .try_into() .map_err(|_| ErrorCode::NumericalOverflow)?, - context.accounts.quote_vault.cpi_handle_mut(), + context.accounts.quote_vault.to_cpi_handle_mut(), ), OrderSide::Ask => ( - context.accounts.user_base_account.cpi_handle_mut(), - context.accounts.base_mint.cpi_handle_mut(), - context.accounts.base_mint.decimals(), + context.accounts.user_base_account.to_cpi_handle_mut(), + context.accounts.base_mint.to_cpi_handle(), + base_decimals, (quantity as u128) .checked_mul(market.base_lot_size as u128) .ok_or(ErrorCode::NumericalOverflow)? .try_into() .map_err(|_| ErrorCode::NumericalOverflow)?, - context.accounts.base_vault.cpi_handle_mut(), + context.accounts.base_vault.to_cpi_handle_mut(), ), }; @@ -111,13 +118,12 @@ pub fn handle_place_order( // transaction's remaining_accounts, in the same price-time-priority // order the book would walk. We plan fills against the resting tree, // then verify the caller's account list matches the plan, then apply. - let maker_accounts = &context.remaining_accounts()?; require!( maker_accounts.len() % ACCOUNTS_PER_MAKER == 0, ErrorCode::MissingMakerAccounts ); - let order_book_loader = &context.accounts.order_book; + let order_book_loader = &mut context.accounts.order_book; // Plan in an immutable load scope, copy the fills out, then drop the // borrow before we re-borrow for mutations. AccountLoader::load() is a @@ -177,8 +183,13 @@ pub fn handle_place_order( let maker_order_info = &maker_accounts[fill_index * ACCOUNTS_PER_MAKER]; let maker_user_info = &maker_accounts[fill_index * ACCOUNTS_PER_MAKER + 1]; - let mut maker_order = Account::::try_from(maker_order_info)?; - let mut maker_market_user = Account::::try_from(maker_user_info)?; + // v2 has no `Account::try_from`. `AnchorAccount::load_mut` is the + // equivalent for a writable account reached through remaining_accounts; + // it is unsafe because the caller must guarantee no other live `&mut` + // to the same data, which the distinct maker accounts here satisfy. + let mut maker_order = unsafe { BorshAccount::::load_mut(*maker_order_info) }?; + let mut maker_market_user = + unsafe { BorshAccount::::load_mut(*maker_user_info) }?; require!( maker_order.owner == maker_market_user.owner, @@ -314,8 +325,8 @@ pub fn handle_place_order( remove_open_order(&mut maker_market_user, maker_order.order_id); } - maker_order.exit(context.program_id)?; - maker_market_user.exit(context.program_id)?; + maker_order.exit()?; + maker_market_user.exit()?; } // --------------------------------------------------------------- @@ -342,29 +353,36 @@ pub fn handle_place_order( // Move accumulated fee from quote_vault → fee_vault (one CPI signed // by the market PDA). if total_fee_quote > 0 { - let market_bump = [market.bump]; + // Copied out because `market` has to release its data borrow before it + // signs: the runtime would otherwise reject the CPI's own borrow of the + // same account with AccountBorrowFailed. + let market_bump = [context.accounts.market.bump]; + let base_mint = context.accounts.market.base_mint; + let quote_mint = context.accounts.market.quote_mint; let signer_seeds: [&[u8]; 4] = [ MARKET_SEED, - market.base_mint.as_ref(), - market.quote_mint.as_ref(), + base_mint.as_ref(), + quote_mint.as_ref(), &market_bump, ]; let signer_seeds = &[&signer_seeds[..]]; + context.accounts.market.release_borrow()?; transfer_checked( CpiContext::new_with_signer( context.accounts.token_program.address(), TransferChecked { - from: context.accounts.quote_vault.cpi_handle_mut(), + from: context.accounts.quote_vault.to_cpi_handle_mut(), mint: CpiHandle::readonly("e_mint_view), - to: context.accounts.fee_vault.cpi_handle_mut(), - authority: market.cpi_handle(), + to: context.accounts.fee_vault.to_cpi_handle_mut(), + authority: context.accounts.market.cpi_handle(), }, signer_seeds, ), total_fee_quote, - context.accounts.quote_mint.decimals(), + quote_decimals, )?; + context.accounts.market.reacquire_borrow_mut()?; } // Apply taker accounting deltas in a single mutation. @@ -398,7 +416,7 @@ pub fn handle_place_order( side, price, taker_remaining, - context.accounts.owner.address(), + *context.accounts.owner.address(), id, timestamp, )?; @@ -407,7 +425,7 @@ pub fn handle_place_order( }; let order = &mut context.accounts.order; - order.market = *market.address(); + order.market = *context.accounts.market.address(); order.owner = *context.accounts.owner.address(); order.order_id = order_id; order.side = side; diff --git a/finance/order-book/anchor/programs/order-book/src/instructions/settle_funds.rs b/finance/order-book/anchor/programs/order-book/src/instructions/settle_funds.rs index 90752ddb5..2a9087284 100644 --- a/finance/order-book/anchor/programs/order-book/src/instructions/settle_funds.rs +++ b/finance/order-book/anchor/programs/order-book/src/instructions/settle_funds.rs @@ -8,7 +8,6 @@ use crate::state::{Market, MarketUser, MARKET_SEED, MARKET_USER_SEED}; pub fn handle_settle_funds(context: &mut Context) -> Result<()> { let market_user = &mut context.accounts.market_user; - let market = &context.accounts.market; // Snapshot the amounts the user is owed, then zero the counters // BEFORE the token transfers. Checks-effects-interactions: even though @@ -23,30 +22,43 @@ pub fn handle_settle_funds(context: &mut Context) market_user.unsettled_quote = 0; // Seeds to sign as the market PDA (the authority of both vaults). Built - // once and reused for the two possible transfers. + // once and reused for the two possible transfers. The values are copied + // out because `market` has to release its data borrow before it signs. + let market = &context.accounts.market; let market_bump = [market.bump]; + let base_mint = market.base_mint; + let quote_mint = market.quote_mint; let signer_seeds: [&[u8]; 4] = [ MARKET_SEED, - market.base_mint.as_ref(), - market.quote_mint.as_ref(), + base_mint.as_ref(), + quote_mint.as_ref(), &market_bump, ]; let signer_seeds = &[&signer_seeds[..]]; + // Read the decimals before the CPI handles borrow the mints. + let base_decimals = context.accounts.base_mint.decimals(); + let quote_decimals = context.accounts.quote_mint.decimals(); + + // `market` signs both transfers. It is a data account holding a live borrow + // on its buffer, which the runtime would reject when the CPI borrows the + // same account, so hand the borrow back across the calls. + context.accounts.market.release_borrow()?; + if base_amount > 0 { transfer_checked( CpiContext::new_with_signer( context.accounts.token_program.address(), TransferChecked { - from: context.accounts.base_vault.cpi_handle_mut(), - mint: context.accounts.base_mint.cpi_handle(), - to: context.accounts.user_base_account.cpi_handle_mut(), - authority: market.cpi_handle(), + from: context.accounts.base_vault.to_cpi_handle_mut(), + mint: context.accounts.base_mint.to_cpi_handle(), + to: context.accounts.user_base_account.to_cpi_handle_mut(), + authority: context.accounts.market.cpi_handle(), }, signer_seeds, ), base_amount, - context.accounts.base_mint.decimals(), + base_decimals, )?; } @@ -55,18 +67,20 @@ pub fn handle_settle_funds(context: &mut Context) CpiContext::new_with_signer( context.accounts.token_program.address(), TransferChecked { - from: context.accounts.quote_vault.cpi_handle_mut(), - mint: context.accounts.quote_mint.cpi_handle(), - to: context.accounts.user_quote_account.cpi_handle_mut(), - authority: market.cpi_handle(), + from: context.accounts.quote_vault.to_cpi_handle_mut(), + mint: context.accounts.quote_mint.to_cpi_handle(), + to: context.accounts.user_quote_account.to_cpi_handle_mut(), + authority: context.accounts.market.cpi_handle(), }, signer_seeds, ), quote_amount, - context.accounts.quote_mint.decimals(), + quote_decimals, )?; } + context.accounts.market.reacquire_borrow_mut()?; + Ok(()) } diff --git a/finance/order-book/anchor/programs/order-book/src/state/slab/ordertree.rs b/finance/order-book/anchor/programs/order-book/src/state/slab/ordertree.rs index 94957dee8..b5a9eb53b 100644 --- a/finance/order-book/anchor/programs/order-book/src/state/slab/ordertree.rs +++ b/finance/order-book/anchor/programs/order-book/src/state/slab/ordertree.rs @@ -282,7 +282,7 @@ impl OrderTreeNodes { let parent_contents = *self .node(parent_handle) .ok_or_else(|| ErrorCode::OrderBookFull)?; - let parent_key = parent_contents.address().unwrap(); + let parent_key = parent_contents.key().unwrap(); // Exact-key collision: only possible if the existing slot is a // leaf (inner nodes' `key` is just a shared prefix). Overwrite diff --git a/finance/order-book/anchor/programs/order-book/tests/test_order_book.rs b/finance/order-book/anchor/programs/order-book/tests/test_order_book.rs index 01c2da90c..8054da73c 100644 --- a/finance/order-book/anchor/programs/order-book/tests/test_order_book.rs +++ b/finance/order-book/anchor/programs/order-book/tests/test_order_book.rs @@ -13,9 +13,11 @@ use { anchor_lang::{ solana_program::{ instruction::{AccountMeta, Instruction}, - system_instruction, system_program, + system_instruction, }, - Discriminator, InstructionData, ToAccountMetas, + // `system_program` moved to the crate root in v2, and `Pubkey` is + // compat-only — `Address` is the same 32-byte type. + system_program, Address, Discriminator, InstructionData, ToAccountMetas, }, litesvm::LiteSVM, solana_keypair::Keypair, From e6ffb97f264fabc3195ee828b6d73a3a537feab3 Mon Sep 17 00:00:00 2001 From: Claude Date: Sun, 16 Aug 2026 01:21:43 +0000 Subject: [PATCH 36/59] tokens: port nft-operations, transfer-fee and interest-bearing nft-operations moves off mpl-token-metadata's raw `*Cpi` builders, which want `&AccountInfo`, onto v2's anchor-spl wrappers. Its metadata accounts implement AnchorAccount directly, so they are the field type rather than something `Account` wraps, and the sized collection verifies with `verify_sized_collection_item`. Read-only CPI slots take the wrapper's own `cpi_handle()`: it borrows `&self`, so one account can fill several, and on a data account it relaxes the runtime borrow check that a hand-built `CpiHandle::readonly` on a copied view still trips. transfer-fee and interest-bearing read mint extension data through a shared borrow of the AccountView instead of a writable handle, and harvest's remaining-accounts scan uses `AnchorAccount::load`. --- docs/anchor-v2-migration.md | 19 ++- .../src/instructions/create_collection.rs | 110 ++++++++-------- .../mint-nft/src/instructions/mint_nft.rs | 118 +++++++++--------- .../src/instructions/verify_collection.rs | 59 +++++---- .../src/instructions/initialize.rs | 2 +- .../src/instructions/update_rate.rs | 2 +- .../programs/interest-bearing/src/lib.rs | 2 +- .../transfer-fee/src/instructions/harvest.rs | 32 +++-- .../src/instructions/initialize.rs | 9 +- .../transfer-fee/src/instructions/transfer.rs | 5 +- 10 files changed, 184 insertions(+), 174 deletions(-) diff --git a/docs/anchor-v2-migration.md b/docs/anchor-v2-migration.md index 42e8ba30e..25cf1e0f2 100644 --- a/docs/anchor-v2-migration.md +++ b/docs/anchor-v2-migration.md @@ -117,16 +117,27 @@ account — a vendored Pyth or Bubblegum type, say — straightforward to declar **This is the rule tests catch and the compiler does not.** v2's typed CPI handles make aliasing a compile error: passing one account into -both a writable and a read-only CPI slot will not build. The obvious workaround -— copying the `AccountView`, which is `Copy` — satisfies the compiler, and is -correct **only for accounts that hold no data borrow** (`Signer`, -`UncheckedAccount`, `Program`): +both a writable and a read-only CPI slot will not build. + +For **two read-only slots** there is nothing to work around: `cpi_handle()` +takes `&self`, so calling it twice on the same field is fine. Prefer it — on a +data account the wrapper's own `cpi_handle()` also relaxes the runtime borrow +check, which a hand-built handle does not. + +When a read-only slot has to coexist with a writable one, copying the +`AccountView` (which is `Copy`) satisfies the compiler. That is correct **only +for accounts that hold no data borrow** (`Signer`, `UncheckedAccount`, +`Program`), or for a data account whose borrow has already been released: ```rust let payer_view = *ctx.accounts.payer.account(); // ... CpiHandle::readonly(&payer_view) in the read-only slots ``` +On a still-borrowed data account the copy passes the compiler and then fails at +runtime with `AccountBorrowFailed`, because `CpiHandle::readonly` keeps the +borrow check on. + For a **data** account (`Account`, `BorshAccount`, `InterfaceAccount`) that copy is not enough. The account holds a live borrow on its buffer, and the runtime rejects the CPI's own borrow with `AccountBorrowFailed`. Release it diff --git a/tokens/nft-operations/anchor/programs/mint-nft/src/instructions/create_collection.rs b/tokens/nft-operations/anchor/programs/mint-nft/src/instructions/create_collection.rs index e4ed10d33..728eb8f4e 100644 --- a/tokens/nft-operations/anchor/programs/mint-nft/src/instructions/create_collection.rs +++ b/tokens/nft-operations/anchor/programs/mint-nft/src/instructions/create_collection.rs @@ -6,13 +6,13 @@ use anchor_spl::{ token::{mint_to, Mint, MintTo, Token, TokenAccount}, }; -use anchor_spl::metadata::mpl_token_metadata::{ - instructions::{ - CreateMasterEditionV3Cpi, CreateMasterEditionV3CpiAccounts, - CreateMasterEditionV3InstructionArgs, CreateMetadataAccountV3Cpi, - CreateMetadataAccountV3CpiAccounts, CreateMetadataAccountV3InstructionArgs, - }, - types::{CollectionDetails, Creator, DataV2}, +// v2's anchor-spl wraps these CPIs in terms of `CpiHandle`s, so the raw +// mpl-token-metadata `*Cpi` builders — which want `&AccountInfo` — are not +// usable here. +use anchor_spl::metadata::{ + create_master_edition_v3, create_metadata_accounts_v3, + mpl_token_metadata::types::{CollectionDetails, Creator, DataV2}, + CreateMasterEditionV3, CreateMetadataAccountsV3, }; use super::validate_metadata_strings; @@ -73,15 +73,6 @@ pub fn handle_create_collection( ) -> Result<()> { validate_metadata_strings(&name, &symbol, &uri)?; - let metadata = &accounts.metadata.cpi_handle_mut(); - let master_edition = &accounts.master_edition.cpi_handle_mut(); - let mint = &accounts.mint.cpi_handle_mut(); - let authority = &accounts.mint_authority.cpi_handle_mut(); - let payer = &accounts.user.cpi_handle_mut(); - let system_program = &accounts.system_program.cpi_handle_mut(); - let spl_token_program = &accounts.token_program.cpi_handle_mut(); - let spl_metadata_program = &accounts.token_metadata_program.cpi_handle_mut(); - let seeds = &[&b"authority"[..], &[bumps.mint_authority]]; let signer_seeds = &[&seeds[..]]; @@ -101,52 +92,55 @@ pub fn handle_create_collection( share: 100, }]; - let metadata_account = CreateMetadataAccountV3Cpi::new( - spl_metadata_program, - CreateMetadataAccountV3CpiAccounts { - metadata, - mint, - mint_authority: authority, - payer, - update_authority: (authority, true), - system_program, - rent: None, - }, - CreateMetadataAccountV3InstructionArgs { - data: DataV2 { - name, - symbol, - uri, - seller_fee_basis_points: 0, - creators: Some(creator), - collection: None, - uses: None, + // Read-only slots use the wrapper's own `cpi_handle()`: it takes `&self`, + // so one account can fill several of them, and on a data account it also + // relaxes the runtime borrow check that a hand-built + // `CpiHandle::readonly(&copied_view)` would still trip. + create_metadata_accounts_v3( + CpiContext::new_with_signer( + accounts.token_metadata_program.address(), + CreateMetadataAccountsV3 { + metadata: accounts.metadata.cpi_handle_mut(), + mint: accounts.mint.cpi_handle(), + mint_authority: accounts.mint_authority.cpi_handle(), + payer: accounts.user.cpi_handle_mut(), + update_authority: accounts.mint_authority.cpi_handle(), + system_program: accounts.system_program.cpi_handle(), + update_authority_is_signer: true, }, - is_mutable: true, - collection_details: Some(CollectionDetails::V1 { size: 0 }), + signer_seeds, + ), + DataV2 { + name, + symbol, + uri, + seller_fee_basis_points: 0, + creators: Some(creator), + collection: None, + uses: None, }, - ); - metadata_account.invoke_signed(signer_seeds)?; + true, + Some(CollectionDetails::V1 { size: 0 }), + )?; msg!("Metadata Account created!"); - let master_edition_account = CreateMasterEditionV3Cpi::new( - spl_metadata_program, - CreateMasterEditionV3CpiAccounts { - edition: master_edition, - update_authority: authority, - mint_authority: authority, - mint, - payer, - metadata, - token_program: spl_token_program, - system_program, - rent: None, - }, - CreateMasterEditionV3InstructionArgs { - max_supply: Some(0), - }, - ); - master_edition_account.invoke_signed(signer_seeds)?; + create_master_edition_v3( + CpiContext::new_with_signer( + accounts.token_metadata_program.address(), + CreateMasterEditionV3 { + edition: accounts.master_edition.cpi_handle_mut(), + mint: accounts.mint.cpi_handle_mut(), + update_authority: accounts.mint_authority.cpi_handle(), + mint_authority: accounts.mint_authority.cpi_handle(), + payer: accounts.user.cpi_handle_mut(), + metadata: accounts.metadata.cpi_handle_mut(), + token_program: accounts.token_program.cpi_handle(), + system_program: accounts.system_program.cpi_handle(), + }, + signer_seeds, + ), + Some(0), + )?; msg!("Master Edition Account created"); Ok(()) diff --git a/tokens/nft-operations/anchor/programs/mint-nft/src/instructions/mint_nft.rs b/tokens/nft-operations/anchor/programs/mint-nft/src/instructions/mint_nft.rs index 77caa0b43..7c706c87e 100644 --- a/tokens/nft-operations/anchor/programs/mint-nft/src/instructions/mint_nft.rs +++ b/tokens/nft-operations/anchor/programs/mint-nft/src/instructions/mint_nft.rs @@ -6,13 +6,13 @@ use anchor_spl::{ token::{mint_to, Mint, MintTo, Token, TokenAccount}, }; -use anchor_spl::metadata::mpl_token_metadata::{ - instructions::{ - CreateMasterEditionV3Cpi, CreateMasterEditionV3CpiAccounts, - CreateMasterEditionV3InstructionArgs, CreateMetadataAccountV3Cpi, - CreateMetadataAccountV3CpiAccounts, CreateMetadataAccountV3InstructionArgs, - }, - types::{Collection, Creator, DataV2}, +// v2's anchor-spl wraps these CPIs in terms of `CpiHandle`s, so the raw +// mpl-token-metadata `*Cpi` builders — which want `&AccountInfo` — are not +// usable here. +use anchor_spl::metadata::{ + create_master_edition_v3, create_metadata_accounts_v3, + mpl_token_metadata::types::{Collection, Creator, DataV2}, + CreateMasterEditionV3, CreateMetadataAccountsV3, }; use super::validate_metadata_strings; @@ -77,15 +77,6 @@ pub fn handle_mint_nft( ) -> Result<()> { validate_metadata_strings(&name, &symbol, &uri)?; - let metadata = &accounts.metadata.cpi_handle_mut(); - let master_edition = &accounts.master_edition.cpi_handle_mut(); - let mint = &accounts.mint.cpi_handle_mut(); - let authority = &accounts.mint_authority.cpi_handle_mut(); - let payer = &accounts.owner.cpi_handle_mut(); - let system_program = &accounts.system_program.cpi_handle_mut(); - let spl_token_program = &accounts.token_program.cpi_handle_mut(); - let spl_metadata_program = &accounts.token_metadata_program.cpi_handle_mut(); - let seeds = &[&b"authority"[..], &[bumps.mint_authority]]; let signer_seeds = &[&seeds[..]]; @@ -105,54 +96,57 @@ pub fn handle_mint_nft( share: 100, }]; - let metadata_account = CreateMetadataAccountV3Cpi::new( - spl_metadata_program, - CreateMetadataAccountV3CpiAccounts { - metadata, - mint, - mint_authority: authority, - payer, - update_authority: (authority, true), - system_program, - rent: None, - }, - CreateMetadataAccountV3InstructionArgs { - data: DataV2 { - name, - symbol, - uri, - seller_fee_basis_points: 0, - creators: Some(creator), - collection: Some(Collection { - verified: false, - key: *accounts.collection_mint.address(), - }), - uses: None, + // Read-only slots use the wrapper's own `cpi_handle()`: it takes `&self`, + // so one account can fill several of them, and on a data account it also + // relaxes the runtime borrow check that a hand-built + // `CpiHandle::readonly(&copied_view)` would still trip. + create_metadata_accounts_v3( + CpiContext::new_with_signer( + accounts.token_metadata_program.address(), + CreateMetadataAccountsV3 { + metadata: accounts.metadata.cpi_handle_mut(), + mint: accounts.mint.cpi_handle(), + mint_authority: accounts.mint_authority.cpi_handle(), + payer: accounts.owner.cpi_handle_mut(), + update_authority: accounts.mint_authority.cpi_handle(), + system_program: accounts.system_program.cpi_handle(), + update_authority_is_signer: true, }, - is_mutable: true, - collection_details: None, - }, - ); - metadata_account.invoke_signed(signer_seeds)?; - - let master_edition_account = CreateMasterEditionV3Cpi::new( - spl_metadata_program, - CreateMasterEditionV3CpiAccounts { - edition: master_edition, - update_authority: authority, - mint_authority: authority, - mint, - payer, - metadata, - token_program: spl_token_program, - system_program, - rent: None, + signer_seeds, + ), + DataV2 { + name, + symbol, + uri, + seller_fee_basis_points: 0, + creators: Some(creator), + collection: Some(Collection { + verified: false, + key: *accounts.collection_mint.address(), + }), + uses: None, }, - CreateMasterEditionV3InstructionArgs { - max_supply: Some(0), - }, - ); - master_edition_account.invoke_signed(signer_seeds)?; + true, + None, + )?; + + create_master_edition_v3( + CpiContext::new_with_signer( + accounts.token_metadata_program.address(), + CreateMasterEditionV3 { + edition: accounts.master_edition.cpi_handle_mut(), + mint: accounts.mint.cpi_handle_mut(), + update_authority: accounts.mint_authority.cpi_handle(), + mint_authority: accounts.mint_authority.cpi_handle(), + payer: accounts.owner.cpi_handle_mut(), + metadata: accounts.metadata.cpi_handle_mut(), + token_program: accounts.token_program.cpi_handle(), + system_program: accounts.system_program.cpi_handle(), + }, + signer_seeds, + ), + Some(0), + )?; Ok(()) } diff --git a/tokens/nft-operations/anchor/programs/mint-nft/src/instructions/verify_collection.rs b/tokens/nft-operations/anchor/programs/mint-nft/src/instructions/verify_collection.rs index ec2157e1b..647ec340c 100644 --- a/tokens/nft-operations/anchor/programs/mint-nft/src/instructions/verify_collection.rs +++ b/tokens/nft-operations/anchor/programs/mint-nft/src/instructions/verify_collection.rs @@ -1,19 +1,24 @@ use anchor_lang::prelude::*; -use anchor_spl::metadata::mpl_token_metadata::instructions::{ - VerifyCollectionV1Cpi, VerifyCollectionV1CpiAccounts, +// v2's anchor-spl wraps this CPI in terms of `CpiHandle`s, so the raw +// mpl-token-metadata `*Cpi` builder — which wants `&AccountInfo` — is not +// usable here. The collection is created sized (`CollectionDetails::V1`), so +// the sized-item variant is the matching instruction. +use anchor_spl::metadata::{ + verify_sized_collection_item, MasterEditionAccount, MetadataAccount, + VerifySizedCollectionItem, }; -use anchor_spl::metadata::{MasterEditionAccount, MetadataAccount}; use anchor_spl::{metadata::Metadata, token::Mint}; -// In Anchor 1.0, sysvar::instructions::ID moved - use the well-known address directly +// pinocchio does not re-export the instructions sysvar id; decode it here. const INSTRUCTIONS_SYSVAR_ID: Address = - anchor_lang::solana_program::pubkey::pubkey!("Sysvar1nstructions1111111111111111111111111"); + anchor_lang::address!("Sysvar1nstructions1111111111111111111111111"); #[derive(Accounts)] pub struct VerifyCollectionMintAccountConstraints { + #[account(mut)] pub authority: Signer, #[account(mut)] - pub metadata: Account, + pub metadata: MetadataAccount, pub mint: Account, #[account( seeds = [b"authority"], @@ -23,8 +28,8 @@ pub struct VerifyCollectionMintAccountConstraints { pub mint_authority: UncheckedAccount, pub collection_mint: Account, #[account(mut)] - pub collection_metadata: Account, - pub collection_master_edition: Account, + pub collection_metadata: MetadataAccount, + pub collection_master_edition: MasterEditionAccount, pub system_program: Program, #[account(address = INSTRUCTIONS_SYSVAR_ID)] /// CHECK: Sysvar instruction account that is being checked with an address constraint @@ -36,32 +41,24 @@ pub fn handle_verify_collection( accounts: &mut VerifyCollectionMintAccountConstraints, bumps: &VerifyCollectionMintAccountConstraintsBumps, ) -> Result<()> { - let metadata = &accounts.metadata.cpi_handle_mut(); - let authority = &accounts.mint_authority.cpi_handle_mut(); - let collection_mint = &accounts.collection_mint.cpi_handle_mut(); - let collection_metadata = &accounts.collection_metadata.cpi_handle_mut(); - let collection_master_edition = &accounts.collection_master_edition.cpi_handle_mut(); - let system_program = &accounts.system_program.cpi_handle_mut(); - let sysvar_instructions = &accounts.sysvar_instruction.cpi_handle_mut(); - let spl_metadata_program = &accounts.token_metadata_program.cpi_handle_mut(); - let seeds = &[&b"authority"[..], &[bumps.mint_authority]]; let signer_seeds = &[&seeds[..]]; - let verify_collection = VerifyCollectionV1Cpi::new( - spl_metadata_program, - VerifyCollectionV1CpiAccounts { - authority, - delegate_record: None, - metadata, - collection_mint, - collection_metadata: Some(collection_metadata), - collection_master_edition: Some(collection_master_edition), - system_program, - sysvar_instructions, - }, - ); - verify_collection.invoke_signed(signer_seeds)?; + verify_sized_collection_item( + CpiContext::new_with_signer( + accounts.token_metadata_program.address(), + VerifySizedCollectionItem { + metadata: accounts.metadata.cpi_handle_mut(), + collection_authority: accounts.mint_authority.cpi_handle(), + payer: accounts.authority.cpi_handle_mut(), + collection_mint: accounts.collection_mint.cpi_handle(), + collection_metadata: accounts.collection_metadata.cpi_handle_mut(), + collection_master_edition: accounts.collection_master_edition.cpi_handle(), + }, + signer_seeds, + ), + None, + )?; msg!("Collection Verified!"); diff --git a/tokens/token-extensions/interest-bearing/anchor/programs/interest-bearing/src/instructions/initialize.rs b/tokens/token-extensions/interest-bearing/anchor/programs/interest-bearing/src/instructions/initialize.rs index c70e1c645..2213d0387 100644 --- a/tokens/token-extensions/interest-bearing/anchor/programs/interest-bearing/src/instructions/initialize.rs +++ b/tokens/token-extensions/interest-bearing/anchor/programs/interest-bearing/src/instructions/initialize.rs @@ -72,7 +72,7 @@ pub fn handler(context: &mut Context, rate: i16) - )?; check_mint_data( - &context.accounts.mint_account.cpi_handle_mut(), + context.accounts.mint_account.account(), &context.accounts.payer.address(), )?; Ok(()) diff --git a/tokens/token-extensions/interest-bearing/anchor/programs/interest-bearing/src/instructions/update_rate.rs b/tokens/token-extensions/interest-bearing/anchor/programs/interest-bearing/src/instructions/update_rate.rs index 362ffd57b..3f1dc1be2 100644 --- a/tokens/token-extensions/interest-bearing/anchor/programs/interest-bearing/src/instructions/update_rate.rs +++ b/tokens/token-extensions/interest-bearing/anchor/programs/interest-bearing/src/instructions/update_rate.rs @@ -29,7 +29,7 @@ pub fn handler(context: &mut Context, rate: i16) - )?; check_mint_data( - &context.accounts.mint_account.cpi_handle_mut(), + context.accounts.mint_account.account(), &context.accounts.authority.address(), )?; Ok(()) diff --git a/tokens/token-extensions/interest-bearing/anchor/programs/interest-bearing/src/lib.rs b/tokens/token-extensions/interest-bearing/anchor/programs/interest-bearing/src/lib.rs index 42b0a12ff..ca8b64d9d 100644 --- a/tokens/token-extensions/interest-bearing/anchor/programs/interest-bearing/src/lib.rs +++ b/tokens/token-extensions/interest-bearing/anchor/programs/interest-bearing/src/lib.rs @@ -33,7 +33,7 @@ pub mod interest_bearing { } pub fn check_mint_data(mint_account_info: &AccountView, authority_key: &Address) -> Result<()> { - let mint_data = mint_account_info.data.borrow(); + let mint_data = mint_account_info.try_borrow()?; let mint_with_extension = StateWithExtensions::::unpack(&mint_data)?; let extension_data = mint_with_extension.get_extension::()?; diff --git a/tokens/token-extensions/transfer-fee/anchor/programs/transfer-fee/src/instructions/harvest.rs b/tokens/token-extensions/transfer-fee/anchor/programs/transfer-fee/src/instructions/harvest.rs index 69a46d25c..7f1823672 100644 --- a/tokens/token-extensions/transfer-fee/anchor/programs/transfer-fee/src/instructions/harvest.rs +++ b/tokens/token-extensions/transfer-fee/anchor/programs/transfer-fee/src/instructions/harvest.rs @@ -15,18 +15,30 @@ pub struct HarvestAccountConstraints { pub fn process_harvest(context: &mut Context) -> Result<()> { // Using remaining accounts to allow for passing in an unknown number of token accounts to harvest from // Check that remaining accounts are token accounts for the mint to harvest to - let sources = context - .remaining_accounts + // `remaining_accounts()` takes `&mut context` and hands back an owned vec, + // so collect it before anything borrows `context.accounts`. + let mut candidates = context.remaining_accounts()?; + let mint_address = *context.accounts.mint_account.address(); + + // v2 has no `InterfaceAccount::try_from`; `AnchorAccount::load` is the + // equivalent for an account reached through remaining_accounts. The + // wrapper is dropped at the end of each iteration, so it holds no borrow + // across the CPI — only the verdict escapes. + let keep: Vec = candidates .iter() - .filter_map(|account| { - InterfaceAccount::::try_from(account) - .ok() - .filter(|token_account| { - token_account.mint == context.accounts.mint_account.address() - }) - .map(|_| account.cpi_handle_mut()) + .map(|account| { + InterfaceAccount::::load(*account) + .map(|token_account| *token_account.mint() == mint_address) + .unwrap_or(false) }) - .collect::>(); + .collect(); + + let sources: Vec = candidates + .iter_mut() + .zip(keep) + .filter(|(_, keep)| *keep) + .map(|(account, _)| CpiHandleMut::writable(account)) + .collect(); harvest_withheld_tokens_to_mint( CpiContext::new( diff --git a/tokens/token-extensions/transfer-fee/anchor/programs/transfer-fee/src/instructions/initialize.rs b/tokens/token-extensions/transfer-fee/anchor/programs/transfer-fee/src/instructions/initialize.rs index edf6cc14c..9c3a36347 100644 --- a/tokens/token-extensions/transfer-fee/anchor/programs/transfer-fee/src/instructions/initialize.rs +++ b/tokens/token-extensions/transfer-fee/anchor/programs/transfer-fee/src/instructions/initialize.rs @@ -92,19 +92,20 @@ pub fn handle_process_initialize( // helper to demonstrate how to read mint extension data within a program pub fn handle_check_mint_data(accounts: &InitializeAccountConstraints) -> Result<()> { - let mint = &accounts.mint_account.cpi_handle_mut(); - let mint_data = mint.data.borrow(); + // Read-only: the account already holds a shared borrow of its buffer, and a + // second shared borrow is fine where a writable handle would be rejected. + let mint_data = accounts.mint_account.account().try_borrow()?; let mint_with_extension = StateWithExtensions::::unpack(&mint_data)?; let extension_data = mint_with_extension.get_extension::()?; assert_eq!( extension_data.transfer_fee_config_authority, - OptionalNonZeroPubkey::try_from(Some(accounts.payer.address()))? + OptionalNonZeroPubkey::try_from(Some(*accounts.payer.address()))? ); assert_eq!( extension_data.withdraw_withheld_authority, - OptionalNonZeroPubkey::try_from(Some(accounts.payer.address()))? + OptionalNonZeroPubkey::try_from(Some(*accounts.payer.address()))? ); msg!("{:?}", extension_data); diff --git a/tokens/token-extensions/transfer-fee/anchor/programs/transfer-fee/src/instructions/transfer.rs b/tokens/token-extensions/transfer-fee/anchor/programs/transfer-fee/src/instructions/transfer.rs index c93d53c6a..f4135db8c 100644 --- a/tokens/token-extensions/transfer-fee/anchor/programs/transfer-fee/src/instructions/transfer.rs +++ b/tokens/token-extensions/transfer-fee/anchor/programs/transfer-fee/src/instructions/transfer.rs @@ -51,8 +51,9 @@ pub fn handle_process_transfer( // account — v2's typed handles make the aliasing a compile error. let mint_account_view = *context.accounts.mint_account.account(); // read mint account extension data - let mint = &context.accounts.mint_account.cpi_handle_mut(); - let mint_data = mint.data.borrow(); + // Read-only: the account already holds a shared borrow of its buffer, and a + // second shared borrow is fine where a writable handle would be rejected. + let mint_data = context.accounts.mint_account.account().try_borrow()?; let mint_with_extension = StateWithExtensions::::unpack(&mint_data)?; let extension_data = mint_with_extension.get_extension::()?; From 582b98af7a1776191586aeccc7e2b9f380b6c8e1 Mon Sep 17 00:00:00 2001 From: Claude Date: Sun, 16 Aug 2026 01:32:26 +0000 Subject: [PATCH 37/59] tokens: port pda-mint-authority, cpi-guard and nft-minter An SPL init constraint cannot name the account being initialized, so the two examples whose whole point is a PDA that is its own authority build the account by hand, create_account plus initialize_mint2 / initialize_account3, which take the authority as an address. Filling a writable and a read-only CPI slot from the same account uses `CpiHandleMut::into_readonly()`, which carries the wrapper's relaxed borrow flag across; a handle built over a copy of the AccountView does not, and fails at runtime with AccountBorrowFailed. nft-minter drops `update_authority_is_signer`, which v2 keeps on CreateMetadataAccountsV3 only. --- docs/anchor-v2-migration.md | 26 +++++++- .../anchor/programs/nft-minter/src/lib.rs | 2 - .../anchor/programs/token-minter/Cargo.toml | 3 + .../token-minter/src/instructions/create.rs | 61 +++++++++++++++---- .../token-minter/src/instructions/mint.rs | 18 +++--- .../anchor/programs/cpi-guard/src/lib.rs | 56 ++++++++++++++--- 6 files changed, 136 insertions(+), 30 deletions(-) diff --git a/docs/anchor-v2-migration.md b/docs/anchor-v2-migration.md index 25cf1e0f2..77749d0a5 100644 --- a/docs/anchor-v2-migration.md +++ b/docs/anchor-v2-migration.md @@ -136,7 +136,31 @@ let payer_view = *ctx.accounts.payer.account(); On a still-borrowed data account the copy passes the compiler and then fails at runtime with `AccountBorrowFailed`, because `CpiHandle::readonly` keeps the -borrow check on. +borrow check on. Use `into_readonly()` instead: `CpiHandleMut` is `Copy`, and +erasing it carries the wrapper's relaxed borrow flag across. + +```rust +// one account filling a writable slot and a read-only one +let mint_handle = ctx.accounts.mint_account.cpi_handle_mut(); +let authority_handle = mint_handle.into_readonly(); +MintTo { mint: mint_handle, to: ..., authority: authority_handle } +``` + +Take the writable handle **last**: it borrows the field mutably for the rest of +the scope, so any `msg!` or `.address()` on the same account has to come first. + +## Constraints that reference the account being initialized + +`mint::authority = mint_account` on `mint_account` itself — a PDA that is its +own mint authority — is rejected at macro-expansion time: an SPL `init` +constraint has to name a *sibling* field. The same goes for +`token::authority = `. + +Where that idiom is the point of the example, build the account by hand +(`create_account` + `initialize_mint2` / `initialize_account3`) rather than +adding a second field for the same address, which would then trip v2's +duplicate-mutable-account check. `initialize_mint2` and `initialize_account3` +take the authority as an address, so nothing is lost. For a **data** account (`Account`, `BorshAccount`, `InterfaceAccount`) that copy is not enough. The account holds a live borrow on its buffer, and the diff --git a/tokens/nft-minter/anchor/programs/nft-minter/src/lib.rs b/tokens/nft-minter/anchor/programs/nft-minter/src/lib.rs index 205a847f7..2a872cae2 100644 --- a/tokens/nft-minter/anchor/programs/nft-minter/src/lib.rs +++ b/tokens/nft-minter/anchor/programs/nft-minter/src/lib.rs @@ -56,7 +56,6 @@ pub mod nft_minter { update_authority: CpiHandle::readonly(&payer_view), payer: context.accounts.payer.cpi_handle_mut(), system_program: context.accounts.system_program.cpi_handle(), - update_authority_is_signer: true, }, ), DataV2 { @@ -87,7 +86,6 @@ pub mod nft_minter { metadata: context.accounts.metadata_account.cpi_handle_mut(), token_program: context.accounts.token_program.cpi_handle(), system_program: context.accounts.system_program.cpi_handle(), - update_authority_is_signer: true, }, ), None, // Max Supply diff --git a/tokens/pda-mint-authority/anchor/programs/token-minter/Cargo.toml b/tokens/pda-mint-authority/anchor/programs/token-minter/Cargo.toml index 3e2b487ad..89d54c306 100644 --- a/tokens/pda-mint-authority/anchor/programs/token-minter/Cargo.toml +++ b/tokens/pda-mint-authority/anchor/programs/token-minter/Cargo.toml @@ -30,6 +30,9 @@ wincode = { version = "0.5", features = ["derive"] } # `SchemaRead`/`SchemaWrite` bound fails. 2.6.1 is the last 0.5-line release. solana-address = ">=2.6, <2.7" anchor-spl = { version = "2.0.0-rc.1", features = ["metadata"] } +# For `Pack`, which gives the classic mint its `LEN`. anchor-spl re-exports +# the state types but not the trait. +solana-program-pack = "3" [dev-dependencies] litesvm = "0.13.1" diff --git a/tokens/pda-mint-authority/anchor/programs/token-minter/src/instructions/create.rs b/tokens/pda-mint-authority/anchor/programs/token-minter/src/instructions/create.rs index b8f76094a..5f786c8ff 100644 --- a/tokens/pda-mint-authority/anchor/programs/token-minter/src/instructions/create.rs +++ b/tokens/pda-mint-authority/anchor/programs/token-minter/src/instructions/create.rs @@ -2,13 +2,15 @@ // This is to demonstrate that the same PDA can be used for both the address of an account and CPI signing use { anchor_lang::prelude::*, + // `Mint::LEN` comes from `Pack`, which anchor-spl does not re-export. + solana_program_pack::Pack, + anchor_lang::system_program::{create_account, CreateAccount}, anchor_spl::{ metadata::{ create_metadata_accounts_v3, mpl_token_metadata::types::DataV2, CreateMetadataAccountsV3, Metadata, }, - mint::{self, Mint}, - token::{self, Token}, + token::{initialize_mint2, spl_token::state::Mint as MintState, InitializeMint2, Token}, }, }; @@ -17,19 +19,19 @@ pub struct CreateTokenAccountConstraints { #[account(mut)] pub payer: Signer, - // Create mint account - // Same PDA as address of the account and mint/freeze authority + // Create mint account. The same PDA is both the account's address and its + // mint/freeze authority — which is the point of this example, and which v2 + // cannot express as an `init` constraint: `mint::authority` has to name a + // sibling field, and referencing the account being initialized is rejected + // at macro-expansion time. So the mint is created by hand in + // `handle_create_token` below. + /// CHECK: created and initialized as a mint by this instruction. #[account( - init, + mut, seeds = [b"mint"], bump, - payer = payer, - mint::decimals = 9, - mint::authority = mint_account, - mint::freeze_authority = mint_account, - )] - pub mint_account: Account, + pub mint_account: UncheckedAccount, /// CHECK: Validate address by deriving pda #[account( @@ -52,11 +54,44 @@ pub fn handle_create_token( token_symbol: String, token_uri: String, ) -> Result<()> { - msg!("Creating metadata account"); - // PDA signer seeds let signer_seeds: &[&[&[u8]]] = &[&[b"mint", &[context.bumps.mint_account]]]; + msg!("Creating mint account"); + + // Allocate and initialize the mint, naming the mint PDA as both mint and + // freeze authority. `create_account` is signed by the PDA because the PDA + // is the account being created. + let mint_address = *context.accounts.mint_account.address(); + let lamports = Rent::get()?.try_minimum_balance(MintState::LEN)?; + create_account( + CpiContext::new( + context.accounts.system_program.address(), + CreateAccount { + from: context.accounts.payer.cpi_handle_mut(), + to: context.accounts.mint_account.cpi_handle_mut(), + }, + ) + .with_signer(signer_seeds), + lamports, + MintState::LEN as u64, + context.accounts.token_program.address(), + )?; + + initialize_mint2( + CpiContext::new( + context.accounts.token_program.address(), + InitializeMint2 { + mint: context.accounts.mint_account.cpi_handle_mut(), + }, + ), + 9, + &mint_address, + Some(&mint_address), + )?; + + msg!("Creating metadata account"); + // Cross Program Invocation (CPI) signed by PDA // Invoking the create_metadata_account_v3 instruction on the token metadata program create_metadata_accounts_v3( diff --git a/tokens/pda-mint-authority/anchor/programs/token-minter/src/instructions/mint.rs b/tokens/pda-mint-authority/anchor/programs/token-minter/src/instructions/mint.rs index a5bc342bf..05dd007bb 100644 --- a/tokens/pda-mint-authority/anchor/programs/token-minter/src/instructions/mint.rs +++ b/tokens/pda-mint-authority/anchor/programs/token-minter/src/instructions/mint.rs @@ -44,27 +44,31 @@ pub fn handle_mint_token( context: &mut Context, amount: u64, ) -> Result<()> { - // `AccountView` is Copy, and a copy still points at the same - // account — v2's typed handles make the aliasing a compile error. - let mint_account_view = *context.accounts.mint_account.account(); msg!("Minting token to associated token account..."); - msg!("Mint: {}", &context.accounts.mint_account.address()); + msg!("Mint: {}", context.accounts.mint_account.address()); msg!( "Token Address: {}", - &context.accounts.associated_token_account.address() + context.accounts.associated_token_account.address() ); // PDA signer seeds let signer_seeds: &[&[&[u8]]] = &[&[b"mint", &[context.bumps.mint_account]]]; + // The mint is its own authority, so it fills a writable slot and a + // read-only one. `CpiHandleMut` is Copy and `into_readonly()` erases it + // while carrying the wrapper's relaxed borrow flag across — which a + // handle built by hand over a copy of the `AccountView` would not. + let mint_handle = context.accounts.mint_account.cpi_handle_mut(); + let mint_authority_handle = mint_handle.into_readonly(); + // Invoke the mint_to instruction on the token program mint_to( CpiContext::new( context.accounts.token_program.address(), MintTo { - mint: context.accounts.mint_account.cpi_handle_mut(), + mint: mint_handle, to: context.accounts.associated_token_account.cpi_handle_mut(), - authority: CpiHandle::readonly(&mint_account_view), // PDA mint authority, required as signer + authority: mint_authority_handle, }, ) .with_signer(signer_seeds), // using PDA to sign diff --git a/tokens/token-extensions/cpi-guard/anchor/programs/cpi-guard/src/lib.rs b/tokens/token-extensions/cpi-guard/anchor/programs/cpi-guard/src/lib.rs index 00396949b..fa22912da 100644 --- a/tokens/token-extensions/cpi-guard/anchor/programs/cpi-guard/src/lib.rs +++ b/tokens/token-extensions/cpi-guard/anchor/programs/cpi-guard/src/lib.rs @@ -1,7 +1,12 @@ use anchor_lang::prelude::*; +use anchor_lang::system_program::{create_account, CreateAccount}; use anchor_spl::token; use anchor_spl::{ - token_2022::{transfer_checked, TransferChecked}, + token_2022::{ + initialize_account3, + spl_token_2022::{extension::ExtensionType, pod::PodAccount}, + transfer_checked, InitializeAccount3, TransferChecked, + }, token_interface::{Mint, Token2022, TokenAccount}, }; @@ -14,6 +19,45 @@ pub mod cpi_guard { use super::*; pub fn cpi_transfer(context: &mut Context) -> Result<()> { + // The recipient token account is a PDA that is its own authority. v2 + // rejects an `init` constraint naming the account being initialized + // (`token::authority` has to name a sibling field), so the account is + // created here instead — the `init_if_needed` semantics become an + // explicit "create when empty". + if context.accounts.recipient_token_account.account().data_len() == 0 { + let space = ExtensionType::try_calculate_account_len::(&[])?; + let lamports = Rent::get()?.try_minimum_balance(space)?; + let signer_seeds: &[&[&[u8]]] = + &[&[b"pda", &[context.bumps.recipient_token_account]]]; + + create_account( + CpiContext::new( + context.accounts.system_program.address(), + CreateAccount { + from: context.accounts.sender.cpi_handle_mut(), + to: context.accounts.recipient_token_account.cpi_handle_mut(), + }, + ) + .with_signer(signer_seeds), + lamports, + space as u64, + context.accounts.token_program.address(), + )?; + + let recipient_handle = context.accounts.recipient_token_account.cpi_handle_mut(); + initialize_account3( + CpiContext::new( + context.accounts.token_program.address(), + InitializeAccount3 { + account: recipient_handle, + mint: context.accounts.mint_account.cpi_handle(), + // The account is its own authority. + authority: recipient_handle.into_readonly(), + }, + ), + )?; + } + transfer_checked( CpiContext::new( context.accounts.token_program.address(), @@ -41,16 +85,14 @@ pub struct CpiTransferAccountConstraints { token::mint = mint_account )] pub sender_token_account: InterfaceAccount, + /// CHECK: created and initialized as a token account by this instruction, + /// with itself as the authority — see `cpi_transfer` above. #[account( - init_if_needed, - payer = sender, + mut, seeds = [b"pda"], bump, - token::mint = mint_account, - token::authority = recipient_token_account, - token::token_program = token_program )] - pub recipient_token_account: InterfaceAccount, + pub recipient_token_account: UncheckedAccount, pub mint_account: InterfaceAccount, pub token_program: Program, pub system_program: Program, From 191f5f30d73ec040f4e0b91dfa16189dc5d5d0e4 Mon Sep 17 00:00:00 2001 From: Claude Date: Sun, 16 Aug 2026 01:39:20 +0000 Subject: [PATCH 38/59] nft-meta-data-pointer: drop the Anchor v1-only session-keys dependency The `session-keys` crate cannot be used from v2: its `Session` derive requires `Option>`, and `SessionToken` is not `Pod`, so v2's zero-copy `Account` cannot hold it either. The token is four addresses of borsh data owned by the session-keys program, so the program reads that layout itself, checking owner, discriminator and PDA, and spells out the `#[session_auth_or]` fallback in the handler. The lesson and the security warning both survive. Also fixes two CPIs that named the token program where the system program belongs, and lines the raw invoke handle lists up with their metas: the metadata-pointer init takes only the mint, writable, and metadata initialize names the mint and the authority twice each. --- .../anchor/programs/extension_nft/Cargo.toml | 7 +- .../src/instructions/chop_tree.rs | 49 ++++++------ .../src/instructions/init_player.rs | 2 +- .../src/instructions/mint_nft.rs | 66 +++++++++------- .../anchor/programs/extension_nft/src/lib.rs | 37 +++++---- .../programs/extension_nft/src/session.rs | 78 +++++++++++++++++++ .../extension_nft/tests/test_extension_nft.rs | 8 +- 7 files changed, 174 insertions(+), 73 deletions(-) create mode 100644 tokens/token-extensions/nft-meta-data-pointer/anchor-example/anchor/programs/extension_nft/src/session.rs diff --git a/tokens/token-extensions/nft-meta-data-pointer/anchor-example/anchor/programs/extension_nft/Cargo.toml b/tokens/token-extensions/nft-meta-data-pointer/anchor-example/anchor/programs/extension_nft/Cargo.toml index 1f3c355b8..58c2d7c76 100644 --- a/tokens/token-extensions/nft-meta-data-pointer/anchor-example/anchor/programs/extension_nft/Cargo.toml +++ b/tokens/token-extensions/nft-meta-data-pointer/anchor-example/anchor/programs/extension_nft/Cargo.toml @@ -31,11 +31,12 @@ wincode = { version = "0.5", features = ["derive"] } # `SchemaRead`/`SchemaWrite` bound fails. 2.6.1 is the last 0.5-line release. solana-address = ">=2.6, <2.7" anchor-spl = { version = "2.0.0-rc.1" } -# session-keys 3.1.1 is the first release that supports Anchor >=0.28,<2.0 +# session-keys is Anchor v1 only — its `Session` derive requires +# `Option>`, which v2 has no equivalent for. The +# account layout is read directly in src/session.rs instead. # (so it builds against Anchor 1.0). Earlier 2.x releases pin Anchor <=0.30 # and fail to compile against the Anchor 1.0 / Solana 3.x API. Provides the # gasless session-token lesson via `#[session_auth_or]` / `SessionToken`. -session-keys = { version = "3.1.1", features = ["no-entrypoint"] } # Token-2022 + token-metadata access goes through anchor-spl's bundled # re-exports (`anchor_spl::token_interface::spl_token_2022`, which is # `spl-token-2022-interface`, and `anchor_spl::token_2022_extensions:: @@ -46,6 +47,8 @@ session-keys = { version = "3.1.1", features = ["no-entrypoint"] } # re-exports keeps a single, consistent type universe. [dev-dependencies] +# Test-side decode of the player account's borsh payload. +borsh = "1.6.1" litesvm = "0.13.1" solana-keypair = "3.0.1" solana-signer = "3.0.0" diff --git a/tokens/token-extensions/nft-meta-data-pointer/anchor-example/anchor/programs/extension_nft/src/instructions/chop_tree.rs b/tokens/token-extensions/nft-meta-data-pointer/anchor-example/anchor/programs/extension_nft/src/instructions/chop_tree.rs index 031bc68d6..87075ca33 100644 --- a/tokens/token-extensions/nft-meta-data-pointer/anchor-example/anchor/programs/extension_nft/src/instructions/chop_tree.rs +++ b/tokens/token-extensions/nft-meta-data-pointer/anchor-example/anchor/programs/extension_nft/src/instructions/chop_tree.rs @@ -5,21 +5,18 @@ use anchor_lang::prelude::*; use anchor_lang::solana_program::program::invoke_signed; use anchor_spl::token_2022_extensions::spl_token_metadata_interface; use anchor_spl::token_interface::{spl_token_2022, Token2022}; -use session_keys::{Session, SessionToken}; pub fn chop_tree( context: &mut Context, counter: u16, amount: u64, ) -> Result<()> { - // `AccountView` is Copy, and a copy still points at the same - // account — v2's typed handles make the aliasing a compile error. - let mint_view = *context.accounts.mint.account(); - let nft_authority_view = *context.accounts.nft_authority.account(); + let mint_address = *context.accounts.mint.address(); + let nft_authority_address = *context.accounts.nft_authority.address(); // Save game_data bump on first creation (init_if_needed). See init_player.rs // for the same pattern. let game_data_bump = context.bumps.game_data; - let account: &mut ChopTreeAccountConstraints<'_> = context.accounts; + let account = &mut context.accounts; account.player.update_energy()?; account.player.print()?; @@ -47,40 +44,48 @@ pub fn chop_tree( let signer: &[&[&[u8]]] = &[&[seeds, &[bump]]]; // Update the metadata account with an additional metadata field in this case the player level + // The handles have to line up positionally with the instruction's account + // metas: `update_field` names the metadata account (the mint, writable) and + // its update authority, in that order. + let wood = context.accounts.player.wood.to_string(); + + // `nft_authority` signs the CPI. It is a data account holding a live borrow + // on its buffer, which the runtime would reject when the CPI borrows the + // same account, so hand the borrow back across the call. + context.accounts.nft_authority.release_borrow()?; invoke_signed( &spl_token_metadata_interface::instruction::update_field( &spl_token_2022::id(), - context.accounts.mint.cpi_handle_mut().key, - context.accounts.nft_authority.cpi_handle_mut().key, + &mint_address, + &nft_authority_address, spl_token_metadata_interface::state::Field::Key("wood".to_string()), - context.accounts.player.wood.to_string(), + wood, ), &[ - CpiHandle::readonly(&mint_view).clone(), - CpiHandle::readonly(&nft_authority_view).clone(), + context.accounts.mint.cpi_handle_mut().into(), + context.accounts.nft_authority.cpi_handle(), ], signer, )?; + context.accounts.nft_authority.reacquire_borrow_mut()?; Ok(()) } -#[derive(Accounts, Session)] +#[derive(Accounts)] #[instruction(level_seed: String)] pub struct ChopTreeAccountConstraints { - #[session( - // The ephemeral key pair signing the transaction - signer = signer, - // The authority of the user account which must have created the session - authority = player.authority.address() - )] - // Session Tokens are passed as optional accounts - pub session_token: Option>, + // Session tokens are passed as optional accounts. The token is validated in + // `chop_tree` (see `session::is_valid_session`) rather than by a derive, + // since the session-keys `Session` derive is Anchor v1 only. + /// CHECK: read as gpl-session's SessionToken; validated by seeds, owner and + /// discriminator before it is trusted. + pub session_token: Option, // There is one PlayerData account #[account( mut, - seeds = [b"player".as_ref(), player.authority.address().as_ref()], + seeds = [b"player".as_ref(), player.authority.as_ref()], bump, )] pub player: BorshAccount, @@ -91,7 +96,7 @@ pub struct ChopTreeAccountConstraints { init_if_needed, payer = signer, space = GameData::DISCRIMINATOR.len() + GameData::INIT_SPACE, - seeds = [level_seed.as_ref()], + seeds = [level_seed.as_bytes()], bump, )] pub game_data: BorshAccount, diff --git a/tokens/token-extensions/nft-meta-data-pointer/anchor-example/anchor/programs/extension_nft/src/instructions/init_player.rs b/tokens/token-extensions/nft-meta-data-pointer/anchor-example/anchor/programs/extension_nft/src/instructions/init_player.rs index 6d8b9d72a..8eb75e8c7 100644 --- a/tokens/token-extensions/nft-meta-data-pointer/anchor-example/anchor/programs/extension_nft/src/instructions/init_player.rs +++ b/tokens/token-extensions/nft-meta-data-pointer/anchor-example/anchor/programs/extension_nft/src/instructions/init_player.rs @@ -34,7 +34,7 @@ pub struct InitPlayerAccountConstraints { init_if_needed, payer = signer, space = GameData::DISCRIMINATOR.len() + GameData::INIT_SPACE, - seeds = [level_seed.as_ref()], + seeds = [level_seed.as_bytes()], bump, )] pub game_data: BorshAccount, diff --git a/tokens/token-extensions/nft-meta-data-pointer/anchor-example/anchor/programs/extension_nft/src/instructions/mint_nft.rs b/tokens/token-extensions/nft-meta-data-pointer/anchor-example/anchor/programs/extension_nft/src/instructions/mint_nft.rs index 04600874d..08bb50051 100644 --- a/tokens/token-extensions/nft-meta-data-pointer/anchor-example/anchor/programs/extension_nft/src/instructions/mint_nft.rs +++ b/tokens/token-extensions/nft-meta-data-pointer/anchor-example/anchor/programs/extension_nft/src/instructions/mint_nft.rs @@ -14,11 +14,22 @@ use anchor_spl::{ }; pub fn handle_mint_nft(context: &mut Context) -> Result<()> { - // `AccountView` is Copy, and a copy still points at the same - // account — v2's typed handles make the aliasing a compile error. - let mint_view = *context.accounts.mint.account(); + // `AccountView` is Copy, and a copy still points at the same account — v2's + // typed handles make the aliasing a compile error. `mint` and `signer` are + // Signers and `nft_authority` releases its data borrow below, so none of + // these copies aliases a live borrow. + let mut mint_view = *context.accounts.mint.account(); + let mint_view_readonly = *context.accounts.mint.account(); let nft_authority_view = *context.accounts.nft_authority.account(); let signer_view = *context.accounts.signer.account(); + let mint_address = *context.accounts.mint.address(); + let nft_authority_address = *context.accounts.nft_authority.address(); + + // `nft_authority` signs every CPI below. It is a data account holding a + // live borrow on its buffer, which the runtime would reject when the CPI + // borrows the same account, so hand the borrow back for the duration. + context.accounts.nft_authority.release_borrow()?; + msg!("Mint nft with meta data extension and additional meta data"); let space = @@ -44,7 +55,7 @@ pub fn handle_mint_nft(context: &mut Context) -> Resu system_program::create_account( CpiContext::new( - context.accounts.token_program.address(), + context.accounts.system_program.address(), system_program::CreateAccount { from: context.accounts.signer.cpi_handle_mut(), to: context.accounts.mint.cpi_handle_mut(), @@ -58,7 +69,7 @@ pub fn handle_mint_nft(context: &mut Context) -> Resu // Assign the mint to the token program system_program::assign( CpiContext::new( - context.accounts.token_program.address(), + context.accounts.system_program.address(), system_program::Assign { account_to_assign: context.accounts.mint.cpi_handle_mut(), }, @@ -80,12 +91,11 @@ pub fn handle_mint_nft(context: &mut Context) -> Resu } }; + // Handles line up positionally with the instruction's metas, and a writable + // meta needs a writable handle: `initialize` names only the mint, writable. invoke( &init_meta_data_pointer_ix, - &[ - CpiHandle::readonly(&mint_view), - CpiHandle::readonly(&nft_authority_view), - ], + &[CpiHandleMut::writable(&mut mint_view).into()], )?; // Initialize the mint cpi @@ -96,12 +106,7 @@ pub fn handle_mint_nft(context: &mut Context) -> Resu }, ); - token_2022::initialize_mint2( - mint_cpi_ix, - 0, - &context.accounts.nft_authority.address(), - None, - ) + token_2022::initialize_mint2(mint_cpi_ix, 0, &nft_authority_address, None) .unwrap(); // We use a PDA as a mint authority for the metadata account because @@ -110,28 +115,29 @@ pub fn handle_mint_nft(context: &mut Context) -> Resu let bump = context.bumps.nft_authority; let signer: &[&[&[u8]]] = &[&[seeds, &[bump]]]; - msg!( - "Init metadata {0}", - context.accounts.nft_authority.cpi_handle_mut().key - ); + msg!("Init metadata {0}", nft_authority_address); // Init the metadata account let init_token_meta_data_ix = &spl_token_metadata_interface::instruction::initialize( &spl_token_2022::id(), - context.accounts.mint.key, - context.accounts.nft_authority.cpi_handle_mut().key, - context.accounts.mint.key, - context.accounts.nft_authority.cpi_handle_mut().key, + &mint_address, + &nft_authority_address, + &mint_address, + &nft_authority_address, "Beaver".to_string(), "BVA".to_string(), "https://arweave.net/MHK3Iopy0GgvDoM7LkkiAdg7pQqExuuWvedApCnzfj0".to_string(), ); + // `initialize` names metadata (the mint, writable), update_authority, mint + // and mint_authority — so the mint and the authority each fill two slots. invoke_signed( init_token_meta_data_ix, &[ - CpiHandle::readonly(&mint_view).clone(), - CpiHandle::readonly(&nft_authority_view).clone(), + CpiHandleMut::writable(&mut mint_view).into(), + CpiHandle::readonly(&nft_authority_view), + CpiHandle::readonly(&mint_view_readonly), + CpiHandle::readonly(&nft_authority_view), ], signer, )?; @@ -140,14 +146,14 @@ pub fn handle_mint_nft(context: &mut Context) -> Resu invoke_signed( &spl_token_metadata_interface::instruction::update_field( &spl_token_2022::id(), - context.accounts.mint.key, - context.accounts.nft_authority.cpi_handle_mut().key, + &mint_address, + &nft_authority_address, spl_token_metadata_interface::state::Field::Key("level".to_string()), "1".to_string(), ), &[ - CpiHandle::readonly(&mint_view).clone(), - CpiHandle::readonly(&nft_authority_view).clone(), + CpiHandleMut::writable(&mut mint_view).into(), + CpiHandle::readonly(&nft_authority_view), ], signer, )?; @@ -193,6 +199,8 @@ pub fn handle_mint_nft(context: &mut Context) -> Resu None, )?; + context.accounts.nft_authority.reacquire_borrow_mut()?; + Ok(()) } diff --git a/tokens/token-extensions/nft-meta-data-pointer/anchor-example/anchor/programs/extension_nft/src/lib.rs b/tokens/token-extensions/nft-meta-data-pointer/anchor-example/anchor/programs/extension_nft/src/lib.rs index 1ef39d0ff..a6ee48b1d 100644 --- a/tokens/token-extensions/nft-meta-data-pointer/anchor-example/anchor/programs/extension_nft/src/lib.rs +++ b/tokens/token-extensions/nft-meta-data-pointer/anchor-example/anchor/programs/extension_nft/src/lib.rs @@ -4,20 +4,20 @@ pub use crate::errors::GameErrorCode; pub use anchor_lang::prelude::*; -pub use session_keys::{session_auth_or, Session, SessionError}; pub mod constants; pub mod errors; pub mod instructions; +pub mod session; pub mod state; use instructions::*; -// WARNING: This example depends on the `session-keys` crate, which has not -// been independently audited. It is included here purely for educational -// purposes - demonstrating how a game might let a player sign with a -// short-lived session token instead of their main wallet. Do not ship this -// crate (or this program) to mainnet in its current form: review the upstream -// `session-keys` source, get an audit, and harden the session-token issuance -// and expiry handling first. +// WARNING: this example reads gpl-session's `SessionToken` account (see +// `session.rs`), and that program has not been independently audited. It is +// included here purely for educational purposes - demonstrating how a game +// might let a player sign with a short-lived session token instead of their +// main wallet. Do not ship this pattern to mainnet in its current form: review +// the upstream session-keys source, get an audit, and harden the session-token +// issuance and expiry handling first. declare_id!("9aZZ7TJ2fQZxY8hMtWXywp5y6BgqC4N2BPcr9FDT47sW"); #[program] @@ -35,18 +35,25 @@ pub mod extension_nft { // lets the player either use their session token or their main wallet. (The counter is only // there so that the player can do multiple transactions in the same block. Without it multiple transactions // in the same block would result in the same signature and therefore fail.) - // NOTE: the `#[session_auth_or]` macro injects code that refers to the - // context binding by the literal name `ctx`, so this handler's context - // parameter must be named `ctx` (not `context`) for the macro to expand. - #[session_auth_or( - ctx.accounts.player.authority.address() == ctx.accounts.signer.address(), - GameErrorCode::WrongAuthority - )] + // The session-keys `#[session_auth_or]` attribute macro is Anchor v1 only, + // so the same check is spelled out: a live session token authorizes the + // call, and otherwise the signer has to be the player's own authority. pub fn chop_tree( ctx: &mut Context, _level_seed: String, counter: u16, ) -> Result<()> { + let signer = *ctx.accounts.signer.address(); + let authority = ctx.accounts.player.authority; + let has_session = match ctx.accounts.session_token.as_ref() { + Some(token) => session::is_valid_session(token, &signer, &authority)?, + None => false, + }; + require!( + has_session || authority == signer, + GameErrorCode::WrongAuthority + ); + chop_tree::chop_tree(ctx, counter, 1) } diff --git a/tokens/token-extensions/nft-meta-data-pointer/anchor-example/anchor/programs/extension_nft/src/session.rs b/tokens/token-extensions/nft-meta-data-pointer/anchor-example/anchor/programs/extension_nft/src/session.rs new file mode 100644 index 000000000..75de1e826 --- /dev/null +++ b/tokens/token-extensions/nft-meta-data-pointer/anchor-example/anchor/programs/extension_nft/src/session.rs @@ -0,0 +1,78 @@ +//! A local reader for gpl-session's `SessionToken` account. +//! +//! The `session-keys` crate is Anchor v1 only: its `Session` derive requires a +//! field of type `Option>`, and `SessionToken` is +//! not `Pod`, so v2's zero-copy `Account` cannot hold it either. The account +//! itself is just four `Address`es worth of borsh data owned by the session-keys +//! program, so this module declares that layout, checks the discriminator and +//! PDA, and leaves the gasless-session lesson intact without the dependency. +//! +//! WARNING, unchanged from before: the session-keys program has not been +//! audited. A session token lets a player sign game transactions with a +//! short-lived key instead of their main wallet. Do not ship this pattern +//! without reviewing the session-keys source and hardening issuance. + +use anchor_lang::prelude::*; + +/// `KeyspM2ssCJbqUhQ4k7sveSiY4WjnYsrXkC8oDbwde5`, the session-keys program. +pub const SESSION_KEYS_ID: Address = + anchor_lang::address!("KeyspM2ssCJbqUhQ4k7sveSiY4WjnYsrXkC8oDbwde5"); + +/// First seed of the session-token PDA. +pub const SESSION_TOKEN_SEED: &[u8] = b"session_token"; + +/// gpl-session's `SessionToken`, as it is laid out on chain. Anchor's borsh +/// account encoding puts the eight-byte discriminator first, then the fields in +/// declaration order. +#[account(borsh)] +pub struct SessionToken { + pub authority: Address, + pub target_program: Address, + pub session_signer: Address, + pub valid_until: i64, +} + +/// Whether `session_token` is a live session for `authority`, signed by +/// `session_signer`, targeting this program. +/// +/// Returns `false` rather than erroring when the account is absent, malformed, +/// or expired, so the caller can fall back to plain wallet authorization. +pub fn is_valid_session( + session_token: &UncheckedAccount, + session_signer: &Address, + authority: &Address, +) -> Result { + if !session_token.account().owned_by(&SESSION_KEYS_ID) { + return Ok(false); + } + + let data = session_token.account().try_borrow()?; + let disc_len = ::DISCRIMINATOR.len(); + if data.len() <= disc_len + || &data[..disc_len] != ::DISCRIMINATOR + { + return Ok(false); + } + let mut payload = &data[disc_len..]; + let Ok(token) = >::get(&mut payload) + else { + return Ok(false); + }; + + // The PDA binds the token to exactly one (target_program, signer, authority) + // triple, so deriving it is what proves the token is the caller's. + let (expected, _bump) = Pubkey::find_program_address( + &[ + SESSION_TOKEN_SEED, + crate::ID.as_ref(), + session_signer.as_ref(), + authority.as_ref(), + ], + &SESSION_KEYS_ID, + ); + if expected != *session_token.address() { + return Ok(false); + } + + Ok(Clock::get()?.unix_timestamp < token.valid_until) +} diff --git a/tokens/token-extensions/nft-meta-data-pointer/anchor-example/anchor/programs/extension_nft/tests/test_extension_nft.rs b/tokens/token-extensions/nft-meta-data-pointer/anchor-example/anchor/programs/extension_nft/tests/test_extension_nft.rs index 747f3efb0..f266b551b 100644 --- a/tokens/token-extensions/nft-meta-data-pointer/anchor-example/anchor/programs/extension_nft/tests/test_extension_nft.rs +++ b/tokens/token-extensions/nft-meta-data-pointer/anchor-example/anchor/programs/extension_nft/tests/test_extension_nft.rs @@ -20,9 +20,9 @@ //! working after the id is regenerated. use { - anchor_lang::{ - prelude::Pubkey, solana_program::system_program, InstructionData, ToAccountMetas, - }, + // `system_program` moved to the crate root in v2, and `Pubkey` is + // compat-only — `Address` is the same 32-byte type. + anchor_lang::{system_program, Address as Pubkey, InstructionData, ToAccountMetas}, litesvm::LiteSVM, solana_instruction::Instruction, solana_keypair::Keypair, @@ -142,7 +142,7 @@ struct Player { } fn fetch_player(svm: &LiteSVM, player: &Pubkey) -> Player { - use anchor_lang::AnchorDeserialize; + use borsh::BorshDeserialize; let account = svm.get_account(player).expect("player account exists"); // Skip the 8-byte Anchor discriminator. let mut data = &account.data[8..]; From 6ce5ca59e0698950037766afcf6681d6a4d0e919 Mon Sep 17 00:00:00 2001 From: Claude Date: Sun, 16 Aug 2026 01:42:06 +0000 Subject: [PATCH 39/59] all: cargo fmt --- .../anchor/programs/hand/tests/test_hand.rs | 2 +- .../anchor/programs/vault-strategy/src/instructions/deposit.rs | 2 +- .../programs/vault-strategy/src/instructions/rebalance.rs | 2 +- .../anchor/programs/vault-strategy/src/state/strategy.rs | 3 +-- .../anchor/programs/vault-strategy/tests/vault_strategy.rs | 2 +- 5 files changed, 5 insertions(+), 6 deletions(-) diff --git a/basics/cross-program-invocation/anchor/programs/hand/tests/test_hand.rs b/basics/cross-program-invocation/anchor/programs/hand/tests/test_hand.rs index 9f79107c1..b6c81b4f2 100644 --- a/basics/cross-program-invocation/anchor/programs/hand/tests/test_hand.rs +++ b/basics/cross-program-invocation/anchor/programs/hand/tests/test_hand.rs @@ -1,7 +1,7 @@ use { anchor_lang::{ solana_program::instruction::{AccountMeta, Instruction}, - Address, system_program, InstructionData, ToAccountMetas, + system_program, Address, InstructionData, ToAccountMetas, }, litesvm::LiteSVM, solana_keypair::Keypair, diff --git a/finance/vault-strategy/anchor/programs/vault-strategy/src/instructions/deposit.rs b/finance/vault-strategy/anchor/programs/vault-strategy/src/instructions/deposit.rs index 74c1372e6..b2686d69c 100644 --- a/finance/vault-strategy/anchor/programs/vault-strategy/src/instructions/deposit.rs +++ b/finance/vault-strategy/anchor/programs/vault-strategy/src/instructions/deposit.rs @@ -73,7 +73,7 @@ pub struct DepositAccountConstraints { #[account( constraint = *swap_router_program.address() == strategy.swap_router @ VaultError::InvalidSwapRouter )] -/// CHECK: validated by the address constraint above + /// CHECK: validated by the address constraint above pub swap_router_program: UncheckedAccount, pub associated_token_program: Program, diff --git a/finance/vault-strategy/anchor/programs/vault-strategy/src/instructions/rebalance.rs b/finance/vault-strategy/anchor/programs/vault-strategy/src/instructions/rebalance.rs index 2234403ae..0ade7b561 100644 --- a/finance/vault-strategy/anchor/programs/vault-strategy/src/instructions/rebalance.rs +++ b/finance/vault-strategy/anchor/programs/vault-strategy/src/instructions/rebalance.rs @@ -98,7 +98,7 @@ pub struct RebalanceAccountConstraints { #[account( constraint = *swap_router_program.address() == strategy.swap_router @ VaultError::InvalidSwapRouter )] -/// CHECK: validated by the address constraint above + /// CHECK: validated by the address constraint above pub swap_router_program: UncheckedAccount, pub associated_token_program: Program, diff --git a/finance/vault-strategy/anchor/programs/vault-strategy/src/state/strategy.rs b/finance/vault-strategy/anchor/programs/vault-strategy/src/state/strategy.rs index bda2b8267..06accae27 100644 --- a/finance/vault-strategy/anchor/programs/vault-strategy/src/state/strategy.rs +++ b/finance/vault-strategy/anchor/programs/vault-strategy/src/state/strategy.rs @@ -78,8 +78,7 @@ impl AssetConfig { let disc_len = ::DISCRIMINATOR.len(); require!( data.len() > disc_len - && &data[..disc_len] - == ::DISCRIMINATOR, + && &data[..disc_len] == ::DISCRIMINATOR, crate::error::VaultError::InvalidAssetAccount ); let mut payload = &data[disc_len..]; diff --git a/finance/vault-strategy/anchor/programs/vault-strategy/tests/vault_strategy.rs b/finance/vault-strategy/anchor/programs/vault-strategy/tests/vault_strategy.rs index 0bcb98bbf..0fdf1e811 100644 --- a/finance/vault-strategy/anchor/programs/vault-strategy/tests/vault_strategy.rs +++ b/finance/vault-strategy/anchor/programs/vault-strategy/tests/vault_strategy.rs @@ -5,7 +5,6 @@ use { }, anchor_spl::token::spl_token, litesvm::LiteSVM, - solana_solana_account::Account as SolanaAccount, solana_keypair::Keypair, solana_kite::{ create_associated_token_account, create_token_mint, create_wallet, @@ -13,6 +12,7 @@ use { send_transaction_from_instructions, }, solana_signer::Signer, + solana_solana_account::Account as SolanaAccount, }; fn token_program_id() -> Address { From 7ab9cbc135a85ef90c2d4bee578f7fdd224ef13c Mon Sep 17 00:00:00 2001 From: Claude Date: Sun, 16 Aug 2026 01:42:54 +0000 Subject: [PATCH 40/59] docs: record the remaining v1-to-v2 accessor renames --- Cargo.lock | 425 ++++++++---------------------------- docs/anchor-v2-migration.md | 17 +- 2 files changed, 104 insertions(+), 338 deletions(-) diff --git a/Cargo.lock b/Cargo.lock index bacbf4b62..5b8ff3334 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -6,7 +6,7 @@ version = 4 name = "account-data-anchor-program" version = "0.1.0" dependencies = [ - "anchor-lang 2.0.0-rc.1", + "anchor-lang", "borsh 1.7.0", "litesvm", "solana-keypair", @@ -197,96 +197,13 @@ version = "0.2.21" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "683d7910e743518b0e34f1186f92494becacb047c7b6bf616c96772180fef923" -[[package]] -name = "anchor-attribute-access-control" -version = "1.1.2" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "9fc4b6b3c3f3e37a0b7d537e03a36bfb0415ee11b4443fa4190437cf26a874b6" -dependencies = [ - "proc-macro2", - "quote", - "syn 2.0.118", -] - -[[package]] -name = "anchor-attribute-account" -version = "1.1.2" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "48e8b1468add67e6a69732883e58d23a18be51538994adaae69c9b3fe967e6be" -dependencies = [ - "anchor-syn", - "proc-macro2", - "quote", - "syn 2.0.118", -] - -[[package]] -name = "anchor-attribute-constant" -version = "1.1.2" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "80e1e3ade39ba05716ddc3b792859d838cccf43f5f4913ae8a163de7a2ed7f7e" -dependencies = [ - "anchor-syn", - "quote", - "syn 2.0.118", -] - -[[package]] -name = "anchor-attribute-error" -version = "1.1.2" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "d4e026f0ff09d740fd1821bbf97d9ac649c123ff92f04b9197b002494af4acf1" -dependencies = [ - "anchor-syn", - "quote", - "syn 2.0.118", -] - -[[package]] -name = "anchor-attribute-event" -version = "1.1.2" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "7aafc8e26eb16a0c4129661f6857c42f301859fc2ee10f4e5287d571f0ea07f5" -dependencies = [ - "anchor-syn", - "proc-macro2", - "quote", - "syn 2.0.118", -] - -[[package]] -name = "anchor-attribute-program" -version = "1.1.2" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "6844e657ffe049b073389efb843bb281d08579b8a47ef294365f729b35ed3dca" -dependencies = [ - "anchor-lang-idl 0.1.4", - "anchor-syn", - "anyhow", - "heck", - "proc-macro2", - "quote", - "syn 2.0.118", -] - -[[package]] -name = "anchor-derive-accounts" -version = "1.1.2" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "2c87a6769d7cf2deed8d3351d6d1d8aa05a3a07751f1418ab6b34aaa307bd34b" -dependencies = [ - "anchor-syn", - "quote", - "syn 2.0.118", -] - [[package]] name = "anchor-derive-accounts" version = "2.0.0-rc.1" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "46a0ce0bc596146ce0a73e2422e951d153cf88bf58af957c46a52e03d3048e4c" dependencies = [ - "anchor-lang-idl 0.2.0", + "anchor-lang-idl", "bs58", "curve25519-dalek", "proc-macro2", @@ -297,83 +214,13 @@ dependencies = [ "syn 2.0.118", ] -[[package]] -name = "anchor-derive-serde" -version = "1.1.2" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "1f014d99530ae48a81f710f150688c5c542fdeee3af8998010e147a64d7a697c" -dependencies = [ - "anchor-syn", - "proc-macro-crate 3.5.0", - "proc-macro2", - "quote", - "syn 2.0.118", -] - -[[package]] -name = "anchor-derive-space" -version = "1.1.2" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "33ba30c2d844e7440c491ad5f3e25f93115c6c9319df480eda460ce96030832b" -dependencies = [ - "proc-macro2", - "quote", - "syn 2.0.118", -] - -[[package]] -name = "anchor-lang" -version = "1.1.2" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "aa6708f356398752b8d7a08fb701c9c17395e103786fb1b33615a7267a6f4774" -dependencies = [ - "anchor-attribute-access-control", - "anchor-attribute-account", - "anchor-attribute-constant", - "anchor-attribute-error", - "anchor-attribute-event", - "anchor-attribute-program", - "anchor-derive-accounts 1.1.2", - "anchor-derive-serde", - "anchor-derive-space", - "anchor-lang-error", - "anchor-lang-idl 0.1.4", - "base64 0.21.7", - "bincode", - "borsh 1.7.0", - "bytemuck", - "const-crypto", - "solana-account-info 3.1.1", - "solana-clock 3.1.1", - "solana-cpi 3.1.0", - "solana-define-syscall 3.0.0", - "solana-feature-gate-interface 3.1.0", - "solana-instruction 3.2.0", - "solana-instructions-sysvar 3.0.0", - "solana-invoke", - "solana-loader-v3-interface 6.1.0", - "solana-msg 3.1.0", - "solana-program-entrypoint 3.1.1", - "solana-program-error 3.0.1", - "solana-program-memory 3.1.0", - "solana-program-option 3.1.0", - "solana-program-pack 3.1.0", - "solana-pubkey 3.0.0", - "solana-sdk-ids 3.1.0", - "solana-stake-interface 2.0.2", - "solana-system-interface 2.0.0", - "solana-sysvar 3.1.1", - "solana-sysvar-id 3.1.0", - "thiserror 1.0.69", -] - [[package]] name = "anchor-lang" version = "2.0.0-rc.1" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "163e7ec1855744f29f116b3fa4c469947b43f17008d086e558f9b9b047fbf47d" dependencies = [ - "anchor-derive-accounts 2.0.0-rc.1", + "anchor-derive-accounts", "bytemuck", "pinocchio 0.11.2", "pinocchio-system 0.6.1", @@ -383,6 +230,7 @@ dependencies = [ "solana-instruction 3.2.0", "solana-msg 3.1.0", "solana-program-error 3.0.1", + "solana-program-log", "solana-program-memory 3.1.0", "solana-sha256-hasher 3.1.0", "solana-system-interface 2.0.0", @@ -390,34 +238,6 @@ dependencies = [ "wincode 0.5.5", ] -[[package]] -name = "anchor-lang-error" -version = "1.1.2" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "20760a8d5b7ddd1aea7a347c43c702ea4e962ccb7b9908c952e4b372b2d9e1f7" -dependencies = [ - "anchor-attribute-error", - "borsh 1.7.0", - "solana-msg 3.1.0", - "solana-program-error 3.0.1", - "solana-pubkey 3.0.0", -] - -[[package]] -name = "anchor-lang-idl" -version = "0.1.4" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "47914b4290ae2bdf4ec203aa821e6eba86d7c78ef497918938038dcc6919f953" -dependencies = [ - "anchor-lang-idl-spec", - "anyhow", - "heck", - "regex", - "serde", - "serde_json", - "sha2 0.10.9", -] - [[package]] name = "anchor-lang-idl" version = "0.2.0" @@ -446,7 +266,7 @@ dependencies = [ name = "anchor-realloc" version = "0.1.0" dependencies = [ - "anchor-lang 2.0.0-rc.1", + "anchor-lang", "borsh 1.7.0", "litesvm", "solana-keypair", @@ -457,12 +277,20 @@ dependencies = [ [[package]] name = "anchor-spl" -version = "1.1.2" +version = "2.0.0-rc.1" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "2f698a45d424351ff695df413adcb3fa603f2c791a0b4a937ebcd2e93a052c77" +checksum = "29717fb1be787b9d71b085d960c217e009787dabd16b5d0fc31118f9a6534793" dependencies = [ - "anchor-lang 1.1.2", - "spl-associated-token-account-interface", + "anchor-lang", + "borsh 1.7.0", + "bytemuck", + "pinocchio 0.11.2", + "pinocchio-token", + "pinocchio-token-2022", + "solana-address 2.6.1", + "solana-instruction 3.2.0", + "solana-program-error 3.0.1", + "solana-pubkey 3.0.0", "spl-pod", "spl-token-2022-interface", "spl-token-group-interface", @@ -470,24 +298,6 @@ dependencies = [ "spl-token-metadata-interface", ] -[[package]] -name = "anchor-syn" -version = "1.1.2" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "f8f6c61ef5b47db60700087bb6e419284b3461ba8f59040e1f6827aa9e9a5bc4" -dependencies = [ - "anyhow", - "bs58", - "cargo_toml", - "heck", - "proc-macro2", - "quote", - "serde", - "sha2 0.11.0", - "syn 2.0.118", - "thiserror 1.0.69", -] - [[package]] name = "ansi_term" version = "0.12.1" @@ -794,12 +604,6 @@ version = "0.12.3" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "3441f0f7b02788e948e47f457ca01f1d7e6d92c693bc132c22b087d3141c03ff" -[[package]] -name = "base64" -version = "0.21.7" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "9d297deb1925b89f2ccc13d7635fa0714f12c87adce1c75356b39ca9b7178567" - [[package]] name = "base64" version = "0.22.1" @@ -1076,21 +880,11 @@ version = "1.12.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "8ae3f5d315924270530207e2a68396c3cc547f6dca3fbdca317cfb1a51edb593" -[[package]] -name = "cargo_toml" -version = "0.22.3" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "374b7c592d9c00c1f4972ea58390ac6b18cbb6ab79011f3bdc90a0b82ca06b77" -dependencies = [ - "serde", - "toml 0.9.12+spec-1.1.0", -] - [[package]] name = "carnival" version = "0.1.0" dependencies = [ - "anchor-lang 2.0.0-rc.1", + "anchor-lang", "litesvm", "solana-keypair", "solana-kite", @@ -1149,7 +943,7 @@ dependencies = [ name = "checking-accounts-anchor-program-example" version = "0.1.0" dependencies = [ - "anchor-lang 2.0.0-rc.1", + "anchor-lang", "borsh 1.7.0", "litesvm", "solana-keypair", @@ -1237,7 +1031,7 @@ dependencies = [ name = "close-account-program" version = "0.1.0" dependencies = [ - "anchor-lang 2.0.0-rc.1", + "anchor-lang", "litesvm", "solana-address 2.6.1", "solana-keypair", @@ -1285,28 +1079,12 @@ dependencies = [ "web-sys", ] -[[package]] -name = "const-crypto" -version = "0.3.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "1c06f1eb05f06cf2e380fdded278fbf056a38974299d77960555a311dcf91a52" -dependencies = [ - "keccak-const", - "sha2-const-stable", -] - [[package]] name = "const-oid" version = "0.9.6" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "c2459377285ad874054d797f3ccebf984978aa39129f6eafde5cdc8315b612f8" -[[package]] -name = "const-oid" -version = "0.10.2" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "a6ef517f0926dd24a1582492c791b6a4818a4d94e789a334894aa15b0d12f55c" - [[package]] name = "constant_time_eq" version = "0.4.2" @@ -1351,7 +1129,7 @@ dependencies = [ name = "counter_anchor" version = "0.1.0" dependencies = [ - "anchor-lang 2.0.0-rc.1", + "anchor-lang", "borsh 1.7.0", "litesvm", "solana-keypair", @@ -1426,7 +1204,7 @@ dependencies = [ name = "create-system-account" version = "0.1.0" dependencies = [ - "anchor-lang 2.0.0-rc.1", + "anchor-lang", "litesvm", "solana-keypair", "solana-kite", @@ -1601,7 +1379,7 @@ version = "0.7.10" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "e7c1832837b905bbfb5101e07cc24c8deddf52f93225eee6ead5f4d63d53ddcb" dependencies = [ - "const-oid 0.9.6", + "const-oid", "zeroize", ] @@ -1638,7 +1416,7 @@ source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "9ed9a281f7bc9b7576e61468ba615a66a5c8cfdff42420a70aa82701a3b1e292" dependencies = [ "block-buffer 0.10.4", - "const-oid 0.9.6", + "const-oid", "crypto-common 0.1.7", "subtle", ] @@ -1650,7 +1428,6 @@ source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "f1dd6dbb5841937940781866fa1281a1ff7bd3bf827091440879f9994983d5c2" dependencies = [ "block-buffer 0.12.1", - "const-oid 0.10.2", "crypto-common 0.2.2", "ctutils", ] @@ -1999,7 +1776,7 @@ dependencies = [ name = "hand" version = "0.1.0" dependencies = [ - "anchor-lang 2.0.0-rc.1", + "anchor-lang", "litesvm", "solana-keypair", "solana-kite", @@ -2053,7 +1830,7 @@ dependencies = [ name = "hello-solana-anchor" version = "0.1.0" dependencies = [ - "anchor-lang 2.0.0-rc.1", + "anchor-lang", "litesvm", "solana-kite", "solana-signer", @@ -2226,12 +2003,6 @@ dependencies = [ "cpufeatures 0.2.17", ] -[[package]] -name = "keccak-const" -version = "0.2.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "57d8d8ce877200136358e0bbff3a77965875db3af755a11e1fa6b1b3e2df13ea" - [[package]] name = "lazy_static" version = "1.5.0" @@ -2242,7 +2013,7 @@ checksum = "bbd2bcb4c963f2ddae06a2efc7e9f3591312473c50c6685e1f298068316e66fe" name = "lever" version = "0.1.0" dependencies = [ - "anchor-lang 2.0.0-rc.1", + "anchor-lang", "litesvm", "solana-keypair", "solana-kite", @@ -2438,13 +2209,15 @@ dependencies = [ name = "mock-swap-router" version = "0.1.0" dependencies = [ - "anchor-lang 1.1.2", + "anchor-lang", "anchor-spl", "borsh 1.7.0", "litesvm", + "solana-address 2.6.1", "solana-keypair", "solana-kite", "solana-signer", + "wincode 0.5.5", ] [[package]] @@ -2709,7 +2482,7 @@ dependencies = [ name = "pda-rent-payer" version = "0.1.0" dependencies = [ - "anchor-lang 2.0.0-rc.1", + "anchor-lang", "litesvm", "solana-keypair", "solana-kite", @@ -2854,6 +2627,30 @@ dependencies = [ "solana-address 2.6.1", ] +[[package]] +name = "pinocchio-token" +version = "0.6.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "825f59c8348e5c2d3fd56432ef927f5819542b3b05fae4f5b6869801113e775e" +dependencies = [ + "solana-account-view 2.0.0", + "solana-address 2.6.1", + "solana-instruction-view 2.1.0", + "solana-program-error 3.0.1", +] + +[[package]] +name = "pinocchio-token-2022" +version = "0.3.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "57dbab5718ab6ae446b2217c47598fabd29173a09bd32b05344f031739db92b8" +dependencies = [ + "solana-account-view 2.0.0", + "solana-address 2.6.1", + "solana-instruction-view 2.1.0", + "solana-program-error 3.0.1", +] + [[package]] name = "pkcs8" version = "0.10.2" @@ -2891,7 +2688,7 @@ version = "0.1.5" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "1d6ea3c4595b96363c13943497db34af4460fb474a95c43f4446ad341b8c9785" dependencies = [ - "toml 0.5.11", + "toml", ] [[package]] @@ -2916,7 +2713,7 @@ dependencies = [ name = "processing-instructions" version = "0.1.0" dependencies = [ - "anchor-lang 2.0.0-rc.1", + "anchor-lang", "litesvm", "solana-keypair", "solana-kite", @@ -3007,7 +2804,7 @@ dependencies = [ name = "program-derived-addresses-program" version = "0.1.0" dependencies = [ - "anchor-lang 2.0.0-rc.1", + "anchor-lang", "borsh 1.7.0", "litesvm", "solana-keypair", @@ -3240,7 +3037,7 @@ checksum = "d6f6ff9a378485b298a5286656da665ba74413d36db0979633275d2e708145d4" name = "rent-example" version = "0.1.0" dependencies = [ - "anchor-lang 2.0.0-rc.1", + "anchor-lang", "litesvm", "solana-keypair", "solana-kite", @@ -3413,15 +3210,6 @@ dependencies = [ "zmij", ] -[[package]] -name = "serde_spanned" -version = "1.1.1" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "6662b5879511e06e8999a8a235d848113e942c9124f211511b16466ee2995f26" -dependencies = [ - "serde_core", -] - [[package]] name = "serde_with" version = "3.21.0" @@ -3468,17 +3256,6 @@ dependencies = [ "digest 0.10.7", ] -[[package]] -name = "sha2" -version = "0.11.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "446ba717509524cb3f22f17ecc096f10f4822d76ab5c0b9822c5f9c284e825f4" -dependencies = [ - "cfg-if", - "cpufeatures 0.3.0", - "digest 0.11.3", -] - [[package]] name = "sha2-const-stable" version = "0.1.0" @@ -4411,19 +4188,6 @@ dependencies = [ "solana-sysvar-id 3.1.0", ] -[[package]] -name = "solana-invoke" -version = "0.5.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "4065031f5c7dd29ef5f5003c1a353011eeabbafa6c5a5033da0cedbfca824b94" -dependencies = [ - "solana-account-info 3.1.1", - "solana-define-syscall 3.0.0", - "solana-instruction 3.2.0", - "solana-program-entrypoint 3.1.1", - "solana-stable-layout 3.0.1", -] - [[package]] name = "solana-keccak-hasher" version = "2.2.1" @@ -4550,7 +4314,6 @@ dependencies = [ "solana-instruction 3.2.0", "solana-pubkey 3.0.0", "solana-sdk-ids 3.1.0", - "solana-system-interface 2.0.0", ] [[package]] @@ -4993,6 +4756,27 @@ dependencies = [ "serde_derive", ] +[[package]] +name = "solana-program-log" +version = "1.2.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "ffd48ddef444e6db138fb11bdb9ab8117bb830a78cef051a453454de5613039b" +dependencies = [ + "solana-define-syscall 5.1.0", + "solana-program-log-macro", +] + +[[package]] +name = "solana-program-log-macro" +version = "1.1.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "73b234f83b71ba6b5d3236b69ba42ccb639907cb724bf58e94ce8398b51ac87c" +dependencies = [ + "quote", + "regex", + "syn 2.0.118", +] + [[package]] name = "solana-program-memory" version = "2.3.1" @@ -6504,30 +6288,6 @@ dependencies = [ "serde", ] -[[package]] -name = "toml" -version = "0.9.12+spec-1.1.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "cf92845e79fc2e2def6a5d828f0801e29a2f8acc037becc5ab08595c7d5e9863" -dependencies = [ - "indexmap", - "serde_core", - "serde_spanned", - "toml_datetime 0.7.5+spec-1.1.0", - "toml_parser", - "toml_writer", - "winnow 0.7.15", -] - -[[package]] -name = "toml_datetime" -version = "0.7.5+spec-1.1.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "92e1cfed4a3038bc5a127e35a2d360f145e1f4b971b551a2ba5fd7aedf7e1347" -dependencies = [ - "serde_core", -] - [[package]] name = "toml_datetime" version = "1.1.1+spec-1.1.0" @@ -6544,9 +6304,9 @@ source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "d2153edc6955a6c354fad8f5efd38b6a8769bdccf9fe50f8e1329f81b0baa5d7" dependencies = [ "indexmap", - "toml_datetime 1.1.1+spec-1.1.0", + "toml_datetime", "toml_parser", - "winnow 1.0.3", + "winnow", ] [[package]] @@ -6555,20 +6315,14 @@ version = "1.1.2+spec-1.1.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "a2abe9b86193656635d2411dc43050282ca48aa31c2451210f4202550afb7526" dependencies = [ - "winnow 1.0.3", + "winnow", ] -[[package]] -name = "toml_writer" -version = "1.1.1+spec-1.1.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "756daf9b1013ebe47a8776667b466417e2d4c5679d441c26230efd9ef78692db" - [[package]] name = "transfer-sol" version = "0.1.0" dependencies = [ - "anchor-lang 2.0.0-rc.1", + "anchor-lang", "litesvm", "solana-keypair", "solana-kite", @@ -6678,15 +6432,18 @@ dependencies = [ name = "vault-strategy" version = "0.1.0" dependencies = [ - "anchor-lang 1.1.2", + "anchor-lang", "anchor-spl", "borsh 1.7.0", "litesvm", "mock-swap-router", "solana-account 3.4.0", + "solana-address 2.6.1", + "solana-clock 3.1.1", "solana-keypair", "solana-kite", "solana-signer", + "wincode 0.5.5", ] [[package]] @@ -6843,12 +6600,6 @@ version = "0.2.1" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "f0805222e57f7521d6a62e36fa9163bc891acd422f971defe97d64e70d0a4fe5" -[[package]] -name = "winnow" -version = "0.7.15" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "df79d97927682d2fd8adb29682d1140b343be4ac0f08fd68b7765d9c059d3945" - [[package]] name = "winnow" version = "1.0.3" diff --git a/docs/anchor-v2-migration.md b/docs/anchor-v2-migration.md index 77749d0a5..6c3a74419 100644 --- a/docs/anchor-v2-migration.md +++ b/docs/anchor-v2-migration.md @@ -56,6 +56,10 @@ older 1.x already in the graph, which does not fix anything. | `#[account(zero_copy(unsafe))]` | `#[account]` — already zero-copy | | `AccountLoader<'info, T>` + `load()` | `Account`, which derefs straight to `T` | | `set_inner(X { .. })` | `*ctx.accounts.foo = X { .. }` | +| `account.data.borrow()` | `account.account().try_borrow()?` | +| `account.reload()?` | `account.revalidate_after_cpi()?` — zero-copy reads are already live | +| `Account::::try_from(view)` | `AnchorAccount::load(view)`, or `load_mut` (unsafe) to write back | +| `.exit(program_id)` | `.exit()` | | `error!(MyError::X)` | `MyError::X` (compat-only macro; `?` converts, tail position needs `.into()`) | | `#[account(has_one = x)]` on the owner | `#[account(address = owner.x)]` on the **sibling** field | @@ -94,7 +98,18 @@ tests keep working. Only keep zero-copy where the program deliberately wants it (a large slab, say) — converting such a program to borsh defeats its purpose. v2 has no `Account::try_from(&AccountView)`. To load a borsh account out of -`remaining_accounts`, check the discriminator and read the payload yourself: +`remaining_accounts` **for writing**, use `AnchorAccount::load_mut`, which is +`unsafe` because the caller has to guarantee no other live `&mut` to the same +data; `exit()` then writes it back: + +```rust +let mut order = unsafe { BorshAccount::::load_mut(*view) }?; +order.filled_quantity += fill; +order.exit()?; +``` + +To read one without taking ownership of the write path, check the discriminator +and decode the payload yourself: ```rust let data = account.try_borrow()?; From 3afdf147f4df9ad6f3e6ac2a3d9bc1e1d7db120b Mon Sep 17 00:00:00 2001 From: Claude Date: Sun, 16 Aug 2026 01:43:30 +0000 Subject: [PATCH 41/59] changelog: record the full Anchor v2 migration --- CHANGELOG.md | 45 +++++++++++++++++++++++++++++++++++++++++++++ 1 file changed, 45 insertions(+) diff --git a/CHANGELOG.md b/CHANGELOG.md index 7a9c1a925..2ecf8500c 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -4,6 +4,51 @@ 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-08-16] - Every Anchor example on Anchor v2.0.0-rc.1 + +### Changed + +- The remaining 39 Anchor examples — all of `tokens/`, `finance/` and + `compression/` — now build against `anchor-lang` 2.0.0-rc.1, joining the + `basics/` examples ported below. `.github/workflows/anchor.yml` installs + 2.0.0-rc.1, since `anchor build` under a v2 CLI will not build v1 programs. +- `docs/anchor-v2-migration.md` collects every difference the port ran into, + ordered by how often it bites. The rules the compiler will not catch for you + are called out: borrows held across CPIs, `Box`'s missing `cpi_handle_mut` + forwarding, and hand-built read-only handles over a live data account. +- `has_one` is deprecated in v2 and this repository's `rust.yml` runs + `cargo clippy -- -D warnings`, so all 152 uses across 59 files move to the + `address` constraint on the sibling field they named. +- The seven `transfer-hook` examples supply their own entrypoint. v2's + `#[program(interface, ...)]` generates a CPI client and no dispatch — a + program declared that way builds to a ~900-byte object with no `entrypoint` + symbol — while an executable `#[program]` limits custom discriminators to one + byte, which the transfer-hook interface's eight-byte values cannot use. Each + crate now builds with `no-entrypoint`, so anchor exports its dispatch as + `__anchor_dispatch`, and `src/entrypoint.rs` maps the interface + discriminators onto handlers before delegating. +- `tokens/pda-mint-authority` and `tokens/token-extensions/cpi-guard` build + their PDA by hand (`create_account` plus `initialize_mint2` / + `initialize_account3`). Both examples exist to show an account that is its own + authority, and a v2 SPL `init` constraint cannot name the account being + initialized. +- `finance/order-book` keeps its ~180 KB zero-copy critbit book zero-copy: v2's + `Account` derefs straight to `T`, so `load_init` / `load_mut` simply go + away rather than the state converting to borsh. +- Tests that asserted on an Anchor error *name* now assert on the numeric custom + code (the `#[error_code]` discriminant plus the default 6000 offset). v2 does + not log variant names, so the old assertions could never match. + +### Removed + +- `tokens/token-extensions/nft-meta-data-pointer` no longer depends on + `session-keys`. That crate is Anchor v1 only — its `Session` derive requires + `Option>`, and `SessionToken` is not `Pod`, so + v2's zero-copy `Account` cannot hold it either. The program reads the + session-token account layout itself (`src/session.rs`), checking owner, + discriminator and PDA, and spells out the `#[session_auth_or]` fallback in the + handler, so the gasless-session lesson and its security warning both survive. + ## [2026-08-13] - Anchor examples in `basics/` move to Anchor v2.0.0-rc.1 ### Changed From 4e496a4cdce86b7612c177a2f754c52ba21923e0 Mon Sep 17 00:00:00 2001 From: Claude Date: Sun, 16 Aug 2026 01:45:44 +0000 Subject: [PATCH 42/59] lint: fix the clippy errors CI would fail on `useless_conversion` on `ProgramError::X.into()`, in v2 the error type is already a ProgramError, and the parentheses left behind where `set_inner(X { .. })` became `*ctx.accounts.foo = X { .. }`. --- .../anchor/programs/carnival/src/instructions/eat_food.rs | 2 +- .../anchor/programs/carnival/src/instructions/get_on_ride.rs | 2 +- .../anchor/programs/carnival/src/instructions/play_game.rs | 2 +- .../programs/betting-market/src/instructions/add_outcome.rs | 4 ++-- .../betting-market/src/instructions/initialize_config.rs | 4 ++-- .../betting-market/src/instructions/initialize_event.rs | 4 ++-- .../anchor/programs/escrow/src/instructions/make_offer.rs | 4 ++-- .../fundraiser/src/instructions/initialize_fundraiser.rs | 4 ++-- .../mock-swap-router/src/instructions/initialize_router.rs | 4 ++-- .../programs/mock-swap-router/src/instructions/set_rate.rs | 4 ++-- .../programs/vault-strategy/src/instructions/add_asset.rs | 4 ++-- .../programs/vault-strategy/src/instructions/approve_asset.rs | 4 ++-- .../vault-strategy/src/instructions/initialize_registry.rs | 4 ++-- .../vault-strategy/src/instructions/initialize_strategy.rs | 4 ++-- .../transfer-switch/src/instructions/configure_admin.rs | 4 ++-- .../programs/transfer-switch/src/instructions/switch.rs | 4 ++-- 16 files changed, 29 insertions(+), 29 deletions(-) diff --git a/basics/repository-layout/anchor/programs/carnival/src/instructions/eat_food.rs b/basics/repository-layout/anchor/programs/carnival/src/instructions/eat_food.rs index 2e3a62a06..555c6623a 100644 --- a/basics/repository-layout/anchor/programs/carnival/src/instructions/eat_food.rs +++ b/basics/repository-layout/anchor/programs/carnival/src/instructions/eat_food.rs @@ -34,5 +34,5 @@ pub fn eat_food(ix: EatFoodInstructionData) -> Result<()> { } } - Err(ProgramError::InvalidInstructionData.into()) + Err(ProgramError::InvalidInstructionData) } diff --git a/basics/repository-layout/anchor/programs/carnival/src/instructions/get_on_ride.rs b/basics/repository-layout/anchor/programs/carnival/src/instructions/get_on_ride.rs index 0427f0c36..43fb2f10b 100644 --- a/basics/repository-layout/anchor/programs/carnival/src/instructions/get_on_ride.rs +++ b/basics/repository-layout/anchor/programs/carnival/src/instructions/get_on_ride.rs @@ -50,5 +50,5 @@ pub fn get_on_ride(ix: GetOnRideInstructionData) -> Result<()> { } } - Err(ProgramError::InvalidInstructionData.into()) + Err(ProgramError::InvalidInstructionData) } diff --git a/basics/repository-layout/anchor/programs/carnival/src/instructions/play_game.rs b/basics/repository-layout/anchor/programs/carnival/src/instructions/play_game.rs index 4867f98e8..5613fc778 100644 --- a/basics/repository-layout/anchor/programs/carnival/src/instructions/play_game.rs +++ b/basics/repository-layout/anchor/programs/carnival/src/instructions/play_game.rs @@ -40,5 +40,5 @@ pub fn play_game(ix: PlayGameInstructionData) -> Result<()> { } } - Err(ProgramError::InvalidInstructionData.into()) + Err(ProgramError::InvalidInstructionData) } diff --git a/finance/betting-market/anchor/programs/betting-market/src/instructions/add_outcome.rs b/finance/betting-market/anchor/programs/betting-market/src/instructions/add_outcome.rs index a97b5fcb1..aa6e4b653 100644 --- a/finance/betting-market/anchor/programs/betting-market/src/instructions/add_outcome.rs +++ b/finance/betting-market/anchor/programs/betting-market/src/instructions/add_outcome.rs @@ -51,14 +51,14 @@ pub fn handle_add_outcome( ); let index = context.accounts.event.outcome_count; - *context.accounts.outcome = (Outcome { + *context.accounts.outcome = Outcome { event: *context.accounts.event.address(), index, label, total_amount: 0, bet_count: 0, bump: context.bumps.outcome, - }); + }; context.accounts.event.outcome_count += 1; Ok(()) diff --git a/finance/betting-market/anchor/programs/betting-market/src/instructions/initialize_config.rs b/finance/betting-market/anchor/programs/betting-market/src/instructions/initialize_config.rs index f334f1d44..fbd0b4fd3 100644 --- a/finance/betting-market/anchor/programs/betting-market/src/instructions/initialize_config.rs +++ b/finance/betting-market/anchor/programs/betting-market/src/instructions/initialize_config.rs @@ -34,13 +34,13 @@ pub fn handle_initialize_config( ) -> Result<()> { require!(fee_bps <= MAX_FEE_BPS, BettingError::FeeTooHigh); - *context.accounts.config = (Config { + *context.accounts.config = Config { admin: *context.accounts.admin.address(), token_mint: *context.accounts.token_mint.address(), fee_recipient, fee_bps, event_count: 0, bump: context.bumps.config, - }); + }; Ok(()) } diff --git a/finance/betting-market/anchor/programs/betting-market/src/instructions/initialize_event.rs b/finance/betting-market/anchor/programs/betting-market/src/instructions/initialize_event.rs index 3964a0fe7..427bbb861 100644 --- a/finance/betting-market/anchor/programs/betting-market/src/instructions/initialize_event.rs +++ b/finance/betting-market/anchor/programs/betting-market/src/instructions/initialize_event.rs @@ -59,7 +59,7 @@ pub fn handle_initialize_event( BettingError::DescriptionTooLong ); - *context.accounts.event = (Event { + *context.accounts.event = Event { event_id, description, outcome_count: 0, @@ -70,7 +70,7 @@ pub fn handle_initialize_event( winning_pool: 0, distributable_losing_pool: 0, bump: context.bumps.event, - }); + }; context.accounts.config.event_count += 1; Ok(()) diff --git a/finance/escrow/anchor/programs/escrow/src/instructions/make_offer.rs b/finance/escrow/anchor/programs/escrow/src/instructions/make_offer.rs index 297b37b11..0566d66fe 100644 --- a/finance/escrow/anchor/programs/escrow/src/instructions/make_offer.rs +++ b/finance/escrow/anchor/programs/escrow/src/instructions/make_offer.rs @@ -88,13 +88,13 @@ pub fn handle_save_offer( id: u64, token_b_wanted_amount: u64, ) -> Result<()> { - *context.accounts.offer = (Offer { + *context.accounts.offer = Offer { id, maker: *context.accounts.maker.address(), token_mint_a: *context.accounts.token_mint_a.address(), token_mint_b: *context.accounts.token_mint_b.address(), token_b_wanted_amount, bump: context.bumps.offer, - }); + }; Ok(()) } diff --git a/finance/token-fundraiser/anchor/programs/fundraiser/src/instructions/initialize_fundraiser.rs b/finance/token-fundraiser/anchor/programs/fundraiser/src/instructions/initialize_fundraiser.rs index 125bf1a38..0f7adf7ce 100644 --- a/finance/token-fundraiser/anchor/programs/fundraiser/src/instructions/initialize_fundraiser.rs +++ b/finance/token-fundraiser/anchor/programs/fundraiser/src/instructions/initialize_fundraiser.rs @@ -57,7 +57,7 @@ pub fn handle_initialize_fundraiser( FundraiserError::InvalidAmount ); - *accounts.fundraiser = (Fundraiser { + *accounts.fundraiser = Fundraiser { maker: *accounts.maker.address(), mint_to_raise: *accounts.mint_to_raise.address(), amount_to_raise: amount, @@ -65,7 +65,7 @@ pub fn handle_initialize_fundraiser( time_started: Clock::get()?.unix_timestamp, duration, bump: bumps.fundraiser, - }); + }; Ok(()) } diff --git a/finance/vault-strategy/anchor/programs/mock-swap-router/src/instructions/initialize_router.rs b/finance/vault-strategy/anchor/programs/mock-swap-router/src/instructions/initialize_router.rs index 3c6856984..a6d24f719 100644 --- a/finance/vault-strategy/anchor/programs/mock-swap-router/src/instructions/initialize_router.rs +++ b/finance/vault-strategy/anchor/programs/mock-swap-router/src/instructions/initialize_router.rs @@ -34,10 +34,10 @@ pub fn handle_initialize_router( context: &mut Context, _usdc_mint: Address, ) -> Result<()> { - *context.accounts.router_config = (RouterConfig { + *context.accounts.router_config = RouterConfig { authority: *context.accounts.authority.address(), usdc_mint: *context.accounts.usdc_mint.address(), bump: context.bumps.router_config, - }); + }; Ok(()) } diff --git a/finance/vault-strategy/anchor/programs/mock-swap-router/src/instructions/set_rate.rs b/finance/vault-strategy/anchor/programs/mock-swap-router/src/instructions/set_rate.rs index 49e7914f1..2de07b58b 100644 --- a/finance/vault-strategy/anchor/programs/mock-swap-router/src/instructions/set_rate.rs +++ b/finance/vault-strategy/anchor/programs/mock-swap-router/src/instructions/set_rate.rs @@ -54,10 +54,10 @@ pub fn handle_set_rate( _mint: Address, usdc_per_token: u64, ) -> Result<()> { - *context.accounts.asset_rate = (AssetRate { + *context.accounts.asset_rate = AssetRate { mint: *context.accounts.asset_mint.address(), usdc_per_token, bump: context.bumps.asset_rate, - }); + }; Ok(()) } diff --git a/finance/vault-strategy/anchor/programs/vault-strategy/src/instructions/add_asset.rs b/finance/vault-strategy/anchor/programs/vault-strategy/src/instructions/add_asset.rs index 5edad14ee..6f9c9ed34 100644 --- a/finance/vault-strategy/anchor/programs/vault-strategy/src/instructions/add_asset.rs +++ b/finance/vault-strategy/anchor/programs/vault-strategy/src/instructions/add_asset.rs @@ -71,7 +71,7 @@ pub fn handle_add_asset( let index = strategy.asset_count; - **context.accounts.asset_config = (AssetConfig { + **context.accounts.asset_config = AssetConfig { strategy: *strategy.address(), index, mint: *context.accounts.asset_mint.address(), @@ -80,7 +80,7 @@ pub fn handle_add_asset( vault: *context.accounts.vault_asset.address(), weight_bps, bump: context.bumps.asset_config, - }); + }; strategy.asset_count = index.checked_add(1).ok_or(VaultError::MathOverflow)?; strategy.total_weight_bps = new_total as u16; diff --git a/finance/vault-strategy/anchor/programs/vault-strategy/src/instructions/approve_asset.rs b/finance/vault-strategy/anchor/programs/vault-strategy/src/instructions/approve_asset.rs index 4aab249b6..0ec135e6c 100644 --- a/finance/vault-strategy/anchor/programs/vault-strategy/src/instructions/approve_asset.rs +++ b/finance/vault-strategy/anchor/programs/vault-strategy/src/instructions/approve_asset.rs @@ -32,11 +32,11 @@ pub fn handle_approve_asset( context: &mut Context, price_feed: Address, ) -> Result<()> { - *context.accounts.approved_asset = (ApprovedAsset { + *context.accounts.approved_asset = ApprovedAsset { registry: *context.accounts.registry.address(), mint: *context.accounts.asset_mint.address(), price_feed, bump: context.bumps.approved_asset, - }); + }; Ok(()) } diff --git a/finance/vault-strategy/anchor/programs/vault-strategy/src/instructions/initialize_registry.rs b/finance/vault-strategy/anchor/programs/vault-strategy/src/instructions/initialize_registry.rs index bf0bfce7d..9c814c566 100644 --- a/finance/vault-strategy/anchor/programs/vault-strategy/src/instructions/initialize_registry.rs +++ b/finance/vault-strategy/anchor/programs/vault-strategy/src/instructions/initialize_registry.rs @@ -22,9 +22,9 @@ pub struct InitializeRegistryAccountConstraints { pub fn handle_initialize_registry( context: &mut Context, ) -> Result<()> { - *context.accounts.registry = (Registry { + *context.accounts.registry = Registry { authority: *context.accounts.authority.address(), bump: context.bumps.registry, - }); + }; Ok(()) } diff --git a/finance/vault-strategy/anchor/programs/vault-strategy/src/instructions/initialize_strategy.rs b/finance/vault-strategy/anchor/programs/vault-strategy/src/instructions/initialize_strategy.rs index 712f23aae..cee516f63 100644 --- a/finance/vault-strategy/anchor/programs/vault-strategy/src/instructions/initialize_strategy.rs +++ b/finance/vault-strategy/anchor/programs/vault-strategy/src/instructions/initialize_strategy.rs @@ -82,7 +82,7 @@ pub fn handle_initialize_strategy( let clock = Clock::get()?; - **context.accounts.strategy = (Strategy { + **context.accounts.strategy = Strategy { index, manager: *context.accounts.manager.address(), registry: *context.accounts.registry.address(), @@ -96,7 +96,7 @@ pub fn handle_initialize_strategy( asset_count: 0, total_weight_bps: 0, bump: context.bumps.strategy, - }); + }; Ok(()) } diff --git a/tokens/token-extensions/transfer-hook/transfer-switch/anchor/programs/transfer-switch/src/instructions/configure_admin.rs b/tokens/token-extensions/transfer-hook/transfer-switch/anchor/programs/transfer-switch/src/instructions/configure_admin.rs index a7fa22fcf..e00ede2ff 100644 --- a/tokens/token-extensions/transfer-hook/transfer-switch/anchor/programs/transfer-switch/src/instructions/configure_admin.rs +++ b/tokens/token-extensions/transfer-hook/transfer-switch/anchor/programs/transfer-switch/src/instructions/configure_admin.rs @@ -47,10 +47,10 @@ pub fn handle_configure_admin( accounts: &mut ConfigureAdminAccountConstraints, bump: u8, ) -> Result<()> { - *accounts.admin_config = (AdminConfig { + *accounts.admin_config = AdminConfig { admin: *accounts.new_admin.address(), // set the admin pubkey that can switch transfers on/off is_initialised: true, // let us know an admin has been set bump, // canonical bump for the admin-config PDA - }); + }; Ok(()) } diff --git a/tokens/token-extensions/transfer-hook/transfer-switch/anchor/programs/transfer-switch/src/instructions/switch.rs b/tokens/token-extensions/transfer-hook/transfer-switch/anchor/programs/transfer-switch/src/instructions/switch.rs index e3b4a586a..15bd1838b 100644 --- a/tokens/token-extensions/transfer-hook/transfer-switch/anchor/programs/transfer-switch/src/instructions/switch.rs +++ b/tokens/token-extensions/transfer-hook/transfer-switch/anchor/programs/transfer-switch/src/instructions/switch.rs @@ -34,11 +34,11 @@ pub struct SwitchAccountConstraints { pub fn handle_switch(accounts: &mut SwitchAccountConstraints, on: bool, bump: u8) -> Result<()> { // toggle switch on/off for the given wallet // - *accounts.wallet_switch = (TransferSwitch { + *accounts.wallet_switch = TransferSwitch { wallet: *accounts.wallet.address(), on, bump, // canonical bump for this wallet's PDA - }); + }; Ok(()) } From 9890b42474b271a601c24e00ea5962e0fa902b53 Mon Sep 17 00:00:00 2001 From: Claude Date: Sun, 16 Aug 2026 01:46:43 +0000 Subject: [PATCH 43/59] lint: silence the last clippy errors Unused `#[instruction(...)]` bindings take an underscore, and two borrows that the compiler immediately dereferences are dropped. --- .../programs/anchor-realloc/src/instructions/initialize.rs | 2 +- .../anchor/programs/anchor-realloc/src/instructions/update.rs | 2 +- .../anchor/programs/vault-strategy/src/instructions/deposit.rs | 2 +- .../vault-strategy/src/instructions/initialize_strategy.rs | 2 +- .../anchor/programs/vault-strategy/src/instructions/withdraw.rs | 2 +- 5 files changed, 5 insertions(+), 5 deletions(-) diff --git a/basics/realloc/anchor/programs/anchor-realloc/src/instructions/initialize.rs b/basics/realloc/anchor/programs/anchor-realloc/src/instructions/initialize.rs index 35040fda1..843b8b2b0 100644 --- a/basics/realloc/anchor/programs/anchor-realloc/src/instructions/initialize.rs +++ b/basics/realloc/anchor/programs/anchor-realloc/src/instructions/initialize.rs @@ -3,7 +3,7 @@ use anchor_lang::prelude::*; use crate::Message; #[derive(Accounts)] -#[instruction(input: String)] +#[instruction(_input: String)] pub struct InitializeAccountConstraints { #[account(mut)] pub payer: Signer, diff --git a/basics/realloc/anchor/programs/anchor-realloc/src/instructions/update.rs b/basics/realloc/anchor/programs/anchor-realloc/src/instructions/update.rs index a1e09511e..f11fe5adc 100644 --- a/basics/realloc/anchor/programs/anchor-realloc/src/instructions/update.rs +++ b/basics/realloc/anchor/programs/anchor-realloc/src/instructions/update.rs @@ -3,7 +3,7 @@ use anchor_lang::prelude::*; use crate::Message; #[derive(Accounts)] -#[instruction(input: String)] +#[instruction(_input: String)] pub struct UpdateAccountConstraints { #[account(mut)] pub payer: Signer, diff --git a/finance/vault-strategy/anchor/programs/vault-strategy/src/instructions/deposit.rs b/finance/vault-strategy/anchor/programs/vault-strategy/src/instructions/deposit.rs index b2686d69c..4bc009eba 100644 --- a/finance/vault-strategy/anchor/programs/vault-strategy/src/instructions/deposit.rs +++ b/finance/vault-strategy/anchor/programs/vault-strategy/src/instructions/deposit.rs @@ -236,7 +236,7 @@ pub fn handle_deposit( let cpi_accounts = RouterSwapAccounts { caller: context.accounts.strategy.cpi_handle(), router_config: context.accounts.router_config.cpi_handle(), - asset_rate: CpiHandle::readonly(&rate_account), + asset_rate: CpiHandle::readonly(rate_account), usdc_mint: context.accounts.usdc_mint.cpi_handle(), asset_mint: CpiHandleMut::writable(&mut mint_account), caller_usdc_account: context.accounts.vault_usdc.cpi_handle_mut(), diff --git a/finance/vault-strategy/anchor/programs/vault-strategy/src/instructions/initialize_strategy.rs b/finance/vault-strategy/anchor/programs/vault-strategy/src/instructions/initialize_strategy.rs index cee516f63..cb24fb902 100644 --- a/finance/vault-strategy/anchor/programs/vault-strategy/src/instructions/initialize_strategy.rs +++ b/finance/vault-strategy/anchor/programs/vault-strategy/src/instructions/initialize_strategy.rs @@ -21,7 +21,7 @@ pub const MAX_FEE_BPS: u16 = 1_000; pub const MAX_SLIPPAGE_BPS: u16 = 1_000; #[derive(Accounts)] -#[instruction(index: u64)] +#[instruction(_index: u64)] pub struct InitializeStrategyAccountConstraints { #[account(mut)] pub manager: Signer, diff --git a/finance/vault-strategy/anchor/programs/vault-strategy/src/instructions/withdraw.rs b/finance/vault-strategy/anchor/programs/vault-strategy/src/instructions/withdraw.rs index 86c474c87..f0cb8c1f5 100644 --- a/finance/vault-strategy/anchor/programs/vault-strategy/src/instructions/withdraw.rs +++ b/finance/vault-strategy/anchor/programs/vault-strategy/src/instructions/withdraw.rs @@ -162,7 +162,7 @@ pub fn handle_withdraw( let mint_ai = remaining[i * 4 + 2]; let mut user_ata_ai = remaining[i * 4 + 3]; - let config = AssetConfig::load_checked(&config_ai)?; + let config = AssetConfig::load_checked(config_ai)?; require_keys_eq!( config.strategy, strategy_key, From a5cd4726ffebf032566fed3d52f1f73a4f9ff66c Mon Sep 17 00:00:00 2001 From: Claude Date: Sun, 16 Aug 2026 01:47:45 +0000 Subject: [PATCH 44/59] lint: silence the last clippy errors The instruction args are used by a `space` constraint, but v2's derive also binds them on a path that does not, so the affected structs carry an `#[allow(unused_variables)]` rather than losing the binding. Plus two needless borrows and four more `ProgramError::X.into()` conversions that are no-ops now the error type is already a ProgramError. --- .../programs/anchor-realloc/src/instructions/initialize.rs | 5 ++++- .../programs/anchor-realloc/src/instructions/update.rs | 5 ++++- basics/rent/anchor/programs/rent-example/src/lib.rs | 2 +- finance/lending/anchor/programs/lending/src/last_restart.rs | 2 +- .../anchor/programs/perpetual-futures/src/last_restart.rs | 2 +- .../prop-amm/anchor/programs/prop-amm/src/last_restart.rs | 2 +- .../vault-strategy/src/instructions/initialize_strategy.rs | 5 ++++- 7 files changed, 16 insertions(+), 7 deletions(-) diff --git a/basics/realloc/anchor/programs/anchor-realloc/src/instructions/initialize.rs b/basics/realloc/anchor/programs/anchor-realloc/src/instructions/initialize.rs index 843b8b2b0..1913415f9 100644 --- a/basics/realloc/anchor/programs/anchor-realloc/src/instructions/initialize.rs +++ b/basics/realloc/anchor/programs/anchor-realloc/src/instructions/initialize.rs @@ -2,8 +2,11 @@ use anchor_lang::prelude::*; use crate::Message; +// v2's `#[derive(Accounts)]` binds the `#[instruction(...)]` args on a code +// path that does not use them, even when a constraint below does. +#[allow(unused_variables)] #[derive(Accounts)] -#[instruction(_input: String)] +#[instruction(input: String)] pub struct InitializeAccountConstraints { #[account(mut)] pub payer: Signer, diff --git a/basics/realloc/anchor/programs/anchor-realloc/src/instructions/update.rs b/basics/realloc/anchor/programs/anchor-realloc/src/instructions/update.rs index f11fe5adc..f1b4ffa22 100644 --- a/basics/realloc/anchor/programs/anchor-realloc/src/instructions/update.rs +++ b/basics/realloc/anchor/programs/anchor-realloc/src/instructions/update.rs @@ -2,8 +2,11 @@ use anchor_lang::prelude::*; use crate::Message; +// v2's `#[derive(Accounts)]` binds the `#[instruction(...)]` args on a code +// path that does not use them, even when a constraint below does. +#[allow(unused_variables)] #[derive(Accounts)] -#[instruction(_input: String)] +#[instruction(input: String)] pub struct UpdateAccountConstraints { #[account(mut)] pub payer: Signer, diff --git a/basics/rent/anchor/programs/rent-example/src/lib.rs b/basics/rent/anchor/programs/rent-example/src/lib.rs index 4f7a11267..0c77f3681 100644 --- a/basics/rent/anchor/programs/rent-example/src/lib.rs +++ b/basics/rent/anchor/programs/rent-example/src/lib.rs @@ -66,6 +66,6 @@ impl AddressData { /// has to be sized — and therefore rent-funded — for. fn serialized_span(&self) -> Result { >::size_of(self) - .map_err(|_| ProgramError::InvalidInstructionData.into()) + .map_err(|_| ProgramError::InvalidInstructionData) } } diff --git a/finance/lending/anchor/programs/lending/src/last_restart.rs b/finance/lending/anchor/programs/lending/src/last_restart.rs index 3ef02b506..7e5733620 100644 --- a/finance/lending/anchor/programs/lending/src/last_restart.rs +++ b/finance/lending/anchor/programs/lending/src/last_restart.rs @@ -65,7 +65,7 @@ impl LastRestartSlot { // SAFETY: on success the syscall (or the zeroing above) has // initialized all 8 bytes. 0 => Ok(unsafe { var.assume_init() }), - _ => Err(ProgramError::UnsupportedSysvar.into()), + _ => Err(ProgramError::UnsupportedSysvar), } } } diff --git a/finance/perpetual-futures/anchor/programs/perpetual-futures/src/last_restart.rs b/finance/perpetual-futures/anchor/programs/perpetual-futures/src/last_restart.rs index 955b59d10..dbe90c30e 100644 --- a/finance/perpetual-futures/anchor/programs/perpetual-futures/src/last_restart.rs +++ b/finance/perpetual-futures/anchor/programs/perpetual-futures/src/last_restart.rs @@ -65,7 +65,7 @@ impl LastRestartSlot { // SAFETY: on success the syscall (or the zeroing above) has // initialized all 8 bytes. 0 => Ok(unsafe { var.assume_init() }), - _ => Err(ProgramError::UnsupportedSysvar.into()), + _ => Err(ProgramError::UnsupportedSysvar), } } } diff --git a/finance/prop-amm/anchor/programs/prop-amm/src/last_restart.rs b/finance/prop-amm/anchor/programs/prop-amm/src/last_restart.rs index 955b59d10..dbe90c30e 100644 --- a/finance/prop-amm/anchor/programs/prop-amm/src/last_restart.rs +++ b/finance/prop-amm/anchor/programs/prop-amm/src/last_restart.rs @@ -65,7 +65,7 @@ impl LastRestartSlot { // SAFETY: on success the syscall (or the zeroing above) has // initialized all 8 bytes. 0 => Ok(unsafe { var.assume_init() }), - _ => Err(ProgramError::UnsupportedSysvar.into()), + _ => Err(ProgramError::UnsupportedSysvar), } } } diff --git a/finance/vault-strategy/anchor/programs/vault-strategy/src/instructions/initialize_strategy.rs b/finance/vault-strategy/anchor/programs/vault-strategy/src/instructions/initialize_strategy.rs index cb24fb902..fbef7a040 100644 --- a/finance/vault-strategy/anchor/programs/vault-strategy/src/instructions/initialize_strategy.rs +++ b/finance/vault-strategy/anchor/programs/vault-strategy/src/instructions/initialize_strategy.rs @@ -20,8 +20,11 @@ pub const MAX_FEE_BPS: u16 = 1_000; /// that the bound is meaningless. pub const MAX_SLIPPAGE_BPS: u16 = 1_000; +// v2's `#[derive(Accounts)]` binds the `#[instruction(...)]` args on a code +// path that does not use them, even when a constraint below does. +#[allow(unused_variables)] #[derive(Accounts)] -#[instruction(_index: u64)] +#[instruction(index: u64)] pub struct InitializeStrategyAccountConstraints { #[account(mut)] pub manager: Signer, From 7298fd38abad50b02be29d6757dc07aeb8e046bc Mon Sep 17 00:00:00 2001 From: Claude Date: Sun, 16 Aug 2026 01:48:44 +0000 Subject: [PATCH 45/59] lint: clear the remaining clippy errors Account fields the derive reads only from generated code are declared `pub`, matching every other example, and the three structs whose `#[instruction(...)]` binding is generated in more than one item carry a module-level allow. --- .../anchor-program-example/src/instructions/create.rs | 6 +++--- .../anchor-program-example/src/instructions/increment.rs | 4 ++-- .../anchor-realloc/src/instructions/initialize.rs | 8 +++++--- .../programs/anchor-realloc/src/instructions/update.rs | 8 +++++--- .../src/instructions/initialize_strategy.rs | 8 +++++--- 5 files changed, 20 insertions(+), 14 deletions(-) diff --git a/basics/program-derived-addresses/anchor/programs/anchor-program-example/src/instructions/create.rs b/basics/program-derived-addresses/anchor/programs/anchor-program-example/src/instructions/create.rs index c2484c337..64c59e769 100644 --- a/basics/program-derived-addresses/anchor/programs/anchor-program-example/src/instructions/create.rs +++ b/basics/program-derived-addresses/anchor/programs/anchor-program-example/src/instructions/create.rs @@ -4,7 +4,7 @@ use anchor_lang::prelude::*; #[derive(Accounts)] pub struct CreatePageVisitsAccountConstraints { #[account(mut)] - payer: Signer, + pub payer: Signer, #[account( init, @@ -16,8 +16,8 @@ pub struct CreatePageVisitsAccountConstraints { ], bump, )] - page_visits: Account, - system_program: Program, + pub page_visits: Account, + pub system_program: Program, } pub fn handle_create_page_visits( diff --git a/basics/program-derived-addresses/anchor/programs/anchor-program-example/src/instructions/increment.rs b/basics/program-derived-addresses/anchor/programs/anchor-program-example/src/instructions/increment.rs index d185f86a2..bfceb1469 100644 --- a/basics/program-derived-addresses/anchor/programs/anchor-program-example/src/instructions/increment.rs +++ b/basics/program-derived-addresses/anchor/programs/anchor-program-example/src/instructions/increment.rs @@ -3,7 +3,7 @@ use anchor_lang::prelude::*; #[derive(Accounts)] pub struct IncrementPageVisitsAccountConstraints { - user: SystemAccount, + pub user: SystemAccount, #[account( mut, seeds = [ @@ -12,7 +12,7 @@ pub struct IncrementPageVisitsAccountConstraints { ], bump = page_visits.bump, )] - page_visits: Account, + pub page_visits: Account, } pub fn handle_increment_page_visits( diff --git a/basics/realloc/anchor/programs/anchor-realloc/src/instructions/initialize.rs b/basics/realloc/anchor/programs/anchor-realloc/src/instructions/initialize.rs index 1913415f9..ffc99aa47 100644 --- a/basics/realloc/anchor/programs/anchor-realloc/src/instructions/initialize.rs +++ b/basics/realloc/anchor/programs/anchor-realloc/src/instructions/initialize.rs @@ -1,10 +1,12 @@ +// v2's `#[derive(Accounts)]` binds the `#[instruction(...)]` args in more +// than one generated item, and only the one evaluating the constraints below +// reads them — so the binding looks unused to rustc even though `space` uses it. +#![allow(unused_variables)] + use anchor_lang::prelude::*; use crate::Message; -// v2's `#[derive(Accounts)]` binds the `#[instruction(...)]` args on a code -// path that does not use them, even when a constraint below does. -#[allow(unused_variables)] #[derive(Accounts)] #[instruction(input: String)] pub struct InitializeAccountConstraints { diff --git a/basics/realloc/anchor/programs/anchor-realloc/src/instructions/update.rs b/basics/realloc/anchor/programs/anchor-realloc/src/instructions/update.rs index f1b4ffa22..167a1a414 100644 --- a/basics/realloc/anchor/programs/anchor-realloc/src/instructions/update.rs +++ b/basics/realloc/anchor/programs/anchor-realloc/src/instructions/update.rs @@ -1,10 +1,12 @@ +// v2's `#[derive(Accounts)]` binds the `#[instruction(...)]` args in more +// than one generated item, and only the one evaluating the constraints below +// reads them — so the binding looks unused to rustc even though `space` uses it. +#![allow(unused_variables)] + use anchor_lang::prelude::*; use crate::Message; -// v2's `#[derive(Accounts)]` binds the `#[instruction(...)]` args on a code -// path that does not use them, even when a constraint below does. -#[allow(unused_variables)] #[derive(Accounts)] #[instruction(input: String)] pub struct UpdateAccountConstraints { diff --git a/finance/vault-strategy/anchor/programs/vault-strategy/src/instructions/initialize_strategy.rs b/finance/vault-strategy/anchor/programs/vault-strategy/src/instructions/initialize_strategy.rs index fbef7a040..149dd3285 100644 --- a/finance/vault-strategy/anchor/programs/vault-strategy/src/instructions/initialize_strategy.rs +++ b/finance/vault-strategy/anchor/programs/vault-strategy/src/instructions/initialize_strategy.rs @@ -1,3 +1,8 @@ +// v2's `#[derive(Accounts)]` binds the `#[instruction(...)]` args in more +// than one generated item, and only the one evaluating the constraints below +// reads them — so the binding looks unused to rustc even though `space` uses it. +#![allow(unused_variables)] + use anchor_lang::prelude::*; use anchor_spl::mint; use anchor_spl::{ @@ -20,9 +25,6 @@ pub const MAX_FEE_BPS: u16 = 1_000; /// that the bound is meaningless. pub const MAX_SLIPPAGE_BPS: u16 = 1_000; -// v2's `#[derive(Accounts)]` binds the `#[instruction(...)]` args on a code -// path that does not use them, even when a constraint below does. -#[allow(unused_variables)] #[derive(Accounts)] #[instruction(index: u64)] pub struct InitializeStrategyAccountConstraints { From 5e8a370885896675ccba7bf895b8c3c34bbb68a8 Mon Sep 17 00:00:00 2001 From: Claude Date: Sun, 16 Aug 2026 01:54:01 +0000 Subject: [PATCH 46/59] pyth: repair the test imports An earlier rename had merged two use paths into a nonexistent `solana_pythexample`, and LiteSVM's get_sysvar needs the host-side Clock. 3/3 passing. --- .../pyth/anchor/programs/pythexample/src/lib.rs | 1 - .../programs/pythexample/tests/test_pyth.rs | 15 ++++++--------- 2 files changed, 6 insertions(+), 10 deletions(-) diff --git a/basics/pyth/anchor/programs/pythexample/src/lib.rs b/basics/pyth/anchor/programs/pythexample/src/lib.rs index af5e0dfe8..c57697c9e 100644 --- a/basics/pyth/anchor/programs/pythexample/src/lib.rs +++ b/basics/pyth/anchor/programs/pythexample/src/lib.rs @@ -121,4 +121,3 @@ impl anchor_lang::Discriminator for PriceUpdateV2 { impl anchor_lang::Owner for PriceUpdateV2 { const OWNER: Address = PYTH_RECEIVER_PROGRAM_ID; } - diff --git a/basics/pyth/anchor/programs/pythexample/tests/test_pyth.rs b/basics/pyth/anchor/programs/pythexample/tests/test_pyth.rs index be6bd0d23..5929d9538 100644 --- a/basics/pyth/anchor/programs/pythexample/tests/test_pyth.rs +++ b/basics/pyth/anchor/programs/pythexample/tests/test_pyth.rs @@ -1,10 +1,11 @@ use { anchor_lang::{ - solana_program::instruction::Instruction, - Address, InstructionData, ToAccountMetas, + solana_program::instruction::Instruction, Address, InstructionData, ToAccountMetas, }, litesvm::LiteSVM, - solana_pythexample::MAXIMUM_PRICE_AGE_SECONDS, + pythexample::MAXIMUM_PRICE_AGE_SECONDS, + // LiteSVM's get_sysvar wants the host-side Clock, not pinocchio's. + solana_clock::Clock, solana_keypair::Keypair, solana_kite::{create_wallet, send_transaction_from_instructions}, solana_signer::Signer, @@ -19,9 +20,7 @@ fn pyth_receiver_program_id() -> anchor_lang::Address { } /// Build mock PriceUpdateV2 account data with Anchor discriminator. -fn build_mock_price_update_account( - write_authority: &anchor_lang::Address, -) -> Vec { +fn build_mock_price_update_account(write_authority: &anchor_lang::Address) -> Vec { // Discriminator: sha256("account:PriceUpdateV2")[..8] let discriminator: [u8; 8] = [34, 241, 35, 99, 157, 126, 244, 205]; @@ -111,9 +110,7 @@ fn setup_with_price_account( (svm, payer, price_update_key) } -fn read_price_instruction( - price_update: anchor_lang::Address, -) -> Instruction { +fn read_price_instruction(price_update: anchor_lang::Address) -> Instruction { let ix_data = pythexample::instruction::ReadPrice {}.data(); let accounts = pythexample::accounts::ReadPriceAccountConstraints { price_update }.to_account_metas(None); From 214995bd1530e6329072062147ae8a1f01669f11 Mon Sep 17 00:00:00 2001 From: Claude Date: Sun, 16 Aug 2026 01:59:50 +0000 Subject: [PATCH 47/59] tests: repair two doubled crate prefixes and a missing Clock import --- .../anchor/programs/fundraiser/tests/test_fundraiser.rs | 4 +++- .../anchor/programs/vault-strategy/tests/vault_strategy.rs | 2 +- 2 files changed, 4 insertions(+), 2 deletions(-) diff --git a/finance/token-fundraiser/anchor/programs/fundraiser/tests/test_fundraiser.rs b/finance/token-fundraiser/anchor/programs/fundraiser/tests/test_fundraiser.rs index ca8277ac5..fb386016c 100644 --- a/finance/token-fundraiser/anchor/programs/fundraiser/tests/test_fundraiser.rs +++ b/finance/token-fundraiser/anchor/programs/fundraiser/tests/test_fundraiser.rs @@ -6,13 +6,15 @@ use { borsh::BorshDeserialize, fundraiser::SECONDS_TO_DAYS, litesvm::LiteSVM, + // LiteSVM's get_sysvar wants the host-side Clock, not pinocchio's. + solana_clock::Clock, + solana_keypair::Keypair, solana_kite::{ create_associated_token_account, create_token_mint, create_wallet, get_token_account_balance, mint_tokens_to_token_account, send_transaction_from_instructions, }, solana_signer::Signer, - solana_solana_keypair::Keypair, }; const MINT_DECIMALS: u8 = 6; diff --git a/finance/vault-strategy/anchor/programs/vault-strategy/tests/vault_strategy.rs b/finance/vault-strategy/anchor/programs/vault-strategy/tests/vault_strategy.rs index 0fdf1e811..1d51f22c6 100644 --- a/finance/vault-strategy/anchor/programs/vault-strategy/tests/vault_strategy.rs +++ b/finance/vault-strategy/anchor/programs/vault-strategy/tests/vault_strategy.rs @@ -12,7 +12,7 @@ use { send_transaction_from_instructions, }, solana_signer::Signer, - solana_solana_account::Account as SolanaAccount, + solana_account::Account as SolanaAccount, }; fn token_program_id() -> Address { From c04f2d77ec2ec286b83feca5bcd9d2db8c7243e7 Mon Sep 17 00:00:00 2001 From: Claude Date: Sun, 16 Aug 2026 02:08:55 +0000 Subject: [PATCH 48/59] nft-minter: restore update_authority_is_signer on the metadata CPI The field is on CreateMetadataAccountsV3, not CreateMasterEditionV3. --- tokens/nft-minter/anchor/programs/nft-minter/src/lib.rs | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/tokens/nft-minter/anchor/programs/nft-minter/src/lib.rs b/tokens/nft-minter/anchor/programs/nft-minter/src/lib.rs index 2a872cae2..4891b6ed3 100644 --- a/tokens/nft-minter/anchor/programs/nft-minter/src/lib.rs +++ b/tokens/nft-minter/anchor/programs/nft-minter/src/lib.rs @@ -51,11 +51,12 @@ pub mod nft_minter { context.accounts.token_metadata_program.address(), CreateMetadataAccountsV3 { metadata: context.accounts.metadata_account.cpi_handle_mut(), - mint: CpiHandle::readonly(&mint_account_view), + mint: context.accounts.mint_account.cpi_handle(), mint_authority: CpiHandle::readonly(&payer_view), update_authority: CpiHandle::readonly(&payer_view), payer: context.accounts.payer.cpi_handle_mut(), system_program: context.accounts.system_program.cpi_handle(), + update_authority_is_signer: true, }, ), DataV2 { From 16d30e4c1d3cd9352f76f58f47a6a394346329e3 Mon Sep 17 00:00:00 2001 From: Claude Date: Sun, 16 Aug 2026 02:20:30 +0000 Subject: [PATCH 49/59] tokens: allow the self-send duplicate and use wrapper CPI handles Minting or sending to yourself makes the authority and the recipient the same account, which v2 rejects while any of the duplicated slots is in the mutable mask; `unsafe(dup)` takes it out of that mask while keeping it writable. transfer-tokens' read-only mint slot also moves to the wrapper's own `cpi_handle()`, since a hand-built handle over a copy of the view keeps the runtime borrow check on and the account's borrow is live. --- .../token-minter/src/instructions/mint.rs | 6 +++++- .../transfer-tokens/src/instructions/mint.rs | 6 +++++- .../src/instructions/transfer.rs | 19 ++++++++++--------- 3 files changed, 20 insertions(+), 11 deletions(-) diff --git a/tokens/token-minter/anchor/programs/token-minter/src/instructions/mint.rs b/tokens/token-minter/anchor/programs/token-minter/src/instructions/mint.rs index 7b60c58e2..68a650163 100644 --- a/tokens/token-minter/anchor/programs/token-minter/src/instructions/mint.rs +++ b/tokens/token-minter/anchor/programs/token-minter/src/instructions/mint.rs @@ -8,7 +8,11 @@ use { #[derive(Accounts)] pub struct MintTokenAccountConstraints { - #[account(mut)] + // Minting to yourself is ordinary, so `mint_authority` and `recipient` may + // be the same account. v2 rejects an account that appears twice while any + // of its slots is in the mutable mask, and `unsafe(dup)` takes this one out + // of that mask while keeping it writable (it is still the init payer). + #[account(unsafe(dup))] pub mint_authority: Signer, pub recipient: SystemAccount, diff --git a/tokens/transfer-tokens/anchor/programs/transfer-tokens/src/instructions/mint.rs b/tokens/transfer-tokens/anchor/programs/transfer-tokens/src/instructions/mint.rs index f8a47299a..894f735cc 100644 --- a/tokens/transfer-tokens/anchor/programs/transfer-tokens/src/instructions/mint.rs +++ b/tokens/transfer-tokens/anchor/programs/transfer-tokens/src/instructions/mint.rs @@ -8,7 +8,11 @@ use { #[derive(Accounts)] pub struct MintTokenAccountConstraints { - #[account(mut)] + // The mint authority and the recipient may be the same account (minting or + // sending to yourself). v2 rejects an account that appears twice while any + // of its slots is in the mutable mask, and `unsafe(dup)` takes this one out + // of that mask while keeping it writable. + #[account(unsafe(dup))] pub mint_authority: Signer, pub recipient: SystemAccount, diff --git a/tokens/transfer-tokens/anchor/programs/transfer-tokens/src/instructions/transfer.rs b/tokens/transfer-tokens/anchor/programs/transfer-tokens/src/instructions/transfer.rs index eb69a917c..afe8c1197 100644 --- a/tokens/transfer-tokens/anchor/programs/transfer-tokens/src/instructions/transfer.rs +++ b/tokens/transfer-tokens/anchor/programs/transfer-tokens/src/instructions/transfer.rs @@ -8,7 +8,11 @@ use { #[derive(Accounts)] pub struct TransferTokensAccountConstraints { - #[account(mut)] + // The sender and the recipient may be the same account (minting or + // sending to yourself). v2 rejects an account that appears twice while any + // of its slots is in the mutable mask, and `unsafe(dup)` takes this one out + // of that mask while keeping it writable. + #[account(unsafe(dup))] pub sender: Signer, pub recipient: SystemAccount, @@ -50,14 +54,8 @@ pub fn handle_transfer_tokens( context: &mut Context, amount: u64, ) -> Result<()> { - // `AccountView` is Copy, and a copy still points at the same - // account — v2's typed handles make the aliasing a compile error. - let mint_account_view = *context.accounts.mint_account.account(); msg!("Transferring tokens..."); - msg!( - "Mint: {}", - &context.accounts.mint_account.cpi_handle_mut().address() - ); + msg!("Mint: {}", context.accounts.mint_account.address()); msg!( "From Token Address: {}", &context.accounts.sender_token_account.address() @@ -73,7 +71,10 @@ pub fn handle_transfer_tokens( context.accounts.token_program.address(), TransferChecked { from: context.accounts.sender_token_account.cpi_handle_mut(), - mint: CpiHandle::readonly(&mint_account_view), + // Read-only slots take the wrapper's own handle: on a data account + // it relaxes the runtime borrow check that a hand-built handle + // over a copy of the view would still trip. + mint: context.accounts.mint_account.cpi_handle(), to: context.accounts.recipient_token_account.cpi_handle_mut(), authority: context.accounts.sender.cpi_handle(), }, From eff9f03049dde517793405cf5a57bd2f83342ac0 Mon Sep 17 00:00:00 2001 From: Claude Date: Sun, 16 Aug 2026 02:34:27 +0000 Subject: [PATCH 50/59] token-extensions: fix the borrow and CPI-meta faults in three examples A `mut` data account is marked exclusively borrowed, so `try_borrow()` on it is rejected: transfer-fee's mint drops its `mut` (the withheld fee accrues on the destination account, not the mint), while interest-bearing and metadata genuinely write theirs and read through the exclusive borrow they hold. metadata's two hand-built invokes also line their handles up with the instruction's metas, the program account is not one of them, and `remove_key` names the mint writable. --- docs/anchor-v2-migration.md | 19 ++++++++++-- .../programs/interest-bearing/src/lib.rs | 9 +++++- .../metadata/src/instructions/emit.rs | 8 ++--- .../metadata/src/instructions/remove_key.rs | 7 +++-- .../metadata/src/instructions/update_field.rs | 12 +++++-- .../transfer-fee/src/instructions/transfer.rs | 31 ++++++++++--------- 6 files changed, 59 insertions(+), 27 deletions(-) diff --git a/docs/anchor-v2-migration.md b/docs/anchor-v2-migration.md index 6c3a74419..c4ce77cd2 100644 --- a/docs/anchor-v2-migration.md +++ b/docs/anchor-v2-migration.md @@ -216,9 +216,22 @@ rejected with `AccountBorrowFailed`. `Box`'s `ToCpiHandleMut` impl does forward to the inner type, which is where the release lives. It compiles either way, so this only shows up in a test. -Not every failure of this kind needs a release: a handler that only *reads* an -account can take a second shared borrow (`account().try_borrow()`) where -`try_borrow_mut` on a copied view would be rejected. +Not every failure of this kind needs a release: a handler that only *reads* a +**read-only** account can take a second shared borrow (`account().try_borrow()`) +where `try_borrow_mut` on a copied view would be rejected. + +A **`mut`** data account is different. Loading one sets pinocchio's exclusive +sentinel (`borrow_state == 0`), so *any* `try_borrow()` on it fails — the +wrapper itself reads through `borrow_unchecked`. Two ways out, in order of +preference: + +1. Drop the `mut` if the account is not actually written. A mint passed to + `transfer_checked_with_fee` is read-only: the withheld fee accrues on the + destination token account. +2. Read through the exclusive borrow you already hold: + `unsafe { account.account().borrow_unchecked() }`. Sound whenever the + instruction holds that borrow for its whole duration and hands out no second + one — which is the case for anything reached through `ctx.accounts`. ## Instruction discriminators, and programs that implement an interface diff --git a/tokens/token-extensions/interest-bearing/anchor/programs/interest-bearing/src/lib.rs b/tokens/token-extensions/interest-bearing/anchor/programs/interest-bearing/src/lib.rs index ca8b64d9d..06958b56b 100644 --- a/tokens/token-extensions/interest-bearing/anchor/programs/interest-bearing/src/lib.rs +++ b/tokens/token-extensions/interest-bearing/anchor/programs/interest-bearing/src/lib.rs @@ -33,7 +33,14 @@ pub mod interest_bearing { } pub fn check_mint_data(mint_account_info: &AccountView, authority_key: &Address) -> Result<()> { - let mint_data = mint_account_info.try_borrow()?; + // The mint is declared `mut`, and v2 marks a mutable data account as + // exclusively borrowed (pinocchio's `borrow_state == 0`), so `try_borrow()` + // on it is rejected. Reading through the exclusive borrow we already hold + // is what the wrapper itself does. + // + // SAFETY: the caller holds the mint's exclusive borrow for the whole + // instruction, and this reads it without handing out a second one. + let mint_data = unsafe { mint_account_info.borrow_unchecked() }; let mint_with_extension = StateWithExtensions::::unpack(&mint_data)?; let extension_data = mint_with_extension.get_extension::()?; diff --git a/tokens/token-extensions/metadata/anchor/programs/metadata/src/instructions/emit.rs b/tokens/token-extensions/metadata/anchor/programs/metadata/src/instructions/emit.rs index 71a445977..fcb2fe8f7 100644 --- a/tokens/token-extensions/metadata/anchor/programs/metadata/src/instructions/emit.rs +++ b/tokens/token-extensions/metadata/anchor/programs/metadata/src/instructions/emit.rs @@ -19,10 +19,10 @@ pub fn process_emit(context: &mut Context) -> Result<()> None, None, ), - &[ - context.accounts.token_program.cpi_handle(), - context.accounts.mint_account.cpi_handle(), - ], + // Handles line up positionally with the instruction's metas: `emit` + // names only the metadata account (the mint), read-only. The program + // account is not one of them. + &[context.accounts.mint_account.cpi_handle()], )?; Ok(()) } diff --git a/tokens/token-extensions/metadata/anchor/programs/metadata/src/instructions/remove_key.rs b/tokens/token-extensions/metadata/anchor/programs/metadata/src/instructions/remove_key.rs index 1b5862bf0..98cdf70e8 100644 --- a/tokens/token-extensions/metadata/anchor/programs/metadata/src/instructions/remove_key.rs +++ b/tokens/token-extensions/metadata/anchor/programs/metadata/src/instructions/remove_key.rs @@ -28,9 +28,12 @@ pub fn process_remove_key( key, // key to remove true, // idempotent flag, if true transaction will not fail if key does not exist ), + // Handles line up positionally with the instruction's metas, and a + // writable meta needs a writable handle: `remove_key` names the metadata + // account (the mint, writable) then the update authority. The program + // account is not one of them. &[ - context.accounts.token_program.cpi_handle(), - context.accounts.mint_account.cpi_handle(), + context.accounts.mint_account.cpi_handle_mut().into(), context.accounts.update_authority.cpi_handle(), ], )?; diff --git a/tokens/token-extensions/metadata/anchor/programs/metadata/src/instructions/update_field.rs b/tokens/token-extensions/metadata/anchor/programs/metadata/src/instructions/update_field.rs index 73e4b6d5f..273f45c9d 100644 --- a/tokens/token-extensions/metadata/anchor/programs/metadata/src/instructions/update_field.rs +++ b/tokens/token-extensions/metadata/anchor/programs/metadata/src/instructions/update_field.rs @@ -34,10 +34,16 @@ pub fn process_update_field( msg!("Field: {:?}, Value: {}", field, value); let (current_lamports, required_lamports) = { - // Get the current state of the mint account. `AccountView` is Copy and - // a copy still points at the same backing buffer. + // Get the current state of the mint account. It is declared `mut`, and + // v2 marks a mutable data account as exclusively borrowed (pinocchio's + // `borrow_state == 0`), so `try_borrow()` on it is rejected — reading + // through the exclusive borrow we already hold is what the wrapper + // itself does. + // + // SAFETY: this instruction holds the mint's exclusive borrow for its + // whole duration, and this reads it without handing out a second one. let mint = *context.accounts.mint_account.account(); - let buffer = mint.try_borrow()?; + let buffer = unsafe { mint.borrow_unchecked() }; let state = PodStateWithExtensions::::unpack(&buffer)?; // Get and update the token metadata diff --git a/tokens/token-extensions/transfer-fee/anchor/programs/transfer-fee/src/instructions/transfer.rs b/tokens/token-extensions/transfer-fee/anchor/programs/transfer-fee/src/instructions/transfer.rs index f4135db8c..94fc41be5 100644 --- a/tokens/token-extensions/transfer-fee/anchor/programs/transfer-fee/src/instructions/transfer.rs +++ b/tokens/token-extensions/transfer-fee/anchor/programs/transfer-fee/src/instructions/transfer.rs @@ -18,7 +18,10 @@ pub struct TransferAccountConstraints { pub sender: Signer, pub recipient: SystemAccount, - #[account(mut)] + // Read-only: `transfer_checked_with_fee` accrues the withheld fee on the + // destination token account, not the mint. It also has to be read-only for + // the extension read below — a mutable data account holds an exclusive + // borrow, so a second `try_borrow()` on it is rejected. pub mint_account: InterfaceAccount, #[account( mut, @@ -47,19 +50,16 @@ pub fn handle_process_transfer( context: &mut Context, amount: u64, ) -> Result<()> { - // `AccountView` is Copy, and a copy still points at the same - // account — v2's typed handles make the aliasing a compile error. - let mint_account_view = *context.accounts.mint_account.account(); - // read mint account extension data - // Read-only: the account already holds a shared borrow of its buffer, and a - // second shared borrow is fine where a writable handle would be rejected. - let mint_data = context.accounts.mint_account.account().try_borrow()?; - let mint_with_extension = StateWithExtensions::::unpack(&mint_data)?; - let extension_data = mint_with_extension.get_extension::()?; - - // calculate expected fee + // Read the mint's extension data in its own scope: the `Ref` has to drop + // before the CPI below, or the runtime rejects the CPI's borrow of the same + // account with AccountBorrowFailed. let epoch = Clock::get()?.epoch; - let fee = extension_data.calculate_epoch_fee(epoch, amount).unwrap(); + let fee = { + let mint_data = context.accounts.mint_account.account().try_borrow()?; + let mint_with_extension = StateWithExtensions::::unpack(&mint_data)?; + let extension_data = mint_with_extension.get_extension::()?; + extension_data.calculate_epoch_fee(epoch, amount).unwrap() + }; // mint account decimals let decimals = context.accounts.mint_account.decimals(); @@ -69,7 +69,10 @@ pub fn handle_process_transfer( context.accounts.token_program.address(), TransferCheckedWithFee { source: context.accounts.sender_token_account.cpi_handle_mut(), - mint: CpiHandle::readonly(&mint_account_view), + // Read-only slots take the wrapper's own handle: on a data account + // it relaxes the runtime borrow check that a hand-built handle + // over a copy of the view would still trip. + mint: context.accounts.mint_account.cpi_handle(), destination: context.accounts.recipient_token_account.cpi_handle_mut(), authority: context.accounts.sender.cpi_handle(), }, From 6f751ebcf35c58bd35fb3063c2ac2f52eeb0891a Mon Sep 17 00:00:00 2001 From: Claude Date: Sun, 16 Aug 2026 02:38:48 +0000 Subject: [PATCH 51/59] token-extensions/basics: spell out the token program for the init constraints Required when the token program is an `Interface`; without it the init CPI is rejected with InvalidArgument. --- .../src/instructions/create_associated_token_account.rs | 3 +++ .../anchor/programs/basics/src/instructions/create_token.rs | 3 +++ .../programs/basics/src/instructions/create_token_account.rs | 3 +++ .../programs/basics/src/instructions/transfer_token.rs | 5 ++++- 4 files changed, 13 insertions(+), 1 deletion(-) diff --git a/tokens/token-extensions/basics/anchor/programs/basics/src/instructions/create_associated_token_account.rs b/tokens/token-extensions/basics/anchor/programs/basics/src/instructions/create_associated_token_account.rs index bc2c293fd..0474ad6f6 100644 --- a/tokens/token-extensions/basics/anchor/programs/basics/src/instructions/create_associated_token_account.rs +++ b/tokens/token-extensions/basics/anchor/programs/basics/src/instructions/create_associated_token_account.rs @@ -12,6 +12,9 @@ pub struct CreateAssociatedTokenAccountAccountConstraints { associated_token::mint = mint, payer = signer, associated_token::authority = signer, + // Required when the token program is an `Interface`: without it the + // init CPI is rejected with InvalidArgument. + associated_token::token_program = token_program, )] pub token_account: InterfaceAccount, pub system_program: Program, diff --git a/tokens/token-extensions/basics/anchor/programs/basics/src/instructions/create_token.rs b/tokens/token-extensions/basics/anchor/programs/basics/src/instructions/create_token.rs index 2e84bffff..bc60b9c25 100644 --- a/tokens/token-extensions/basics/anchor/programs/basics/src/instructions/create_token.rs +++ b/tokens/token-extensions/basics/anchor/programs/basics/src/instructions/create_token.rs @@ -12,6 +12,9 @@ pub struct CreateTokenAccountConstraints { payer = signer, mint::decimals = 6, mint::authority = signer, + // Required when the token program is an `Interface`: without it + // the init CPI is rejected with InvalidArgument. + mint::token_program = token_program, seeds = [b"token-2022-token", signer.address().as_ref(), token_name.as_bytes()], bump, )] diff --git a/tokens/token-extensions/basics/anchor/programs/basics/src/instructions/create_token_account.rs b/tokens/token-extensions/basics/anchor/programs/basics/src/instructions/create_token_account.rs index cfe3976f8..0a607a723 100644 --- a/tokens/token-extensions/basics/anchor/programs/basics/src/instructions/create_token_account.rs +++ b/tokens/token-extensions/basics/anchor/programs/basics/src/instructions/create_token_account.rs @@ -11,6 +11,9 @@ pub struct CreateTokenAccountAccountConstraints { init, token::mint = mint, token::authority = signer, + // Required when the token program is an `Interface`: without it + // the init CPI is rejected with InvalidArgument. + token::token_program = token_program, payer = signer, seeds = [b"token-2022-token-account", signer.address().as_ref(), mint.address().as_ref()], bump, diff --git a/tokens/token-extensions/basics/anchor/programs/basics/src/instructions/transfer_token.rs b/tokens/token-extensions/basics/anchor/programs/basics/src/instructions/transfer_token.rs index 10c9518f6..d062a1c65 100644 --- a/tokens/token-extensions/basics/anchor/programs/basics/src/instructions/transfer_token.rs +++ b/tokens/token-extensions/basics/anchor/programs/basics/src/instructions/transfer_token.rs @@ -13,7 +13,10 @@ pub struct TransferTokenAccountConstraints { init, associated_token::mint = mint, payer = signer, - associated_token::authority = to + associated_token::authority = to, + // Required when the token program is an `Interface`: without it the + // init CPI is rejected with InvalidArgument. + associated_token::token_program = token_program, )] pub to_ata: InterfaceAccount, #[account(mut)] From c76f124c5e60d5d01c3f212930b7da444bf40850 Mon Sep 17 00:00:00 2001 From: Claude Date: Sun, 16 Aug 2026 02:42:45 +0000 Subject: [PATCH 52/59] finance: use to_cpi_handle on the Boxed accounts perpetual-futures 26/26 and prop-amm 22/22. `Box` does not forward `cpi_handle_mut` to the inner type's borrow-releasing override, so every CPI in these two was rejected with AccountBorrowFailed. --- .../src/instructions/add_liquidity.rs | 10 ++++---- .../src/instructions/close_position.rs | 6 ++--- .../src/instructions/collect_fees.rs | 6 ++--- .../src/instructions/liquidate_position.rs | 12 +++++----- .../src/instructions/open_position.rs | 6 ++--- .../src/instructions/remove_liquidity.rs | 10 ++++---- .../src/instructions/deposit_inventory.rs | 12 +++++----- .../prop-amm/src/instructions/swap.rs | 24 +++++++++---------- .../src/instructions/withdraw_inventory.rs | 12 +++++----- 9 files changed, 49 insertions(+), 49 deletions(-) diff --git a/finance/perpetual-futures/anchor/programs/perpetual-futures/src/instructions/add_liquidity.rs b/finance/perpetual-futures/anchor/programs/perpetual-futures/src/instructions/add_liquidity.rs index c5f64d9c6..e6d83746d 100644 --- a/finance/perpetual-futures/anchor/programs/perpetual-futures/src/instructions/add_liquidity.rs +++ b/finance/perpetual-futures/anchor/programs/perpetual-futures/src/instructions/add_liquidity.rs @@ -55,9 +55,9 @@ pub fn handle_add_liquidity( CpiContext::new( context.accounts.token_program.address(), TransferChecked { - from: context.accounts.provider_collateral.cpi_handle_mut(), - mint: context.accounts.collateral_mint.cpi_handle(), - to: context.accounts.custody_vault.cpi_handle_mut(), + from: context.accounts.provider_collateral.to_cpi_handle_mut(), + mint: context.accounts.collateral_mint.to_cpi_handle(), + to: context.accounts.custody_vault.to_cpi_handle_mut(), authority: context.accounts.provider.cpi_handle(), }, ), @@ -71,8 +71,8 @@ pub fn handle_add_liquidity( CpiContext::new_with_signer( context.accounts.token_program.address(), MintTo { - mint: context.accounts.lp_mint.cpi_handle_mut(), - to: context.accounts.provider_lp.cpi_handle_mut(), + mint: context.accounts.lp_mint.to_cpi_handle_mut(), + to: context.accounts.provider_lp.to_cpi_handle_mut(), authority: context.accounts.pool_authority.cpi_handle(), }, &[authority_seeds], diff --git a/finance/perpetual-futures/anchor/programs/perpetual-futures/src/instructions/close_position.rs b/finance/perpetual-futures/anchor/programs/perpetual-futures/src/instructions/close_position.rs index 1b51778d3..692ad7d87 100644 --- a/finance/perpetual-futures/anchor/programs/perpetual-futures/src/instructions/close_position.rs +++ b/finance/perpetual-futures/anchor/programs/perpetual-futures/src/instructions/close_position.rs @@ -72,9 +72,9 @@ pub fn handle_close_position( CpiContext::new_with_signer( context.accounts.token_program.address(), TransferChecked { - from: context.accounts.custody_vault.cpi_handle_mut(), - mint: context.accounts.collateral_mint.cpi_handle(), - to: context.accounts.trader_collateral.cpi_handle_mut(), + from: context.accounts.custody_vault.to_cpi_handle_mut(), + mint: context.accounts.collateral_mint.to_cpi_handle(), + to: context.accounts.trader_collateral.to_cpi_handle_mut(), authority: context.accounts.pool_authority.cpi_handle(), }, &[authority_seeds], diff --git a/finance/perpetual-futures/anchor/programs/perpetual-futures/src/instructions/collect_fees.rs b/finance/perpetual-futures/anchor/programs/perpetual-futures/src/instructions/collect_fees.rs index 4a30485fc..094790e39 100644 --- a/finance/perpetual-futures/anchor/programs/perpetual-futures/src/instructions/collect_fees.rs +++ b/finance/perpetual-futures/anchor/programs/perpetual-futures/src/instructions/collect_fees.rs @@ -22,9 +22,9 @@ pub fn handle_collect_fees(context: &mut Context) CpiContext::new_with_signer( context.accounts.token_program.address(), TransferChecked { - from: context.accounts.custody_vault.cpi_handle_mut(), - mint: context.accounts.collateral_mint.cpi_handle(), - to: context.accounts.authority_collateral.cpi_handle_mut(), + from: context.accounts.custody_vault.to_cpi_handle_mut(), + mint: context.accounts.collateral_mint.to_cpi_handle(), + to: context.accounts.authority_collateral.to_cpi_handle_mut(), authority: context.accounts.pool_authority.cpi_handle(), }, &[authority_seeds], diff --git a/finance/perpetual-futures/anchor/programs/perpetual-futures/src/instructions/liquidate_position.rs b/finance/perpetual-futures/anchor/programs/perpetual-futures/src/instructions/liquidate_position.rs index 2e88553ae..09bc517d6 100644 --- a/finance/perpetual-futures/anchor/programs/perpetual-futures/src/instructions/liquidate_position.rs +++ b/finance/perpetual-futures/anchor/programs/perpetual-futures/src/instructions/liquidate_position.rs @@ -68,9 +68,9 @@ pub fn handle_liquidate_position( CpiContext::new_with_signer( context.accounts.token_program.address(), TransferChecked { - from: context.accounts.custody_vault.cpi_handle_mut(), - mint: context.accounts.collateral_mint.cpi_handle(), - to: context.accounts.liquidator_collateral.cpi_handle_mut(), + from: context.accounts.custody_vault.to_cpi_handle_mut(), + mint: context.accounts.collateral_mint.to_cpi_handle(), + to: context.accounts.liquidator_collateral.to_cpi_handle_mut(), authority: context.accounts.pool_authority.cpi_handle(), }, &[authority_seeds], @@ -85,9 +85,9 @@ pub fn handle_liquidate_position( CpiContext::new_with_signer( context.accounts.token_program.address(), TransferChecked { - from: context.accounts.custody_vault.cpi_handle_mut(), - mint: context.accounts.collateral_mint.cpi_handle(), - to: context.accounts.trader_collateral.cpi_handle_mut(), + from: context.accounts.custody_vault.to_cpi_handle_mut(), + mint: context.accounts.collateral_mint.to_cpi_handle(), + to: context.accounts.trader_collateral.to_cpi_handle_mut(), authority: context.accounts.pool_authority.cpi_handle(), }, &[authority_seeds], diff --git a/finance/perpetual-futures/anchor/programs/perpetual-futures/src/instructions/open_position.rs b/finance/perpetual-futures/anchor/programs/perpetual-futures/src/instructions/open_position.rs index 3a7ac4917..4c6d543bf 100644 --- a/finance/perpetual-futures/anchor/programs/perpetual-futures/src/instructions/open_position.rs +++ b/finance/perpetual-futures/anchor/programs/perpetual-futures/src/instructions/open_position.rs @@ -112,9 +112,9 @@ pub fn handle_open_position( CpiContext::new( context.accounts.token_program.address(), TransferChecked { - from: context.accounts.trader_collateral.cpi_handle_mut(), - mint: context.accounts.collateral_mint.cpi_handle(), - to: context.accounts.custody_vault.cpi_handle_mut(), + from: context.accounts.trader_collateral.to_cpi_handle_mut(), + mint: context.accounts.collateral_mint.to_cpi_handle(), + to: context.accounts.custody_vault.to_cpi_handle_mut(), authority: context.accounts.owner.cpi_handle(), }, ), diff --git a/finance/perpetual-futures/anchor/programs/perpetual-futures/src/instructions/remove_liquidity.rs b/finance/perpetual-futures/anchor/programs/perpetual-futures/src/instructions/remove_liquidity.rs index c15f61610..294f4cb05 100644 --- a/finance/perpetual-futures/anchor/programs/perpetual-futures/src/instructions/remove_liquidity.rs +++ b/finance/perpetual-futures/anchor/programs/perpetual-futures/src/instructions/remove_liquidity.rs @@ -60,8 +60,8 @@ pub fn handle_remove_liquidity( CpiContext::new( context.accounts.token_program.address(), Burn { - mint: context.accounts.lp_mint.cpi_handle_mut(), - from: context.accounts.provider_lp.cpi_handle_mut(), + mint: context.accounts.lp_mint.to_cpi_handle_mut(), + from: context.accounts.provider_lp.to_cpi_handle_mut(), authority: context.accounts.provider.cpi_handle(), }, ), @@ -74,9 +74,9 @@ pub fn handle_remove_liquidity( CpiContext::new_with_signer( context.accounts.token_program.address(), TransferChecked { - from: context.accounts.custody_vault.cpi_handle_mut(), - mint: context.accounts.collateral_mint.cpi_handle(), - to: context.accounts.provider_collateral.cpi_handle_mut(), + from: context.accounts.custody_vault.to_cpi_handle_mut(), + mint: context.accounts.collateral_mint.to_cpi_handle(), + to: context.accounts.provider_collateral.to_cpi_handle_mut(), authority: context.accounts.pool_authority.cpi_handle(), }, &[authority_seeds], diff --git a/finance/prop-amm/anchor/programs/prop-amm/src/instructions/deposit_inventory.rs b/finance/prop-amm/anchor/programs/prop-amm/src/instructions/deposit_inventory.rs index 883dd718a..d2d7fe8e4 100644 --- a/finance/prop-amm/anchor/programs/prop-amm/src/instructions/deposit_inventory.rs +++ b/finance/prop-amm/anchor/programs/prop-amm/src/instructions/deposit_inventory.rs @@ -22,9 +22,9 @@ pub fn handle_deposit_inventory( CpiContext::new( context.accounts.token_program.address(), TransferChecked { - from: context.accounts.operator_base.cpi_handle_mut(), - mint: context.accounts.base_mint.cpi_handle(), - to: context.accounts.base_vault.cpi_handle_mut(), + from: context.accounts.operator_base.to_cpi_handle_mut(), + mint: context.accounts.base_mint.to_cpi_handle(), + to: context.accounts.base_vault.to_cpi_handle_mut(), authority: context.accounts.operator.cpi_handle(), }, ), @@ -38,9 +38,9 @@ pub fn handle_deposit_inventory( CpiContext::new( context.accounts.token_program.address(), TransferChecked { - from: context.accounts.operator_quote.cpi_handle_mut(), - mint: context.accounts.quote_mint.cpi_handle(), - to: context.accounts.quote_vault.cpi_handle_mut(), + from: context.accounts.operator_quote.to_cpi_handle_mut(), + mint: context.accounts.quote_mint.to_cpi_handle(), + to: context.accounts.quote_vault.to_cpi_handle_mut(), authority: context.accounts.operator.cpi_handle(), }, ), diff --git a/finance/prop-amm/anchor/programs/prop-amm/src/instructions/swap.rs b/finance/prop-amm/anchor/programs/prop-amm/src/instructions/swap.rs index 122bebb71..30087c746 100644 --- a/finance/prop-amm/anchor/programs/prop-amm/src/instructions/swap.rs +++ b/finance/prop-amm/anchor/programs/prop-amm/src/instructions/swap.rs @@ -125,9 +125,9 @@ pub fn handle_swap( CpiContext::new( context.accounts.token_program.address(), TransferChecked { - from: context.accounts.trader_quote.cpi_handle_mut(), - mint: context.accounts.quote_mint.cpi_handle(), - to: context.accounts.quote_vault.cpi_handle_mut(), + from: context.accounts.trader_quote.to_cpi_handle_mut(), + mint: context.accounts.quote_mint.to_cpi_handle(), + to: context.accounts.quote_vault.to_cpi_handle_mut(), authority: context.accounts.trader.cpi_handle(), }, ), @@ -138,9 +138,9 @@ pub fn handle_swap( CpiContext::new_with_signer( context.accounts.token_program.address(), TransferChecked { - from: context.accounts.base_vault.cpi_handle_mut(), - mint: context.accounts.base_mint.cpi_handle(), - to: context.accounts.trader_base.cpi_handle_mut(), + from: context.accounts.base_vault.to_cpi_handle_mut(), + mint: context.accounts.base_mint.to_cpi_handle(), + to: context.accounts.trader_base.to_cpi_handle_mut(), authority: context.accounts.market_authority.cpi_handle(), }, &[authority_seeds], @@ -154,9 +154,9 @@ pub fn handle_swap( CpiContext::new( context.accounts.token_program.address(), TransferChecked { - from: context.accounts.trader_base.cpi_handle_mut(), - mint: context.accounts.base_mint.cpi_handle(), - to: context.accounts.base_vault.cpi_handle_mut(), + from: context.accounts.trader_base.to_cpi_handle_mut(), + mint: context.accounts.base_mint.to_cpi_handle(), + to: context.accounts.base_vault.to_cpi_handle_mut(), authority: context.accounts.trader.cpi_handle(), }, ), @@ -167,9 +167,9 @@ pub fn handle_swap( CpiContext::new_with_signer( context.accounts.token_program.address(), TransferChecked { - from: context.accounts.quote_vault.cpi_handle_mut(), - mint: context.accounts.quote_mint.cpi_handle(), - to: context.accounts.trader_quote.cpi_handle_mut(), + from: context.accounts.quote_vault.to_cpi_handle_mut(), + mint: context.accounts.quote_mint.to_cpi_handle(), + to: context.accounts.trader_quote.to_cpi_handle_mut(), authority: context.accounts.market_authority.cpi_handle(), }, &[authority_seeds], diff --git a/finance/prop-amm/anchor/programs/prop-amm/src/instructions/withdraw_inventory.rs b/finance/prop-amm/anchor/programs/prop-amm/src/instructions/withdraw_inventory.rs index f4d117980..cc4c7800d 100644 --- a/finance/prop-amm/anchor/programs/prop-amm/src/instructions/withdraw_inventory.rs +++ b/finance/prop-amm/anchor/programs/prop-amm/src/instructions/withdraw_inventory.rs @@ -44,9 +44,9 @@ pub fn handle_withdraw_inventory( CpiContext::new_with_signer( context.accounts.token_program.address(), TransferChecked { - from: context.accounts.base_vault.cpi_handle_mut(), - mint: context.accounts.base_mint.cpi_handle(), - to: context.accounts.operator_base.cpi_handle_mut(), + from: context.accounts.base_vault.to_cpi_handle_mut(), + mint: context.accounts.base_mint.to_cpi_handle(), + to: context.accounts.operator_base.to_cpi_handle_mut(), authority: context.accounts.market_authority.cpi_handle(), }, &[authority_seeds], @@ -61,9 +61,9 @@ pub fn handle_withdraw_inventory( CpiContext::new_with_signer( context.accounts.token_program.address(), TransferChecked { - from: context.accounts.quote_vault.cpi_handle_mut(), - mint: context.accounts.quote_mint.cpi_handle(), - to: context.accounts.operator_quote.cpi_handle_mut(), + from: context.accounts.quote_vault.to_cpi_handle_mut(), + mint: context.accounts.quote_mint.to_cpi_handle(), + to: context.accounts.operator_quote.to_cpi_handle_mut(), authority: context.accounts.market_authority.cpi_handle(), }, &[authority_seeds], From 2c18e42058c6fff00900dbf5580a3a660422ef71 Mon Sep 17 00:00:00 2001 From: Claude Date: Sun, 16 Aug 2026 02:51:03 +0000 Subject: [PATCH 53/59] vault-strategy: 22/22 tests pass under Anchor v2 Boxed accounts take `to_cpi_handle*`, the strategy PDA releases its data borrow while it signs, and the hand-built handles over copied AccountViews go back to the wrappers' own, a mutable data account is marked exclusively borrowed, so a copy trips the runtime check the wrapper's handle relaxes. --- .../src/instructions/swap_asset_for_usdc.rs | 10 +++---- .../src/instructions/swap_usdc_for_asset.rs | 10 +++---- .../src/instructions/collect_fees.rs | 11 ++++++-- .../src/instructions/deposit.rs | 27 ++++++++++++------- .../src/instructions/rebalance.rs | 27 ++++++++++++------- .../src/instructions/withdraw.rs | 26 +++++++----------- .../vault-strategy/tests/vault_strategy.rs | 4 ++- 7 files changed, 67 insertions(+), 48 deletions(-) diff --git a/finance/vault-strategy/anchor/programs/mock-swap-router/src/instructions/swap_asset_for_usdc.rs b/finance/vault-strategy/anchor/programs/mock-swap-router/src/instructions/swap_asset_for_usdc.rs index 0a84d8c44..4145db113 100644 --- a/finance/vault-strategy/anchor/programs/mock-swap-router/src/instructions/swap_asset_for_usdc.rs +++ b/finance/vault-strategy/anchor/programs/mock-swap-router/src/instructions/swap_asset_for_usdc.rs @@ -87,8 +87,8 @@ pub fn handle_swap_asset_for_usdc( // Burn asset tokens from caller let burn_accounts = Burn { - mint: context.accounts.asset_mint.cpi_handle_mut(), - from: context.accounts.caller_asset_account.cpi_handle_mut(), + mint: context.accounts.asset_mint.to_cpi_handle_mut(), + from: context.accounts.caller_asset_account.to_cpi_handle_mut(), authority: context.accounts.caller.cpi_handle(), }; burn( @@ -101,9 +101,9 @@ pub fn handle_swap_asset_for_usdc( let signer_seeds: &[&[&[u8]]] = &[&[b"router_authority", &[router_authority_bump]]]; let transfer_accounts = TransferChecked { - from: context.accounts.router_usdc_treasury.cpi_handle_mut(), - mint: context.accounts.usdc_mint.cpi_handle(), - to: context.accounts.caller_usdc_account.cpi_handle_mut(), + from: context.accounts.router_usdc_treasury.to_cpi_handle_mut(), + mint: context.accounts.usdc_mint.to_cpi_handle(), + to: context.accounts.caller_usdc_account.to_cpi_handle_mut(), authority: context.accounts.router_authority.cpi_handle(), }; transfer_checked( diff --git a/finance/vault-strategy/anchor/programs/mock-swap-router/src/instructions/swap_usdc_for_asset.rs b/finance/vault-strategy/anchor/programs/mock-swap-router/src/instructions/swap_usdc_for_asset.rs index a91cadec1..7d0955890 100644 --- a/finance/vault-strategy/anchor/programs/mock-swap-router/src/instructions/swap_usdc_for_asset.rs +++ b/finance/vault-strategy/anchor/programs/mock-swap-router/src/instructions/swap_usdc_for_asset.rs @@ -91,9 +91,9 @@ pub fn handle_swap_usdc_for_asset( // Transfer USDC from caller to router treasury let transfer_accounts = TransferChecked { - from: context.accounts.caller_usdc_account.cpi_handle_mut(), - mint: context.accounts.usdc_mint.cpi_handle(), - to: context.accounts.router_usdc_treasury.cpi_handle_mut(), + from: context.accounts.caller_usdc_account.to_cpi_handle_mut(), + mint: context.accounts.usdc_mint.to_cpi_handle(), + to: context.accounts.router_usdc_treasury.to_cpi_handle_mut(), authority: context.accounts.caller.cpi_handle(), }; let cpi_ctx = CpiContext::new(context.accounts.token_program.address(), transfer_accounts); @@ -108,8 +108,8 @@ pub fn handle_swap_usdc_for_asset( let signer_seeds: &[&[&[u8]]] = &[&[b"router_authority", &[router_authority_bump]]]; let mint_accounts = MintTo { - mint: context.accounts.asset_mint.cpi_handle_mut(), - to: context.accounts.caller_asset_account.cpi_handle_mut(), + mint: context.accounts.asset_mint.to_cpi_handle_mut(), + to: context.accounts.caller_asset_account.to_cpi_handle_mut(), authority: context.accounts.router_authority.cpi_handle(), }; let cpi_ctx = CpiContext::new_with_signer( diff --git a/finance/vault-strategy/anchor/programs/vault-strategy/src/instructions/collect_fees.rs b/finance/vault-strategy/anchor/programs/vault-strategy/src/instructions/collect_fees.rs index bec362e61..4a34f321a 100644 --- a/finance/vault-strategy/anchor/programs/vault-strategy/src/instructions/collect_fees.rs +++ b/finance/vault-strategy/anchor/programs/vault-strategy/src/instructions/collect_fees.rs @@ -89,10 +89,15 @@ pub fn handle_collect_fees(context: &mut Context) let index_bytes = strategy_index.to_le_bytes(); let signer_seeds: &[&[&[u8]]] = &[&[b"strategy", index_bytes.as_ref(), &[strategy_bump]]]; + // `strategy` signs the CPI(s) below. It is a data account holding a live + // borrow on its buffer, which the runtime would reject when the CPI borrows + // the same account — so hand the borrow back for the duration. + context.accounts.strategy.release_borrow()?; + let mint_accounts = MintTo { - mint: context.accounts.share_mint.cpi_handle_mut(), + mint: context.accounts.share_mint.to_cpi_handle_mut(), to: context.accounts.manager_share_account.cpi_handle_mut(), - authority: context.accounts.strategy.cpi_handle(), + authority: context.accounts.strategy.to_cpi_handle(), }; let cpi_ctx = CpiContext::new_with_signer( context.accounts.token_program.address(), @@ -101,5 +106,7 @@ pub fn handle_collect_fees(context: &mut Context) ); mint_to(cpi_ctx, fee_shares)?; + context.accounts.strategy.reacquire_borrow_mut()?; + Ok(()) } diff --git a/finance/vault-strategy/anchor/programs/vault-strategy/src/instructions/deposit.rs b/finance/vault-strategy/anchor/programs/vault-strategy/src/instructions/deposit.rs index 4bc009eba..4c08dc6e8 100644 --- a/finance/vault-strategy/anchor/programs/vault-strategy/src/instructions/deposit.rs +++ b/finance/vault-strategy/anchor/programs/vault-strategy/src/instructions/deposit.rs @@ -174,9 +174,9 @@ pub fn handle_deposit( // Pull the depositor's USDC into the strategy's USDC vault. let transfer_accounts = TransferChecked { - from: context.accounts.depositor_usdc_account.cpi_handle_mut(), - mint: context.accounts.usdc_mint.cpi_handle(), - to: context.accounts.vault_usdc.cpi_handle_mut(), + from: context.accounts.depositor_usdc_account.to_cpi_handle_mut(), + mint: context.accounts.usdc_mint.to_cpi_handle(), + to: context.accounts.vault_usdc.to_cpi_handle_mut(), authority: context.accounts.depositor.cpi_handle(), }; let cpi_ctx = CpiContext::new(context.accounts.token_program.address(), transfer_accounts); @@ -185,6 +185,13 @@ pub fn handle_deposit( let index_bytes = strategy_index.to_le_bytes(); let signer_seeds: &[&[&[u8]]] = &[&[b"strategy", index_bytes.as_ref(), &[strategy_bump]]]; + // `strategy` signs every CPI below. It is a data account holding a live + // borrow on its buffer, which the runtime would reject when the CPI borrows + // the same account — so hand the borrow back for the duration. + // `release_borrow` flushes the pending writes and `reacquire_borrow_mut` + // re-reads them. + context.accounts.strategy.release_borrow()?; + // Deploy the deposit across the basket at its target weights. Each leg swaps a // weight-sized slice of the deposit through the router, under an oracle-computed // slippage floor. The strategy PDA signs, since the USDC leaves a vault only it @@ -234,12 +241,12 @@ pub fn handle_deposit( .map_err(|_| VaultError::MathOverflow)?; let cpi_accounts = RouterSwapAccounts { - caller: context.accounts.strategy.cpi_handle(), + caller: context.accounts.strategy.to_cpi_handle(), router_config: context.accounts.router_config.cpi_handle(), asset_rate: CpiHandle::readonly(rate_account), - usdc_mint: context.accounts.usdc_mint.cpi_handle(), + usdc_mint: context.accounts.usdc_mint.to_cpi_handle(), asset_mint: CpiHandleMut::writable(&mut mint_account), - caller_usdc_account: context.accounts.vault_usdc.cpi_handle_mut(), + caller_usdc_account: context.accounts.vault_usdc.to_cpi_handle_mut(), caller_asset_account: CpiHandleMut::writable(&mut vault_account), router_usdc_treasury: context.accounts.router_usdc_treasury.cpi_handle_mut(), router_authority: context.accounts.router_authority.cpi_handle(), @@ -257,9 +264,9 @@ pub fn handle_deposit( // Mint the shares last, with the strategy PDA signing as the share mint authority. let mint_accounts = MintTo { - mint: context.accounts.share_mint.cpi_handle_mut(), - to: context.accounts.depositor_share_account.cpi_handle_mut(), - authority: context.accounts.strategy.cpi_handle(), + mint: context.accounts.share_mint.to_cpi_handle_mut(), + to: context.accounts.depositor_share_account.to_cpi_handle_mut(), + authority: context.accounts.strategy.to_cpi_handle(), }; let cpi_ctx = CpiContext::new_with_signer( context.accounts.token_program.address(), @@ -268,5 +275,7 @@ pub fn handle_deposit( ); mint_to(cpi_ctx, shares_to_mint)?; + context.accounts.strategy.reacquire_borrow_mut()?; + Ok(()) } diff --git a/finance/vault-strategy/anchor/programs/vault-strategy/src/instructions/rebalance.rs b/finance/vault-strategy/anchor/programs/vault-strategy/src/instructions/rebalance.rs index 0ade7b561..2680dcec3 100644 --- a/finance/vault-strategy/anchor/programs/vault-strategy/src/instructions/rebalance.rs +++ b/finance/vault-strategy/anchor/programs/vault-strategy/src/instructions/rebalance.rs @@ -164,15 +164,20 @@ pub fn handle_rebalance( let index_bytes = strategy_index.to_le_bytes(); let signer_seeds: &[&[&[u8]]] = &[&[b"strategy", index_bytes.as_ref(), &[strategy_bump]]]; + // `strategy` signs the CPI(s) below. It is a data account holding a live + // borrow on its buffer, which the runtime would reject when the CPI borrows + // the same account — so hand the borrow back for the duration. + context.accounts.strategy.release_borrow()?; + // Step 1: sell basket token -> USDC let sell_cpi_accounts = RouterSellAccounts { - caller: context.accounts.strategy.cpi_handle(), + caller: context.accounts.strategy.to_cpi_handle(), router_config: context.accounts.router_config.cpi_handle(), asset_rate: context.accounts.sell_rate.cpi_handle(), - usdc_mint: context.accounts.usdc_mint.cpi_handle(), - asset_mint: context.accounts.sell_mint.cpi_handle_mut(), - caller_asset_account: context.accounts.vault_sell.cpi_handle_mut(), - caller_usdc_account: context.accounts.vault_usdc.cpi_handle_mut(), + usdc_mint: context.accounts.usdc_mint.to_cpi_handle(), + asset_mint: context.accounts.sell_mint.to_cpi_handle_mut(), + caller_asset_account: context.accounts.vault_sell.to_cpi_handle_mut(), + caller_usdc_account: context.accounts.vault_usdc.to_cpi_handle_mut(), router_usdc_treasury: context.accounts.router_usdc_treasury.cpi_handle_mut(), router_authority: context.accounts.router_authority.cpi_handle(), associated_token_program: context.accounts.associated_token_program.cpi_handle(), @@ -191,13 +196,13 @@ pub fn handle_rebalance( // Step 2: buy basket token with USDC let buy_cpi_accounts = RouterBuyAccounts { - caller: context.accounts.strategy.cpi_handle(), + caller: context.accounts.strategy.to_cpi_handle(), router_config: context.accounts.router_config.cpi_handle(), asset_rate: context.accounts.buy_rate.cpi_handle(), - usdc_mint: context.accounts.usdc_mint.cpi_handle(), - asset_mint: context.accounts.buy_mint.cpi_handle_mut(), - caller_usdc_account: context.accounts.vault_usdc.cpi_handle_mut(), - caller_asset_account: context.accounts.vault_buy.cpi_handle_mut(), + usdc_mint: context.accounts.usdc_mint.to_cpi_handle(), + asset_mint: context.accounts.buy_mint.to_cpi_handle_mut(), + caller_usdc_account: context.accounts.vault_usdc.to_cpi_handle_mut(), + caller_asset_account: context.accounts.vault_buy.to_cpi_handle_mut(), router_usdc_treasury: context.accounts.router_usdc_treasury.cpi_handle_mut(), router_authority: context.accounts.router_authority.cpi_handle(), associated_token_program: context.accounts.associated_token_program.cpi_handle(), @@ -214,5 +219,7 @@ pub fn handle_rebalance( minimum_buy_amount, )?; + context.accounts.strategy.reacquire_borrow_mut()?; + Ok(()) } diff --git a/finance/vault-strategy/anchor/programs/vault-strategy/src/instructions/withdraw.rs b/finance/vault-strategy/anchor/programs/vault-strategy/src/instructions/withdraw.rs index f0cb8c1f5..ab99d3cf8 100644 --- a/finance/vault-strategy/anchor/programs/vault-strategy/src/instructions/withdraw.rs +++ b/finance/vault-strategy/anchor/programs/vault-strategy/src/instructions/withdraw.rs @@ -111,29 +111,23 @@ pub fn handle_withdraw( // per-account views below borrow `context.accounts`. let remaining = context.remaining_accounts()?; - // Hoist owned account-info handles for every CPI up front, so the asset loop - // can borrow remaining_accounts without also re-borrowing `context.accounts`. // `strategy` signs the payouts below. It is a data account holding a live // borrow on its buffer, so release it across the CPIs and take it back // afterwards — the runtime rejects a CPI that borrows an account we hold. context.accounts.strategy.release_borrow()?; - // `AccountView` is Copy; each copy still points at the same account, and the - // typed handles below carry the borrow provenance v2 wants. + // Every other account here goes through its own wrapper handle. A handle + // built by hand over a copy of the `AccountView` keeps the runtime borrow + // check on, and a mutable data account is marked exclusively borrowed, so + // the copy would be rejected where the wrapper's handle is not. let strategy_view = *context.accounts.strategy.account(); - let mut share_mint_view = *context.accounts.share_mint.account(); - let usdc_mint_view = *context.accounts.usdc_mint.account(); - let mut vault_usdc_view = *context.accounts.vault_usdc.account(); - let user_view = *context.accounts.user.account(); - let mut user_share_view = *context.accounts.user_share_account.account(); - let mut user_usdc_view = *context.accounts.user_usdc_account.account(); let token_program_key = context.accounts.token_program.address(); // Burn the user's shares. let burn_accounts = Burn { - mint: CpiHandleMut::writable(&mut share_mint_view), - from: CpiHandleMut::writable(&mut user_share_view), - authority: CpiHandle::readonly(&user_view), + mint: context.accounts.share_mint.to_cpi_handle_mut(), + from: context.accounts.user_share_account.to_cpi_handle_mut(), + authority: context.accounts.user.cpi_handle(), }; burn( CpiContext::new(token_program_key, burn_accounts), @@ -143,9 +137,9 @@ pub fn handle_withdraw( // USDC payout. if amount_usdc > 0 { let transfer_accounts = TransferChecked { - from: CpiHandleMut::writable(&mut vault_usdc_view), - mint: CpiHandle::readonly(&usdc_mint_view), - to: CpiHandleMut::writable(&mut user_usdc_view), + from: context.accounts.vault_usdc.to_cpi_handle_mut(), + mint: context.accounts.usdc_mint.to_cpi_handle(), + to: context.accounts.user_usdc_account.to_cpi_handle_mut(), authority: CpiHandle::readonly(&strategy_view), }; transfer_checked( diff --git a/finance/vault-strategy/anchor/programs/vault-strategy/tests/vault_strategy.rs b/finance/vault-strategy/anchor/programs/vault-strategy/tests/vault_strategy.rs index 1d51f22c6..392883277 100644 --- a/finance/vault-strategy/anchor/programs/vault-strategy/tests/vault_strategy.rs +++ b/finance/vault-strategy/anchor/programs/vault-strategy/tests/vault_strategy.rs @@ -5,6 +5,9 @@ use { }, anchor_spl::token::spl_token, litesvm::LiteSVM, + solana_account::Account as SolanaAccount, + // LiteSVM's get_sysvar / set_sysvar want the host-side Clock, not pinocchio's. + solana_clock::Clock, solana_keypair::Keypair, solana_kite::{ create_associated_token_account, create_token_mint, create_wallet, @@ -12,7 +15,6 @@ use { send_transaction_from_instructions, }, solana_signer::Signer, - solana_account::Account as SolanaAccount, }; fn token_program_id() -> Address { From 34de4dff478da1184ec1f6b4569a603b8eac1fdf Mon Sep 17 00:00:00 2001 From: Claude Date: Sun, 16 Aug 2026 04:19:09 +0000 Subject: [PATCH 54/59] docs: record the multi-program build hazard and the final tally --- CHANGELOG.md | 3 +++ docs/anchor-v2-migration.md | 12 ++++++++++++ 2 files changed, 15 insertions(+) diff --git a/CHANGELOG.md b/CHANGELOG.md index 2ecf8500c..57d63ad27 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -6,6 +6,9 @@ The format is based on [Keep a Changelog](https://keepachangelog.com/en/1.1.0/). ## [2026-08-16] - Every Anchor example on Anchor v2.0.0-rc.1 +All 55 Anchor examples build and pass their tests on 2.0.0-rc.1 (304 tests), +and `cargo fmt --check` and `cargo clippy -- -D warnings` are clean. + ### Changed - The remaining 39 Anchor examples — all of `tokens/`, `finance/` and diff --git a/docs/anchor-v2-migration.md b/docs/anchor-v2-migration.md index c4ce77cd2..376eafe53 100644 --- a/docs/anchor-v2-migration.md +++ b/docs/anchor-v2-migration.md @@ -418,6 +418,18 @@ Tests that decode account bytes with borsh keep working, because that grew explicit padding needs that padding mirrored in the test's decode struct, since `try_from_slice` rejects trailing bytes. +## Building a workspace with more than one program + +`cargo-build-sbf` at an anchor workspace root builds every member in one +invocation, and cargo unifies features across them. A program that depends on a +sibling with `features = ["cpi"]` — which implies `no-entrypoint` — therefore +makes that sibling's *own* `.so` build with `no-entrypoint` too, and in v2 that +exports its dispatch as `__anchor_dispatch` rather than `entrypoint`. The +result loads with `ProgramLoad("Entrypoint out of bounds")`. + +`anchor build` builds each program separately and is unaffected. Anything else +driving `cargo-build-sbf` has to do the same. + ## Toolchain CI installs the CLI with `avm install 2.0.0-rc.1`; it is a pre-release, so it From 44f7c35dd35cf8c102702c9ce56fde36a9f13620 Mon Sep 17 00:00:00 2001 From: Claude Date: Mon, 17 Aug 2026 13:14:51 +0000 Subject: [PATCH 55/59] Remove every avoidable unsafe block from the Anchor programs Four sites reached for `unsafe` where v2 has a safe route: - `token-extensions/interest-bearing` read the InterestBearingConfig extension through `borrow_unchecked`. anchor-spl exposes `TokenInterfaceAccountExtensions::get_extension::()`, which parses the TLV through the borrow the wrapper already holds and checks Token-2022 ownership on the way. `check_mint_data` splits into `check_rate_authority` with two call routes: `initialize`'s mint is a `Signer`, which holds no data borrow, so it reads the buffer directly; `update_rate`'s is an `InterfaceAccount` and uses the accessor. - `token-extensions/metadata` could not use that accessor, which is bounded on `Pod`, because `TokenMetadata` is variable-length. The mint is now an `UncheckedAccount` in the derive and is loaded as an `InterfaceAccount` in the handler. `AnchorAccount::load` is safe, runs the same validation the derive would have run, and registers a shared borrow rather than an exclusive one, which leaves room for the TLV read. The `mut` stays so the IDL still marks the account writable. - `lending`, `prop-amm` and `perpetual-futures` read the LastRestartSlot sysvar through `solana-define-syscall` into a `MaybeUninit`. Replaced with `pinocchio::sysvars::get_sysvar`, a safe wrapper over the same syscall that is a no-op off-chain, so the `cfg` split goes away with it. - `transfer-tokens`' `transfer` carried `unsafe(dup)` on the sender for an aliasing case that never occurs: the test funds a fresh keypair as the recipient. Restored to `mut`, so the duplicate-account check stays live. What is left is the transfer-hook entrypoints, where `unsafe extern "C"` is the loader ABI, and order-book's `load_mut` on remaining_accounts, which has no safe equivalent. Both are now documented with why. docs/anchor-v2-migration.md gains the safe alternatives, the reasoning behind the duplicate-mutable-account rule, and an inventory of what still needs `unsafe`. Tests: metadata 2, interest-bearing 2, transfer-tokens 2, token-minter 3, lending 24, perpetual-futures 26, prop-amm 22, order-book 28. --- docs/anchor-v2-migration.md | 85 +++++++++++++++++-- .../anchor/programs/lending/Cargo.toml | 4 +- .../src/instructions/liquidate_obligation.rs | 5 +- .../programs/lending/src/last_restart.rs | 48 +++-------- .../anchor/programs/lending/src/lib.rs | 2 +- .../programs/lending/tests/common/mod.rs | 4 +- .../src/instructions/place_order.rs | 12 ++- .../programs/perpetual-futures/Cargo.toml | 8 +- .../src/instructions/initialize_pool.rs | 2 +- .../perpetual-futures/src/last_restart.rs | 48 +++-------- .../programs/perpetual-futures/src/lib.rs | 6 +- .../perpetual-futures/src/state/oracle.rs | 2 +- .../perpetual-futures/src/state/position.rs | 4 +- .../tests/test_perpetual_futures.rs | 16 ++-- .../anchor/programs/prop-amm/Cargo.toml | 8 +- .../programs/prop-amm/src/last_restart.rs | 48 +++-------- .../anchor/programs/prop-amm/src/lib.rs | 2 +- .../programs/prop-amm/src/quote_math.rs | 5 +- .../programs/prop-amm/src/state/oracle.rs | 2 +- .../programs/prop-amm/tests/test_prop_amm.rs | 72 +++++++++------- .../src/instructions/initialize.rs | 20 ++++- .../src/instructions/update_rate.rs | 15 +++- .../programs/interest-bearing/src/lib.rs | 28 ++---- .../metadata/src/instructions/update_field.rs | 30 ++++--- .../src/instructions/transfer.rs | 6 +- 25 files changed, 255 insertions(+), 227 deletions(-) diff --git a/docs/anchor-v2-migration.md b/docs/anchor-v2-migration.md index 376eafe53..788191e18 100644 --- a/docs/anchor-v2-migration.md +++ b/docs/anchor-v2-migration.md @@ -222,16 +222,65 @@ where `try_borrow_mut` on a copied view would be rejected. A **`mut`** data account is different. Loading one sets pinocchio's exclusive sentinel (`borrow_state == 0`), so *any* `try_borrow()` on it fails — the -wrapper itself reads through `borrow_unchecked`. Two ways out, in order of +wrapper itself reads through `borrow_unchecked`. Three ways out, in order of preference: -1. Drop the `mut` if the account is not actually written. A mint passed to +1. **For a Token-2022 extension, use anchor-spl's accessor.** + `TokenInterfaceAccountExtensions::get_extension::()` on an + `InterfaceAccount` or `InterfaceAccount` parses the TLV + through the borrow the wrapper already holds, and checks the account is + owned by Token-2022 on the way. It is bounded on `Pod`, so it covers every + fixed-size extension but not a variable-length one like `TokenMetadata`. +2. **Drop the `mut`** if the account is not actually written. A mint passed to `transfer_checked_with_fee` is read-only: the withheld fee accrues on the destination token account. -2. Read through the exclusive borrow you already hold: - `unsafe { account.account().borrow_unchecked() }`. Sound whenever the - instruction holds that borrow for its whole duration and hands out no second - one — which is the case for anything reached through `ctx.accounts`. +3. **Declare the field `UncheckedAccount` and load the typed wrapper by hand.** + `AnchorAccount::load` is safe, runs exactly the validation the derive would + have run, and registers a *shared* borrow rather than an exclusive one, so + an ordinary `try_borrow()` still has room: + + ```rust + /// CHECK: loaded and validated as an `InterfaceAccount` below. + #[account(mut)] + pub mint_account: UncheckedAccount, + ... + let mint = InterfaceAccount::::load(*ctx.accounts.mint_account.account())?; + let buffer = mint.account().try_borrow()?; + ``` + + Keep the `mut`: it is what marks the account writable in the IDL, which the + client needs for the CPI that writes to it. `cpi_handle_mut()` on an + `UncheckedAccount` checks the runtime writable flag, not the wrapper's own + mutability. Drop the loaded wrapper before the CPI so its shared borrow is + released. This is what `token-extensions/metadata` does to reach + `TokenMetadata`, which `get_extension` cannot. + +`unsafe { account.account().borrow_unchecked() }` — reading through the +exclusive borrow you already hold — is what `get_extension` does internally, and +it is sound whenever the instruction holds that borrow for its whole duration +and hands out no second one. It is nonetheless not needed anywhere in this +repository's Anchor programs, and it should stay that way: one of the three +options above has covered every case so far. + +## What still needs `unsafe` + +Three Anchor programs read the `LastRestartSlot` sysvar, which pinocchio has no +typed accessor for. Call **`pinocchio::sysvars::get_sysvar(&mut buf, &id, 0)`** +rather than the `sol_get_sysvar` syscall directly: it is a safe wrapper, and +off-chain it is a no-op that leaves the buffer zeroed, so IDL and client builds +read "the cluster has never restarted" without a `cfg` split of their own. + +After that, two kinds of `unsafe` are left in the Anchor programs, and neither +has a safe equivalent in v2: + +| Site | Why | +| --- | --- | +| `transfer-hook/*/anchor/…/entrypoint.rs` | `pub unsafe extern "C" fn entrypoint` is the loader ABI. These programs claim the entrypoint themselves to remap an SPL interface discriminator, so the `unsafe` the `#[program]` macro would have hidden is written out. | +| `order-book/…/place_order.rs` | `AnchorAccount::load_mut` is an `unsafe fn` on the trait, and it is the only way to get a writable typed wrapper for an account arriving through `remaining_accounts`. `BorshAccount`'s implementation borrows through the checked `try_borrow_mut`, so a duplicate fails with `AccountBorrowFailed` rather than aliasing. | + +Any lint that bans `unsafe` repository-wide has to exempt those, plus the +Quasar and pinocchio programs, where raw zero-copy access is the point of the +example rather than an escape hatch. ## Instruction discriminators, and programs that implement an interface @@ -270,6 +319,30 @@ The catch: the walker flags **both** indices of a duplicate, so marking only the second one still leaves the first intersecting the mutable mask. Every slot that can legitimately alias needs the constraint, including a `payer`. +Why the rule exists: v1 deserialized each `Account` into an owned copy and +serialized it back at the end of the instruction. Two slots over one account +meant two independent copies, and the second write-back silently clobbered the +first — the classic self-transfer bug, where debiting one copy and crediting +the other and writing both leaves the balance higher than it started. v2 +accounts are zero-copy views into the runtime's buffer, so two mutable wrappers +over one account would be two `&mut` to the same bytes. Hence the loader +rejects rather than warns. + +The `unsafe` is not decoration, so check two things before reaching for it: + +1. **That the accounts can actually alias.** If no caller ever passes the same + address twice, the constraint disables a live check for nothing. Leave it + off. `transfer-tokens`' `transfer` is this case — its test funds a fresh + `Keypair` as the recipient. +2. **That the aliasing slots hold no deserialized state.** `Signer`, + `SystemAccount` and `UncheckedAccount` carry no copy to write back, so + there is no lost update to worry about. The two `mint` instructions here are + this case: minting to yourself makes `mint_authority` and `recipient` the + same `Signer`, and the duplication comes from the caller rather than the + program, so refusing it would make an ordinary mint fail with 2040. Two + aliasing `Account` slots are what the check is *for*, and the fix there + is to restructure the accounts so only one of them exists. + ## anchor-spl `Mint` moved from `anchor_spl::token` to `anchor_spl::mint`, and the namespaced diff --git a/finance/lending/anchor/programs/lending/Cargo.toml b/finance/lending/anchor/programs/lending/Cargo.toml index a2ff63429..a0c47fa08 100644 --- a/finance/lending/anchor/programs/lending/Cargo.toml +++ b/finance/lending/anchor/programs/lending/Cargo.toml @@ -31,8 +31,8 @@ wincode = { version = "0.5", features = ["derive"] } # `SchemaRead`/`SchemaWrite` bound fails. 2.6.1 is the last 0.5-line release. solana-address = ">=2.6, <2.7" # pinocchio ships only Clock and Rent, so LastRestartSlot is read through -# the raw sol_get_sysvar syscall (see src/last_restart.rs). -solana-define-syscall = "5.0" +# pinocchio's own `get_sysvar` wrapper (see src/last_restart.rs). +pinocchio = "0.11" anchor-spl = "2.0.0-rc.1" [dev-dependencies] diff --git a/finance/lending/anchor/programs/lending/src/instructions/liquidate_obligation.rs b/finance/lending/anchor/programs/lending/src/instructions/liquidate_obligation.rs index bac49a996..6b55aec92 100644 --- a/finance/lending/anchor/programs/lending/src/instructions/liquidate_obligation.rs +++ b/finance/lending/anchor/programs/lending/src/instructions/liquidate_obligation.rs @@ -177,7 +177,10 @@ pub fn handle_liquidate_obligation( .obligation_collateral_vault .to_cpi_handle_mut(), mint: context.accounts.collateral_share_mint.to_cpi_handle(), - to: context.accounts.liquidator_collateral_dest.to_cpi_handle_mut(), + to: context + .accounts + .liquidator_collateral_dest + .to_cpi_handle_mut(), authority: context.accounts.obligation.cpi_handle(), }, &[&seeds], diff --git a/finance/lending/anchor/programs/lending/src/last_restart.rs b/finance/lending/anchor/programs/lending/src/last_restart.rs index 7e5733620..89c11f01c 100644 --- a/finance/lending/anchor/programs/lending/src/last_restart.rs +++ b/finance/lending/anchor/programs/lending/src/last_restart.rs @@ -1,10 +1,10 @@ //! The LastRestartSlot sysvar: the slot of the most recent cluster restart, //! or 0 if the cluster has never restarted (SIMD-0047). anchor-lang v2 is //! built on pinocchio, which ships only the Clock and Rent sysvars, so this -//! program declares the 8-byte layout itself and reads it through the same -//! `sol_get_sysvar` syscall pinocchio's own sysvars use. (`solana-sysvar`'s -//! `LastRestartSlot::get` is not usable here: it is bound to that crate's -//! `Sysvar` trait, not pinocchio's.) +//! program declares the 8-byte layout itself and reads it through +//! `pinocchio::sysvars::get_sysvar`, the same syscall wrapper pinocchio's own +//! sysvars use. (`solana-sysvar`'s `LastRestartSlot::get` is not usable here: +//! it is bound to that crate's `Sysvar` trait, not pinocchio's.) //! //! Why the program reads it: a halt stops the slot count but not the wall //! clock, so after a restart an oracle price can look fresh in slots while @@ -15,8 +15,6 @@ use anchor_lang::prelude::*; /// `SysvarLastRestartS1ot1111111111111111111111`, decoded at compile time. -/// Only the on-chain branch of `get` reads it. -#[cfg_attr(not(any(target_os = "solana", target_arch = "bpf")), allow(dead_code))] const LAST_RESTART_SLOT_ID: Address = anchor_lang::address!("SysvarLastRestartS1ot1111111111111111111111"); @@ -28,7 +26,6 @@ pub struct LastRestartSlot { } const _: () = assert!(core::mem::size_of::() == 8); -const _: () = assert!(core::mem::align_of::() == 1); impl LastRestartSlot { /// Slot of the most recent cluster restart, 0 if there has never been one. @@ -37,35 +34,12 @@ impl LastRestartSlot { } pub fn get() -> Result { - let mut var = core::mem::MaybeUninit::::uninit(); - let var_addr = var.as_mut_ptr() as *mut u8; - - #[cfg(any(target_os = "solana", target_arch = "bpf"))] - // SAFETY: `var_addr` points at 8 writable bytes and the syscall - // writes exactly 8. - let result = unsafe { - solana_define_syscall::definitions::sol_get_sysvar( - &LAST_RESTART_SLOT_ID as *const _ as *const u8, - var_addr, - 0, - core::mem::size_of::() as u64, - ) - }; - - // Off-chain (IDL builds, client compilation) the sysvar reads as - // zero: the cluster has never restarted. - #[cfg(not(any(target_os = "solana", target_arch = "bpf")))] - let result: u64 = { - // SAFETY: `var_addr` points at 8 writable bytes. - unsafe { var_addr.write_bytes(0, core::mem::size_of::()) }; - 0 - }; - - match result { - // SAFETY: on success the syscall (or the zeroing above) has - // initialized all 8 bytes. - 0 => Ok(unsafe { var.assume_init() }), - _ => Err(ProgramError::UnsupportedSysvar), - } + // `pinocchio::sysvars::get_sysvar` is the safe wrapper over the + // `sol_get_sysvar` syscall. Off-chain (IDL builds, client compilation) + // it is a no-op that leaves the buffer zeroed, which reads as "the + // cluster has never restarted". + let mut last_restart_slot = [0u8; 8]; + pinocchio::sysvars::get_sysvar(&mut last_restart_slot, &LAST_RESTART_SLOT_ID, 0)?; + Ok(Self { last_restart_slot }) } } diff --git a/finance/lending/anchor/programs/lending/src/lib.rs b/finance/lending/anchor/programs/lending/src/lib.rs index e038da5a8..2662ac1ab 100644 --- a/finance/lending/anchor/programs/lending/src/lib.rs +++ b/finance/lending/anchor/programs/lending/src/lib.rs @@ -1,9 +1,9 @@ use anchor_lang::prelude::*; -pub mod last_restart; pub mod constants; pub mod errors; pub mod instructions; +pub mod last_restart; pub mod math; pub mod state; diff --git a/finance/lending/anchor/programs/lending/tests/common/mod.rs b/finance/lending/anchor/programs/lending/tests/common/mod.rs index 4edd78ff1..04f080284 100644 --- a/finance/lending/anchor/programs/lending/tests/common/mod.rs +++ b/finance/lending/anchor/programs/lending/tests/common/mod.rs @@ -134,9 +134,7 @@ impl Env { } pub fn current_slot(&self) -> u64 { - self.svm - .get_sysvar::() - .slot + self.svm.get_sysvar::().slot } /// Create a second lending market owned by `market_owner`, for tests that diff --git a/finance/order-book/anchor/programs/order-book/src/instructions/place_order.rs b/finance/order-book/anchor/programs/order-book/src/instructions/place_order.rs index eafbd147e..936f3e52d 100644 --- a/finance/order-book/anchor/programs/order-book/src/instructions/place_order.rs +++ b/finance/order-book/anchor/programs/order-book/src/instructions/place_order.rs @@ -184,9 +184,15 @@ pub fn handle_place_order( let maker_user_info = &maker_accounts[fill_index * ACCOUNTS_PER_MAKER + 1]; // v2 has no `Account::try_from`. `AnchorAccount::load_mut` is the - // equivalent for a writable account reached through remaining_accounts; - // it is unsafe because the caller must guarantee no other live `&mut` - // to the same data, which the distinct maker accounts here satisfy. + // equivalent for a writable account reached through remaining_accounts, + // and it is the only route to one: the derive cannot type a variable + // number of accounts. + // + // SAFETY: the obligation is that no other `&mut` to the same data is + // live. The trait method is unsafe because `Slab` bypasses the runtime + // borrow check; `BorshAccount` does not, taking its guard through + // `try_borrow_mut`, so an account already borrowed elsewhere in this + // handler yields `AccountBorrowFailed` instead of aliasing. let mut maker_order = unsafe { BorshAccount::::load_mut(*maker_order_info) }?; let mut maker_market_user = unsafe { BorshAccount::::load_mut(*maker_user_info) }?; diff --git a/finance/perpetual-futures/anchor/programs/perpetual-futures/Cargo.toml b/finance/perpetual-futures/anchor/programs/perpetual-futures/Cargo.toml index 950500d3e..80c7602c8 100644 --- a/finance/perpetual-futures/anchor/programs/perpetual-futures/Cargo.toml +++ b/finance/perpetual-futures/anchor/programs/perpetual-futures/Cargo.toml @@ -27,10 +27,10 @@ anchor-lang = "2.0.0-rc.1" # The `#[program]` macro expands to `wincode` paths for instruction-data # (de)serialization, so the crate has to be a direct dependency. wincode = { version = "0.5", features = ["derive"] } -# `last_restart.rs` reads the LastRestartSlot sysvar through the raw -# `sol_get_sysvar` syscall: pinocchio (and so anchor-lang v2) ships only the -# Clock and Rent sysvars. -solana-define-syscall = "5.0" +# `last_restart.rs` reads the LastRestartSlot sysvar through pinocchio's +# `get_sysvar` wrapper: pinocchio (and so anchor-lang v2) ships only the +# Clock and Rent sysvars as typed accessors. +pinocchio = "0.11" # anchor-lang 2.0.0-rc.1 is built against wincode 0.5, but solana-address 2.7 # moved to wincode 0.6. With both in the graph, `Address`'s wincode impls # belong to the version the account derives are not using and every diff --git a/finance/perpetual-futures/anchor/programs/perpetual-futures/src/instructions/initialize_pool.rs b/finance/perpetual-futures/anchor/programs/perpetual-futures/src/instructions/initialize_pool.rs index b3a8e5183..fdedfa08d 100644 --- a/finance/perpetual-futures/anchor/programs/perpetual-futures/src/instructions/initialize_pool.rs +++ b/finance/perpetual-futures/anchor/programs/perpetual-futures/src/instructions/initialize_pool.rs @@ -1,6 +1,6 @@ use anchor_lang::prelude::*; -use anchor_spl::token; use anchor_spl::mint; +use anchor_spl::token; use anchor_spl::{ associated_token::AssociatedToken, token_interface::{Mint, TokenAccount, TokenInterface}, diff --git a/finance/perpetual-futures/anchor/programs/perpetual-futures/src/last_restart.rs b/finance/perpetual-futures/anchor/programs/perpetual-futures/src/last_restart.rs index dbe90c30e..a341431f0 100644 --- a/finance/perpetual-futures/anchor/programs/perpetual-futures/src/last_restart.rs +++ b/finance/perpetual-futures/anchor/programs/perpetual-futures/src/last_restart.rs @@ -1,10 +1,10 @@ //! The LastRestartSlot sysvar: the slot of the most recent cluster restart, //! or 0 if the cluster has never restarted (SIMD-0047). anchor-lang v2 is //! built on pinocchio, which ships only the Clock and Rent sysvars, so this -//! program declares the 8-byte layout itself and reads it through the same -//! `sol_get_sysvar` syscall pinocchio's own sysvars use. (`solana-sysvar`'s -//! `LastRestartSlot::get` is not usable here: it is bound to that crate's -//! `Sysvar` trait, not pinocchio's.) +//! program declares the 8-byte layout itself and reads it through +//! `pinocchio::sysvars::get_sysvar`, the same syscall wrapper pinocchio's own +//! sysvars use. (`solana-sysvar`'s `LastRestartSlot::get` is not usable here: +//! it is bound to that crate's `Sysvar` trait, not pinocchio's.) //! //! Why the program reads it: a halt stops the slot count but not the wall //! clock, so after a restart an oracle price can look fresh in slots while @@ -15,8 +15,6 @@ use anchor_lang::prelude::*; /// `SysvarLastRestartS1ot1111111111111111111111`, decoded at compile time. -/// Only the on-chain branch of `get` reads it. -#[cfg_attr(not(any(target_os = "solana", target_arch = "bpf")), allow(dead_code))] const LAST_RESTART_SLOT_ID: Address = anchor_lang::address!("SysvarLastRestartS1ot1111111111111111111111"); @@ -28,7 +26,6 @@ pub struct LastRestartSlot { } const _: () = assert!(core::mem::size_of::() == 8); -const _: () = assert!(core::mem::align_of::() == 1); impl LastRestartSlot { /// Slot of the most recent cluster restart, 0 if there has never been one. @@ -37,35 +34,12 @@ impl LastRestartSlot { } pub fn get() -> Result { - let mut var = core::mem::MaybeUninit::::uninit(); - let var_addr = var.as_mut_ptr() as *mut u8; - - #[cfg(any(target_os = "solana", target_arch = "bpf"))] - // SAFETY: `var_addr` points at 8 writable bytes and the syscall - // writes exactly 8. - let result = unsafe { - solana_define_syscall::definitions::sol_get_sysvar( - &LAST_RESTART_SLOT_ID as *const _ as *const u8, - var_addr, - 0, - core::mem::size_of::() as u64, - ) - }; - - // Off-chain (IDL builds, client compilation) the sysvar reads as - // zero: the cluster has never restarted. - #[cfg(not(any(target_os = "solana", target_arch = "bpf")))] - let result: u64 = { - // SAFETY: `var_addr` points at 8 writable bytes. - unsafe { var_addr.write_bytes(0, core::mem::size_of::()) }; - 0 - }; - - match result { - // SAFETY: on success the syscall (or the zeroing above) has - // initialized all 8 bytes. - 0 => Ok(unsafe { var.assume_init() }), - _ => Err(ProgramError::UnsupportedSysvar), - } + // `pinocchio::sysvars::get_sysvar` is the safe wrapper over the + // `sol_get_sysvar` syscall. Off-chain (IDL builds, client compilation) + // it is a no-op that leaves the buffer zeroed, which reads as "the + // cluster has never restarted". + let mut last_restart_slot = [0u8; 8]; + pinocchio::sysvars::get_sysvar(&mut last_restart_slot, &LAST_RESTART_SLOT_ID, 0)?; + Ok(Self { last_restart_slot }) } } diff --git a/finance/perpetual-futures/anchor/programs/perpetual-futures/src/lib.rs b/finance/perpetual-futures/anchor/programs/perpetual-futures/src/lib.rs index b983697fc..cd5a722d1 100644 --- a/finance/perpetual-futures/anchor/programs/perpetual-futures/src/lib.rs +++ b/finance/perpetual-futures/anchor/programs/perpetual-futures/src/lib.rs @@ -1,8 +1,8 @@ use anchor_lang::prelude::*; mod constants; -mod last_restart; mod errors; +mod last_restart; // Public so the LiteSVM integration tests can build instruction arguments // (`PoolParameters`, `Side`) against the program's own types. pub mod instructions; @@ -72,7 +72,9 @@ pub mod perpetual_futures { /// Permissionlessly close a position whose equity has fallen to or below /// the maintenance margin. The caller earns the liquidation fee. - pub fn liquidate_position(context: &mut Context) -> Result<()> { + pub fn liquidate_position( + context: &mut Context, + ) -> Result<()> { instructions::handle_liquidate_position(context) } diff --git a/finance/perpetual-futures/anchor/programs/perpetual-futures/src/state/oracle.rs b/finance/perpetual-futures/anchor/programs/perpetual-futures/src/state/oracle.rs index ee1064a94..04b0b5057 100644 --- a/finance/perpetual-futures/anchor/programs/perpetual-futures/src/state/oracle.rs +++ b/finance/perpetual-futures/anchor/programs/perpetual-futures/src/state/oracle.rs @@ -1,5 +1,5 @@ -use anchor_lang::prelude::*; use crate::last_restart::LastRestartSlot; +use anchor_lang::prelude::*; use crate::constants::{BASIS_POINTS_DENOMINATOR, MAX_PRICE_STALENESS_SLOTS}; use crate::errors::PerpError; diff --git a/finance/perpetual-futures/anchor/programs/perpetual-futures/src/state/position.rs b/finance/perpetual-futures/anchor/programs/perpetual-futures/src/state/position.rs index 950e0a504..1f55efe60 100644 --- a/finance/perpetual-futures/anchor/programs/perpetual-futures/src/state/position.rs +++ b/finance/perpetual-futures/anchor/programs/perpetual-futures/src/state/position.rs @@ -1,6 +1,8 @@ use anchor_lang::prelude::*; -#[derive(InitSpace, Clone, Copy, PartialEq, Eq, Debug, IdlType, wincode::SchemaRead, wincode::SchemaWrite)] +#[derive( + InitSpace, Clone, Copy, PartialEq, Eq, Debug, IdlType, wincode::SchemaRead, wincode::SchemaWrite, +)] pub enum Side { Long, Short, diff --git a/finance/perpetual-futures/anchor/programs/perpetual-futures/tests/test_perpetual_futures.rs b/finance/perpetual-futures/anchor/programs/perpetual-futures/tests/test_perpetual_futures.rs index 5d6c9237e..cfd32878c 100644 --- a/finance/perpetual-futures/anchor/programs/perpetual-futures/tests/test_perpetual_futures.rs +++ b/finance/perpetual-futures/anchor/programs/perpetual-futures/tests/test_perpetual_futures.rs @@ -1,7 +1,7 @@ use { anchor_lang::{ - solana_program::instruction::Instruction, - AccountDeserialize, Address, system_program, InstructionData, ToAccountMetas, + solana_program::instruction::Instruction, system_program, AccountDeserialize, Address, + InstructionData, ToAccountMetas, }, litesvm::LiteSVM, perpetual_futures::{instructions::initialize_pool::PoolParameters, state::Pool, state::Side}, @@ -97,7 +97,8 @@ impl Market { "/../../target/deploy/mock_switchboard.so" )) .expect("mock_switchboard.so not found - run `anchor build` first"); - svm.add_program(mock_switchboard::id(), &switchboard_bytes).unwrap(); + svm.add_program(mock_switchboard::id(), &switchboard_bytes) + .unwrap(); let payer = create_wallet(&mut svm, 100_000_000_000).unwrap(); let admin = create_wallet(&mut svm, 100_000_000_000).unwrap(); @@ -226,9 +227,10 @@ impl Market { /// Simulate a cluster restart at `slot`: prices stamped at or before it /// must be rejected until the publisher posts again. fn set_last_restart_slot(&mut self, slot: u64) { - self.svm.set_sysvar(&solana_sysvar::last_restart_slot::LastRestartSlot { - last_restart_slot: slot, - }); + self.svm + .set_sysvar(&solana_sysvar::last_restart_slot::LastRestartSlot { + last_restart_slot: slot, + }); } /// Create a wallet holding `amount` collateral tokens in its associated @@ -824,7 +826,7 @@ fn test_open_rejects_price_from_before_a_restart() { Side::Long, collateral, 5_000 * ONE_USDC, - u64::MAX + u64::MAX, ) .expect("a freshly published price must be accepted after a restart"); } diff --git a/finance/prop-amm/anchor/programs/prop-amm/Cargo.toml b/finance/prop-amm/anchor/programs/prop-amm/Cargo.toml index 0a3414190..1d25f29d9 100644 --- a/finance/prop-amm/anchor/programs/prop-amm/Cargo.toml +++ b/finance/prop-amm/anchor/programs/prop-amm/Cargo.toml @@ -26,10 +26,10 @@ anchor-lang = "2.0.0-rc.1" # The `#[program]` macro expands to `wincode` paths for instruction-data # (de)serialization, so the crate has to be a direct dependency. wincode = { version = "0.5", features = ["derive"] } -# `last_restart.rs` reads the LastRestartSlot sysvar through the raw -# `sol_get_sysvar` syscall: pinocchio (and so anchor-lang v2) ships only the -# Clock and Rent sysvars. -solana-define-syscall = "5.0" +# `last_restart.rs` reads the LastRestartSlot sysvar through pinocchio's +# `get_sysvar` wrapper: pinocchio (and so anchor-lang v2) ships only the +# Clock and Rent sysvars as typed accessors. +pinocchio = "0.11" # anchor-lang 2.0.0-rc.1 is built against wincode 0.5, but solana-address 2.7 # moved to wincode 0.6. With both in the graph, `Address`'s wincode impls # belong to the version the account derives are not using and every diff --git a/finance/prop-amm/anchor/programs/prop-amm/src/last_restart.rs b/finance/prop-amm/anchor/programs/prop-amm/src/last_restart.rs index dbe90c30e..a341431f0 100644 --- a/finance/prop-amm/anchor/programs/prop-amm/src/last_restart.rs +++ b/finance/prop-amm/anchor/programs/prop-amm/src/last_restart.rs @@ -1,10 +1,10 @@ //! The LastRestartSlot sysvar: the slot of the most recent cluster restart, //! or 0 if the cluster has never restarted (SIMD-0047). anchor-lang v2 is //! built on pinocchio, which ships only the Clock and Rent sysvars, so this -//! program declares the 8-byte layout itself and reads it through the same -//! `sol_get_sysvar` syscall pinocchio's own sysvars use. (`solana-sysvar`'s -//! `LastRestartSlot::get` is not usable here: it is bound to that crate's -//! `Sysvar` trait, not pinocchio's.) +//! program declares the 8-byte layout itself and reads it through +//! `pinocchio::sysvars::get_sysvar`, the same syscall wrapper pinocchio's own +//! sysvars use. (`solana-sysvar`'s `LastRestartSlot::get` is not usable here: +//! it is bound to that crate's `Sysvar` trait, not pinocchio's.) //! //! Why the program reads it: a halt stops the slot count but not the wall //! clock, so after a restart an oracle price can look fresh in slots while @@ -15,8 +15,6 @@ use anchor_lang::prelude::*; /// `SysvarLastRestartS1ot1111111111111111111111`, decoded at compile time. -/// Only the on-chain branch of `get` reads it. -#[cfg_attr(not(any(target_os = "solana", target_arch = "bpf")), allow(dead_code))] const LAST_RESTART_SLOT_ID: Address = anchor_lang::address!("SysvarLastRestartS1ot1111111111111111111111"); @@ -28,7 +26,6 @@ pub struct LastRestartSlot { } const _: () = assert!(core::mem::size_of::() == 8); -const _: () = assert!(core::mem::align_of::() == 1); impl LastRestartSlot { /// Slot of the most recent cluster restart, 0 if there has never been one. @@ -37,35 +34,12 @@ impl LastRestartSlot { } pub fn get() -> Result { - let mut var = core::mem::MaybeUninit::::uninit(); - let var_addr = var.as_mut_ptr() as *mut u8; - - #[cfg(any(target_os = "solana", target_arch = "bpf"))] - // SAFETY: `var_addr` points at 8 writable bytes and the syscall - // writes exactly 8. - let result = unsafe { - solana_define_syscall::definitions::sol_get_sysvar( - &LAST_RESTART_SLOT_ID as *const _ as *const u8, - var_addr, - 0, - core::mem::size_of::() as u64, - ) - }; - - // Off-chain (IDL builds, client compilation) the sysvar reads as - // zero: the cluster has never restarted. - #[cfg(not(any(target_os = "solana", target_arch = "bpf")))] - let result: u64 = { - // SAFETY: `var_addr` points at 8 writable bytes. - unsafe { var_addr.write_bytes(0, core::mem::size_of::()) }; - 0 - }; - - match result { - // SAFETY: on success the syscall (or the zeroing above) has - // initialized all 8 bytes. - 0 => Ok(unsafe { var.assume_init() }), - _ => Err(ProgramError::UnsupportedSysvar), - } + // `pinocchio::sysvars::get_sysvar` is the safe wrapper over the + // `sol_get_sysvar` syscall. Off-chain (IDL builds, client compilation) + // it is a no-op that leaves the buffer zeroed, which reads as "the + // cluster has never restarted". + let mut last_restart_slot = [0u8; 8]; + pinocchio::sysvars::get_sysvar(&mut last_restart_slot, &LAST_RESTART_SLOT_ID, 0)?; + Ok(Self { last_restart_slot }) } } diff --git a/finance/prop-amm/anchor/programs/prop-amm/src/lib.rs b/finance/prop-amm/anchor/programs/prop-amm/src/lib.rs index fa09fd200..d89023ea2 100644 --- a/finance/prop-amm/anchor/programs/prop-amm/src/lib.rs +++ b/finance/prop-amm/anchor/programs/prop-amm/src/lib.rs @@ -1,8 +1,8 @@ use anchor_lang::prelude::*; mod constants; -mod last_restart; mod errors; +mod last_restart; // Public so the LiteSVM integration tests can build instruction arguments // (`MarketParameters`, `Direction`) against the program's own types. pub mod instructions; diff --git a/finance/prop-amm/anchor/programs/prop-amm/src/quote_math.rs b/finance/prop-amm/anchor/programs/prop-amm/src/quote_math.rs index 849e1c303..ef6c689c0 100644 --- a/finance/prop-amm/anchor/programs/prop-amm/src/quote_math.rs +++ b/finance/prop-amm/anchor/programs/prop-amm/src/quote_math.rs @@ -67,8 +67,9 @@ pub fn quote_out_for_base_in( let numerator = (base_in as u128) .checked_mul(bid)? .checked_mul(10u128.checked_pow(quote_decimals as u32)?)?; - let denominator = - 10u128.checked_pow(oracle_scale)?.checked_mul(10u128.checked_pow(base_decimals as u32)?)?; + let denominator = 10u128 + .checked_pow(oracle_scale)? + .checked_mul(10u128.checked_pow(base_decimals as u32)?)?; if denominator == 0 { return None; } diff --git a/finance/prop-amm/anchor/programs/prop-amm/src/state/oracle.rs b/finance/prop-amm/anchor/programs/prop-amm/src/state/oracle.rs index 6c9f18882..f7aba055b 100644 --- a/finance/prop-amm/anchor/programs/prop-amm/src/state/oracle.rs +++ b/finance/prop-amm/anchor/programs/prop-amm/src/state/oracle.rs @@ -1,5 +1,5 @@ -use anchor_lang::prelude::*; use crate::last_restart::LastRestartSlot; +use anchor_lang::prelude::*; use crate::constants::{BASIS_POINTS_DENOMINATOR, MAX_PRICE_STALENESS_SLOTS}; use crate::errors::PropAmmError; diff --git a/finance/prop-amm/anchor/programs/prop-amm/tests/test_prop_amm.rs b/finance/prop-amm/anchor/programs/prop-amm/tests/test_prop_amm.rs index fce4761c6..8ec07e9e4 100644 --- a/finance/prop-amm/anchor/programs/prop-amm/tests/test_prop_amm.rs +++ b/finance/prop-amm/anchor/programs/prop-amm/tests/test_prop_amm.rs @@ -1,7 +1,7 @@ use { anchor_lang::{ - solana_program::instruction::Instruction, - AccountDeserialize, Address, system_program, InstructionData, ToAccountMetas, + solana_program::instruction::Instruction, system_program, AccountDeserialize, Address, + InstructionData, ToAccountMetas, }, litesvm::LiteSVM, prop_amm::{ @@ -175,20 +175,12 @@ impl Market { .map_err(|_| ())?; // Fund the operator's inventory accounts. - let operator_base = create_associated_token_account( - &mut svm, - &operator.pubkey(), - &base_mint, - &payer, - ) - .unwrap(); - let operator_quote = create_associated_token_account( - &mut svm, - &operator.pubkey(), - "e_mint, - &payer, - ) - .unwrap(); + let operator_base = + create_associated_token_account(&mut svm, &operator.pubkey(), &base_mint, &payer) + .unwrap(); + let operator_quote = + create_associated_token_account(&mut svm, &operator.pubkey(), "e_mint, &payer) + .unwrap(); mint_tokens_to_token_account( &mut svm, &base_mint, @@ -271,9 +263,10 @@ impl Market { /// Simulate a cluster restart at `slot`: prices stamped at or before it /// must be rejected until the publisher posts again. fn set_last_restart_slot(&mut self, slot: u64) { - self.svm.set_sysvar(&solana_sysvar::last_restart_slot::LastRestartSlot { - last_restart_slot: slot, - }); + self.svm + .set_sysvar(&solana_sysvar::last_restart_slot::LastRestartSlot { + last_restart_slot: slot, + }); } /// Create a wallet holding `base` and `quote` minor units in associated @@ -372,9 +365,14 @@ impl Market { .to_account_metas(None), ) }; - send_transaction_from_instructions(&mut self.svm, vec![instruction], &[signer], &signer.pubkey()) - .map(|_| ()) - .map_err(|_| ()) + send_transaction_from_instructions( + &mut self.svm, + vec![instruction], + &[signer], + &signer.pubkey(), + ) + .map(|_| ()) + .map_err(|_| ()) } fn deposit_inventory(&mut self, base_amount: u64, quote_amount: u64) -> Result<(), ()> { @@ -397,9 +395,14 @@ impl Market { } .to_account_metas(None), ); - send_transaction_from_instructions(&mut self.svm, vec![instruction], &[signer], &signer.pubkey()) - .map(|_| ()) - .map_err(|_| ()) + send_transaction_from_instructions( + &mut self.svm, + vec![instruction], + &[signer], + &signer.pubkey(), + ) + .map(|_| ()) + .map_err(|_| ()) } fn set_quote(&mut self, spread_bps: u16, paused: bool) -> Result<(), ()> { @@ -441,9 +444,14 @@ impl Market { } .to_account_metas(None), ); - send_transaction_from_instructions(&mut self.svm, vec![instruction], &[trader], &trader.pubkey()) - .map(|_| ()) - .map_err(|_| ()) + send_transaction_from_instructions( + &mut self.svm, + vec![instruction], + &[trader], + &trader.pubkey(), + ) + .map(|_| ()) + .map_err(|_| ()) } fn balance(&self, token_account: &Address) -> u64 { @@ -587,9 +595,7 @@ fn test_operator_can_withdraw_everything_and_swaps_then_fail() { #[test] fn test_withdraw_more_than_inventory_fails() { let mut market = Market::default_market(); - assert!(market - .withdraw_inventory(1_001 * ONE_TOKEN, 0) - .is_err()); + assert!(market.withdraw_inventory(1_001 * ONE_TOKEN, 0).is_err()); } #[test] @@ -723,7 +729,9 @@ fn test_swap_rejects_insufficient_inventory() { // but the vault only holds 1,000 NVDAx. let quote_in = 181_681_500_000; let (whale, _, _) = market.funded_trader(0, quote_in); - assert!(market.swap(&whale, Direction::BuyBase, quote_in, 0).is_err()); + assert!(market + .swap(&whale, Direction::BuyBase, quote_in, 0) + .is_err()); } // =========================================================================== diff --git a/tokens/token-extensions/interest-bearing/anchor/programs/interest-bearing/src/instructions/initialize.rs b/tokens/token-extensions/interest-bearing/anchor/programs/interest-bearing/src/instructions/initialize.rs index 2213d0387..523792575 100644 --- a/tokens/token-extensions/interest-bearing/anchor/programs/interest-bearing/src/instructions/initialize.rs +++ b/tokens/token-extensions/interest-bearing/anchor/programs/interest-bearing/src/instructions/initialize.rs @@ -9,7 +9,14 @@ use anchor_spl::{ token_interface::{interest_bearing_mint_initialize, InterestBearingMintInitialize, Token2022}, }; -use crate::check_mint_data; +use anchor_spl::token_2022::spl_token_2022::{ + extension::{ + interest_bearing_mint::InterestBearingConfig, BaseStateWithExtensions, StateWithExtensions, + }, + state::Mint as MintState, +}; + +use crate::check_rate_authority; #[derive(Accounts)] pub struct InitializeAccountConstraints { @@ -71,9 +78,14 @@ pub fn handler(context: &mut Context, rate: i16) - Some(&context.accounts.payer.address()), // freeze authority )?; - check_mint_data( - context.accounts.mint_account.account(), - &context.accounts.payer.address(), + // The mint is a `Signer` here, which holds no borrow on the account's data, + // so the TLV is read straight from the buffer. + let payer_address = *context.accounts.payer.address(); + let mint_data = context.accounts.mint_account.account().try_borrow()?; + let mint = StateWithExtensions::::unpack(&mint_data)?; + check_rate_authority( + mint.get_extension::()?, + &payer_address, )?; Ok(()) } diff --git a/tokens/token-extensions/interest-bearing/anchor/programs/interest-bearing/src/instructions/update_rate.rs b/tokens/token-extensions/interest-bearing/anchor/programs/interest-bearing/src/instructions/update_rate.rs index 3f1dc1be2..c35712356 100644 --- a/tokens/token-extensions/interest-bearing/anchor/programs/interest-bearing/src/instructions/update_rate.rs +++ b/tokens/token-extensions/interest-bearing/anchor/programs/interest-bearing/src/instructions/update_rate.rs @@ -3,7 +3,9 @@ use anchor_spl::token_interface::{ interest_bearing_mint_update_rate, InterestBearingMintUpdateRate, Mint, Token2022, }; -use crate::check_mint_data; +use anchor_spl::token_interface::TokenInterfaceAccountExtensions; + +use crate::check_rate_authority; #[derive(Accounts)] pub struct UpdateRateAccountConstraints { @@ -28,9 +30,14 @@ pub fn handler(context: &mut Context, rate: i16) - rate, )?; - check_mint_data( - context.accounts.mint_account.account(), - &context.accounts.authority.address(), + // `mint_account` is an `InterfaceAccount` declared `mut`, so it holds + // the account's exclusive borrow and the program cannot take a second one. + // anchor-spl's accessor parses the TLV through that same borrow, and checks + // the mint is owned by Token-2022 on the way. + let authority_address = *context.accounts.authority.address(); + check_rate_authority( + context.accounts.mint_account.get_extension()?, + &authority_address, )?; Ok(()) } diff --git a/tokens/token-extensions/interest-bearing/anchor/programs/interest-bearing/src/lib.rs b/tokens/token-extensions/interest-bearing/anchor/programs/interest-bearing/src/lib.rs index 06958b56b..b15c1bfaf 100644 --- a/tokens/token-extensions/interest-bearing/anchor/programs/interest-bearing/src/lib.rs +++ b/tokens/token-extensions/interest-bearing/anchor/programs/interest-bearing/src/lib.rs @@ -1,10 +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; @@ -32,23 +27,16 @@ pub mod interest_bearing { } } -pub fn check_mint_data(mint_account_info: &AccountView, authority_key: &Address) -> Result<()> { - // The mint is declared `mut`, and v2 marks a mutable data account as - // exclusively borrowed (pinocchio's `borrow_state == 0`), so `try_borrow()` - // on it is rejected. Reading through the exclusive borrow we already hold - // is what the wrapper itself does. - // - // SAFETY: the caller holds the mint's exclusive borrow for the whole - // instruction, and this reads it without handing out a second one. - let mint_data = unsafe { mint_account_info.borrow_unchecked() }; - let mint_with_extension = StateWithExtensions::::unpack(&mint_data)?; - let extension_data = mint_with_extension.get_extension::()?; - +/// Assert the extension names `authority_key` as the account allowed to change +/// the rate. The two callers reach the extension by different routes: see +/// `initialize` for a raw TLV read, and `update_rate` for the accessor +/// anchor-spl puts on a typed mint. +pub fn check_rate_authority(config: &InterestBearingConfig, authority_key: &Address) -> Result<()> { assert_eq!( - extension_data.rate_authority, + config.rate_authority, OptionalNonZeroPubkey::try_from(Some(*authority_key))? ); - msg!("{:?}", extension_data); + msg!("{:?}", config); Ok(()) } diff --git a/tokens/token-extensions/metadata/anchor/programs/metadata/src/instructions/update_field.rs b/tokens/token-extensions/metadata/anchor/programs/metadata/src/instructions/update_field.rs index 273f45c9d..e568d1883 100644 --- a/tokens/token-extensions/metadata/anchor/programs/metadata/src/instructions/update_field.rs +++ b/tokens/token-extensions/metadata/anchor/programs/metadata/src/instructions/update_field.rs @@ -14,8 +14,16 @@ pub struct UpdateFieldAccountConstraints { #[account(mut)] pub authority: Signer, + /// CHECK: loaded and validated as an `InterfaceAccount` in the handler. + /// + /// It is declared unchecked here only so that the derive does not take the + /// account's exclusive borrow. This instruction has to read the + /// variable-length `TokenMetadata` extension out of the TLV, and a wrapper + /// loaded by `#[account(mut)]` holds an exclusive borrow that makes any + /// `try_borrow()` fail. Loading the same wrapper by hand registers a shared + /// borrow instead, which leaves room for the read. #[account(mut)] - pub mint_account: InterfaceAccount, + pub mint_account: UncheckedAccount, pub token_program: Program, pub system_program: Program, } @@ -34,16 +42,16 @@ pub fn process_update_field( msg!("Field: {:?}, Value: {}", field, value); let (current_lamports, required_lamports) = { - // Get the current state of the mint account. It is declared `mut`, and - // v2 marks a mutable data account as exclusively borrowed (pinocchio's - // `borrow_state == 0`), so `try_borrow()` on it is rejected — reading - // through the exclusive borrow we already hold is what the wrapper - // itself does. + // Validate the account as a mint. `load` runs the same owner and + // layout checks the derive would have run for an + // `InterfaceAccount` field, and registers a *shared* borrow, so + // the TLV read below is an ordinary `try_borrow()`. // - // SAFETY: this instruction holds the mint's exclusive borrow for its - // whole duration, and this reads it without handing out a second one. - let mint = *context.accounts.mint_account.account(); - let buffer = unsafe { mint.borrow_unchecked() }; + // anchor-spl's `TokenInterfaceAccountExtensions::get_extension` would + // be the shorter route, and is what the other extension examples use, + // but it is bounded on `Pod` and `TokenMetadata` is variable-length. + let mint = InterfaceAccount::::load(*context.accounts.mint_account.account())?; + let buffer = mint.account().try_borrow()?; let state = PodStateWithExtensions::::unpack(&buffer)?; // Get and update the token metadata @@ -58,7 +66,7 @@ pub fn process_update_field( // Calculate the required lamports for the new account length let required_lamports = Rent::get()?.try_minimum_balance(new_account_len)?; // Get the current lamports of the mint account - let current_lamports = mint.lamports(); + let current_lamports = mint.account().lamports(); msg!("Required lamports: {}", required_lamports); msg!("Current lamports: {}", current_lamports); diff --git a/tokens/transfer-tokens/anchor/programs/transfer-tokens/src/instructions/transfer.rs b/tokens/transfer-tokens/anchor/programs/transfer-tokens/src/instructions/transfer.rs index afe8c1197..ee4039127 100644 --- a/tokens/transfer-tokens/anchor/programs/transfer-tokens/src/instructions/transfer.rs +++ b/tokens/transfer-tokens/anchor/programs/transfer-tokens/src/instructions/transfer.rs @@ -8,11 +8,7 @@ use { #[derive(Accounts)] pub struct TransferTokensAccountConstraints { - // The sender and the recipient may be the same account (minting or - // sending to yourself). v2 rejects an account that appears twice while any - // of its slots is in the mutable mask, and `unsafe(dup)` takes this one out - // of that mask while keeping it writable. - #[account(unsafe(dup))] + #[account(mut)] pub sender: Signer, pub recipient: SystemAccount, From b3587d94fa5555994a6cec8151dc9c0aaedc6a5e Mon Sep 17 00:00:00 2001 From: Claude Date: Wed, 19 Aug 2026 19:31:46 +0000 Subject: [PATCH 56/59] anchor: take the em-dashes out of the ported comments The Solana skill bans em-dashes in code comments, and this repository's own CONTRIBUTING.md bans them in prose. The v2 port added 70 across 65 files. The 70 sites are 39 distinct comment texts, the most repeated seven times, so these are hand-written rewrites applied as fixed strings rather than a character substitution. An em-dash here almost always joined a consequence to its cause, so most become the comma the sentence already implied; a few become a colon where the second clause restates the first, parentheses where the dashes bracketed an aside, or a full stop where the clause stands alone. Comments only. The diff contains no non-comment lines. (cherry picked from commit 1d540d4d99ad2bc655ed7ac485a258b99dfaf962) --- basics/close-account/anchor/programs/close-account/Cargo.toml | 2 +- .../cross-program-invocation/anchor/programs/lever/src/lib.rs | 2 +- .../programs/anchor-program-example/src/state/page_visits.rs | 2 +- .../tests/test_program_derived_addresses.rs | 2 +- .../programs/anchor-realloc/src/instructions/initialize.rs | 2 +- .../anchor/programs/anchor-realloc/src/instructions/update.rs | 2 +- basics/rent/anchor/programs/rent-example/src/lib.rs | 2 +- .../programs/betting-market/src/instructions/claim_refund.rs | 2 +- .../betting-market/src/instructions/claim_winnings.rs | 2 +- .../programs/betting-market/src/instructions/settle_event.rs | 2 +- .../lending/src/instructions/admin/collect_protocol_fees.rs | 2 +- .../lending/src/instructions/borrow_obligation_liquidity.rs | 2 +- .../lending/src/instructions/deposit_reserve_liquidity.rs | 2 +- .../programs/lending/src/instructions/liquidate_obligation.rs | 2 +- .../lending/src/instructions/redeem_reserve_collateral.rs | 2 +- .../src/instructions/withdraw_obligation_collateral.rs | 2 +- .../programs/order-book/src/instructions/place_order.rs | 2 +- .../anchor/programs/order-book/tests/test_order_book.rs | 2 +- .../anchor/programs/fundraiser/src/instructions/checker.rs | 4 ++-- .../anchor/programs/fundraiser/src/instructions/close.rs | 4 ++-- .../programs/token-swap/src/instructions/swap_tokens.rs | 2 +- finance/token-swap/anchor/programs/token-swap/src/lib.rs | 2 +- .../programs/vault-strategy/src/instructions/collect_fees.rs | 2 +- .../programs/vault-strategy/src/instructions/deposit.rs | 2 +- .../vault-strategy/src/instructions/initialize_strategy.rs | 2 +- .../programs/vault-strategy/src/instructions/rebalance.rs | 2 +- .../programs/vault-strategy/src/instructions/withdraw.rs | 2 +- tokens/create-token/anchor/programs/create-token/src/lib.rs | 2 +- tokens/nft-minter/anchor/programs/nft-minter/src/lib.rs | 2 +- .../programs/mint-nft/src/instructions/create_collection.rs | 2 +- .../anchor/programs/mint-nft/src/instructions/mint_nft.rs | 2 +- .../programs/mint-nft/src/instructions/verify_collection.rs | 2 +- .../anchor/programs/token-minter/src/instructions/create.rs | 2 +- .../anchor/programs/token-minter/src/instructions/mint.rs | 2 +- .../cpi-guard/anchor/programs/cpi-guard/src/lib.rs | 4 ++-- .../programs/group/src/instructions/test_initialize_group.rs | 2 +- .../anchor/programs/immutable-owner/src/lib.rs | 2 +- .../programs/memo-transfer/src/instructions/initialize.rs | 2 +- .../anchor/programs/metadata/src/instructions/initialize.rs | 2 +- .../programs/metadata/src/instructions/update_authority.rs | 2 +- .../anchor/programs/metadata/src/instructions/update_field.rs | 2 +- .../programs/mint-close-authority/src/instructions/close.rs | 2 +- .../anchor-example/anchor/programs/extension_nft/Cargo.toml | 2 +- .../programs/extension_nft/src/instructions/mint_nft.rs | 4 ++-- .../anchor/programs/extension_nft/tests/test_extension_nft.rs | 2 +- .../anchor/programs/transfer-fee/src/instructions/harvest.rs | 2 +- .../anchor/programs/transfer-fee/src/instructions/transfer.rs | 2 +- .../anchor/programs/transfer-hook/src/entrypoint.rs | 2 +- .../anchor/programs/transfer-hook/src/lib.rs | 2 +- .../anchor/programs/abl-token/src/entrypoint.rs | 4 ++-- .../anchor/programs/abl-token/src/instructions/init_mint.rs | 2 +- .../anchor/programs/abl-token/src/lib.rs | 2 +- .../counter/anchor/programs/transfer-hook/src/entrypoint.rs | 2 +- .../counter/anchor/programs/transfer-hook/src/lib.rs | 2 +- .../anchor/programs/transfer-hook/src/entrypoint.rs | 2 +- .../hello-world/anchor/programs/transfer-hook/src/lib.rs | 2 +- .../anchor/programs/transfer-hook/src/entrypoint.rs | 2 +- .../transfer-cost/anchor/programs/transfer-hook/src/lib.rs | 2 +- .../anchor/programs/transfer-switch/src/entrypoint.rs | 2 +- .../transfer-switch/src/instructions/configure_admin.rs | 2 +- .../anchor/programs/transfer-switch/src/lib.rs | 2 +- .../whitelist/anchor/programs/transfer-hook/src/entrypoint.rs | 2 +- .../whitelist/anchor/programs/transfer-hook/src/lib.rs | 2 +- .../anchor/programs/token-minter/src/instructions/create.rs | 2 +- .../programs/transfer-tokens/src/instructions/create.rs | 2 +- 65 files changed, 70 insertions(+), 70 deletions(-) diff --git a/basics/close-account/anchor/programs/close-account/Cargo.toml b/basics/close-account/anchor/programs/close-account/Cargo.toml index ee6627d7c..f1a78ee1f 100644 --- a/basics/close-account/anchor/programs/close-account/Cargo.toml +++ b/basics/close-account/anchor/programs/close-account/Cargo.toml @@ -26,7 +26,7 @@ anchor-lang = "2.0.0-rc.1" wincode = { version = "0.5", features = ["derive"] } # anchor-lang 2.0.0-rc.1 is built against wincode 0.5, but solana-address 2.7 # moved to wincode 0.6. Both end up in the graph, and `Address`'s wincode impls -# then belong to the version the `#[account(borsh)]` derive is not using — so an +# then belong to the version the `#[account(borsh)]` derive is not using, so an # `Address` field fails to satisfy `SchemaRead`/`SchemaWrite`. 2.6.1 is the last # release on the 0.5 line. solana-address = ">=2.6, <2.7" diff --git a/basics/cross-program-invocation/anchor/programs/lever/src/lib.rs b/basics/cross-program-invocation/anchor/programs/lever/src/lib.rs index 2a09b4663..14b414c20 100644 --- a/basics/cross-program-invocation/anchor/programs/lever/src/lib.rs +++ b/basics/cross-program-invocation/anchor/programs/lever/src/lib.rs @@ -24,7 +24,7 @@ pub mod lever { // `borsh` rather than v2's zero-copy default. A zero-copy `PowerStatus` would // have to store `is_on` as `PodBool` (bytemuck rejects `bool`, since only // `0x00`/`0x01` are valid bit patterns), and the generated IDL renders -// `PodBool` as a plain `bool` alias — so `declare_program!` in the `hand` +// `PodBool` as a plain `bool` alias, so `declare_program!` in the `hand` // program would regenerate a struct that is no longer `Pod`. Borsh keeps the // field a real `bool` on both sides of the CPI. #[account(borsh)] diff --git a/basics/program-derived-addresses/anchor/programs/anchor-program-example/src/state/page_visits.rs b/basics/program-derived-addresses/anchor/programs/anchor-program-example/src/state/page_visits.rs index 82e36f314..e82a29388 100644 --- a/basics/program-derived-addresses/anchor/programs/anchor-program-example/src/state/page_visits.rs +++ b/basics/program-derived-addresses/anchor/programs/anchor-program-example/src/state/page_visits.rs @@ -5,7 +5,7 @@ use anchor_lang::prelude::*; pub struct PageVisits { pub page_visits: u32, pub bump: u8, - // v2's `#[account]` is zero-copy, so the struct has to be Pod — and Pod + // v2's `#[account]` is zero-copy, so the struct has to be Pod, and Pod // rejects implicit padding. u32 + u8 leaves three bytes, so name them. pub _padding: [u8; 3], } diff --git a/basics/program-derived-addresses/anchor/programs/anchor-program-example/tests/test_program_derived_addresses.rs b/basics/program-derived-addresses/anchor/programs/anchor-program-example/tests/test_program_derived_addresses.rs index a9b7274a6..96d5b028a 100644 --- a/basics/program-derived-addresses/anchor/programs/anchor-program-example/tests/test_program_derived_addresses.rs +++ b/basics/program-derived-addresses/anchor/programs/anchor-program-example/tests/test_program_derived_addresses.rs @@ -19,7 +19,7 @@ fn setup() -> (LiteSVM, solana_keypair::Keypair) { } /// Mirrors the on-chain layout. v2's `#[account]` is zero-copy, so the struct -/// carries explicit padding out to its alignment — `try_from_slice` rejects +/// carries explicit padding out to its alignment: `try_from_slice` rejects /// trailing bytes, so the test type has to spell the padding out too. #[derive(BorshDeserialize)] struct PageVisits { diff --git a/basics/realloc/anchor/programs/anchor-realloc/src/instructions/initialize.rs b/basics/realloc/anchor/programs/anchor-realloc/src/instructions/initialize.rs index ffc99aa47..eb09532d6 100644 --- a/basics/realloc/anchor/programs/anchor-realloc/src/instructions/initialize.rs +++ b/basics/realloc/anchor/programs/anchor-realloc/src/instructions/initialize.rs @@ -1,6 +1,6 @@ // v2's `#[derive(Accounts)]` binds the `#[instruction(...)]` args in more // than one generated item, and only the one evaluating the constraints below -// reads them — so the binding looks unused to rustc even though `space` uses it. +// reads them, so the binding looks unused to rustc even though `space` uses it. #![allow(unused_variables)] use anchor_lang::prelude::*; diff --git a/basics/realloc/anchor/programs/anchor-realloc/src/instructions/update.rs b/basics/realloc/anchor/programs/anchor-realloc/src/instructions/update.rs index 167a1a414..2e1327de3 100644 --- a/basics/realloc/anchor/programs/anchor-realloc/src/instructions/update.rs +++ b/basics/realloc/anchor/programs/anchor-realloc/src/instructions/update.rs @@ -1,6 +1,6 @@ // v2's `#[derive(Accounts)]` binds the `#[instruction(...)]` args in more // than one generated item, and only the one evaluating the constraints below -// reads them — so the binding looks unused to rustc even though `space` uses it. +// reads them, so the binding looks unused to rustc even though `space` uses it. #![allow(unused_variables)] use anchor_lang::prelude::*; diff --git a/basics/rent/anchor/programs/rent-example/src/lib.rs b/basics/rent/anchor/programs/rent-example/src/lib.rs index 0c77f3681..b97c4b08f 100644 --- a/basics/rent/anchor/programs/rent-example/src/lib.rs +++ b/basics/rent/anchor/programs/rent-example/src/lib.rs @@ -63,7 +63,7 @@ pub struct AddressData { impl AddressData { /// Bytes this struct occupies on the wire, which is what the new account - /// has to be sized — and therefore rent-funded — for. + /// has to be sized (and therefore rent-funded) for. fn serialized_span(&self) -> Result { >::size_of(self) .map_err(|_| ProgramError::InvalidInstructionData) diff --git a/finance/betting-market/anchor/programs/betting-market/src/instructions/claim_refund.rs b/finance/betting-market/anchor/programs/betting-market/src/instructions/claim_refund.rs index f4917c8f5..45b4ee352 100644 --- a/finance/betting-market/anchor/programs/betting-market/src/instructions/claim_refund.rs +++ b/finance/betting-market/anchor/programs/betting-market/src/instructions/claim_refund.rs @@ -80,7 +80,7 @@ pub fn handle_claim_refund(context: &mut Context) let event_id = context.accounts.event.event_id; let event_bump = context.accounts.event.bump; // `event` signs the transfer below. Release its borrow across - // the CPI — the runtime rejects a CPI that borrows an account we hold. + // 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(); diff --git a/finance/betting-market/anchor/programs/betting-market/src/instructions/claim_winnings.rs b/finance/betting-market/anchor/programs/betting-market/src/instructions/claim_winnings.rs index bda74a5e9..2406aaab6 100644 --- a/finance/betting-market/anchor/programs/betting-market/src/instructions/claim_winnings.rs +++ b/finance/betting-market/anchor/programs/betting-market/src/instructions/claim_winnings.rs @@ -105,7 +105,7 @@ pub fn handle_claim_winnings(context: &mut Context 0 { let event_id = context.accounts.event.event_id; let event_bump = context.accounts.event.bump; - // `event` signs the transfer below. Release its borrow across the CPI — + // `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(); diff --git a/finance/lending/anchor/programs/lending/src/instructions/admin/collect_protocol_fees.rs b/finance/lending/anchor/programs/lending/src/instructions/admin/collect_protocol_fees.rs index 3a1c2a54d..ccab9bbea 100644 --- a/finance/lending/anchor/programs/lending/src/instructions/admin/collect_protocol_fees.rs +++ b/finance/lending/anchor/programs/lending/src/instructions/admin/collect_protocol_fees.rs @@ -38,7 +38,7 @@ pub fn handle_collect_protocol_fees(context: &mut Context) let seeds = reserve_signer_seeds(&lending_market, &liquidity_mint, &bump); // `reserve` signs this CPI. It is a data account holding a live borrow on // its buffer, which the runtime would reject when the CPI borrows the same - // account — so hand the borrow back across the call. `release_borrow` + // account, so hand the borrow back across the call. `release_borrow` // flushes the pending writes, and `reacquire_borrow_mut` re-reads them. context.accounts.reserve.release_borrow()?; transfer_checked( diff --git a/finance/lending/anchor/programs/lending/src/instructions/borrow_obligation_liquidity.rs b/finance/lending/anchor/programs/lending/src/instructions/borrow_obligation_liquidity.rs index 31a864898..e3557b958 100644 --- a/finance/lending/anchor/programs/lending/src/instructions/borrow_obligation_liquidity.rs +++ b/finance/lending/anchor/programs/lending/src/instructions/borrow_obligation_liquidity.rs @@ -78,7 +78,7 @@ pub fn handle_borrow_obligation_liquidity( let seeds = reserve_signer_seeds(&lending_market, &liquidity_mint, &bump); // `reserve` signs this CPI. It is a data account holding a live borrow on // its buffer, which the runtime would reject when the CPI borrows the same - // account — so hand the borrow back across the call. `release_borrow` + // account, so hand the borrow back across the call. `release_borrow` // flushes the pending writes, and `reacquire_borrow_mut` re-reads them. context.accounts.reserve.release_borrow()?; transfer_checked( diff --git a/finance/lending/anchor/programs/lending/src/instructions/deposit_reserve_liquidity.rs b/finance/lending/anchor/programs/lending/src/instructions/deposit_reserve_liquidity.rs index 1d6aa6815..e932fc157 100644 --- a/finance/lending/anchor/programs/lending/src/instructions/deposit_reserve_liquidity.rs +++ b/finance/lending/anchor/programs/lending/src/instructions/deposit_reserve_liquidity.rs @@ -63,7 +63,7 @@ pub fn handle_deposit_reserve_liquidity( let seeds = reserve_signer_seeds(&lending_market, &liquidity_mint, &bump); // `reserve` signs this CPI. It is a data account holding a live borrow on // its buffer, which the runtime would reject when the CPI borrows the same - // account — so hand the borrow back across the call. `release_borrow` + // account, so hand the borrow back across the call. `release_borrow` // flushes the pending writes, and `reacquire_borrow_mut` re-reads them. context.accounts.reserve.release_borrow()?; mint_to( diff --git a/finance/lending/anchor/programs/lending/src/instructions/liquidate_obligation.rs b/finance/lending/anchor/programs/lending/src/instructions/liquidate_obligation.rs index 6b55aec92..da1ab81d3 100644 --- a/finance/lending/anchor/programs/lending/src/instructions/liquidate_obligation.rs +++ b/finance/lending/anchor/programs/lending/src/instructions/liquidate_obligation.rs @@ -165,7 +165,7 @@ pub fn handle_liquidate_obligation( ]; // `obligation` signs this CPI. It is a data account holding a live borrow on // its buffer, which the runtime would reject when the CPI borrows the same - // account — so hand the borrow back across the call. `release_borrow` + // account, so hand the borrow back across the call. `release_borrow` // flushes the pending writes, and `reacquire_borrow_mut` re-reads them. context.accounts.obligation.release_borrow()?; transfer_checked( diff --git a/finance/lending/anchor/programs/lending/src/instructions/redeem_reserve_collateral.rs b/finance/lending/anchor/programs/lending/src/instructions/redeem_reserve_collateral.rs index 464eda399..2416fc2ed 100644 --- a/finance/lending/anchor/programs/lending/src/instructions/redeem_reserve_collateral.rs +++ b/finance/lending/anchor/programs/lending/src/instructions/redeem_reserve_collateral.rs @@ -62,7 +62,7 @@ pub fn handle_redeem_reserve_collateral( let seeds = reserve_signer_seeds(&lending_market, &liquidity_mint, &bump); // `reserve` signs this CPI. It is a data account holding a live borrow on // its buffer, which the runtime would reject when the CPI borrows the same - // account — so hand the borrow back across the call. `release_borrow` + // account, so hand the borrow back across the call. `release_borrow` // flushes the pending writes, and `reacquire_borrow_mut` re-reads them. context.accounts.reserve.release_borrow()?; transfer_checked( diff --git a/finance/lending/anchor/programs/lending/src/instructions/withdraw_obligation_collateral.rs b/finance/lending/anchor/programs/lending/src/instructions/withdraw_obligation_collateral.rs index 24fbb966b..ec810d751 100644 --- a/finance/lending/anchor/programs/lending/src/instructions/withdraw_obligation_collateral.rs +++ b/finance/lending/anchor/programs/lending/src/instructions/withdraw_obligation_collateral.rs @@ -87,7 +87,7 @@ pub fn handle_withdraw_obligation_collateral( ]; // `obligation` signs this CPI. It is a data account holding a live borrow on // its buffer, which the runtime would reject when the CPI borrows the same - // account — so hand the borrow back across the call. `release_borrow` + // account, so hand the borrow back across the call. `release_borrow` // flushes the pending writes, and `reacquire_borrow_mut` re-reads them. context.accounts.obligation.release_borrow()?; transfer_checked( diff --git a/finance/order-book/anchor/programs/order-book/src/instructions/place_order.rs b/finance/order-book/anchor/programs/order-book/src/instructions/place_order.rs index 936f3e52d..e65da4fb1 100644 --- a/finance/order-book/anchor/programs/order-book/src/instructions/place_order.rs +++ b/finance/order-book/anchor/programs/order-book/src/instructions/place_order.rs @@ -36,7 +36,7 @@ pub fn handle_place_order( let maker_accounts = context.remaining_accounts()?; // `AccountView` is Copy, and a copy still points at the same - // account — v2's typed handles make the aliasing a compile error. + // account. v2's typed handles make the aliasing a compile error. let quote_mint_view = *context.accounts.quote_mint.account(); // Read the decimals up front: the CPI handles below borrow the mints. let quote_decimals = context.accounts.quote_mint.decimals(); diff --git a/finance/order-book/anchor/programs/order-book/tests/test_order_book.rs b/finance/order-book/anchor/programs/order-book/tests/test_order_book.rs index 8054da73c..28445131c 100644 --- a/finance/order-book/anchor/programs/order-book/tests/test_order_book.rs +++ b/finance/order-book/anchor/programs/order-book/tests/test_order_book.rs @@ -16,7 +16,7 @@ use { system_instruction, }, // `system_program` moved to the crate root in v2, and `Pubkey` is - // compat-only — `Address` is the same 32-byte type. + // compat-only: `Address` is the same 32-byte type. system_program, Address, Discriminator, InstructionData, ToAccountMetas, }, litesvm::LiteSVM, diff --git a/finance/token-fundraiser/anchor/programs/fundraiser/src/instructions/checker.rs b/finance/token-fundraiser/anchor/programs/fundraiser/src/instructions/checker.rs index 2121be708..16b5916bd 100644 --- a/finance/token-fundraiser/anchor/programs/fundraiser/src/instructions/checker.rs +++ b/finance/token-fundraiser/anchor/programs/fundraiser/src/instructions/checker.rs @@ -64,8 +64,8 @@ pub fn handle_check_contributions( let mint_decimals = accounts.mint_to_raise.decimals(); // `fundraiser` signs both CPIs below. It is a data account holding a live - // borrow on its buffer, so release it across the CPIs — the runtime rejects - // a CPI that borrows an account we still hold — and take it back after. + // borrow on its buffer, so release it across the CPIs. The runtime rejects + // a CPI that borrows an account we still hold. Take it back after. let fundraiser_bump = accounts.fundraiser.bump; accounts.fundraiser.release_borrow()?; let fundraiser_view = *accounts.fundraiser.account(); diff --git a/finance/token-fundraiser/anchor/programs/fundraiser/src/instructions/close.rs b/finance/token-fundraiser/anchor/programs/fundraiser/src/instructions/close.rs index fd20a3a48..4873bf3b7 100644 --- a/finance/token-fundraiser/anchor/programs/fundraiser/src/instructions/close.rs +++ b/finance/token-fundraiser/anchor/programs/fundraiser/src/instructions/close.rs @@ -89,8 +89,8 @@ pub fn handle_close_fundraiser(accounts: &mut CloseFundraiserAccountConstraints) let mint_decimals = accounts.mint_to_raise.decimals(); // `fundraiser` signs both CPIs below. It is a data account holding a live - // borrow on its buffer, so release it across the CPIs — the runtime rejects - // a CPI that borrows an account we still hold — and take it back after. + // borrow on its buffer, so release it across the CPIs. The runtime rejects + // a CPI that borrows an account we still hold. Take it back after. let fundraiser_bump = accounts.fundraiser.bump; accounts.fundraiser.release_borrow()?; let fundraiser_view = *accounts.fundraiser.account(); diff --git a/finance/token-swap/anchor/programs/token-swap/src/instructions/swap_tokens.rs b/finance/token-swap/anchor/programs/token-swap/src/instructions/swap_tokens.rs index 27bca8a91..12ce59314 100644 --- a/finance/token-swap/anchor/programs/token-swap/src/instructions/swap_tokens.rs +++ b/finance/token-swap/anchor/programs/token-swap/src/instructions/swap_tokens.rs @@ -250,7 +250,7 @@ pub fn handle_swap_tokens( // // u128 + checked: same overflow concern as the pre-trade invariant. // v2's token accounts are zero-copy, so the balances below are read live - // from the runtime buffer — there is nothing to reload. What the CPI can + // from the runtime buffer, so there is nothing to reload. What the CPI can // change is the schema, so re-run the load-time checks instead. context.accounts.pool_a.revalidate_after_cpi()?; context.accounts.pool_b.revalidate_after_cpi()?; diff --git a/finance/token-swap/anchor/programs/token-swap/src/lib.rs b/finance/token-swap/anchor/programs/token-swap/src/lib.rs index 1ab23c2b7..df2acb44c 100644 --- a/finance/token-swap/anchor/programs/token-swap/src/lib.rs +++ b/finance/token-swap/anchor/programs/token-swap/src/lib.rs @@ -6,7 +6,7 @@ pub mod instructions; mod state; // The `#[derive(Accounts)]` client modules are generated beside their structs, -// and `#[program]` resolves them at `super::` — the crate root. Re-exporting +// and `#[program]` resolves them at `super::`, the crate root. Re-exporting // here rather than inside the module puts them where it looks. use instructions::*; diff --git a/finance/vault-strategy/anchor/programs/vault-strategy/src/instructions/collect_fees.rs b/finance/vault-strategy/anchor/programs/vault-strategy/src/instructions/collect_fees.rs index 4a34f321a..126cf8917 100644 --- a/finance/vault-strategy/anchor/programs/vault-strategy/src/instructions/collect_fees.rs +++ b/finance/vault-strategy/anchor/programs/vault-strategy/src/instructions/collect_fees.rs @@ -91,7 +91,7 @@ pub fn handle_collect_fees(context: &mut Context) // `strategy` signs the CPI(s) below. It is a data account holding a live // borrow on its buffer, which the runtime would reject when the CPI borrows - // the same account — so hand the borrow back for the duration. + // the same account, so hand the borrow back for the duration. context.accounts.strategy.release_borrow()?; let mint_accounts = MintTo { diff --git a/finance/vault-strategy/anchor/programs/vault-strategy/src/instructions/deposit.rs b/finance/vault-strategy/anchor/programs/vault-strategy/src/instructions/deposit.rs index 4c08dc6e8..00babe66d 100644 --- a/finance/vault-strategy/anchor/programs/vault-strategy/src/instructions/deposit.rs +++ b/finance/vault-strategy/anchor/programs/vault-strategy/src/instructions/deposit.rs @@ -187,7 +187,7 @@ pub fn handle_deposit( // `strategy` signs every CPI below. It is a data account holding a live // borrow on its buffer, which the runtime would reject when the CPI borrows - // the same account — so hand the borrow back for the duration. + // the same account, so hand the borrow back for the duration. // `release_borrow` flushes the pending writes and `reacquire_borrow_mut` // re-reads them. context.accounts.strategy.release_borrow()?; diff --git a/finance/vault-strategy/anchor/programs/vault-strategy/src/instructions/initialize_strategy.rs b/finance/vault-strategy/anchor/programs/vault-strategy/src/instructions/initialize_strategy.rs index 149dd3285..8bd80099d 100644 --- a/finance/vault-strategy/anchor/programs/vault-strategy/src/instructions/initialize_strategy.rs +++ b/finance/vault-strategy/anchor/programs/vault-strategy/src/instructions/initialize_strategy.rs @@ -1,6 +1,6 @@ // v2's `#[derive(Accounts)]` binds the `#[instruction(...)]` args in more // than one generated item, and only the one evaluating the constraints below -// reads them — so the binding looks unused to rustc even though `space` uses it. +// reads them, so the binding looks unused to rustc even though `space` uses it. #![allow(unused_variables)] use anchor_lang::prelude::*; diff --git a/finance/vault-strategy/anchor/programs/vault-strategy/src/instructions/rebalance.rs b/finance/vault-strategy/anchor/programs/vault-strategy/src/instructions/rebalance.rs index 2680dcec3..ff354e6be 100644 --- a/finance/vault-strategy/anchor/programs/vault-strategy/src/instructions/rebalance.rs +++ b/finance/vault-strategy/anchor/programs/vault-strategy/src/instructions/rebalance.rs @@ -166,7 +166,7 @@ pub fn handle_rebalance( // `strategy` signs the CPI(s) below. It is a data account holding a live // borrow on its buffer, which the runtime would reject when the CPI borrows - // the same account — so hand the borrow back for the duration. + // the same account, so hand the borrow back for the duration. context.accounts.strategy.release_borrow()?; // Step 1: sell basket token -> USDC diff --git a/finance/vault-strategy/anchor/programs/vault-strategy/src/instructions/withdraw.rs b/finance/vault-strategy/anchor/programs/vault-strategy/src/instructions/withdraw.rs index ab99d3cf8..d6f7d1e77 100644 --- a/finance/vault-strategy/anchor/programs/vault-strategy/src/instructions/withdraw.rs +++ b/finance/vault-strategy/anchor/programs/vault-strategy/src/instructions/withdraw.rs @@ -113,7 +113,7 @@ pub fn handle_withdraw( // `strategy` signs the payouts below. It is a data account holding a live // borrow on its buffer, so release it across the CPIs and take it back - // afterwards — the runtime rejects a CPI that borrows an account we hold. + // afterwards: the runtime rejects a CPI that borrows an account we hold. context.accounts.strategy.release_borrow()?; // Every other account here goes through its own wrapper handle. A handle diff --git a/tokens/create-token/anchor/programs/create-token/src/lib.rs b/tokens/create-token/anchor/programs/create-token/src/lib.rs index 3a12ab26d..92cf36cf7 100644 --- a/tokens/create-token/anchor/programs/create-token/src/lib.rs +++ b/tokens/create-token/anchor/programs/create-token/src/lib.rs @@ -34,7 +34,7 @@ pub mod create_token { // `payer` fills three CPI slots (payer, mint authority, update // authority). v2's typed handles enforce borrow exclusivity at compile // time, so the two read-only slots are built from a copy of the - // `AccountView` — it still points at the same underlying account. + // `AccountView`, and it still points at the same underlying account. let payer_view = *context.accounts.payer.account(); create_metadata_accounts_v3( diff --git a/tokens/nft-minter/anchor/programs/nft-minter/src/lib.rs b/tokens/nft-minter/anchor/programs/nft-minter/src/lib.rs index 4891b6ed3..7643a3737 100644 --- a/tokens/nft-minter/anchor/programs/nft-minter/src/lib.rs +++ b/tokens/nft-minter/anchor/programs/nft-minter/src/lib.rs @@ -25,7 +25,7 @@ pub mod nft_minter { nft_uri: String, ) -> Result<()> { // `AccountView` is Copy, and a copy still points at the same - // account — v2's typed handles make the aliasing a compile error. + // account. v2's typed handles make the aliasing a compile error. let mint_account_view = *context.accounts.mint_account.account(); let payer_view = *context.accounts.payer.account(); msg!("Minting Token"); diff --git a/tokens/nft-operations/anchor/programs/mint-nft/src/instructions/create_collection.rs b/tokens/nft-operations/anchor/programs/mint-nft/src/instructions/create_collection.rs index 728eb8f4e..9da7392a0 100644 --- a/tokens/nft-operations/anchor/programs/mint-nft/src/instructions/create_collection.rs +++ b/tokens/nft-operations/anchor/programs/mint-nft/src/instructions/create_collection.rs @@ -7,7 +7,7 @@ use anchor_spl::{ }; // v2's anchor-spl wraps these CPIs in terms of `CpiHandle`s, so the raw -// mpl-token-metadata `*Cpi` builders — which want `&AccountInfo` — are not +// mpl-token-metadata `*Cpi` builders (which want `&AccountInfo`) are not // usable here. use anchor_spl::metadata::{ create_master_edition_v3, create_metadata_accounts_v3, diff --git a/tokens/nft-operations/anchor/programs/mint-nft/src/instructions/mint_nft.rs b/tokens/nft-operations/anchor/programs/mint-nft/src/instructions/mint_nft.rs index 7c706c87e..961dd5158 100644 --- a/tokens/nft-operations/anchor/programs/mint-nft/src/instructions/mint_nft.rs +++ b/tokens/nft-operations/anchor/programs/mint-nft/src/instructions/mint_nft.rs @@ -7,7 +7,7 @@ use anchor_spl::{ }; // v2's anchor-spl wraps these CPIs in terms of `CpiHandle`s, so the raw -// mpl-token-metadata `*Cpi` builders — which want `&AccountInfo` — are not +// mpl-token-metadata `*Cpi` builders (which want `&AccountInfo`) are not // usable here. use anchor_spl::metadata::{ create_master_edition_v3, create_metadata_accounts_v3, diff --git a/tokens/nft-operations/anchor/programs/mint-nft/src/instructions/verify_collection.rs b/tokens/nft-operations/anchor/programs/mint-nft/src/instructions/verify_collection.rs index 647ec340c..1fbd1ebb7 100644 --- a/tokens/nft-operations/anchor/programs/mint-nft/src/instructions/verify_collection.rs +++ b/tokens/nft-operations/anchor/programs/mint-nft/src/instructions/verify_collection.rs @@ -1,7 +1,7 @@ use anchor_lang::prelude::*; // v2's anchor-spl wraps this CPI in terms of `CpiHandle`s, so the raw -// mpl-token-metadata `*Cpi` builder — which wants `&AccountInfo` — is not +// mpl-token-metadata `*Cpi` builder (which wants `&AccountInfo`) is not // usable here. The collection is created sized (`CollectionDetails::V1`), so // the sized-item variant is the matching instruction. use anchor_spl::metadata::{ diff --git a/tokens/pda-mint-authority/anchor/programs/token-minter/src/instructions/create.rs b/tokens/pda-mint-authority/anchor/programs/token-minter/src/instructions/create.rs index 5f786c8ff..f39019e75 100644 --- a/tokens/pda-mint-authority/anchor/programs/token-minter/src/instructions/create.rs +++ b/tokens/pda-mint-authority/anchor/programs/token-minter/src/instructions/create.rs @@ -20,7 +20,7 @@ pub struct CreateTokenAccountConstraints { pub payer: Signer, // Create mint account. The same PDA is both the account's address and its - // mint/freeze authority — which is the point of this example, and which v2 + // mint/freeze authority, which is the point of this example, and which v2 // cannot express as an `init` constraint: `mint::authority` has to name a // sibling field, and referencing the account being initialized is rejected // at macro-expansion time. So the mint is created by hand in diff --git a/tokens/pda-mint-authority/anchor/programs/token-minter/src/instructions/mint.rs b/tokens/pda-mint-authority/anchor/programs/token-minter/src/instructions/mint.rs index 05dd007bb..492cd30d2 100644 --- a/tokens/pda-mint-authority/anchor/programs/token-minter/src/instructions/mint.rs +++ b/tokens/pda-mint-authority/anchor/programs/token-minter/src/instructions/mint.rs @@ -56,7 +56,7 @@ pub fn handle_mint_token( // The mint is its own authority, so it fills a writable slot and a // read-only one. `CpiHandleMut` is Copy and `into_readonly()` erases it - // while carrying the wrapper's relaxed borrow flag across — which a + // while carrying the wrapper's relaxed borrow flag across, which a // handle built by hand over a copy of the `AccountView` would not. let mint_handle = context.accounts.mint_account.cpi_handle_mut(); let mint_authority_handle = mint_handle.into_readonly(); diff --git a/tokens/token-extensions/cpi-guard/anchor/programs/cpi-guard/src/lib.rs b/tokens/token-extensions/cpi-guard/anchor/programs/cpi-guard/src/lib.rs index fa22912da..ac1616027 100644 --- a/tokens/token-extensions/cpi-guard/anchor/programs/cpi-guard/src/lib.rs +++ b/tokens/token-extensions/cpi-guard/anchor/programs/cpi-guard/src/lib.rs @@ -22,7 +22,7 @@ pub mod cpi_guard { // The recipient token account is a PDA that is its own authority. v2 // rejects an `init` constraint naming the account being initialized // (`token::authority` has to name a sibling field), so the account is - // created here instead — the `init_if_needed` semantics become an + // created here instead: the `init_if_needed` semantics become an // explicit "create when empty". if context.accounts.recipient_token_account.account().data_len() == 0 { let space = ExtensionType::try_calculate_account_len::(&[])?; @@ -86,7 +86,7 @@ pub struct CpiTransferAccountConstraints { )] pub sender_token_account: InterfaceAccount, /// CHECK: created and initialized as a token account by this instruction, - /// with itself as the authority — see `cpi_transfer` above. + /// with itself as the authority. See `cpi_transfer` above. #[account( mut, seeds = [b"pda"], diff --git a/tokens/token-extensions/group/anchor/programs/group/src/instructions/test_initialize_group.rs b/tokens/token-extensions/group/anchor/programs/group/src/instructions/test_initialize_group.rs index 4e4543408..2e83d505c 100644 --- a/tokens/token-extensions/group/anchor/programs/group/src/instructions/test_initialize_group.rs +++ b/tokens/token-extensions/group/anchor/programs/group/src/instructions/test_initialize_group.rs @@ -62,7 +62,7 @@ pub fn handler(context: &mut Context) -> Resu let lamports = Rent::get()?.try_minimum_balance(mint_size)?; // The mint PDA is both the authority and the group address, so take a copy - // of its `AccountView` for the read-only uses — v2's typed handles enforce + // of its `AccountView` for the read-only uses. v2's typed handles enforce // borrow exclusivity at compile time. let mint_address = *context.accounts.mint_account.address(); diff --git a/tokens/token-extensions/immutable-owner/anchor/programs/immutable-owner/src/lib.rs b/tokens/token-extensions/immutable-owner/anchor/programs/immutable-owner/src/lib.rs index 9daa0ea2e..176d8bb1f 100644 --- a/tokens/token-extensions/immutable-owner/anchor/programs/immutable-owner/src/lib.rs +++ b/tokens/token-extensions/immutable-owner/anchor/programs/immutable-owner/src/lib.rs @@ -19,7 +19,7 @@ pub mod immutable_owner { // We can manually create and initialize the token account via CPIs in the instruction handler pub fn initialize(context: &mut Context) -> Result<()> { // `AccountView` is Copy, and a copy still points at the same - // account — v2's typed handles make the aliasing a compile error. + // account. v2's typed handles make the aliasing a compile error. let payer_view = *context.accounts.payer.account(); // Calculate space required for token and extension data let token_account_size = ExtensionType::try_calculate_account_len::(&[ diff --git a/tokens/token-extensions/memo-transfer/anchor/programs/memo-transfer/src/instructions/initialize.rs b/tokens/token-extensions/memo-transfer/anchor/programs/memo-transfer/src/instructions/initialize.rs index b260b9acf..76a30d16c 100644 --- a/tokens/token-extensions/memo-transfer/anchor/programs/memo-transfer/src/instructions/initialize.rs +++ b/tokens/token-extensions/memo-transfer/anchor/programs/memo-transfer/src/instructions/initialize.rs @@ -23,7 +23,7 @@ pub struct InitializeAccountConstraints { pub fn handler(context: &mut Context) -> Result<()> { // `AccountView` is Copy, and a copy still points at the same - // account — v2's typed handles make the aliasing a compile error. + // account. v2's typed handles make the aliasing a compile error. let payer_view = *context.accounts.payer.account(); // Calculate space required for token and extension data let token_account_size = diff --git a/tokens/token-extensions/metadata/anchor/programs/metadata/src/instructions/initialize.rs b/tokens/token-extensions/metadata/anchor/programs/metadata/src/instructions/initialize.rs index 9748d8a8c..86c8b915e 100644 --- a/tokens/token-extensions/metadata/anchor/programs/metadata/src/instructions/initialize.rs +++ b/tokens/token-extensions/metadata/anchor/programs/metadata/src/instructions/initialize.rs @@ -104,7 +104,7 @@ pub fn process_initialize( )?; // Initialize token metadata. `AccountView` is Copy and a copy still points - // at the same account, so the read-only slots come from copies — v2's typed + // at the same account, so the read-only slots come from copies. v2's typed // handles enforce borrow exclusivity at compile time. let payer_view = *context.accounts.payer.account(); let mint_view = *context.accounts.mint_account.account(); diff --git a/tokens/token-extensions/metadata/anchor/programs/metadata/src/instructions/update_authority.rs b/tokens/token-extensions/metadata/anchor/programs/metadata/src/instructions/update_authority.rs index b6485a687..c17ff2a84 100644 --- a/tokens/token-extensions/metadata/anchor/programs/metadata/src/instructions/update_authority.rs +++ b/tokens/token-extensions/metadata/anchor/programs/metadata/src/instructions/update_authority.rs @@ -28,7 +28,7 @@ pub fn process_update_authority( .map(|account| *account.address()); // Change update authority. v2's struct drops the program-id and - // new-authority slots — neither is passed as an account. + // new-authority slots: neither is passed as an account. token_metadata_update_authority( CpiContext::new( context.accounts.token_program.address(), diff --git a/tokens/token-extensions/metadata/anchor/programs/metadata/src/instructions/update_field.rs b/tokens/token-extensions/metadata/anchor/programs/metadata/src/instructions/update_field.rs index e568d1883..89a1c5588 100644 --- a/tokens/token-extensions/metadata/anchor/programs/metadata/src/instructions/update_field.rs +++ b/tokens/token-extensions/metadata/anchor/programs/metadata/src/instructions/update_field.rs @@ -33,7 +33,7 @@ pub fn process_update_field( args: UpdateFieldArgs, ) -> Result<()> { // `AccountView` is Copy, and a copy still points at the same - // account — v2's typed handles make the aliasing a compile error. + // account. v2's typed handles make the aliasing a compile error. let authority_view = *context.accounts.authority.account(); let UpdateFieldArgs { field, value } = args; diff --git a/tokens/token-extensions/mint-close-authority/anchor/programs/mint-close-authority/src/instructions/close.rs b/tokens/token-extensions/mint-close-authority/anchor/programs/mint-close-authority/src/instructions/close.rs index fb60596ed..39c6f7f22 100644 --- a/tokens/token-extensions/mint-close-authority/anchor/programs/mint-close-authority/src/instructions/close.rs +++ b/tokens/token-extensions/mint-close-authority/anchor/programs/mint-close-authority/src/instructions/close.rs @@ -19,7 +19,7 @@ pub struct CloseAccountConstraints { pub fn handler(context: &mut Context) -> Result<()> { // `authority` fills both the destination and authority CPI slots. v2's typed // handles enforce borrow exclusivity at compile time, so the read-only slot - // is built from a copy of the `AccountView` — it still points at the same + // is built from a copy of the `AccountView`, and it still points at the same // underlying account. let authority_view = *context.accounts.authority.account(); diff --git a/tokens/token-extensions/nft-meta-data-pointer/anchor-example/anchor/programs/extension_nft/Cargo.toml b/tokens/token-extensions/nft-meta-data-pointer/anchor-example/anchor/programs/extension_nft/Cargo.toml index 58c2d7c76..f0168ff5d 100644 --- a/tokens/token-extensions/nft-meta-data-pointer/anchor-example/anchor/programs/extension_nft/Cargo.toml +++ b/tokens/token-extensions/nft-meta-data-pointer/anchor-example/anchor/programs/extension_nft/Cargo.toml @@ -31,7 +31,7 @@ wincode = { version = "0.5", features = ["derive"] } # `SchemaRead`/`SchemaWrite` bound fails. 2.6.1 is the last 0.5-line release. solana-address = ">=2.6, <2.7" anchor-spl = { version = "2.0.0-rc.1" } -# session-keys is Anchor v1 only — its `Session` derive requires +# session-keys is Anchor v1 only: its `Session` derive requires # `Option>`, which v2 has no equivalent for. The # account layout is read directly in src/session.rs instead. # (so it builds against Anchor 1.0). Earlier 2.x releases pin Anchor <=0.30 diff --git a/tokens/token-extensions/nft-meta-data-pointer/anchor-example/anchor/programs/extension_nft/src/instructions/mint_nft.rs b/tokens/token-extensions/nft-meta-data-pointer/anchor-example/anchor/programs/extension_nft/src/instructions/mint_nft.rs index 08bb50051..d3eeb70c8 100644 --- a/tokens/token-extensions/nft-meta-data-pointer/anchor-example/anchor/programs/extension_nft/src/instructions/mint_nft.rs +++ b/tokens/token-extensions/nft-meta-data-pointer/anchor-example/anchor/programs/extension_nft/src/instructions/mint_nft.rs @@ -14,7 +14,7 @@ use anchor_spl::{ }; pub fn handle_mint_nft(context: &mut Context) -> Result<()> { - // `AccountView` is Copy, and a copy still points at the same account — v2's + // `AccountView` is Copy, and a copy still points at the same account. v2's // typed handles make the aliasing a compile error. `mint` and `signer` are // Signers and `nft_authority` releases its data borrow below, so none of // these copies aliases a live borrow. @@ -130,7 +130,7 @@ pub fn handle_mint_nft(context: &mut Context) -> Resu ); // `initialize` names metadata (the mint, writable), update_authority, mint - // and mint_authority — so the mint and the authority each fill two slots. + // and mint_authority, so the mint and the authority each fill two slots. invoke_signed( init_token_meta_data_ix, &[ diff --git a/tokens/token-extensions/nft-meta-data-pointer/anchor-example/anchor/programs/extension_nft/tests/test_extension_nft.rs b/tokens/token-extensions/nft-meta-data-pointer/anchor-example/anchor/programs/extension_nft/tests/test_extension_nft.rs index f266b551b..18c656b02 100644 --- a/tokens/token-extensions/nft-meta-data-pointer/anchor-example/anchor/programs/extension_nft/tests/test_extension_nft.rs +++ b/tokens/token-extensions/nft-meta-data-pointer/anchor-example/anchor/programs/extension_nft/tests/test_extension_nft.rs @@ -21,7 +21,7 @@ use { // `system_program` moved to the crate root in v2, and `Pubkey` is - // compat-only — `Address` is the same 32-byte type. + // compat-only: `Address` is the same 32-byte type. anchor_lang::{system_program, Address as Pubkey, InstructionData, ToAccountMetas}, litesvm::LiteSVM, solana_instruction::Instruction, diff --git a/tokens/token-extensions/transfer-fee/anchor/programs/transfer-fee/src/instructions/harvest.rs b/tokens/token-extensions/transfer-fee/anchor/programs/transfer-fee/src/instructions/harvest.rs index 7f1823672..c7b251dd2 100644 --- a/tokens/token-extensions/transfer-fee/anchor/programs/transfer-fee/src/instructions/harvest.rs +++ b/tokens/token-extensions/transfer-fee/anchor/programs/transfer-fee/src/instructions/harvest.rs @@ -23,7 +23,7 @@ pub fn process_harvest(context: &mut Context) -> Resu // v2 has no `InterfaceAccount::try_from`; `AnchorAccount::load` is the // equivalent for an account reached through remaining_accounts. The // wrapper is dropped at the end of each iteration, so it holds no borrow - // across the CPI — only the verdict escapes. + // across the CPI: only the verdict escapes. let keep: Vec = candidates .iter() .map(|account| { diff --git a/tokens/token-extensions/transfer-fee/anchor/programs/transfer-fee/src/instructions/transfer.rs b/tokens/token-extensions/transfer-fee/anchor/programs/transfer-fee/src/instructions/transfer.rs index 94fc41be5..0d8ad8495 100644 --- a/tokens/token-extensions/transfer-fee/anchor/programs/transfer-fee/src/instructions/transfer.rs +++ b/tokens/token-extensions/transfer-fee/anchor/programs/transfer-fee/src/instructions/transfer.rs @@ -20,7 +20,7 @@ pub struct TransferAccountConstraints { // Read-only: `transfer_checked_with_fee` accrues the withheld fee on the // destination token account, not the mint. It also has to be read-only for - // the extension read below — a mutable data account holds an exclusive + // the extension read below: a mutable data account holds an exclusive // borrow, so a second `try_borrow()` on it is rejected. pub mint_account: InterfaceAccount, #[account( diff --git a/tokens/token-extensions/transfer-hook/account-data-as-seed/anchor/programs/transfer-hook/src/entrypoint.rs b/tokens/token-extensions/transfer-hook/account-data-as-seed/anchor/programs/transfer-hook/src/entrypoint.rs index 26ef91dbb..577e8f2b6 100644 --- a/tokens/token-extensions/transfer-hook/account-data-as-seed/anchor/programs/transfer-hook/src/entrypoint.rs +++ b/tokens/token-extensions/transfer-hook/account-data-as-seed/anchor/programs/transfer-hook/src/entrypoint.rs @@ -6,7 +6,7 @@ //! to a single byte. The transfer-hook interface calls its instructions under //! their own eight-byte values, which leaves no way to declare those handlers //! directly. `#[program(interface, ...)]` accepts arbitrary discriminator bytes -//! but only generates a CPI client — no dispatch, and so no deployable program. +//! but only generates a CPI client: no dispatch, and so no deployable program. //! //! So the crate builds with `no-entrypoint` (which makes anchor export its //! dispatch as `__anchor_dispatch` instead of claiming the `entrypoint` symbol) diff --git a/tokens/token-extensions/transfer-hook/account-data-as-seed/anchor/programs/transfer-hook/src/lib.rs b/tokens/token-extensions/transfer-hook/account-data-as-seed/anchor/programs/transfer-hook/src/lib.rs index 35401c3b3..1a89d0cd2 100644 --- a/tokens/token-extensions/transfer-hook/account-data-as-seed/anchor/programs/transfer-hook/src/lib.rs +++ b/tokens/token-extensions/transfer-hook/account-data-as-seed/anchor/programs/transfer-hook/src/lib.rs @@ -30,7 +30,7 @@ pub mod entrypoint; // v2's `#[program(interface, ...)]` declares an interface for other programs to // CPI into and emits no entrypoint, and an executable `#[program]` only accepts -// one-byte custom discriminators — so the transfer-hook interface's eight-byte +// one-byte custom discriminators, so the transfer-hook interface's eight-byte // discriminators have no direct spelling. `entrypoint` bridges the gap: it maps // each of them onto a handler before anchor's dispatch runs. #[program] diff --git a/tokens/token-extensions/transfer-hook/allow-block-list-token/anchor/programs/abl-token/src/entrypoint.rs b/tokens/token-extensions/transfer-hook/allow-block-list-token/anchor/programs/abl-token/src/entrypoint.rs index 4c5098602..5d9e9fd95 100644 --- a/tokens/token-extensions/transfer-hook/allow-block-list-token/anchor/programs/abl-token/src/entrypoint.rs +++ b/tokens/token-extensions/transfer-hook/allow-block-list-token/anchor/programs/abl-token/src/entrypoint.rs @@ -6,13 +6,13 @@ //! to a single byte. The transfer-hook interface calls `Execute` under its own //! eight-byte value, which leaves no way to declare that handler directly. //! `#[program(interface, ...)]` accepts arbitrary discriminator bytes but only -//! generates a CPI client — no dispatch, and so no deployable program. +//! generates a CPI client: no dispatch, and so no deployable program. //! //! So the crate builds with `no-entrypoint` (which makes anchor export its //! dispatch as `__anchor_dispatch` instead of claiming the `entrypoint` symbol) //! and this module claims `entrypoint` itself. All it does is swap the //! interface's discriminator for `tx_hook`'s before delegating; the payload -//! behind it — a single `u64` amount — is identical either way. +//! behind it (a single `u64` amount) is identical either way. use anchor_lang::pinocchio; diff --git a/tokens/token-extensions/transfer-hook/allow-block-list-token/anchor/programs/abl-token/src/instructions/init_mint.rs b/tokens/token-extensions/transfer-hook/allow-block-list-token/anchor/programs/abl-token/src/instructions/init_mint.rs index 02a68ce3b..7c5b8faa2 100644 --- a/tokens/token-extensions/transfer-hook/allow-block-list-token/anchor/programs/abl-token/src/instructions/init_mint.rs +++ b/tokens/token-extensions/transfer-hook/allow-block-list-token/anchor/programs/abl-token/src/instructions/init_mint.rs @@ -125,7 +125,7 @@ impl InitMintAccountConstraints { // `payer` and `mint` each fill more than one CPI slot below. v2's typed // handles enforce borrow exclusivity at compile time, so the read-only - // slots are built from copies of the `AccountView` — each copy still + // slots are built from copies of the `AccountView`, and each copy still // points at the same underlying account. let payer_view = *self.payer.account(); let mint_view = *self.mint.account(); diff --git a/tokens/token-extensions/transfer-hook/allow-block-list-token/anchor/programs/abl-token/src/lib.rs b/tokens/token-extensions/transfer-hook/allow-block-list-token/anchor/programs/abl-token/src/lib.rs index e3be4b6d7..fbdc1893e 100644 --- a/tokens/token-extensions/transfer-hook/allow-block-list-token/anchor/programs/abl-token/src/lib.rs +++ b/tokens/token-extensions/transfer-hook/allow-block-list-token/anchor/programs/abl-token/src/lib.rs @@ -19,7 +19,7 @@ pub mod entrypoint; // v2's `#[program(interface, ...)]` declares an interface for other programs to // CPI into and emits no entrypoint, and an executable `#[program]` only accepts -// one-byte custom discriminators — so the transfer-hook interface's eight-byte +// one-byte custom discriminators, so the transfer-hook interface's eight-byte // `Execute` discriminator has no direct spelling. `entrypoint` bridges the gap: // it routes `Execute` to `tx_hook` and hands everything else to anchor. #[program] diff --git a/tokens/token-extensions/transfer-hook/counter/anchor/programs/transfer-hook/src/entrypoint.rs b/tokens/token-extensions/transfer-hook/counter/anchor/programs/transfer-hook/src/entrypoint.rs index 26ef91dbb..577e8f2b6 100644 --- a/tokens/token-extensions/transfer-hook/counter/anchor/programs/transfer-hook/src/entrypoint.rs +++ b/tokens/token-extensions/transfer-hook/counter/anchor/programs/transfer-hook/src/entrypoint.rs @@ -6,7 +6,7 @@ //! to a single byte. The transfer-hook interface calls its instructions under //! their own eight-byte values, which leaves no way to declare those handlers //! directly. `#[program(interface, ...)]` accepts arbitrary discriminator bytes -//! but only generates a CPI client — no dispatch, and so no deployable program. +//! but only generates a CPI client: no dispatch, and so no deployable program. //! //! So the crate builds with `no-entrypoint` (which makes anchor export its //! dispatch as `__anchor_dispatch` instead of claiming the `entrypoint` symbol) diff --git a/tokens/token-extensions/transfer-hook/counter/anchor/programs/transfer-hook/src/lib.rs b/tokens/token-extensions/transfer-hook/counter/anchor/programs/transfer-hook/src/lib.rs index 79c91f6c8..4aabf0894 100644 --- a/tokens/token-extensions/transfer-hook/counter/anchor/programs/transfer-hook/src/lib.rs +++ b/tokens/token-extensions/transfer-hook/counter/anchor/programs/transfer-hook/src/lib.rs @@ -30,7 +30,7 @@ pub mod entrypoint; // v2's `#[program(interface, ...)]` declares an interface for other programs to // CPI into and emits no entrypoint, and an executable `#[program]` only accepts -// one-byte custom discriminators — so the transfer-hook interface's eight-byte +// one-byte custom discriminators, so the transfer-hook interface's eight-byte // discriminators have no direct spelling. `entrypoint` bridges the gap: it maps // each of them onto a handler before anchor's dispatch runs. #[program] diff --git a/tokens/token-extensions/transfer-hook/hello-world/anchor/programs/transfer-hook/src/entrypoint.rs b/tokens/token-extensions/transfer-hook/hello-world/anchor/programs/transfer-hook/src/entrypoint.rs index 26ef91dbb..577e8f2b6 100644 --- a/tokens/token-extensions/transfer-hook/hello-world/anchor/programs/transfer-hook/src/entrypoint.rs +++ b/tokens/token-extensions/transfer-hook/hello-world/anchor/programs/transfer-hook/src/entrypoint.rs @@ -6,7 +6,7 @@ //! to a single byte. The transfer-hook interface calls its instructions under //! their own eight-byte values, which leaves no way to declare those handlers //! directly. `#[program(interface, ...)]` accepts arbitrary discriminator bytes -//! but only generates a CPI client — no dispatch, and so no deployable program. +//! but only generates a CPI client: no dispatch, and so no deployable program. //! //! So the crate builds with `no-entrypoint` (which makes anchor export its //! dispatch as `__anchor_dispatch` instead of claiming the `entrypoint` symbol) diff --git a/tokens/token-extensions/transfer-hook/hello-world/anchor/programs/transfer-hook/src/lib.rs b/tokens/token-extensions/transfer-hook/hello-world/anchor/programs/transfer-hook/src/lib.rs index 7a8acc0f2..503b2ff52 100644 --- a/tokens/token-extensions/transfer-hook/hello-world/anchor/programs/transfer-hook/src/lib.rs +++ b/tokens/token-extensions/transfer-hook/hello-world/anchor/programs/transfer-hook/src/lib.rs @@ -28,7 +28,7 @@ pub mod entrypoint; // v2's `#[program(interface, ...)]` declares an interface for other programs to // CPI into and emits no entrypoint, and an executable `#[program]` only accepts -// one-byte custom discriminators — so the transfer-hook interface's eight-byte +// one-byte custom discriminators, so the transfer-hook interface's eight-byte // discriminators have no direct spelling. `entrypoint` bridges the gap: it maps // each of them onto a handler before anchor's dispatch runs. #[program] diff --git a/tokens/token-extensions/transfer-hook/transfer-cost/anchor/programs/transfer-hook/src/entrypoint.rs b/tokens/token-extensions/transfer-hook/transfer-cost/anchor/programs/transfer-hook/src/entrypoint.rs index 26ef91dbb..577e8f2b6 100644 --- a/tokens/token-extensions/transfer-hook/transfer-cost/anchor/programs/transfer-hook/src/entrypoint.rs +++ b/tokens/token-extensions/transfer-hook/transfer-cost/anchor/programs/transfer-hook/src/entrypoint.rs @@ -6,7 +6,7 @@ //! to a single byte. The transfer-hook interface calls its instructions under //! their own eight-byte values, which leaves no way to declare those handlers //! directly. `#[program(interface, ...)]` accepts arbitrary discriminator bytes -//! but only generates a CPI client — no dispatch, and so no deployable program. +//! but only generates a CPI client: no dispatch, and so no deployable program. //! //! So the crate builds with `no-entrypoint` (which makes anchor export its //! dispatch as `__anchor_dispatch` instead of claiming the `entrypoint` symbol) diff --git a/tokens/token-extensions/transfer-hook/transfer-cost/anchor/programs/transfer-hook/src/lib.rs b/tokens/token-extensions/transfer-hook/transfer-cost/anchor/programs/transfer-hook/src/lib.rs index 0081da36a..f6e121bfe 100644 --- a/tokens/token-extensions/transfer-hook/transfer-cost/anchor/programs/transfer-hook/src/lib.rs +++ b/tokens/token-extensions/transfer-hook/transfer-cost/anchor/programs/transfer-hook/src/lib.rs @@ -40,7 +40,7 @@ pub mod entrypoint; // v2's `#[program(interface, ...)]` declares an interface for other programs to // CPI into and emits no entrypoint, and an executable `#[program]` only accepts -// one-byte custom discriminators — so the transfer-hook interface's eight-byte +// one-byte custom discriminators, so the transfer-hook interface's eight-byte // discriminators have no direct spelling. `entrypoint` bridges the gap: it maps // each of them onto a handler before anchor's dispatch runs. #[program] diff --git a/tokens/token-extensions/transfer-hook/transfer-switch/anchor/programs/transfer-switch/src/entrypoint.rs b/tokens/token-extensions/transfer-hook/transfer-switch/anchor/programs/transfer-switch/src/entrypoint.rs index d99c8748b..eb0294f7c 100644 --- a/tokens/token-extensions/transfer-hook/transfer-switch/anchor/programs/transfer-switch/src/entrypoint.rs +++ b/tokens/token-extensions/transfer-hook/transfer-switch/anchor/programs/transfer-switch/src/entrypoint.rs @@ -6,7 +6,7 @@ //! to a single byte. The transfer-hook interface calls its instructions under //! their own eight-byte values, which leaves no way to declare those handlers //! directly. `#[program(interface, ...)]` accepts arbitrary discriminator bytes -//! but only generates a CPI client — no dispatch, and so no deployable program. +//! but only generates a CPI client: no dispatch, and so no deployable program. //! //! So the crate builds with `no-entrypoint` (which makes anchor export its //! dispatch as `__anchor_dispatch` instead of claiming the `entrypoint` symbol) diff --git a/tokens/token-extensions/transfer-hook/transfer-switch/anchor/programs/transfer-switch/src/instructions/configure_admin.rs b/tokens/token-extensions/transfer-hook/transfer-switch/anchor/programs/transfer-switch/src/instructions/configure_admin.rs index e00ede2ff..5ba16083a 100644 --- a/tokens/token-extensions/transfer-hook/transfer-switch/anchor/programs/transfer-switch/src/instructions/configure_admin.rs +++ b/tokens/token-extensions/transfer-hook/transfer-switch/anchor/programs/transfer-switch/src/instructions/configure_admin.rs @@ -5,7 +5,7 @@ pub struct ConfigureAdminAccountConstraints { // Bootstrapping the config passes the same key as both `admin` and // `new_admin`, so the two slots legitimately alias. v2 rejects an account // that appears twice while any of its slots is in the mutable mask, and it - // flags *both* indices — so both carry `unsafe(dup)`, which keeps them + // flags *both* indices, so both carry `unsafe(dup)`, which keeps them // writable while taking them out of that mask. #[account(unsafe(dup))] pub admin: Signer, diff --git a/tokens/token-extensions/transfer-hook/transfer-switch/anchor/programs/transfer-switch/src/lib.rs b/tokens/token-extensions/transfer-hook/transfer-switch/anchor/programs/transfer-switch/src/lib.rs index 4304617b0..c80aa0918 100644 --- a/tokens/token-extensions/transfer-hook/transfer-switch/anchor/programs/transfer-switch/src/lib.rs +++ b/tokens/token-extensions/transfer-hook/transfer-switch/anchor/programs/transfer-switch/src/lib.rs @@ -15,7 +15,7 @@ pub mod entrypoint; // v2's `#[program(interface, ...)]` declares an interface for other programs to // CPI into and emits no entrypoint, and an executable `#[program]` only accepts -// one-byte custom discriminators — so the transfer-hook interface's eight-byte +// one-byte custom discriminators, so the transfer-hook interface's eight-byte // discriminators have no direct spelling. `entrypoint` bridges the gap: it maps // each of them onto a handler before anchor's dispatch runs. #[program] diff --git a/tokens/token-extensions/transfer-hook/whitelist/anchor/programs/transfer-hook/src/entrypoint.rs b/tokens/token-extensions/transfer-hook/whitelist/anchor/programs/transfer-hook/src/entrypoint.rs index 26ef91dbb..577e8f2b6 100644 --- a/tokens/token-extensions/transfer-hook/whitelist/anchor/programs/transfer-hook/src/entrypoint.rs +++ b/tokens/token-extensions/transfer-hook/whitelist/anchor/programs/transfer-hook/src/entrypoint.rs @@ -6,7 +6,7 @@ //! to a single byte. The transfer-hook interface calls its instructions under //! their own eight-byte values, which leaves no way to declare those handlers //! directly. `#[program(interface, ...)]` accepts arbitrary discriminator bytes -//! but only generates a CPI client — no dispatch, and so no deployable program. +//! but only generates a CPI client: no dispatch, and so no deployable program. //! //! So the crate builds with `no-entrypoint` (which makes anchor export its //! dispatch as `__anchor_dispatch` instead of claiming the `entrypoint` symbol) diff --git a/tokens/token-extensions/transfer-hook/whitelist/anchor/programs/transfer-hook/src/lib.rs b/tokens/token-extensions/transfer-hook/whitelist/anchor/programs/transfer-hook/src/lib.rs index e9adcf43e..315d898da 100644 --- a/tokens/token-extensions/transfer-hook/whitelist/anchor/programs/transfer-hook/src/lib.rs +++ b/tokens/token-extensions/transfer-hook/whitelist/anchor/programs/transfer-hook/src/lib.rs @@ -28,7 +28,7 @@ pub mod entrypoint; // v2's `#[program(interface, ...)]` declares an interface for other programs to // CPI into and emits no entrypoint, and an executable `#[program]` only accepts -// one-byte custom discriminators — so the transfer-hook interface's eight-byte +// one-byte custom discriminators, so the transfer-hook interface's eight-byte // discriminators have no direct spelling. `entrypoint` bridges the gap: it maps // each of them onto a handler before anchor's dispatch runs. #[program] diff --git a/tokens/token-minter/anchor/programs/token-minter/src/instructions/create.rs b/tokens/token-minter/anchor/programs/token-minter/src/instructions/create.rs index 1ce1de6a1..bc423c828 100644 --- a/tokens/token-minter/anchor/programs/token-minter/src/instructions/create.rs +++ b/tokens/token-minter/anchor/programs/token-minter/src/instructions/create.rs @@ -46,7 +46,7 @@ pub fn handle_create_token( token_uri: String, ) -> Result<()> { // `AccountView` is Copy, and a copy still points at the same - // account — v2's typed handles make the aliasing a compile error. + // account. v2's typed handles make the aliasing a compile error. let payer_view = *context.accounts.payer.account(); msg!("Creating metadata account"); diff --git a/tokens/transfer-tokens/anchor/programs/transfer-tokens/src/instructions/create.rs b/tokens/transfer-tokens/anchor/programs/transfer-tokens/src/instructions/create.rs index 628219363..f2865ad74 100644 --- a/tokens/transfer-tokens/anchor/programs/transfer-tokens/src/instructions/create.rs +++ b/tokens/transfer-tokens/anchor/programs/transfer-tokens/src/instructions/create.rs @@ -47,7 +47,7 @@ pub fn handle_create_token( token_uri: String, ) -> Result<()> { // `AccountView` is Copy, and a copy still points at the same - // account — v2's typed handles make the aliasing a compile error. + // account. v2's typed handles make the aliasing a compile error. let payer_view = *context.accounts.payer.account(); msg!("Creating metadata account"); From ce23a3a67ce5dea48361cda7557d08ee1c1b09ac Mon Sep 17 00:00:00 2001 From: Claude Date: Wed, 19 Aug 2026 19:31:46 +0000 Subject: [PATCH 57/59] docs: correct the READMEs the v2 port made untrue The port changed code that ten documents still described in its pre-port form. Seven claimed an Anchor 1.x version, including the repository README, which said every example builds on Anchor 1.1. They say 2.0.0-rc.1 now, which is what `.github/workflows/anchor.yml` installs. Nine lines across five READMEs documented a `has_one` constraint. All 151 uses in the Anchor programs moved to `address` on the sibling field, so each line now names the constraint the code actually carries: `address = offer.maker`, `address = market.fee_vault`, `address = lending_market.owner`, `address = vault.authority`, `address = config.admin`. Every one of those greps in the source. The 118 remaining `has_one` uses are all in Quasar crates, where the constraint still exists, so their documentation is unchanged. `tokens/nft-operations` said the instructions sysvar ID was defined locally because it moved in Anchor 1.0. `verify_collection.rs` says why: pinocchio, which anchor-lang v2 is built on, does not re-export it. `basics/pyth` needed more than a version bump. It documented, in the README and again in `lib.rs`, that `pyth-solana-receiver-sdk` 1.2.0 builds against anchor-lang 0.32 and pulls a pythnet-sdk still on borsh 0.10. The SDK is at 2.0.0 now, on anchor-lang 1.0.2 and borsh 1.5.3, so that blocker is fixed upstream and the error text and issue link no longer describe anything. The vendored type is still needed, for the simpler reason that 1.0.2 and 2.0.0-rc.1 are different account models, and both places say that instead. `README.md`'s "since Anchor 1.0" is untouched: it dates when `anchor init` began scaffolding LiteSVM, which is still true. (cherry picked from commit b24acb7340277bae64e1bdd4fa972cfedd6303d2) --- README.md | 2 +- basics/pyth/anchor/README.md | 12 ++------ .../anchor/programs/pythexample/src/lib.rs | 30 +++++-------------- compression/cnft-vault/anchor/README.md | 2 +- finance/escrow/anchor/README.md | 2 +- finance/lending/anchor/README.md | 4 +-- finance/order-book/anchor/README.md | 20 ++++++------- finance/token-swap/README.md | 8 ++--- finance/vault-strategy/anchor/app/README.md | 2 +- tokens/nft-operations/anchor/README.md | 2 +- 10 files changed, 32 insertions(+), 52 deletions(-) diff --git a/README.md b/README.md index a5ce485d1..f25073ec6 100644 --- a/README.md +++ b/README.md @@ -4,7 +4,7 @@ _Solana program examples ('smart contracts') in Anchor, Quasar, Pinocchio, native Rust, and sBPF assembly. Focused on financial software ('DeFi'), plus the basics, tokens, Token Extensions, state compression, and more._ -Working, tested, up-to-date examples of common Solana programs (what other chains call smart contracts), maintained by [Quicknode](https://www.quicknode.com/chains/solana). Current as of July 2026 (see [CHANGELOG.md](./CHANGELOG.md)): every example builds and passes CI on **Anchor 1.1**, the current multi-file program layout (one file per instruction handler, account type, etc), and [LiteSVM](https://github.com/LiteSVM/litesvm) tests rather than the older `solana-test-validator` / web3.js stack. +Working, tested, up-to-date examples of common Solana programs (what other chains call smart contracts), maintained by [Quicknode](https://www.quicknode.com/chains/solana). Current as of August 2026 (see [CHANGELOG.md](./CHANGELOG.md)): every example builds and passes CI on **Anchor 2.0.0-rc.1**, the current multi-file program layout (one file per instruction handler, account type, etc), and [LiteSVM](https://github.com/LiteSVM/litesvm) tests rather than the older `solana-test-validator` / web3.js stack. [![Anchor](../../actions/workflows/anchor.yml/badge.svg)](../../actions/workflows/anchor.yml) [![Quasar](../../actions/workflows/quasar.yml/badge.svg)](../../actions/workflows/quasar.yml) [![Pinocchio](../../actions/workflows/pinocchio.yml/badge.svg)](../../actions/workflows/pinocchio.yml) [![Native](../../actions/workflows/native.yml/badge.svg)](../../actions/workflows/native.yml) [![ASM](../../actions/workflows/solana-asm.yml/badge.svg)](../../actions/workflows/solana-asm.yml) diff --git a/basics/pyth/anchor/README.md b/basics/pyth/anchor/README.md index 2b579deeb..a82eceb9a 100644 --- a/basics/pyth/anchor/README.md +++ b/basics/pyth/anchor/README.md @@ -5,17 +5,11 @@ Read a [Pyth](https://pyth.network/) price feed account and log price, confidenc See also: [Pyth overview](../README.md) and the [repository catalog](../../../README.md). > [!NOTE] -> **The official `pyth-solana-receiver-sdk` is not Anchor 1.0 compatible (as of June 2026), so this example vendors the `PriceUpdateV2` account type instead of importing it.** +> **This example vendors the `PriceUpdateV2` account type rather than importing `pyth-solana-receiver-sdk`.** > -> The latest `pyth-solana-receiver-sdk` (1.2.0) builds against `anchor-lang` 0.32 and pulls `pythnet-sdk` (2.3.1), which still derives **borsh 0.10** on `PriceFeedMessage`. Anchor 0.32's `AnchorSerialize`/`AnchorDeserialize` derives require **borsh 1.x**, so `pyth-solana-receiver-sdk`'s own `PriceUpdateV2` fails to compile: +> The SDK's current release (2.0.0, checked August 2026) builds against `anchor-lang` 1.0.2, and this repository is on 2.0.0-rc.1, whose account wrappers are a different set of types. Importing the SDK's `PriceUpdateV2` would pull a second `anchor-lang` into the graph. > -> ``` -> error[E0277]: the trait bound `pythnet_sdk::messages::PriceFeedMessage: BorshSerialize` is not satisfied -> ``` -> -> No published `pyth-solana-receiver-sdk` targets `anchor-lang` 1.0 (which this repo standardizes on), and no `pythnet-sdk` release has migrated to borsh 1.x - so the dependency can't simply be upgraded. Tracked upstream at [pyth-network/pyth-crosschain#3756](https://github.com/pyth-network/pyth-crosschain/issues/3756). -> -> As a workaround, `programs/pythexample/src/lib.rs` mirrors the onchain `PriceUpdateV2` layout locally (same fields, same 8-byte discriminator, owned by the Pyth Receiver program) so accounts written by Pyth deserialize unchanged. Replace the vendored type with the SDK import once an Anchor 1.0 / borsh 1.x compatible release ships. +> `programs/pythexample/src/lib.rs` mirrors the onchain layout instead: same fields in the same order, same 8-byte discriminator, owned by the Pyth Receiver program, so accounts written by Pyth deserialize unchanged. Import the SDK type once a release targeting `anchor-lang` 2.x ships. ## Major concepts diff --git a/basics/pyth/anchor/programs/pythexample/src/lib.rs b/basics/pyth/anchor/programs/pythexample/src/lib.rs index c57697c9e..fe2911fc6 100644 --- a/basics/pyth/anchor/programs/pythexample/src/lib.rs +++ b/basics/pyth/anchor/programs/pythexample/src/lib.rs @@ -58,29 +58,15 @@ pub struct ReadPriceAccountConstraints { // --------------------------------------------------------------------------- // Pyth `PriceUpdateV2` account, vendored from `pyth-solana-receiver-sdk`. // -// The official `pyth-solana-receiver-sdk` is NOT Anchor 1.0 compatible (as of -// June 2026), so this example mirrors the `PriceUpdateV2` account type locally -// instead of importing it. +// The SDK's current release (2.0.0, checked August 2026) builds against +// `anchor-lang` 1.0.2, and this repository is on 2.0.0-rc.1, whose account +// wrappers are a different set of types. Importing the SDK's `PriceUpdateV2` +// would pull a second `anchor-lang` into the graph. // -// Details: the latest `pyth-solana-receiver-sdk` (1.2.0) builds against -// `anchor-lang` 0.32 and pulls `pythnet-sdk` (2.3.1), which still derives -// borsh 0.10 on `PriceFeedMessage`. Anchor 0.32's `AnchorSerialize` / -// `AnchorDeserialize` derives require borsh 1.x, so the SDK's own -// `PriceUpdateV2` fails to compile: -// -// error[E0277]: the trait bound -// `pythnet_sdk::messages::PriceFeedMessage: BorshSerialize` is not satisfied -// -// No published `pyth-solana-receiver-sdk` targets `anchor-lang` 1.0 (which this -// repo standardizes on) and no `pythnet-sdk` release has migrated to borsh 1.x, -// so the dependency can't simply be upgraded. Tracked upstream at -// https://github.com/pyth-network/pyth-crosschain/issues/3756 -// -// The fields, order, and 8-byte -// discriminator below match the onchain account exactly, and it is owned by -// the Pyth Receiver program (see the `Owner` impl), so accounts written by Pyth -// deserialize unchanged. Replace this with the SDK type once an Anchor 1.0 / -// borsh 1.x compatible `pyth-solana-receiver-sdk` release ships. +// The fields, order, and 8-byte discriminator below match the onchain account +// exactly, and it is owned by the Pyth Receiver program (see the `Owner` impl), +// so accounts written by Pyth deserialize unchanged. Import the SDK type once a +// release targeting `anchor-lang` 2.x ships. // --------------------------------------------------------------------------- #[derive(Clone, Debug, PartialEq, Eq, wincode::SchemaRead, wincode::SchemaWrite)] diff --git a/compression/cnft-vault/anchor/README.md b/compression/cnft-vault/anchor/README.md index 0bf600222..26096f2d4 100644 --- a/compression/cnft-vault/anchor/README.md +++ b/compression/cnft-vault/anchor/README.md @@ -6,7 +6,7 @@ The program keeps a PDA-owned vault. You send cNFTs to the vault, then the vault ## Authority model -Deposits are plain Bubblegum transfers to the **vault PDA** (seeds `["cNFT-vault"]`); no program instruction runs on deposit. Because of that, withdraw authorization is per-vault, not per-deposit: `initialize_vault` creates the vault PDA as a `Vault` state account and stores the signer as its **authority**. Both withdraw handlers require that stored authority as a `Signer` (`has_one = authority`) and reject any other signer with `VaultError::InvalidWithdrawAuthority` before the Bubblegum CPI runs. The same PDA doubles as the Bubblegum leaf owner and signs the transfer CPIs via `invoke_signed`. +Deposits are plain Bubblegum transfers to the **vault PDA** (seeds `["cNFT-vault"]`); no program instruction runs on deposit. Because of that, withdraw authorization is per-vault, not per-deposit: `initialize_vault` creates the vault PDA as a `Vault` state account and stores the signer as its **authority**. Both withdraw handlers require that stored authority as a `Signer` (`address = vault.authority`) and reject any other signer with `VaultError::InvalidWithdrawAuthority` before the Bubblegum CPI runs. The same PDA doubles as the Bubblegum leaf owner and signs the transfer CPIs via `invoke_signed`. Three handlers: diff --git a/finance/escrow/anchor/README.md b/finance/escrow/anchor/README.md index e88da57c5..20fd380d5 100644 --- a/finance/escrow/anchor/README.md +++ b/finance/escrow/anchor/README.md @@ -17,7 +17,7 @@ The maker pays the rent for the offer account and the vault, and every path that A maker opens an offer with `make_offer`, passing the `id`, `token_a_offered_amount`, and `token_b_wanted_amount`. The maker signs and pays all rent. The handler creates the offer PDA and the vault, creates the maker's token-B associated token account if needed (paid by the maker, so the eventual taker never funds a maker-owned account), moves the offered token A into the vault with `transfer_checked`, and records the offer state. -A taker settles the offer with `take_offer`. The taker signs. Anchor's constraints bind every account to the stored offer state (`has_one` on the maker and both mints, associated-token constraints on the vault and all token accounts, and the PDA seeds on the offer itself). The handler sends the wanted token B from the taker to the maker, releases the vault's token A to the taker signed by the offer PDA, and closes both the vault and the offer account back to the maker, who paid their rent. The taker's own token-A account is created on the fly if needed, paid by the taker. +A taker settles the offer with `take_offer`. The taker signs. Anchor's constraints bind every account to the stored offer state (`address = offer.maker` on the maker and `address = offer.token_mint_a` / `address = offer.token_mint_b` on the mints, associated-token constraints on the vault and all token accounts, and the PDA seeds on the offer itself). The handler sends the wanted token B from the taker to the maker, releases the vault's token A to the taker signed by the offer PDA, and closes both the vault and the offer account back to the maker, who paid their rent. The taker's own token-A account is created on the fly if needed, paid by the taker. A maker abandons an offer with `cancel_offer`. Only the maker can call it; without it, an unwanted offer would lock the maker's tokens in the vault forever. The handler returns the vault's token A to the maker and closes the vault and offer accounts, refunding both rents to the maker. diff --git a/finance/lending/anchor/README.md b/finance/lending/anchor/README.md index 50e07f7f6..4a9f434ae 100644 --- a/finance/lending/anchor/README.md +++ b/finance/lending/anchor/README.md @@ -113,7 +113,7 @@ wall-clock time in zero slots, so `price_scaled` also rejects any price stamped at or before the `LastRestartSlot` sysvar's slot, pausing valuation until the publisher posts again. The feed PDA is seeded by `[b"price_feed", market, mint]` (scoped to a market, not to any individual) and only that market's `owner` may write it -(`set_price` checks `has_one = owner`). So prices can't be squatted, a reserve +(`set_price` checks `address = lending_market.owner`). So prices can't be squatted, a reserve trusts exactly its own market's feed for the mint, and isolated markets can price the same asset independently. @@ -161,7 +161,7 @@ refreshed in the same transaction, so a typical action transaction is ## Setup -- Rust and the Solana toolchain (`cargo-build-sbf`), Anchor 1.0.x, Solana 3.1.8. +- Rust and the Solana toolchain (`cargo-build-sbf`), Anchor 2.0.0-rc.1, Solana 3.1.8. - This program has no client/JavaScript code; tests are Rust + LiteSVM. ## Testing diff --git a/finance/order-book/anchor/README.md b/finance/order-book/anchor/README.md index 867333433..67854b149 100644 --- a/finance/order-book/anchor/README.md +++ b/finance/order-book/anchor/README.md @@ -366,7 +366,7 @@ Alice's remaining 2-NVDAx [bid](https://www.investopedia.com/terms/b/bid.asp) st ### State / data accounts - `Market`: PDA yes, seeds `["market", base_mint, quote_mint]`, authority program, holds fee rate, tick size, min order size, base/quote mint pubkeys, vault pubkeys, order book pubkey, `authority` wallet (allowed to withdraw fees) -- `OrderBook`: PDA no (client-allocated keypair), seeds n/a: too large (~180 KB) for an `init`/CPI PDA, so created via `create_account` (which needs a signing key a PDA lacks); tied to its market via `has_one`; authority program, holds two critbit trees (bids highest-first, asks lowest-first, 1024 leaves each), `next_order_id` +- `OrderBook`: PDA no (client-allocated keypair), seeds n/a: too large (~180 KB) for an `init`/CPI PDA, so created via `create_account` (which needs a signing key a PDA lacks); tied to its market via `address = market.order_book`; authority program, holds two critbit trees (bids highest-first, asks lowest-first, 1024 leaves each), `next_order_id` - `Order`: PDA yes, seeds `["order", market, order_id.to_le_bytes()]`, authority program, holds owner, side, price, original_quantity, filled_quantity, status, timestamp - `MarketUser`: PDA yes, seeds `["market_user", market, owner]`, authority program, holds `unsettled_base`, `unsettled_quote`, `open_orders: Vec` (max 20) @@ -382,7 +382,7 @@ accounts created with `init` in `initialize_market.rs`; their Their addresses are computed by the caller (e.g. generated Keypairs in the tests) and then written to `market.base_vault` / `quote_vault` / `fee_vault` for the program to validate them on later calls via -`has_one = fee_vault` etc. +`address = market.fee_vault` etc. ### Leaf layout in the `OrderBook` slab @@ -596,7 +596,7 @@ pub fn place_order<'info>( **Accounts in (named):** -- `market` (mut, `has_one = fee_vault`) +- `market` (mut, with `fee_vault` bound by `address = market.fee_vault`) - `order_book` (mut, PDA seeds-checked) - `order` (PDA, **init**, seeds `["order", market, next_order_id.to_le_bytes()]`) @@ -821,7 +821,7 @@ double-withdraw. **Accounts in:** -- `market` (mut, `has_one = fee_vault`) +- `market` (mut, with `fee_vault` bound by `address = market.fee_vault`) - `fee_vault` (mut, boxed) - `authority_quote_account` (mut, boxed - destination) - `quote_mint` (boxed) @@ -1283,7 +1283,7 @@ From [`errors.rs`](programs/order-book/src/errors.rs): - `OrderNotCancellable`: `cancel_order` on a Filled or Cancelled order - `NumericalOverflow`: Any checked arithmetic returned `None` - `InvalidFeeBasisPoints`: `fee_basis_points > 10_000` at init -- `InvalidFeeVault`: `market.fee_vault` on the struct does not match the passed `fee_vault` (Anchor `has_one`) +- `InvalidFeeVault`: `market.fee_vault` on the struct does not match the passed `fee_vault` (the `address` constraint on `fee_vault`) - `MakerAccountMismatch`: Wrong number of maker accounts, wrong order, wrong market, or caller walked the book out of order - `MissingMakerAccounts`: `remaining_accounts.len()` not a multiple of 2 - `MakerOwnerMismatch`: Maker Order and MarketUser have different owners @@ -1342,10 +1342,10 @@ From [`errors.rs`](programs/order-book/src/errors.rs): stack and the Solana VM gives handlers a tight budget. Don't unbox these without testing the compute output size. -- **Discriminator + `has_one`.** Every state account carries an 8- - byte discriminator that Anchor checks. `Market` has - `has_one = fee_vault`, so the `place_order` handler can trust the - `fee_vault` account without re-checking its mint or authority. +- **Discriminator + `address`.** Every state account carries an 8- + byte discriminator that Anchor checks, and `fee_vault` carries + `address = market.fee_vault`, so the `place_order` handler can trust + the `fee_vault` account without re-checking its mint or authority. - **Book capacity check after matching.** The taker's remainder check happens at the end. A bid that clears enough asks to free @@ -1393,7 +1393,7 @@ run first. ### Prerequisites -- Anchor 1.0.0 +- Anchor 2.0.0-rc.1 - Solana CLI (`solana -V`) - Rust stable (pinned at the repo root) diff --git a/finance/token-swap/README.md b/finance/token-swap/README.md index b833026bf..4ed34a75f 100644 --- a/finance/token-swap/README.md +++ b/finance/token-swap/README.md @@ -16,7 +16,7 @@ The pool keeps `x * y = K` invariant: if `x` is the reserve of token A and `y` i - **Caller-supplied [slippage](https://www.investopedia.com/terms/s/slippage.asp) floors on every state-changing [instruction](https://solana.com/docs/terminology#instruction):** swaps revert with `SlippageExceeded` if the output falls below `min_output_amount`, deposits revert with `DepositBelowMinimum` if the LP mint amount falls below `minimum_lp_tokens_out`, withdrawals revert with `WithdrawalBelowMinimum` if either side falls below its floor. - **Defence-in-depth invariant check:** every swap re-verifies `effective_pool_a * effective_pool_b` doesn't decrease after the transfers, so a bug in the curve math fails the transaction instead of silently giving the trader too much. - All financial math in `u128` with checked arithmetic, matching how production Solana AMMs (Orca, Raydium, Meteora, Saber) do it. -- Anchor 1.0 Rust [program](https://solana.com/docs/terminology#program) with LiteSVM integration tests. +- Anchor 2.0.0-rc.1 Rust [program](https://solana.com/docs/terminology#program) with LiteSVM integration tests. ## Why a CPAMM @@ -45,7 +45,7 @@ Implementation choices: ## Onchain-design principles applied here -- **Store keys in the account.** Even for PDAs, storing the parent keys in the account state makes lookups easier (you can rebuild the PDA without consulting external data) and works well with Anchor's `has_one` constraint. +- **Store keys in the account.** Even for PDAs, storing the parent keys in the account state makes lookups easier (you can rebuild the PDA without consulting external data) and works well with Anchor's `address` constraint on the sibling field. - **Keep seeds simple.** Start with the parent's seeds, then the current object's identifiers in alphabetical order. For the pool, that means `[config, mint_a, mint_b]`. - **Keep instruction scope small.** Smaller instructions touch fewer accounts, leaving room in the transaction and improving composability and security. @@ -142,7 +142,7 @@ Burns LP tokens and returns a proportional share of the **effective reserves** ( Lets the address stored in `Config.admin` sweep their accumulated trading-fee claim out of a pool. Transfers `admin_fees_owed_a` from `pool_a` to the admin's token-A account and `admin_fees_owed_b` from `pool_b` to the admin's token-B account, signed by `pool_authority`. Then resets both accumulators to zero. -- Authorisation: enforced by Anchor's `has_one = admin` constraint on `config` plus the `Signer` constraint on `admin`. Calls from any other signer are rejected. +- Authorisation: enforced by Anchor's `address = config.admin` constraint on `admin` plus the `Signer` constraint on the same field. Calls from any other signer are rejected. - The admin's token accounts (`admin_token_a`, `admin_token_b`) must already exist - this handler doesn't auto-create them (keeps the example small). - Idempotent: calling again with the accumulators at zero is a successful no-op (transfers are skipped when owed = 0). @@ -342,7 +342,7 @@ After trading activity on both pools, Alice sweeps her accumulated slice from th - **Handler:** `claim_admin_fees` - **Accounts (`ClaimAdminFeesAccounts`):** - - `config` - Alice's `Config` (the `has_one = admin` constraint enforces that only she can call this) + - `config` - Alice's `Config` (the `address = config.admin` constraint on `admin` enforces that only she can call this) - `pool_config`, `pool_authority` - `mint_a`, `mint_b` - `pool_a`, `pool_b` (the pool's reserves - the source of the transfers) diff --git a/finance/vault-strategy/anchor/app/README.md b/finance/vault-strategy/anchor/app/README.md index 528983423..d85b472af 100644 --- a/finance/vault-strategy/anchor/app/README.md +++ b/finance/vault-strategy/anchor/app/README.md @@ -65,7 +65,7 @@ client mirrors it call-for-call. ## Where the IDL comes from `src/idl/vault_strategy.json` is generated from the program source with -`anchor idl build` (Anchor 1.1.2, the version CI installs) and committed. If you change +`anchor idl build` (Anchor 2.0.0-rc.1, the version CI installs) and committed. If you change the program, regenerate it: ```sh diff --git a/tokens/nft-operations/anchor/README.md b/tokens/nft-operations/anchor/README.md index f60df0abd..4dbe277d9 100644 --- a/tokens/nft-operations/anchor/README.md +++ b/tokens/nft-operations/anchor/README.md @@ -379,7 +379,7 @@ pub fn handle_verify_collection( } ``` -> `INSTRUCTIONS_SYSVAR_ID` is the well-known sysvar address `Sysvar1nstructions1111111111111111111111111`, defined directly in [`verify_collection.rs`](programs/mint-nft/src/instructions/verify_collection.rs) because `sysvar::instructions::ID` moved in Anchor 1.0. +> `INSTRUCTIONS_SYSVAR_ID` is the well-known sysvar address `Sysvar1nstructions1111111111111111111111111`, defined directly in [`verify_collection.rs`](programs/mint-nft/src/instructions/verify_collection.rs) because pinocchio, which anchor-lang v2 is built on, does not re-export it. `verify_collection` performs a CPI to the Token Metadata program with the right accounts. The collection NFT's mint authority signs the CPI, and the NFT is verified as part of the collection. From a171394c538559e136a1663cf3d25ca14b6b70bd Mon Sep 17 00:00:00 2001 From: Claude Date: Wed, 19 Aug 2026 19:34:46 +0000 Subject: [PATCH 58/59] changelog: correct the has_one tally and drop the em-dashes The entry claimed "all 152 uses across 59 files" of `has_one` moved to `address`. Measured against the base commit, the Anchor programs carried 161 across 66 files, and every one moved. "All" was also wrong without a scope: 43 Quasar crates still use `has_one`, correctly, because Quasar is not Anchor v2. The 55 examples and 304 tests in the opening line are right, re-derived here rather than taken on trust: `find -name Anchor.toml` and `find -type d -name anchor` both give 55, with no project counted by one and missed by the other, and the test total comes from summing the sweep per project. Also removes the five em-dashes on added lines, which CONTRIBUTING.md bans. (cherry picked from commit defd44a34d7f50f955c29f1076a14f018291caa7) --- CHANGELOG.md | 15 ++++++++------- 1 file changed, 8 insertions(+), 7 deletions(-) diff --git a/CHANGELOG.md b/CHANGELOG.md index 57d63ad27..61e0318df 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -11,8 +11,8 @@ and `cargo fmt --check` and `cargo clippy -- -D warnings` are clean. ### Changed -- The remaining 39 Anchor examples — all of `tokens/`, `finance/` and - `compression/` — now build against `anchor-lang` 2.0.0-rc.1, joining the +- The remaining 39 Anchor examples, all of `tokens/`, `finance/` and + `compression/`, now build against `anchor-lang` 2.0.0-rc.1, joining the `basics/` examples ported below. `.github/workflows/anchor.yml` installs 2.0.0-rc.1, since `anchor build` under a v2 CLI will not build v1 programs. - `docs/anchor-v2-migration.md` collects every difference the port ran into, @@ -20,12 +20,13 @@ and `cargo fmt --check` and `cargo clippy -- -D warnings` are clean. are called out: borrows held across CPIs, `Box`'s missing `cpi_handle_mut` forwarding, and hand-built read-only handles over a live data account. - `has_one` is deprecated in v2 and this repository's `rust.yml` runs - `cargo clippy -- -D warnings`, so all 152 uses across 59 files move to the - `address` constraint on the sibling field they named. + `cargo clippy -- -D warnings`, so every one of the 161 uses across 66 files + in the Anchor programs moves to the `address` constraint on the sibling field + it named. The Quasar crates keep `has_one`, which is still current there. - The seven `transfer-hook` examples supply their own entrypoint. v2's - `#[program(interface, ...)]` generates a CPI client and no dispatch — a + `#[program(interface, ...)]` generates a CPI client and no dispatch, so the program declared that way builds to a ~900-byte object with no `entrypoint` - symbol — while an executable `#[program]` limits custom discriminators to one + crate has no entrypoint symbol, while an executable `#[program]` limits byte, which the transfer-hook interface's eight-byte values cannot use. Each crate now builds with `no-entrypoint`, so anchor exports its dispatch as `__anchor_dispatch`, and `src/entrypoint.rs` maps the interface @@ -45,7 +46,7 @@ and `cargo fmt --check` and `cargo clippy -- -D warnings` are clean. ### Removed - `tokens/token-extensions/nft-meta-data-pointer` no longer depends on - `session-keys`. That crate is Anchor v1 only — its `Session` derive requires + `session-keys`. That crate is Anchor v1 only: its `Session` derive requires `Option>`, and `SessionToken` is not `Pod`, so v2's zero-copy `Account` cannot hold it either. The program reads the session-token account layout itself (`src/session.rs`), checking owner, From e67c4a127bb34bc59d42fb713456f6030026ee7f Mon Sep 17 00:00:00 2001 From: Claude Date: Wed, 19 Aug 2026 19:38:06 +0000 Subject: [PATCH 59/59] docs: rewrite the v2 migration guide against the writing rules The guide is new in this branch and was written before the audit, so it broke four of the skill's rules at once. They overlap in the same paragraphs, so this is one pass rather than four. The four tables become nested bullet lists. Each was a two-column key and description table, which is a definition list. The 24-row signatures table already existed in bullet form in the plugin's ANCHOR-V2.md, and that rendering is better than the table was: it merges related rows and carries detail the cells had no room for, such as `remaining_accounts()?` returning an owned `Vec` and therefore needing to be called before anything borrows `ctx.accounts`. Ported, with the four rows it does not cover added back. 42 lines carried an em-dash. Each is rewritten rather than character-swapped: most become the comma the sentence already implied, some a colon where the second clause restates the first, a few parentheses or a full stop. Terminology: `on-chain` and `off-chain` are one word, and `Token-2022` is the Token Extensions Program. Nine headings were bare nouns (`Account backing`, `Tests`, `anchor-spl`) that say nothing read alone out of a table of contents, which is how a reader meets them. They name what the section gives you now. The two in-document links to `#borrows-across-cpis` follow the renamed heading, so they still resolve. (cherry picked from commit e849dfcbc845c33a0566cc6ade75f7a4af1febe1) --- docs/anchor-v2-migration.md | 241 ++++++++++++++++++++---------------- 1 file changed, 131 insertions(+), 110 deletions(-) diff --git a/docs/anchor-v2-migration.md b/docs/anchor-v2-migration.md index 788191e18..daf856155 100644 --- a/docs/anchor-v2-migration.md +++ b/docs/anchor-v2-migration.md @@ -6,19 +6,19 @@ wincode, and accounts are zero-copy by default. This is the list of every difference that came up porting the examples in this repository, in rough order of how often it bites. -The single most important one is **[borrows across CPIs](#borrows-across-cpis)**: +The single most important one is **[borrows held across CPIs](#borrows-held-across-cpis-are-what-tests-catch)**: it is the only rule here that the compiler will not catch for you. -## Manifest +## Manifest Changes Every Crate Needs -| v1 | v2 | -|---|---| -| `anchor-lang = "1.1.2"` | `anchor-lang = "2.0.0-rc.1"` | -| `anchor-spl = "1.1.2"` | `anchor-spl = "2.0.0-rc.1"` | -| — | `wincode = { version = "0.5", features = ["derive"] }` | -| `features = ["init-if-needed"]` | no such feature; the constraint is always available | -| `idl-build = [... "anchor-spl/idl-build"]` | anchor-spl has no `idl-build`; drop it | -| `anchor-spl` features `metadata`, `spl-token-interface` | only `guardrails` (default) and `metadata` exist | +- `anchor-lang` and `anchor-spl` both move from `1.1.2` to `2.0.0-rc.1`. +- `wincode = { version = "0.5", features = ["derive"] }` is new. +- `features = ["init-if-needed"]` goes away. There is no such feature, and the + constraint is always available. +- `"anchor-spl/idl-build"` comes out of the `idl-build` feature list, because + anchor-spl has no `idl-build` feature. +- anchor-spl's features are `guardrails` (on by default) and `metadata`. The + Token Extensions modules are unconditional. Every program crate needs `wincode` as a **direct** dependency: the `#[program]` macro expands to `wincode` paths for instruction-data (de)serialization. @@ -36,34 +36,43 @@ solana-address = ">=2.6, <2.7" Note the range: a bare `<2.7` lets cargo satisfy the requirement by reusing an older 1.x already in the graph, which does not fix anything. -## Signatures and types - -| v1 | v2 | -|---|---| -| `Context` | `&mut Context` | -| `Context<'info, T<'info>>` | `Context` — no user lifetime | -| `Pubkey` | `Address` | -| `AccountInfo<'info>` | `AccountView` (or `UncheckedAccount` in an `Accounts` struct) | -| `Signer<'info>`, `Program<'info, T>`, … | `Signer`, `Program` — the `'info` lifetime is gone | -| `Interface<'info, TokenInterface>` | `Interface<'static, TokenInterface>` — the one wrapper that keeps a lifetime | -| `.key()` / `.key` | `.address()`, which returns `&Address` | -| `.to_account_info()` | `.cpi_handle()` / `.cpi_handle_mut()` | -| `ctx.remaining_accounts` (field) | `ctx.remaining_accounts()?` — fallible, and takes `&mut self` | -| `account.owner` | `account.owner()` | -| `Rent::minimum_balance` | `Rent::try_minimum_balance` | -| `#[instruction(discriminator = X)]` | `#[discrim = X]`, and it takes a literal | -| `#[account(zero)]` | `#[account(zeroed)]` | -| `#[account(zero_copy(unsafe))]` | `#[account]` — already zero-copy | -| `AccountLoader<'info, T>` + `load()` | `Account`, which derefs straight to `T` | -| `set_inner(X { .. })` | `*ctx.accounts.foo = X { .. }` | -| `account.data.borrow()` | `account.account().try_borrow()?` | -| `account.reload()?` | `account.revalidate_after_cpi()?` — zero-copy reads are already live | -| `Account::::try_from(view)` | `AnchorAccount::load(view)`, or `load_mut` (unsafe) to write back | -| `.exit(program_id)` | `.exit()` | -| `error!(MyError::X)` | `MyError::X` (compat-only macro; `?` converts, tail position needs `.into()`) | -| `#[account(has_one = x)]` on the owner | `#[account(address = owner.x)]` on the **sibling** field | - -`has_one` is deprecated, not removed — but this repository's `rust.yml` runs +## Renamed Accessors and Handler Signatures + +- `Context` becomes `&mut Context`, and the user `'info` lifetime is gone: + `Context<'info, T<'info>>` is `Context`. +- `Signer<'info>` and `Program<'info, T>` are `Signer` and `Program`. + `Interface<'info, TokenInterface>` is the one wrapper that keeps a lifetime, + as `Interface<'static, TokenInterface>`. +- `Pubkey` is `Address`. `AccountInfo<'info>` is `AccountView`, or + `UncheckedAccount` inside an `Accounts` struct. +- `.key()` and `.key` become `.address()`, which returns `&Address`. + Dereference where a value is wanted. +- `.to_account_info()` becomes `.cpi_handle()` or `.cpi_handle_mut()`. +- `ctx.remaining_accounts` is now the method `ctx.remaining_accounts()?`. It is + fallible, takes `&mut self`, and returns an owned `Vec`, so call + it before anything borrows `ctx.accounts`. +- `account.owner` is `account.owner()`. `Rent::minimum_balance` is + `Rent::try_minimum_balance`. +- `account.data.borrow()` is `account.account().try_borrow()?`. +- `set_inner(X { .. })` is `*ctx.accounts.foo = X { .. }`. +- `account.reload()?` is `account.revalidate_after_cpi()?`. Zero-copy reads are + already live, so nothing is reloaded; the call re-runs the schema checks a CPI + could have invalidated. +- `Account::::try_from(view)` is `AnchorAccount::load(view)`, or `load_mut` + (which is unsafe) to write back. +- `.exit(program_id)` is `.exit()`. +- `error!(MyError::X)` is just `MyError::X`. The macro is compat-only. `?` + converts, and a tail position needs `.into()`. +- `#[instruction(discriminator = X)]` is `#[discrim = X]`, and it takes a + literal. +- `#[account(zero)]` is `#[account(zeroed)]`, and `#[account(zero_copy(unsafe))]` + is plain `#[account]`, which is already zero-copy. +- `AccountLoader<'info, T>` with `load()` is `Account`, which derefs straight + to `T`. +- `#[account(has_one = x)]` on the owning account is `#[account(address = + owner.x)]` on the **sibling** field. + +`has_one` is deprecated rather than removed, but this repository's `rust.yml` runs `cargo clippy -- -D warnings`, so every remaining use is a **hard CI failure**. The check moves off the owning account and onto the sibling it names: @@ -78,24 +87,24 @@ pub maker: SystemAccount, ``` Dropping the `'info` lifetime from handler signatures is mechanical, but do not -strip `<'a>` from free functions that genuinely use it — a helper returning +strip `<'a>` from free functions that genuinely use it: a helper returning `[&'a [u8]; 4]` still needs its parameter. -## Account backing +## Account State Chooses Between Zero-Copy and Borsh v2's `#[account]` is **zero-copy** and requires a `Pod` layout: no implicit padding, no `bool` (use `PodBool`), no `String` or `Vec`. - State holding `String` / `Vec` / enums → `#[account(borsh)]` + `BorshAccount`. - Fixed-layout state can stay zero-copy but must carry **explicit padding** - (`u32` + `u8` leaves three bytes — name them). + (`u32` + `u8` leaves three bytes, so name them). - `#[derive(InitSpace)]` and `#[max_len(N)]` work as before on borsh accounts; Pod accounts should size with `core::mem::size_of`. **Default to `#[account(borsh)]` when porting.** v1 accounts were borsh-encoded, -so it reproduces the on-chain layout byte-for-byte and existing clients and +so it reproduces the onchain layout byte-for-byte and existing clients and tests keep working. Only keep zero-copy where the program deliberately wants it -(a large slab, say) — converting such a program to borsh defeats its purpose. +(a large slab, say), because converting one to borsh defeats its purpose. v2 has no `Account::try_from(&AccountView)`. To load a borsh account out of `remaining_accounts` **for writing**, use `AnchorAccount::load_mut`, which is @@ -125,9 +134,9 @@ let mut payload = &data[disc_len..]; ``` `Owner` is a const in v2 (`const OWNER: Address`), which makes a foreign-owned -account — a vendored Pyth or Bubblegum type, say — straightforward to declare. +account (a vendored Pyth or Bubblegum type, say) straightforward to declare. -## Borrows across CPIs +## Borrows Held Across CPIs Are What Tests Catch **This is the rule tests catch and the compiler does not.** @@ -135,7 +144,7 @@ v2's typed CPI handles make aliasing a compile error: passing one account into both a writable and a read-only CPI slot will not build. For **two read-only slots** there is nothing to work around: `cpi_handle()` -takes `&self`, so calling it twice on the same field is fine. Prefer it — on a +takes `&self`, so calling it twice on the same field is fine. Prefer it: on a data account the wrapper's own `cpi_handle()` also relaxes the runtime borrow check, which a hand-built handle does not. @@ -164,10 +173,10 @@ MintTo { mint: mint_handle, to: ..., authority: authority_handle } Take the writable handle **last**: it borrows the field mutably for the rest of the scope, so any `msg!` or `.address()` on the same account has to come first. -## Constraints that reference the account being initialized +## An Init Constraint Cannot Name the Account Being Initialized -`mint::authority = mint_account` on `mint_account` itself — a PDA that is its -own mint authority — is rejected at macro-expansion time: an SPL `init` +`mint::authority = mint_account` on `mint_account` itself, a PDA that is its +own mint authority, is rejected at macro-expansion time: an SPL `init` constraint has to name a *sibling* field. The same goes for `token::authority = `. @@ -195,13 +204,13 @@ because a CPI in the release window could have mutated either. Three ways this goes wrong: 1. **Dereferencing after release panics** (`account borrow released (closed)`). - That includes the derive's own use of the account after the handler returns — + 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. 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. -3. **A read-only account cannot be reacquired** — there is no read-only +3. **A read-only account cannot be reacquired.** There is no read-only reacquire. If the derive references the account after the handler, it has to be declared `mut`. @@ -211,7 +220,7 @@ Also: asking a read-only account for a writable handle panics, so read **On a `Box`ed account, call `to_cpi_handle_mut()` / `to_cpi_handle()`, not `cpi_handle_mut()` / `cpi_handle()`.** `Box`'s `AnchorAccount` impl supplies only `account()`, so `cpi_handle_mut()` falls through to the default, which -builds a handle *without* releasing the wrapper's data borrow — the CPI is then +builds a handle *without* releasing the wrapper's data borrow, so the CPI is then rejected with `AccountBorrowFailed`. `Box`'s `ToCpiHandleMut` impl does forward to the inner type, which is where the release lives. It compiles either way, so this only shows up in a test. @@ -221,15 +230,16 @@ Not every failure of this kind needs a release: a handler that only *reads* a where `try_borrow_mut` on a copied view would be rejected. A **`mut`** data account is different. Loading one sets pinocchio's exclusive -sentinel (`borrow_state == 0`), so *any* `try_borrow()` on it fails — the +sentinel (`borrow_state == 0`), so *any* `try_borrow()` on it fails. The wrapper itself reads through `borrow_unchecked`. Three ways out, in order of preference: -1. **For a Token-2022 extension, use anchor-spl's accessor.** +1. **For a Token Extensions Program extension, use anchor-spl's accessor.** `TokenInterfaceAccountExtensions::get_extension::()` on an `InterfaceAccount` or `InterfaceAccount` parses the TLV through the borrow the wrapper already holds, and checks the account is - owned by Token-2022 on the way. It is bounded on `Pod`, so it covers every + owned by the Token Extensions Program on the way. It is bounded on `Pod`, so + it covers every fixed-size extension but not a variable-length one like `TokenMetadata`. 2. **Drop the `mut`** if the account is not actually written. A mint passed to `transfer_checked_with_fee` is read-only: the withheld fee accrues on the @@ -255,41 +265,46 @@ preference: released. This is what `token-extensions/metadata` does to reach `TokenMetadata`, which `get_extension` cannot. -`unsafe { account.account().borrow_unchecked() }` — reading through the -exclusive borrow you already hold — is what `get_extension` does internally, and +`unsafe { account.account().borrow_unchecked() }`, reading through the +exclusive borrow you already hold, is what `get_extension` does internally, and it is sound whenever the instruction holds that borrow for its whole duration and hands out no second one. It is nonetheless not needed anywhere in this repository's Anchor programs, and it should stay that way: one of the three options above has covered every case so far. -## What still needs `unsafe` +## Where `unsafe` Is Still Unavoidable Three Anchor programs read the `LastRestartSlot` sysvar, which pinocchio has no typed accessor for. Call **`pinocchio::sysvars::get_sysvar(&mut buf, &id, 0)`** rather than the `sol_get_sysvar` syscall directly: it is a safe wrapper, and -off-chain it is a no-op that leaves the buffer zeroed, so IDL and client builds +offchain it is a no-op that leaves the buffer zeroed, so IDL and client builds read "the cluster has never restarted" without a `cfg` split of their own. After that, two kinds of `unsafe` are left in the Anchor programs, and neither has a safe equivalent in v2: -| Site | Why | -| --- | --- | -| `transfer-hook/*/anchor/…/entrypoint.rs` | `pub unsafe extern "C" fn entrypoint` is the loader ABI. These programs claim the entrypoint themselves to remap an SPL interface discriminator, so the `unsafe` the `#[program]` macro would have hidden is written out. | -| `order-book/…/place_order.rs` | `AnchorAccount::load_mut` is an `unsafe fn` on the trait, and it is the only way to get a writable typed wrapper for an account arriving through `remaining_accounts`. `BorshAccount`'s implementation borrows through the checked `try_borrow_mut`, so a duplicate fails with `AccountBorrowFailed` rather than aliasing. | +- **`transfer-hook/*/anchor/…/entrypoint.rs`**: `pub unsafe extern "C" fn + entrypoint` is the loader ABI. These programs claim the entrypoint themselves + to remap an SPL interface discriminator, so the `unsafe` the `#[program]` + macro would have hidden is written out. +- **`order-book/…/place_order.rs`**: `AnchorAccount::load_mut` is an `unsafe fn` + on the trait, and it is the only way to get a writable typed wrapper for an + account arriving through `remaining_accounts`. `BorshAccount`'s implementation + borrows through the checked `try_borrow_mut`, so a duplicate fails with + `AccountBorrowFailed` rather than aliasing. Any lint that bans `unsafe` repository-wide has to exempt those, plus the Quasar and pinocchio programs, where raw zero-copy access is the point of the example rather than an escape hatch. -## Instruction discriminators, and programs that implement an interface +## Discriminators, and Programs That Implement an Interface By default a handler still dispatches on `sha256("global:")[..8]`, so existing clients and tests keep working. What changed is the override: - `#[interface(...)]` and the `interface-instructions` feature are **gone**. - `#[discrim = N]` on an executable `#[program]` takes a **single byte**, and - it is all-or-nothing — if one handler has it, every handler needs one. + it is all-or-nothing: if one handler has it, every handler needs one. - `#[program(interface, program_id = ID)]` accepts arbitrary discriminator bytes, but it declares an interface for *other* programs to CPI into. It generates a CPI client and no dispatch, so the crate builds to a ~900-byte @@ -297,7 +312,7 @@ existing clients and tests keep working. What changed is the override: `ProgramLoad("Entrypoint out of bounds")`. That leaves no direct way to write a program that answers to a foreign -eight-byte discriminator — an SPL transfer hook's `Execute`, say. The +eight-byte discriminator, an SPL transfer hook's `Execute`, say. The transfer-hook examples here handle it by taking the entrypoint over: the crate sets `default = ["no-entrypoint"]`, which makes anchor export its dispatch as `__anchor_dispatch` instead of claiming `entrypoint`, and `src/entrypoint.rs` @@ -305,13 +320,13 @@ claims `entrypoint` itself, swaps the interface discriminator for the matching handler's, and delegates. The payload behind the discriminator is unchanged, so nothing else has to be replicated. With `no-entrypoint` set, the crate also has to invoke `pinocchio::default_allocator!()` and -`pinocchio::default_panic_handler!()` itself — anchor only emits those on the +`pinocchio::default_panic_handler!()` itself: anchor only emits those on the path where it owns the entrypoint. -## Duplicate accounts +## Duplicate Mutable Accounts Are Rejected v2 rejects an account that appears in more than one declared slot when any of -those slots is mutable — `ConstraintDuplicateMutableAccount`, custom error +those slots is mutable, with `ConstraintDuplicateMutableAccount`, custom error 2040. `#[account(unsafe(dup))]` opts a slot out; it implies `mut`, so it replaces the `mut` rather than joining it. @@ -322,7 +337,7 @@ that can legitimately alias needs the constraint, including a `payer`. Why the rule exists: v1 deserialized each `Account` into an owned copy and serialized it back at the end of the instruction. Two slots over one account meant two independent copies, and the second write-back silently clobbered the -first — the classic self-transfer bug, where debiting one copy and crediting +first, the classic self-transfer bug, where debiting one copy and crediting the other and writing both leaves the balance higher than it started. v2 accounts are zero-copy views into the runtime's buffer, so two mutable wrappers over one account would be two `&mut` to the same bytes. Hence the loader @@ -332,7 +347,7 @@ The `unsafe` is not decoration, so check two things before reaching for it: 1. **That the accounts can actually alias.** If no caller ever passes the same address twice, the constraint disables a live check for nothing. Leave it - off. `transfer-tokens`' `transfer` is this case — its test funds a fresh + off. `transfer-tokens`' `transfer` is this case: its test funds a fresh `Keypair` as the recipient. 2. **That the aliasing slots hold no deserialized state.** `Signer`, `SystemAccount` and `UncheckedAccount` carry no copy to write back, so @@ -343,7 +358,7 @@ The `unsafe` is not decoration, so check two things before reaching for it: aliasing `Account` slots are what the check is *for*, and the fix there is to restructure the accounts so only one of them exists. -## anchor-spl +## anchor-spl Moved Its Types and Dropped Its Features `Mint` moved from `anchor_spl::token` to `anchor_spl::mint`, and the namespaced constraints (`mint::decimals`, `token::authority`, …) expand to paths rooted at @@ -357,13 +372,14 @@ a field read off another account: `mint::authority = payer`, not `mint::authority = payer.address()`; `associated_token::mint = mint_to_raise`, not `= fundraiser.mint_to_raise`. -CPI structs dropped their `token_program_id` / `program_id` slots — the program +CPI structs dropped their `token_program_id` / `program_id` slots. The program comes from the `CpiContext`. `create_metadata_accounts_v3` takes four arguments (the signer flag moved into the accounts struct, and the optional `rent` account is gone). There are **no `extensions::*` constraints**, for init or validation. To create a -mint with a Token-2022 extension, do it by hand — which is what this repo's +mint with a Token Extensions Program extension, do it by hand, which is what +this repository's `non-transferable` example always did: ```rust @@ -375,44 +391,48 @@ let mint_size = ExtensionType::try_calculate_account_len::(&[ Extension initialization must come before `InitializeMint2`. -## v2-only primitives worth reaching for +## v2-Only Primitives Worth Reaching For -These have no v1 equivalent, so a mechanical port never produces them — but they +These have no v1 equivalent, so a mechanical port never produces them, but they are often the right answer when a straight translation gets ugly: -| Primitive | Use | -|---|---| -| `Slab` | Zero-copy header + dynamic item tail (ledgers, order books, event logs). `Account` is `Slab` underneath. | -| `PodVec` | Fixed-capacity vec with a `u16` length, stored inline — variable-length data without leaving Pod. | -| `PodU64`, `PodI128`, `PodBool`, … | Alignment-1 integer wrappers, so a `#[repr(C)]` struct packs with no padding. | -| `#[pod_wrapper]` | Safe enum-to-Pod conversion that validates the discriminant on equality and conversion. | -| `Nested` | Share one `#[derive(Accounts)]` validation block across instructions. | -| `#[event(bytemuck)]` | Fixed-size events: disc + one memcpy. Plain `#[event]` defaults to wincode (borsh-wire-compatible). | +- `Slab`: zero-copy header with a dynamic item tail, for ledgers, + order books and event logs. `Account` is `Slab` underneath. +- `PodVec`: fixed-capacity vec with a `u16` length, stored inline, so + variable-length data without leaving Pod. +- `PodU64`, `PodI128`, `PodBool` and friends: alignment-1 integer wrappers, so a + `#[repr(C)]` struct packs with no padding. +- `#[pod_wrapper]`: safe enum-to-Pod conversion that validates the discriminant + on equality and conversion. +- `Nested`: share one `#[derive(Accounts)]` validation block across + instructions. +- `#[event(bytemuck)]`: fixed-size events, a discriminator plus one memcpy. + Plain `#[event]` defaults to wincode, which is borsh-wire-compatible. If a program was zero-copy in v1 and the port is fighting Pod's rules, the answer is usually one of the first four rather than moving it to borsh. ### Feature flags -- `guardrails` (default on) — runtime safety nets. Dropping it saves ~300 bytes +- `guardrails` (default on): runtime safety nets. Dropping it saves ~300 bytes and 1–2 CU per account, at the cost of diagnostic panics on misuse. -- `const-rent` (default off) — folds `Rent::get()` to a compile-time constant, +- `const-rent` (default off): folds `Rent::get()` to a compile-time constant, ~85 CU per `create_account`. Burns the rent formula into the binary, so a formula change (SIMD-0194) needs a rebuild. -- `compat` (default off) — restores v1-shaped `error!`, `err!`, `pubkey!`, +- `compat` (default off): restores v1-shaped `error!`, `err!`, `pubkey!`, `debug!`. Useful mid-port; `debug!` heap-allocates through `alloc::format!`. ### Tooling -- `anchor debugger` — TUI stepping through SBF instructions per test. -- `anchor test --profile` — per-test register-trace flamegraphs under +- `anchor debugger`: TUI stepping through SBF instructions per test. +- `anchor test --profile`: per-test register-trace flamegraphs under `target/anchor-v2-profile/`. -- `anchor-v2-testing` — wraps LiteSVM with optional register-trace capture. +- `anchor-v2-testing`: wraps LiteSVM with optional register-trace capture. -## Hand-built CPIs (`invoke` / `invoke_signed`) +## Hand-Built CPIs Take Handles, Not Account Infos -An example that builds its own `Instruction` — because the callee's crate is -not on a compatible Solana version — hands `invoke` a slice of `CpiHandle` +An example that builds its own `Instruction`, because the callee's crate is +not on a compatible Solana version, hands `invoke` a slice of `CpiHandle` rather than a slice of `AccountInfo`. Four rules, all of them enforced at runtime rather than by the compiler: @@ -425,7 +445,7 @@ runtime rather than by the compiler: what breaks the positional match. - **An account filling two meta slots supplies two handles.** Bubblegum's `Transfer` names the same PDA as both `leaf_owner` and `leaf_delegate`, so the - handle appears twice. Read-only handles make this trivial — `cpi_handle()` + handle appears twice. Read-only handles make this trivial: `cpi_handle()` takes `&self`. - **Writability has to be at least as strong as the meta.** A writable meta needs a writable handle; a read-only meta accepts either. Going the other way, @@ -433,33 +453,34 @@ runtime rather than by the compiler: *"cpi_handle_mut called on a read-only account"*, which surfaces as `ProgramFailedToComplete` and a `src/traits.rs` log line. -Mixing the two handle kinds in one vec means converting element-wise — -`vec![a.cpi_handle_mut().into(), b.cpi_handle()]` — since a trailing +Mixing the two handle kinds in one vec means converting element-wise, as in +`vec![a.cpi_handle_mut().into(), b.cpi_handle()]`, since a trailing `.map(CpiHandle::from)` forces every element to be a `CpiHandleMut`. A `BorshAccount` that signs such a CPI still needs `release_borrow()` first, per -[Borrows across CPIs](#borrows-across-cpis). When the account is read-only and -nothing reads it after the CPI, there is no reacquire — `reacquire_borrow_mut` +[Borrows held across CPIs](#borrows-held-across-cpis-are-what-tests-catch). When the account is read-only and +nothing reads it after the CPI, there is no reacquire, because `reacquire_borrow_mut` asserts the account was loaded mutably. Vendored types that derive `BorshSerialize` over `Address` fields need the `borsh` feature on `solana-address`; `Address` is pinocchio's re-export of that crate's type and carries no borsh impls by default. Their `serialize` returns -`io::Error`, which no longer converts into `ProgramError` — map it: +`io::Error`, which does not convert into `ProgramError`, so map it: ```rust args.serialize(&mut data) .map_err(|_| ProgramError::InvalidInstructionData)?; ``` -## Seeds, sysvars, cross-program types +## Seeds, Sysvars and Cross-Program Types -`seeds` takes a byte array directly and binds it itself — `id.to_le_bytes()`, +`seeds` takes a byte array directly and binds it itself: `id.to_le_bytes()`, not `id.to_le_bytes().as_ref()`, which produces a temporary that dies before the derive uses it. -pinocchio ships only the `Clock` and `Rent` sysvars. Anything else — this repo -needs `LastRestartSlot` — has to be declared locally and read through the +pinocchio ships only the `Clock` and `Rent` sysvars. Anything else (this +repository needs `LastRestartSlot`) has to be declared locally and read through +the `sol_get_sysvar` syscall (`solana-define-syscall`). The Quasar variants of these same examples carry the identical workaround. @@ -471,7 +492,7 @@ Ambiguity to watch for: the prelude exports an `Event` trait, so a state struct named `Event` that reaches the crate root via glob re-export becomes ambiguous. Import it explicitly (`use crate::state::Event;`). -## Tests +## Tests Assert Numeric Error Codes The test-side surface barely changed, but three things move: @@ -479,7 +500,7 @@ The test-side surface barely changed, but three things move: module is at the crate root and exposes `ID`, not `id()`. - `solana_program::pubkey::Pubkey` only exists under the `compat` feature; `anchor_lang::Address` is the same 32-byte type. -- `anchor_lang::prelude::Clock` is pinocchio's on-chain type. LiteSVM's +- `anchor_lang::prelude::Clock` is pinocchio's onchain type. LiteSVM's `get_sysvar` / `set_sysvar` want the host-side `solana_clock::Clock`. - `#[error_code]` no longer generates `From for u32`. It makes the enum `#[repr(u32)]` and generates only `From for anchor_lang::Error`, so a @@ -487,15 +508,15 @@ The test-side surface barely changed, but three things move: the default offset, overridable with `#[error_code(offset = ...)]`). Tests that decode account bytes with borsh keep working, because -`BorshConfig` makes wincode's wire format byte-identical — but a Pod account +`BorshConfig` makes wincode's wire format byte-identical, but a Pod account that grew explicit padding needs that padding mirrored in the test's decode struct, since `try_from_slice` rejects trailing bytes. -## Building a workspace with more than one program +## A Workspace With More Than One Program Needs Per-Crate Builds `cargo-build-sbf` at an anchor workspace root builds every member in one invocation, and cargo unifies features across them. A program that depends on a -sibling with `features = ["cpi"]` — which implies `no-entrypoint` — therefore +sibling with `features = ["cpi"]`, which implies `no-entrypoint`, therefore makes that sibling's *own* `.so` build with `no-entrypoint` too, and in v2 that exports its dispatch as `__anchor_dispatch` rather than `entrypoint`. The result loads with `ProgramLoad("Entrypoint out of bounds")`. @@ -503,11 +524,11 @@ result loads with `ProgramLoad("Entrypoint out of bounds")`. `anchor build` builds each program separately and is unaffected. Anything else driving `cargo-build-sbf` has to do the same. -## Toolchain +## Toolchain Versions the Port Depends On CI installs the CLI with `avm install 2.0.0-rc.1`; it is a pre-release, so it has to be named explicitly rather than resolved as latest. A v2 CLI cannot build v1 programs, so that pin can only move once every example is ported. `anchor idl build` compiles the test targets too, so the `.so` has to exist -first — run `cargo-build-sbf` before regenerating an IDL. +first, so run `cargo-build-sbf` before regenerating an IDL.