diff --git a/.github/workflows/quasar.yml b/.github/workflows/quasar.yml index 55d20477b..f580ea1f4 100644 --- a/.github/workflows/quasar.yml +++ b/.github/workflows/quasar.yml @@ -290,6 +290,63 @@ jobs: if: always() run: sccache --show-stats + # Rustfmt and Clippy for the Quasar crates. + # + # The repository-wide `Rust Lint` workflow runs `cargo fmt --check` and + # `cargo clippy` from the repository root, and those only ever see members of + # the root workspace. Every Quasar crate declares its own `[workspace]`, + # because `quasar build` runs `cargo metadata --locked` against a per-project + # lockfile, so they cannot be members and that workflow never sees them. + # + # This walks Cargo manifests rather than reusing `build-and-test`'s project + # list, which is directories named exactly `quasar`. That list misses + # `tokens/quasar-metadata`, a vendored library three of the examples depend on. + # Linting is a host build that needs neither the Quasar CLI nor platform-tools, + # and it does not vary by Solana version, so it runs once rather than per + # matrix entry. + lint: + needs: changes + if: needs.changes.outputs.total_projects != '0' + runs-on: ubuntu-latest + steps: + - uses: actions/checkout@v5 + - uses: dtolnay/rust-toolchain@stable + with: + components: rustfmt, clippy + - name: Cache Cargo registry and git + uses: actions/cache@v4 + with: + path: | + ~/.cargo/registry + ~/.cargo/git + key: cargo-lint-${{ runner.os }}-${{ hashFiles('**/Cargo.toml') }} + restore-keys: | + cargo-lint-${{ runner.os }}- + - name: Rustfmt and Clippy + run: | + failed=() + while read -r manifest; do + crate=$(dirname "$manifest") + echo "::group::$crate" + if ! ( cd "$crate" && cargo fmt --check ); then + echo "::error::cargo fmt --check failed for $crate" + failed+=("$crate (fmt)") + fi + # Matches the repository-wide Clippy job: diverging_sub_expression is + # a false positive from the program macro's expansion. + if ! ( cd "$crate" && cargo clippy -- -D warnings -A clippy::diverging_sub_expression ); then + echo "::error::cargo clippy failed for $crate" + failed+=("$crate (clippy)") + fi + echo "::endgroup::" + done < <(grep -rl --include=Cargo.toml 'quasar-lang' . | grep -v '/target/' | sort) + + if [ ${#failed[@]} -ne 0 ]; then + printf '%s\n' "Lint failures:" "${failed[@]}" + exit 1 + fi + echo "All Quasar crates pass rustfmt and clippy." + summary: needs: [changes, build-and-test] if: always() diff --git a/basics/account-data/quasar/Cargo.toml b/basics/account-data/quasar/Cargo.toml index 140ad4e4a..977935864 100644 --- a/basics/account-data/quasar/Cargo.toml +++ b/basics/account-data/quasar/Cargo.toml @@ -30,6 +30,11 @@ idl-build = ["quasar-lang/idl-build"] # git. Keep this rev in lockstep with the CLI rev installed by # .github/workflows/quasar.yml. quasar-lang = { git = "https://github.com/blueshift-gg/quasar", rev = "be60fca" } +# Pinned: quasar-lang asks for zeropod "0.3.3" and wincode 0.4, but zeropod +# 0.3.4 moved to wincode 0.5, so a fresh resolve (no lockfile is committed) +# splits the graph across two wincode versions and every Pod* trait bound +# fails. Unpin when quasar-lang's pinned rev accepts zeropod 0.3.4+. +zeropod = "=0.3.3" solana-instruction = { version = "3.2.0" } [dev-dependencies] diff --git a/basics/account-data/quasar/src/instructions/create.rs b/basics/account-data/quasar/src/instructions/create.rs index 423caa4f0..a0b2c34b3 100644 --- a/basics/account-data/quasar/src/instructions/create.rs +++ b/basics/account-data/quasar/src/instructions/create.rs @@ -23,7 +23,12 @@ pub fn handle_create_address_info( ) -> Result<(), ProgramError> { let rent = Rent::get()?; accounts.address_info.set_inner( - AddressInfoInner { house_number, name, street, city }, + AddressInfoInner { + house_number, + name, + street, + city, + }, accounts.payer.to_account_view(), rent.lamports_per_byte(), rent.exemption_threshold_raw(), diff --git a/basics/account-data/quasar/src/lib.rs b/basics/account-data/quasar/src/lib.rs index 5063a4bd7..448c14359 100644 --- a/basics/account-data/quasar/src/lib.rs +++ b/basics/account-data/quasar/src/lib.rs @@ -2,9 +2,9 @@ use quasar_lang::prelude::*; -mod instructions; +pub mod instructions; use instructions::*; -mod state; +pub mod state; #[cfg(test)] mod tests; @@ -30,6 +30,12 @@ mod quasar_account_data { street: String<50>, city: String<50>, ) -> Result<(), ProgramError> { - instructions::handle_create_address_info(&mut ctx.accounts, name, house_number, street, city) + instructions::handle_create_address_info( + &mut ctx.accounts, + name, + house_number, + street, + city, + ) } } diff --git a/basics/checking-accounts/quasar/Cargo.toml b/basics/checking-accounts/quasar/Cargo.toml index f021f3b40..8cb375e2b 100644 --- a/basics/checking-accounts/quasar/Cargo.toml +++ b/basics/checking-accounts/quasar/Cargo.toml @@ -30,6 +30,11 @@ idl-build = ["quasar-lang/idl-build"] # git. Keep this rev in lockstep with the CLI rev installed by # .github/workflows/quasar.yml. quasar-lang = { git = "https://github.com/blueshift-gg/quasar", rev = "be60fca" } +# Pinned: quasar-lang asks for zeropod "0.3.3" and wincode 0.4, but zeropod +# 0.3.4 moved to wincode 0.5, so a fresh resolve (no lockfile is committed) +# splits the graph across two wincode versions and every Pod* trait bound +# fails. Unpin when quasar-lang's pinned rev accepts zeropod 0.3.4+. +zeropod = "=0.3.3" solana-instruction = { version = "3.2.0" } [dev-dependencies] diff --git a/basics/checking-accounts/quasar/src/instructions/check_accounts.rs b/basics/checking-accounts/quasar/src/instructions/check_accounts.rs index 8de395b36..1949cd3c1 100644 --- a/basics/checking-accounts/quasar/src/instructions/check_accounts.rs +++ b/basics/checking-accounts/quasar/src/instructions/check_accounts.rs @@ -22,7 +22,9 @@ pub struct CheckAccountsAccountConstraints { } #[inline(always)] -pub fn handle_check_accounts(_accounts: &mut CheckAccountsAccountConstraints) -> Result<(), ProgramError> { +pub fn handle_check_accounts( + _accounts: &mut CheckAccountsAccountConstraints, +) -> Result<(), ProgramError> { // All validation happens declaratively via the account types above. // If any check fails, the runtime rejects the transaction before this runs. Ok(()) diff --git a/basics/checking-accounts/quasar/src/lib.rs b/basics/checking-accounts/quasar/src/lib.rs index 289910bb4..b89d5589e 100644 --- a/basics/checking-accounts/quasar/src/lib.rs +++ b/basics/checking-accounts/quasar/src/lib.rs @@ -2,7 +2,7 @@ use quasar_lang::prelude::*; -mod instructions; +pub mod instructions; use instructions::*; #[cfg(test)] mod tests; diff --git a/basics/close-account/quasar/Cargo.toml b/basics/close-account/quasar/Cargo.toml index bee4ab640..4a5a04630 100644 --- a/basics/close-account/quasar/Cargo.toml +++ b/basics/close-account/quasar/Cargo.toml @@ -30,6 +30,11 @@ idl-build = ["quasar-lang/idl-build"] # git. Keep this rev in lockstep with the CLI rev installed by # .github/workflows/quasar.yml. quasar-lang = { git = "https://github.com/blueshift-gg/quasar", rev = "be60fca" } +# Pinned: quasar-lang asks for zeropod "0.3.3" and wincode 0.4, but zeropod +# 0.3.4 moved to wincode 0.5, so a fresh resolve (no lockfile is committed) +# splits the graph across two wincode versions and every Pod* trait bound +# fails. Unpin when quasar-lang's pinned rev accepts zeropod 0.3.4+. +zeropod = "=0.3.3" solana-instruction = { version = "3.2.0" } [dev-dependencies] diff --git a/basics/close-account/quasar/src/instructions/create_user.rs b/basics/close-account/quasar/src/instructions/create_user.rs index efa283229..04e3600d8 100644 --- a/basics/close-account/quasar/src/instructions/create_user.rs +++ b/basics/close-account/quasar/src/instructions/create_user.rs @@ -22,7 +22,11 @@ pub fn handle_create_user( let user_address = *accounts.user.to_account_view().address(); let rent = Rent::get()?; accounts.user_account.set_inner( - UserInner { bump, user: user_address, name }, + UserInner { + bump, + user: user_address, + name, + }, accounts.user.to_account_view(), rent.lamports_per_byte(), rent.exemption_threshold_raw(), diff --git a/basics/close-account/quasar/src/instructions/mod.rs b/basics/close-account/quasar/src/instructions/mod.rs index 9f1c542a3..fe570ffec 100644 --- a/basics/close-account/quasar/src/instructions/mod.rs +++ b/basics/close-account/quasar/src/instructions/mod.rs @@ -1,5 +1,5 @@ -pub mod create_user; pub mod close_user; +pub mod create_user; -pub use create_user::*; pub use close_user::*; +pub use create_user::*; diff --git a/basics/close-account/quasar/src/lib.rs b/basics/close-account/quasar/src/lib.rs index 7abb5f8a5..d5e32df94 100644 --- a/basics/close-account/quasar/src/lib.rs +++ b/basics/close-account/quasar/src/lib.rs @@ -2,9 +2,9 @@ use quasar_lang::prelude::*; -mod instructions; +pub mod instructions; use instructions::*; -mod state; +pub mod state; #[cfg(test)] mod tests; @@ -16,7 +16,10 @@ mod quasar_close_account { /// Create a user account with a name. #[instruction(discriminator = 0)] - pub fn create_user(ctx: Ctx, name: String<50>) -> Result<(), ProgramError> { + pub fn create_user( + ctx: Ctx, + name: String<50>, + ) -> Result<(), ProgramError> { let bump = ctx.bumps.user_account; instructions::handle_create_user(&mut ctx.accounts, name, bump) } diff --git a/basics/close-account/quasar/src/tests.rs b/basics/close-account/quasar/src/tests.rs index 3776cfa20..eb2ac551c 100644 --- a/basics/close-account/quasar/src/tests.rs +++ b/basics/close-account/quasar/src/tests.rs @@ -72,7 +72,8 @@ fn close_user_rejects_a_non_owner(test: &mut Test) { test.send(instruction).fails_with(QuasarError::InvalidPda); assert!( - test.account(victim_account).is_some_and(|account| !account.data.is_empty()), + test.account(victim_account) + .is_some_and(|account| !account.data.is_empty()), "the victim's account must survive the failed close" ); } diff --git a/basics/counter/quasar/Cargo.toml b/basics/counter/quasar/Cargo.toml index 28b1bae5f..d927e7596 100644 --- a/basics/counter/quasar/Cargo.toml +++ b/basics/counter/quasar/Cargo.toml @@ -30,6 +30,11 @@ idl-build = ["quasar-lang/idl-build"] # git. Keep this rev in lockstep with the CLI rev installed by # .github/workflows/quasar.yml. quasar-lang = { git = "https://github.com/blueshift-gg/quasar", rev = "be60fca" } +# Pinned: quasar-lang asks for zeropod "0.3.3" and wincode 0.4, but zeropod +# 0.3.4 moved to wincode 0.5, so a fresh resolve (no lockfile is committed) +# splits the graph across two wincode versions and every Pod* trait bound +# fails. Unpin when quasar-lang's pinned rev accepts zeropod 0.3.4+. +zeropod = "=0.3.3" solana-instruction = { version = "3.2.0" } [dev-dependencies] diff --git a/basics/counter/quasar/src/instructions/increment.rs b/basics/counter/quasar/src/instructions/increment.rs index a8c1a9d66..021f5ea05 100644 --- a/basics/counter/quasar/src/instructions/increment.rs +++ b/basics/counter/quasar/src/instructions/increment.rs @@ -13,9 +13,7 @@ pub struct IncrementAccountConstraints { #[inline(always)] pub fn handle_increment(accounts: &mut IncrementAccountConstraints) -> Result<(), ProgramError> { let current: u64 = accounts.counter.count.into(); - let next = current - .checked_add(1) - .ok_or(CounterError::MathOverflow)?; + let next = current.checked_add(1).ok_or(CounterError::MathOverflow)?; accounts.counter.count = PodU64::from(next); Ok(()) } diff --git a/basics/counter/quasar/src/instructions/initialize_counter.rs b/basics/counter/quasar/src/instructions/initialize_counter.rs index 666951e81..0955b8101 100644 --- a/basics/counter/quasar/src/instructions/initialize_counter.rs +++ b/basics/counter/quasar/src/instructions/initialize_counter.rs @@ -13,7 +13,9 @@ pub struct InitializeCounterAccountConstraints { } #[inline(always)] -pub fn handle_initialize_counter(accounts: &mut InitializeCounterAccountConstraints) -> Result<(), ProgramError> { +pub fn handle_initialize_counter( + accounts: &mut InitializeCounterAccountConstraints, +) -> Result<(), ProgramError> { accounts.counter.set_inner(CounterInner { count: 0 }); Ok(()) } diff --git a/basics/counter/quasar/src/instructions/mod.rs b/basics/counter/quasar/src/instructions/mod.rs index 7dfbf3f8f..0dd7c5cac 100644 --- a/basics/counter/quasar/src/instructions/mod.rs +++ b/basics/counter/quasar/src/instructions/mod.rs @@ -1,5 +1,5 @@ -pub mod initialize_counter; pub mod increment; +pub mod initialize_counter; -pub use initialize_counter::*; pub use increment::*; +pub use initialize_counter::*; diff --git a/basics/counter/quasar/src/lib.rs b/basics/counter/quasar/src/lib.rs index c1bf1ce2e..395640e10 100644 --- a/basics/counter/quasar/src/lib.rs +++ b/basics/counter/quasar/src/lib.rs @@ -3,9 +3,9 @@ use quasar_lang::prelude::*; mod error; -mod instructions; +pub mod instructions; use instructions::*; -mod state; +pub mod state; #[cfg(test)] mod tests; @@ -16,7 +16,9 @@ mod quasar_counter { use super::*; #[instruction(discriminator = 0)] - pub fn initialize_counter(ctx: Ctx) -> Result<(), ProgramError> { + pub fn initialize_counter( + ctx: Ctx, + ) -> Result<(), ProgramError> { instructions::handle_initialize_counter(&mut ctx.accounts) } diff --git a/basics/counter/quasar/src/tests.rs b/basics/counter/quasar/src/tests.rs index dfa5e2217..1275b1344 100644 --- a/basics/counter/quasar/src/tests.rs +++ b/basics/counter/quasar/src/tests.rs @@ -16,7 +16,8 @@ fn initialize_counter_creates_the_pda(test: &mut Test) { // The counter PDA and system program are canonical derivations, so the // generated instruction only asks for the payer. - test.send(InitializeCounterInstruction { payer: PAYER }).succeeds(); + test.send(InitializeCounterInstruction { payer: PAYER }) + .succeeds(); let state = test.read::(counter); assert_eq!(u64::from(state.count), 0); @@ -26,7 +27,8 @@ fn initialize_counter_creates_the_pda(test: &mut Test) { fn increment_advances_the_count(test: &mut Test) { test.add(Wallet::new().at(PAYER)); let counter = test.derive_pda(Counter::seeds(&PAYER)); - test.send(InitializeCounterInstruction { payer: PAYER }).succeeds(); + test.send(InitializeCounterInstruction { payer: PAYER }) + .succeeds(); test.send(IncrementInstruction { counter }).succeeds(); diff --git a/basics/create-account/quasar/Cargo.toml b/basics/create-account/quasar/Cargo.toml index ace0e06c3..2ac559b71 100644 --- a/basics/create-account/quasar/Cargo.toml +++ b/basics/create-account/quasar/Cargo.toml @@ -30,6 +30,11 @@ idl-build = ["quasar-lang/idl-build"] # git. Keep this rev in lockstep with the CLI rev installed by # .github/workflows/quasar.yml. quasar-lang = { git = "https://github.com/blueshift-gg/quasar", rev = "be60fca" } +# Pinned: quasar-lang asks for zeropod "0.3.3" and wincode 0.4, but zeropod +# 0.3.4 moved to wincode 0.5, so a fresh resolve (no lockfile is committed) +# splits the graph across two wincode versions and every Pod* trait bound +# fails. Unpin when quasar-lang's pinned rev accepts zeropod 0.3.4+. +zeropod = "=0.3.3" solana-instruction = { version = "3.2.0" } [dev-dependencies] diff --git a/basics/create-account/quasar/src/instructions/create_system_account.rs b/basics/create-account/quasar/src/instructions/create_system_account.rs index 80e6d92db..d218389b6 100644 --- a/basics/create-account/quasar/src/instructions/create_system_account.rs +++ b/basics/create-account/quasar/src/instructions/create_system_account.rs @@ -20,6 +20,12 @@ pub fn handle_create_system_account( let lamports = rent.minimum_balance_unchecked(0); accounts .system_program - .create_account(&accounts.payer, &accounts.new_account, lamports, 0u64, &system_program_address) + .create_account( + &accounts.payer, + &accounts.new_account, + lamports, + 0u64, + &system_program_address, + ) .invoke() } diff --git a/basics/create-account/quasar/src/lib.rs b/basics/create-account/quasar/src/lib.rs index ae8ad0334..354f144f6 100644 --- a/basics/create-account/quasar/src/lib.rs +++ b/basics/create-account/quasar/src/lib.rs @@ -2,7 +2,7 @@ use quasar_lang::prelude::*; -mod instructions; +pub mod instructions; use instructions::*; #[cfg(test)] mod tests; @@ -15,7 +15,9 @@ mod quasar_create_account { /// Create a new system-owned account via CPI to the system program. #[instruction(discriminator = 0)] - pub fn create_system_account(ctx: Ctx) -> Result<(), ProgramError> { + pub fn create_system_account( + ctx: Ctx, + ) -> Result<(), ProgramError> { instructions::handle_create_system_account(&mut ctx.accounts) } } diff --git a/basics/cross-program-invocation/quasar/hand/Cargo.toml b/basics/cross-program-invocation/quasar/hand/Cargo.toml index d1d87a587..eb128f6a5 100644 --- a/basics/cross-program-invocation/quasar/hand/Cargo.toml +++ b/basics/cross-program-invocation/quasar/hand/Cargo.toml @@ -29,6 +29,11 @@ idl-build = ["quasar-lang/idl-build"] # git. Keep this rev in lockstep with the CLI rev installed by # .github/workflows/quasar.yml. quasar-lang = { git = "https://github.com/blueshift-gg/quasar", rev = "be60fca" } +# Pinned: quasar-lang asks for zeropod "0.3.3" and wincode 0.4, but zeropod +# 0.3.4 moved to wincode 0.5, so a fresh resolve (no lockfile is committed) +# splits the graph across two wincode versions and every Pod* trait bound +# fails. Unpin when quasar-lang's pinned rev accepts zeropod 0.3.4+. +zeropod = "=0.3.3" solana-instruction = { version = "3.2.0" } [dev-dependencies] diff --git a/basics/cross-program-invocation/quasar/hand/src/instructions/pull_lever.rs b/basics/cross-program-invocation/quasar/hand/src/instructions/pull_lever.rs index 8e0cb25de..695296069 100644 --- a/basics/cross-program-invocation/quasar/hand/src/instructions/pull_lever.rs +++ b/basics/cross-program-invocation/quasar/hand/src/instructions/pull_lever.rs @@ -12,7 +12,10 @@ pub struct PullLeverAccountConstraints { } #[inline(always)] -pub fn handle_pull_lever(accounts: &PullLeverAccountConstraints, name: &str) -> Result<(), ProgramError> { +pub fn handle_pull_lever( + accounts: &PullLeverAccountConstraints, + name: &str, +) -> Result<(), ProgramError> { log("Hand is pulling the lever!"); // Build the switch_power instruction data for the lever program. diff --git a/basics/cross-program-invocation/quasar/hand/src/lib.rs b/basics/cross-program-invocation/quasar/hand/src/lib.rs index 6fa7e61c0..769450e84 100644 --- a/basics/cross-program-invocation/quasar/hand/src/lib.rs +++ b/basics/cross-program-invocation/quasar/hand/src/lib.rs @@ -2,7 +2,7 @@ use quasar_lang::prelude::*; -mod instructions; +pub mod instructions; use instructions::*; #[cfg(test)] mod tests; @@ -26,7 +26,10 @@ mod quasar_hand { /// Pull the lever by invoking the lever program's switch_power via CPI. #[instruction(discriminator = 0)] - pub fn pull_lever(ctx: Ctx, name: String<50>) -> Result<(), ProgramError> { - instructions::handle_pull_lever(&mut ctx.accounts, name) + pub fn pull_lever( + ctx: Ctx, + name: String<50>, + ) -> Result<(), ProgramError> { + instructions::handle_pull_lever(&ctx.accounts, name) } } diff --git a/basics/cross-program-invocation/quasar/hand/src/tests.rs b/basics/cross-program-invocation/quasar/hand/src/tests.rs index 921850509..c579b44f5 100644 --- a/basics/cross-program-invocation/quasar/hand/src/tests.rs +++ b/basics/cross-program-invocation/quasar/hand/src/tests.rs @@ -1,8 +1,4 @@ -use { - crate::cpi::PullLeverInstruction, - quasar_lang::client::DynString, - quasar_test::prelude::*, -}; +use {crate::cpi::PullLeverInstruction, quasar_lang::client::DynString, quasar_test::prelude::*}; /// PowerStatus discriminator from the lever program. const POWER_STATUS_DISCRIMINATOR: u8 = 1; @@ -51,7 +47,10 @@ fn pull_lever_turns_the_power_on(test: &mut Test) { let logs = outcome.logs().join("\n"); assert!(logs.contains("Hand is pulling"), "hand should log"); - assert!(logs.contains("pulling the power switch"), "lever should log"); + assert!( + logs.contains("pulling the power switch"), + "lever should log" + ); assert!(logs.contains("now on"), "power should turn on"); // Verifies the CPI wire format: the lever logs the name it // deserialised. A stale u32 length prefix on either the inbound diff --git a/basics/cross-program-invocation/quasar/lever/Cargo.toml b/basics/cross-program-invocation/quasar/lever/Cargo.toml index 46746067f..54ef52603 100644 --- a/basics/cross-program-invocation/quasar/lever/Cargo.toml +++ b/basics/cross-program-invocation/quasar/lever/Cargo.toml @@ -29,6 +29,11 @@ idl-build = ["quasar-lang/idl-build"] # git. Keep this rev in lockstep with the CLI rev installed by # .github/workflows/quasar.yml. quasar-lang = { git = "https://github.com/blueshift-gg/quasar", rev = "be60fca" } +# Pinned: quasar-lang asks for zeropod "0.3.3" and wincode 0.4, but zeropod +# 0.3.4 moved to wincode 0.5, so a fresh resolve (no lockfile is committed) +# splits the graph across two wincode versions and every Pod* trait bound +# fails. Unpin when quasar-lang's pinned rev accepts zeropod 0.3.4+. +zeropod = "=0.3.3" solana-instruction = { version = "3.2.0" } [dev-dependencies] diff --git a/basics/cross-program-invocation/quasar/lever/src/instructions/initialize.rs b/basics/cross-program-invocation/quasar/lever/src/instructions/initialize.rs index a7c19715d..b08f497f5 100644 --- a/basics/cross-program-invocation/quasar/lever/src/instructions/initialize.rs +++ b/basics/cross-program-invocation/quasar/lever/src/instructions/initialize.rs @@ -14,8 +14,12 @@ pub struct InitializeLeverAccountConstraints { } #[inline(always)] -pub fn handle_initialize(accounts: &mut InitializeLeverAccountConstraints) -> Result<(), ProgramError> { +pub fn handle_initialize( + accounts: &mut InitializeLeverAccountConstraints, +) -> Result<(), ProgramError> { // Power starts off (false). Counter-style fixed-size set_inner takes only the inner value. - accounts.power.set_inner(PowerStatusInner { is_on: PodBool::from(false) }); + accounts.power.set_inner(PowerStatusInner { + is_on: PodBool::from(false), + }); Ok(()) } diff --git a/basics/cross-program-invocation/quasar/lever/src/instructions/switch_power.rs b/basics/cross-program-invocation/quasar/lever/src/instructions/switch_power.rs index a4cccf078..0ad01fe01 100644 --- a/basics/cross-program-invocation/quasar/lever/src/instructions/switch_power.rs +++ b/basics/cross-program-invocation/quasar/lever/src/instructions/switch_power.rs @@ -1,7 +1,4 @@ -use { - crate::state::PowerStatus, - quasar_lang::prelude::*, -}; +use {crate::state::PowerStatus, quasar_lang::prelude::*}; /// Accounts for toggling the power switch. #[derive(Accounts)] @@ -11,7 +8,10 @@ pub struct SwitchPowerAccountConstraints { } #[inline(always)] -pub fn handle_switch_power(accounts: &mut SwitchPowerAccountConstraints, name: &str) -> Result<(), ProgramError> { +pub fn handle_switch_power( + accounts: &mut SwitchPowerAccountConstraints, + name: &str, +) -> Result<(), ProgramError> { let current: bool = accounts.power.is_on.into(); let new_state = !current; accounts.power.is_on = PodBool::from(new_state); diff --git a/basics/cross-program-invocation/quasar/lever/src/lib.rs b/basics/cross-program-invocation/quasar/lever/src/lib.rs index 644cd8686..cb94e797b 100644 --- a/basics/cross-program-invocation/quasar/lever/src/lib.rs +++ b/basics/cross-program-invocation/quasar/lever/src/lib.rs @@ -2,9 +2,9 @@ use quasar_lang::prelude::*; -mod instructions; +pub mod instructions; use instructions::*; -mod state; +pub mod state; #[cfg(test)] mod tests; @@ -22,7 +22,10 @@ mod quasar_lever { /// Toggle the power switch. Logs who is pulling the lever. #[instruction(discriminator = 1)] - pub fn switch_power(ctx: Ctx, name: String<50>) -> Result<(), ProgramError> { + pub fn switch_power( + ctx: Ctx, + name: String<50>, + ) -> Result<(), ProgramError> { instructions::handle_switch_power(&mut ctx.accounts, name) } } diff --git a/basics/cross-program-invocation/quasar/lever/src/tests.rs b/basics/cross-program-invocation/quasar/lever/src/tests.rs index 3168acf11..d46abcc89 100644 --- a/basics/cross-program-invocation/quasar/lever/src/tests.rs +++ b/basics/cross-program-invocation/quasar/lever/src/tests.rs @@ -29,7 +29,12 @@ fn initialize_creates_the_power_status_switched_off(test: &mut Test) { fn switch_power_turns_the_power_on(test: &mut Test) { let power = test.derive_pda(PowerStatus::seeds()); // Start with power off. - test.write(power, PowerStatusData { is_on: PodBool::from(false) }); + test.write( + power, + PowerStatusData { + is_on: PodBool::from(false), + }, + ); let outcome = test.send(SwitchPowerInstruction { power, @@ -38,7 +43,10 @@ fn switch_power_turns_the_power_on(test: &mut Test) { outcome.succeeds(); let logs = outcome.logs().join("\n"); - assert!(logs.contains("pulling the power switch"), "should log switch"); + assert!( + logs.contains("pulling the power switch"), + "should log switch" + ); assert!(logs.contains("now on"), "should say power is on"); // Verifies wire format: a stale u32 length prefix would corrupt the // deserialised name (e.g. "\0\0\0Al" instead of "Alice"). @@ -55,7 +63,12 @@ fn switch_power_turns_the_power_on(test: &mut Test) { fn switch_power_turns_the_power_off(test: &mut Test) { let power = test.derive_pda(PowerStatus::seeds()); // Start with power on. - test.write(power, PowerStatusData { is_on: PodBool::from(true) }); + test.write( + power, + PowerStatusData { + is_on: PodBool::from(true), + }, + ); let outcome = test.send(SwitchPowerInstruction { power, diff --git a/basics/favorites/quasar/Cargo.toml b/basics/favorites/quasar/Cargo.toml index 73deeccf3..beedea131 100644 --- a/basics/favorites/quasar/Cargo.toml +++ b/basics/favorites/quasar/Cargo.toml @@ -29,6 +29,11 @@ idl-build = ["quasar-lang/idl-build"] # git. Keep this rev in lockstep with the CLI rev installed by # .github/workflows/quasar.yml. quasar-lang = { git = "https://github.com/blueshift-gg/quasar", rev = "be60fca" } +# Pinned: quasar-lang asks for zeropod "0.3.3" and wincode 0.4, but zeropod +# 0.3.4 moved to wincode 0.5, so a fresh resolve (no lockfile is committed) +# splits the graph across two wincode versions and every Pod* trait bound +# fails. Unpin when quasar-lang's pinned rev accepts zeropod 0.3.4+. +zeropod = "=0.3.3" solana-instruction = { version = "3.2.0" } [dev-dependencies] diff --git a/basics/favorites/quasar/src/instructions/set_favorites.rs b/basics/favorites/quasar/src/instructions/set_favorites.rs index 0abe21d81..153d634a5 100644 --- a/basics/favorites/quasar/src/instructions/set_favorites.rs +++ b/basics/favorites/quasar/src/instructions/set_favorites.rs @@ -15,7 +15,11 @@ pub struct SetFavoritesAccountConstraints { } #[inline(always)] -pub fn handle_set_favorites(accounts: &mut SetFavoritesAccountConstraints, number: u64, color: &str) -> Result<(), ProgramError> { +pub fn handle_set_favorites( + accounts: &mut SetFavoritesAccountConstraints, + number: u64, + color: &str, +) -> Result<(), ProgramError> { let rent = Rent::get()?; accounts.favorites.set_inner( FavoritesInner { number, color }, diff --git a/basics/favorites/quasar/src/lib.rs b/basics/favorites/quasar/src/lib.rs index d188a08f5..c069bb375 100644 --- a/basics/favorites/quasar/src/lib.rs +++ b/basics/favorites/quasar/src/lib.rs @@ -2,9 +2,9 @@ use quasar_lang::prelude::*; -mod instructions; +pub mod instructions; use instructions::*; -mod state; +pub mod state; #[cfg(test)] mod tests; diff --git a/basics/hello-solana/quasar/Cargo.toml b/basics/hello-solana/quasar/Cargo.toml index 7312f406a..621712762 100644 --- a/basics/hello-solana/quasar/Cargo.toml +++ b/basics/hello-solana/quasar/Cargo.toml @@ -30,6 +30,11 @@ idl-build = ["quasar-lang/idl-build"] # git. Keep this rev in lockstep with the CLI rev installed by # .github/workflows/quasar.yml. quasar-lang = { git = "https://github.com/blueshift-gg/quasar", rev = "be60fca" } +# Pinned: quasar-lang asks for zeropod "0.3.3" and wincode 0.4, but zeropod +# 0.3.4 moved to wincode 0.5, so a fresh resolve (no lockfile is committed) +# splits the graph across two wincode versions and every Pod* trait bound +# fails. Unpin when quasar-lang's pinned rev accepts zeropod 0.3.4+. +zeropod = "=0.3.3" solana-instruction = { version = "3.2.0" } [dev-dependencies] diff --git a/basics/hello-solana/quasar/src/lib.rs b/basics/hello-solana/quasar/src/lib.rs index 9a9fbb4e9..9123e79f1 100644 --- a/basics/hello-solana/quasar/src/lib.rs +++ b/basics/hello-solana/quasar/src/lib.rs @@ -2,7 +2,7 @@ use quasar_lang::prelude::*; -mod instructions; +pub mod instructions; use instructions::*; #[cfg(test)] mod tests; diff --git a/basics/pda-rent-payer/quasar/Cargo.toml b/basics/pda-rent-payer/quasar/Cargo.toml index 1173b3989..b7b175c01 100644 --- a/basics/pda-rent-payer/quasar/Cargo.toml +++ b/basics/pda-rent-payer/quasar/Cargo.toml @@ -30,6 +30,11 @@ idl-build = ["quasar-lang/idl-build"] # git. Keep this rev in lockstep with the CLI rev installed by # .github/workflows/quasar.yml. quasar-lang = { git = "https://github.com/blueshift-gg/quasar", rev = "be60fca" } +# Pinned: quasar-lang asks for zeropod "0.3.3" and wincode 0.4, but zeropod +# 0.3.4 moved to wincode 0.5, so a fresh resolve (no lockfile is committed) +# splits the graph across two wincode versions and every Pod* trait bound +# fails. Unpin when quasar-lang's pinned rev accepts zeropod 0.3.4+. +zeropod = "=0.3.3" solana-instruction = { version = "3.2.0" } [dev-dependencies] diff --git a/basics/pda-rent-payer/quasar/src/instructions/create_new_account.rs b/basics/pda-rent-payer/quasar/src/instructions/create_new_account.rs index 8cb15b64a..0f75b6a51 100644 --- a/basics/pda-rent-payer/quasar/src/instructions/create_new_account.rs +++ b/basics/pda-rent-payer/quasar/src/instructions/create_new_account.rs @@ -15,7 +15,10 @@ pub struct CreateNewAccountAccountConstraints { } #[inline(always)] -pub fn handle_create_new_account(accounts: &mut CreateNewAccountAccountConstraints, rent_vault_bump: u8) -> Result<(), ProgramError> { +pub fn handle_create_new_account( + accounts: &mut CreateNewAccountAccountConstraints, + rent_vault_bump: u8, +) -> Result<(), ProgramError> { // Build PDA signer seeds: ["rent_vault", bump]. let bump_bytes = [rent_vault_bump]; let seeds: &[Seed] = &[ @@ -27,7 +30,14 @@ pub fn handle_create_new_account(accounts: &mut CreateNewAccountAccountConstrain let rent = Rent::get()?; let lamports = rent.minimum_balance_unchecked(0); - accounts.system_program - .create_account(&accounts.rent_vault, &accounts.new_account, lamports, 0u64, &system_program_address) + accounts + .system_program + .create_account( + &accounts.rent_vault, + &accounts.new_account, + lamports, + 0u64, + &system_program_address, + ) .invoke_signed(seeds) } diff --git a/basics/pda-rent-payer/quasar/src/instructions/init_rent_vault.rs b/basics/pda-rent-payer/quasar/src/instructions/init_rent_vault.rs index c452f40ac..8cc3589f4 100644 --- a/basics/pda-rent-payer/quasar/src/instructions/init_rent_vault.rs +++ b/basics/pda-rent-payer/quasar/src/instructions/init_rent_vault.rs @@ -21,8 +21,12 @@ pub struct InitRentVaultAccountConstraints { } #[inline(always)] -pub fn handle_init_rent_vault(accounts: &mut InitRentVaultAccountConstraints, fund_lamports: u64) -> Result<(), ProgramError> { - accounts.system_program +pub fn handle_init_rent_vault( + accounts: &mut InitRentVaultAccountConstraints, + fund_lamports: u64, +) -> Result<(), ProgramError> { + accounts + .system_program .transfer(&accounts.payer, &accounts.rent_vault, fund_lamports) .invoke() } diff --git a/basics/pda-rent-payer/quasar/src/instructions/mod.rs b/basics/pda-rent-payer/quasar/src/instructions/mod.rs index 03c2589d1..31d096b95 100644 --- a/basics/pda-rent-payer/quasar/src/instructions/mod.rs +++ b/basics/pda-rent-payer/quasar/src/instructions/mod.rs @@ -1,5 +1,5 @@ -pub mod init_rent_vault; pub mod create_new_account; +pub mod init_rent_vault; -pub use init_rent_vault::*; pub use create_new_account::*; +pub use init_rent_vault::*; diff --git a/basics/pda-rent-payer/quasar/src/lib.rs b/basics/pda-rent-payer/quasar/src/lib.rs index 4ad3b689b..963c55e64 100644 --- a/basics/pda-rent-payer/quasar/src/lib.rs +++ b/basics/pda-rent-payer/quasar/src/lib.rs @@ -2,7 +2,7 @@ use quasar_lang::prelude::*; -mod instructions; +pub mod instructions; use instructions::*; #[cfg(test)] mod tests; @@ -15,14 +15,19 @@ mod quasar_pda_rent_payer { /// Fund a PDA "rent vault" by transferring lamports from the payer. #[instruction(discriminator = 0)] - pub fn init_rent_vault(ctx: Ctx, fund_lamports: u64) -> Result<(), ProgramError> { + pub fn init_rent_vault( + ctx: Ctx, + fund_lamports: u64, + ) -> Result<(), ProgramError> { instructions::handle_init_rent_vault(&mut ctx.accounts, fund_lamports) } /// Create a new account using the rent vault PDA as the funding source. /// The vault signs the CPI via PDA seeds. #[instruction(discriminator = 1)] - pub fn create_new_account(ctx: Ctx) -> Result<(), ProgramError> { + pub fn create_new_account( + ctx: Ctx, + ) -> Result<(), ProgramError> { instructions::handle_create_new_account(&mut ctx.accounts, ctx.bumps.rent_vault) } } diff --git a/basics/processing-instructions/quasar/Cargo.toml b/basics/processing-instructions/quasar/Cargo.toml index b74bd5ae5..740418656 100644 --- a/basics/processing-instructions/quasar/Cargo.toml +++ b/basics/processing-instructions/quasar/Cargo.toml @@ -30,6 +30,11 @@ idl-build = ["quasar-lang/idl-build"] # git. Keep this rev in lockstep with the CLI rev installed by # .github/workflows/quasar.yml. quasar-lang = { git = "https://github.com/blueshift-gg/quasar", rev = "be60fca" } +# Pinned: quasar-lang asks for zeropod "0.3.3" and wincode 0.4, but zeropod +# 0.3.4 moved to wincode 0.5, so a fresh resolve (no lockfile is committed) +# splits the graph across two wincode versions and every Pod* trait bound +# fails. Unpin when quasar-lang's pinned rev accepts zeropod 0.3.4+. +zeropod = "=0.3.3" solana-instruction = { version = "3.2.0" } [dev-dependencies] diff --git a/basics/processing-instructions/quasar/src/instructions/go_to_park.rs b/basics/processing-instructions/quasar/src/instructions/go_to_park.rs index 902603869..d3638ad24 100644 --- a/basics/processing-instructions/quasar/src/instructions/go_to_park.rs +++ b/basics/processing-instructions/quasar/src/instructions/go_to_park.rs @@ -9,7 +9,11 @@ pub struct ParkAccountConstraints { } #[inline(always)] -pub fn handle_go_to_park(_accounts: &mut ParkAccountConstraints, _name: &str, height: u32) -> Result<(), ProgramError> { +pub fn handle_go_to_park( + _accounts: &mut ParkAccountConstraints, + _name: &str, + height: u32, +) -> Result<(), ProgramError> { // Quasar's `log()` takes &str, no format! macro available in no_std. // We can't interpolate the name or height into the log message, so // we use static messages - same logic as the Anchor version, just diff --git a/basics/processing-instructions/quasar/src/lib.rs b/basics/processing-instructions/quasar/src/lib.rs index 105184967..78d926a5e 100644 --- a/basics/processing-instructions/quasar/src/lib.rs +++ b/basics/processing-instructions/quasar/src/lib.rs @@ -2,7 +2,7 @@ use quasar_lang::prelude::*; -mod instructions; +pub mod instructions; use instructions::*; #[cfg(test)] mod tests; @@ -17,7 +17,11 @@ mod quasar_processing_instructions { /// Quasar can parse String instruction args (u32-prefixed wire format) but /// can't interpolate them into log messages (no format! in no_std). #[instruction(discriminator = 0)] - pub fn go_to_park(ctx: Ctx, height: u32, name: String<50>) -> Result<(), ProgramError> { + pub fn go_to_park( + ctx: Ctx, + height: u32, + name: String<50>, + ) -> Result<(), ProgramError> { instructions::handle_go_to_park(&mut ctx.accounts, name, height) } } diff --git a/basics/processing-instructions/quasar/src/tests.rs b/basics/processing-instructions/quasar/src/tests.rs index e585d4d4f..458f2a92b 100644 --- a/basics/processing-instructions/quasar/src/tests.rs +++ b/basics/processing-instructions/quasar/src/tests.rs @@ -16,7 +16,10 @@ fn tall_visitor_is_allowed_on_the_ride(test: &mut Test) { let logs = outcome.logs().join("\n"); assert!(logs.contains("Welcome to the park!"), "should welcome"); - assert!(logs.contains("tall enough to ride"), "should say tall enough"); + assert!( + logs.contains("tall enough to ride"), + "should say tall enough" + ); } #[quasar_test] @@ -32,5 +35,8 @@ fn short_visitor_is_turned_away(test: &mut Test) { let logs = outcome.logs().join("\n"); assert!(logs.contains("Welcome to the park!"), "should welcome"); - assert!(logs.contains("NOT tall enough"), "should say not tall enough"); + assert!( + logs.contains("NOT tall enough"), + "should say not tall enough" + ); } diff --git a/basics/program-derived-addresses/quasar/Cargo.toml b/basics/program-derived-addresses/quasar/Cargo.toml index b17c35ff6..c942db154 100644 --- a/basics/program-derived-addresses/quasar/Cargo.toml +++ b/basics/program-derived-addresses/quasar/Cargo.toml @@ -30,6 +30,11 @@ idl-build = ["quasar-lang/idl-build"] # git. Keep this rev in lockstep with the CLI rev installed by # .github/workflows/quasar.yml. quasar-lang = { git = "https://github.com/blueshift-gg/quasar", rev = "be60fca" } +# Pinned: quasar-lang asks for zeropod "0.3.3" and wincode 0.4, but zeropod +# 0.3.4 moved to wincode 0.5, so a fresh resolve (no lockfile is committed) +# splits the graph across two wincode versions and every Pod* trait bound +# fails. Unpin when quasar-lang's pinned rev accepts zeropod 0.3.4+. +zeropod = "=0.3.3" solana-instruction = { version = "3.2.0" } [dev-dependencies] diff --git a/basics/program-derived-addresses/quasar/src/instructions/create.rs b/basics/program-derived-addresses/quasar/src/instructions/create.rs index 2df91aded..c5e01970a 100644 --- a/basics/program-derived-addresses/quasar/src/instructions/create.rs +++ b/basics/program-derived-addresses/quasar/src/instructions/create.rs @@ -15,7 +15,11 @@ pub struct CreatePageVisitsAccountConstraints { } #[inline(always)] -pub fn handle_create_page_visits(accounts: &mut CreatePageVisitsAccountConstraints) -> Result<(), ProgramError> { - accounts.page_visits.set_inner(PageVisitsInner { page_visits: 0 }); +pub fn handle_create_page_visits( + accounts: &mut CreatePageVisitsAccountConstraints, +) -> Result<(), ProgramError> { + accounts + .page_visits + .set_inner(PageVisitsInner { page_visits: 0 }); Ok(()) } diff --git a/basics/program-derived-addresses/quasar/src/instructions/increment.rs b/basics/program-derived-addresses/quasar/src/instructions/increment.rs index f3aaa56a5..af65a3bd8 100644 --- a/basics/program-derived-addresses/quasar/src/instructions/increment.rs +++ b/basics/program-derived-addresses/quasar/src/instructions/increment.rs @@ -13,7 +13,9 @@ pub struct IncrementPageVisitsAccountConstraints { } #[inline(always)] -pub fn handle_increment_page_visits(accounts: &mut IncrementPageVisitsAccountConstraints) -> Result<(), ProgramError> { +pub fn handle_increment_page_visits( + accounts: &mut IncrementPageVisitsAccountConstraints, +) -> Result<(), ProgramError> { let current: u64 = accounts.page_visits.page_visits.into(); let next = current .checked_add(1) diff --git a/basics/program-derived-addresses/quasar/src/lib.rs b/basics/program-derived-addresses/quasar/src/lib.rs index b729cbf0f..fb55cf0f4 100644 --- a/basics/program-derived-addresses/quasar/src/lib.rs +++ b/basics/program-derived-addresses/quasar/src/lib.rs @@ -3,9 +3,9 @@ use quasar_lang::prelude::*; mod error; -mod instructions; +pub mod instructions; use instructions::*; -mod state; +pub mod state; #[cfg(test)] mod tests; @@ -17,13 +17,17 @@ mod quasar_program_derived_addresses { /// Create a PDA-based page visits counter for the payer. #[instruction(discriminator = 0)] - pub fn create_page_visits(ctx: Ctx) -> Result<(), ProgramError> { + pub fn create_page_visits( + ctx: Ctx, + ) -> Result<(), ProgramError> { instructions::handle_create_page_visits(&mut ctx.accounts) } /// Increment the page visits counter. #[instruction(discriminator = 1)] - pub fn increment_page_visits(ctx: Ctx) -> Result<(), ProgramError> { + pub fn increment_page_visits( + ctx: Ctx, + ) -> Result<(), ProgramError> { instructions::handle_increment_page_visits(&mut ctx.accounts) } } diff --git a/basics/program-derived-addresses/quasar/src/tests.rs b/basics/program-derived-addresses/quasar/src/tests.rs index 0830d30a6..364fcfc35 100644 --- a/basics/program-derived-addresses/quasar/src/tests.rs +++ b/basics/program-derived-addresses/quasar/src/tests.rs @@ -16,7 +16,8 @@ fn create_page_visits_initializes_the_pda(test: &mut Test) { // The page-visits PDA and system program are canonical derivations, so // the generated instruction only asks for the payer. - test.send(CreatePageVisitsInstruction { payer: PAYER }).succeeds(); + test.send(CreatePageVisitsInstruction { payer: PAYER }) + .succeeds(); // Byte layout is part of what this example demonstrates: // 1 byte discriminator (1) + 8 bytes u64 count (0). @@ -33,7 +34,8 @@ fn create_page_visits_initializes_the_pda(test: &mut Test) { fn increment_page_visits_advances_the_count(test: &mut Test) { test.add(Wallet::new().at(PAYER)); let page_visits = test.derive_pda(PageVisits::seeds(&PAYER)); - test.send(CreatePageVisitsInstruction { payer: PAYER }).succeeds(); + test.send(CreatePageVisitsInstruction { payer: PAYER }) + .succeeds(); // The user account is only used for PDA derivation, not as a signer. test.send(IncrementPageVisitsInstruction { diff --git a/basics/pyth/quasar/Cargo.toml b/basics/pyth/quasar/Cargo.toml index b9205616d..84eefe42d 100644 --- a/basics/pyth/quasar/Cargo.toml +++ b/basics/pyth/quasar/Cargo.toml @@ -30,6 +30,11 @@ idl-build = ["quasar-lang/idl-build"] # git. Keep this rev in lockstep with the CLI rev installed by # .github/workflows/quasar.yml. quasar-lang = { git = "https://github.com/blueshift-gg/quasar", rev = "be60fca" } +# Pinned: quasar-lang asks for zeropod "0.3.3" and wincode 0.4, but zeropod +# 0.3.4 moved to wincode 0.5, so a fresh resolve (no lockfile is committed) +# splits the graph across two wincode versions and every Pod* trait bound +# fails. Unpin when quasar-lang's pinned rev accepts zeropod 0.3.4+. +zeropod = "=0.3.3" solana-instruction = { version = "3.2.0" } [dev-dependencies] diff --git a/basics/pyth/quasar/src/lib.rs b/basics/pyth/quasar/src/lib.rs index e52d3c74d..7908e91a2 100644 --- a/basics/pyth/quasar/src/lib.rs +++ b/basics/pyth/quasar/src/lib.rs @@ -2,7 +2,7 @@ use quasar_lang::prelude::*; -mod instructions; +pub mod instructions; use instructions::*; #[cfg(test)] mod tests; diff --git a/basics/realloc/quasar/Cargo.toml b/basics/realloc/quasar/Cargo.toml index ff00c68c1..0ecf44afa 100644 --- a/basics/realloc/quasar/Cargo.toml +++ b/basics/realloc/quasar/Cargo.toml @@ -30,6 +30,11 @@ idl-build = ["quasar-lang/idl-build"] # git. Keep this rev in lockstep with the CLI rev installed by # .github/workflows/quasar.yml. quasar-lang = { git = "https://github.com/blueshift-gg/quasar", rev = "be60fca" } +# Pinned: quasar-lang asks for zeropod "0.3.3" and wincode 0.4, but zeropod +# 0.3.4 moved to wincode 0.5, so a fresh resolve (no lockfile is committed) +# splits the graph across two wincode versions and every Pod* trait bound +# fails. Unpin when quasar-lang's pinned rev accepts zeropod 0.3.4+. +zeropod = "=0.3.3" solana-instruction = { version = "3.2.0" } [dev-dependencies] diff --git a/basics/realloc/quasar/src/instructions/initialize.rs b/basics/realloc/quasar/src/instructions/initialize.rs index 2c1b52db7..8910b052f 100644 --- a/basics/realloc/quasar/src/instructions/initialize.rs +++ b/basics/realloc/quasar/src/instructions/initialize.rs @@ -15,7 +15,10 @@ pub struct InitializeAccountConstraints { } #[inline(always)] -pub fn handle_initialize(accounts: &mut InitializeAccountConstraints, message: &str) -> Result<(), ProgramError> { +pub fn handle_initialize( + accounts: &mut InitializeAccountConstraints, + message: &str, +) -> Result<(), ProgramError> { let rent = Rent::get()?; accounts.message_account.set_inner( MessageAccountInner { message }, diff --git a/basics/realloc/quasar/src/instructions/update.rs b/basics/realloc/quasar/src/instructions/update.rs index 04b7265b8..48dca5252 100644 --- a/basics/realloc/quasar/src/instructions/update.rs +++ b/basics/realloc/quasar/src/instructions/update.rs @@ -16,7 +16,10 @@ pub struct UpdateAccountConstraints { } #[inline(always)] -pub fn handle_update(accounts: &mut UpdateAccountConstraints, message: &str) -> Result<(), ProgramError> { +pub fn handle_update( + accounts: &mut UpdateAccountConstraints, + message: &str, +) -> Result<(), ProgramError> { let rent = Rent::get()?; accounts.message_account.set_inner( MessageAccountInner { message }, diff --git a/basics/realloc/quasar/src/lib.rs b/basics/realloc/quasar/src/lib.rs index 56f543b31..0a5345dba 100644 --- a/basics/realloc/quasar/src/lib.rs +++ b/basics/realloc/quasar/src/lib.rs @@ -2,9 +2,9 @@ use quasar_lang::prelude::*; -mod instructions; +pub mod instructions; use instructions::*; -mod state; +pub mod state; #[cfg(test)] mod tests; @@ -16,14 +16,20 @@ mod quasar_realloc { /// Create a message account with an initial message. #[instruction(discriminator = 0)] - pub fn initialize(ctx: Ctx, message: String<1024, 2>) -> Result<(), ProgramError> { + pub fn initialize( + ctx: Ctx, + message: String<1024, 2>, + ) -> Result<(), ProgramError> { instructions::handle_initialize(&mut ctx.accounts, message) } /// Update the message, reallocating if the new message is longer. /// Quasar's `set_inner` handles realloc transparently. #[instruction(discriminator = 1)] - pub fn update(ctx: Ctx, message: String<1024, 2>) -> Result<(), ProgramError> { + pub fn update( + ctx: Ctx, + message: String<1024, 2>, + ) -> Result<(), ProgramError> { instructions::handle_update(&mut ctx.accounts, message) } } diff --git a/basics/rent/quasar/Cargo.toml b/basics/rent/quasar/Cargo.toml index 44e8d2682..0941f245b 100644 --- a/basics/rent/quasar/Cargo.toml +++ b/basics/rent/quasar/Cargo.toml @@ -30,6 +30,11 @@ idl-build = ["quasar-lang/idl-build"] # git. Keep this rev in lockstep with the CLI rev installed by # .github/workflows/quasar.yml. quasar-lang = { git = "https://github.com/blueshift-gg/quasar", rev = "be60fca" } +# Pinned: quasar-lang asks for zeropod "0.3.3" and wincode 0.4, but zeropod +# 0.3.4 moved to wincode 0.5, so a fresh resolve (no lockfile is committed) +# splits the graph across two wincode versions and every Pod* trait bound +# fails. Unpin when quasar-lang's pinned rev accepts zeropod 0.3.4+. +zeropod = "=0.3.3" solana-instruction = { version = "3.2.0" } [dev-dependencies] diff --git a/basics/rent/quasar/src/instructions/create_system_account.rs b/basics/rent/quasar/src/instructions/create_system_account.rs index 396c98be0..83b4df0a3 100644 --- a/basics/rent/quasar/src/instructions/create_system_account.rs +++ b/basics/rent/quasar/src/instructions/create_system_account.rs @@ -11,7 +11,11 @@ pub struct CreateSystemAccountAccountConstraints { } #[inline(always)] -pub fn handle_create_system_account(accounts: &mut CreateSystemAccountAccountConstraints, name: &str, address: &str) -> Result<(), ProgramError> { +pub fn handle_create_system_account( + accounts: &mut CreateSystemAccountAccountConstraints, + name: &str, + address: &str, +) -> Result<(), ProgramError> { // Calculate space needed for the serialised AddressData: // borsh-style: 4-byte length prefix + bytes for each String field. let space = 4 + name.len() + 4 + address.len(); @@ -22,8 +26,15 @@ pub fn handle_create_system_account(accounts: &mut CreateSystemAccountAccountCon let rent = Rent::get()?; let lamports = rent.minimum_balance_unchecked(space); - accounts.system_program - .create_account(&accounts.payer, &accounts.new_account, lamports, space as u64, &system_program_address) + accounts + .system_program + .create_account( + &accounts.payer, + &accounts.new_account, + lamports, + space as u64, + &system_program_address, + ) .invoke()?; log("Account created successfully."); diff --git a/basics/rent/quasar/src/lib.rs b/basics/rent/quasar/src/lib.rs index 5f17ba841..39da7f424 100644 --- a/basics/rent/quasar/src/lib.rs +++ b/basics/rent/quasar/src/lib.rs @@ -2,7 +2,7 @@ use quasar_lang::prelude::*; -mod instructions; +pub mod instructions; use instructions::*; #[cfg(test)] mod tests; diff --git a/basics/rent/quasar/src/tests.rs b/basics/rent/quasar/src/tests.rs index d4bb570b0..bbc1e6c80 100644 --- a/basics/rent/quasar/src/tests.rs +++ b/basics/rent/quasar/src/tests.rs @@ -29,9 +29,18 @@ fn create_system_account_sized_for_address_data(test: &mut Test) { expected_space, "account data should be sized for the address data" ); - assert!(account.lamports > 0, "account should have rent-exempt lamports"); + assert!( + account.lamports > 0, + "account should have rent-exempt lamports" + ); let logs = outcome.logs().join("\n"); - assert!(logs.contains("Creating a system account"), "should log creation"); - assert!(logs.contains("Account created successfully"), "should log success"); + assert!( + logs.contains("Creating a system account"), + "should log creation" + ); + assert!( + logs.contains("Account created successfully"), + "should log success" + ); } diff --git a/basics/repository-layout/quasar/Cargo.toml b/basics/repository-layout/quasar/Cargo.toml index ec7ca3bd4..7767f26fc 100644 --- a/basics/repository-layout/quasar/Cargo.toml +++ b/basics/repository-layout/quasar/Cargo.toml @@ -30,6 +30,11 @@ idl-build = ["quasar-lang/idl-build"] # git. Keep this rev in lockstep with the CLI rev installed by # .github/workflows/quasar.yml. quasar-lang = { git = "https://github.com/blueshift-gg/quasar", rev = "be60fca" } +# Pinned: quasar-lang asks for zeropod "0.3.3" and wincode 0.4, but zeropod +# 0.3.4 moved to wincode 0.5, so a fresh resolve (no lockfile is committed) +# splits the graph across two wincode versions and every Pod* trait bound +# fails. Unpin when quasar-lang's pinned rev accepts zeropod 0.3.4+. +zeropod = "=0.3.3" solana-instruction = { version = "3.2.0" } [dev-dependencies] diff --git a/basics/repository-layout/quasar/src/instructions/eat_food.rs b/basics/repository-layout/quasar/src/instructions/eat_food.rs index 48592bb9f..5fd0628a6 100644 --- a/basics/repository-layout/quasar/src/instructions/eat_food.rs +++ b/basics/repository-layout/quasar/src/instructions/eat_food.rs @@ -3,11 +3,7 @@ use quasar_lang::prelude::*; use crate::state::food; /// Validate food stand ticket requirements and log the result. -pub fn eat_food( - _name: &str, - ticket_count: u32, - food_stand_name: &str, -) -> Result<(), ProgramError> { +pub fn eat_food(_name: &str, ticket_count: u32, food_stand_name: &str) -> Result<(), ProgramError> { let stands = food::get_food_stands(); let mut i = 0; diff --git a/basics/repository-layout/quasar/src/instructions/play_game.rs b/basics/repository-layout/quasar/src/instructions/play_game.rs index fdac0e9cf..aec74f533 100644 --- a/basics/repository-layout/quasar/src/instructions/play_game.rs +++ b/basics/repository-layout/quasar/src/instructions/play_game.rs @@ -3,11 +3,7 @@ use quasar_lang::prelude::*; use crate::state::game; /// Validate game ticket requirements and log the result. -pub fn play_game( - _name: &str, - ticket_count: u32, - game_name: &str, -) -> Result<(), ProgramError> { +pub fn play_game(_name: &str, ticket_count: u32, game_name: &str) -> Result<(), ProgramError> { let games = game::get_games(); let mut i = 0; diff --git a/basics/repository-layout/quasar/src/lib.rs b/basics/repository-layout/quasar/src/lib.rs index 65c5c1087..30a7cee71 100644 --- a/basics/repository-layout/quasar/src/lib.rs +++ b/basics/repository-layout/quasar/src/lib.rs @@ -2,9 +2,9 @@ use quasar_lang::prelude::*; -mod instructions; +pub mod instructions; use instructions::*; -mod state; +pub mod state; #[cfg(test)] mod tests; diff --git a/basics/repository-layout/quasar/src/state/food.rs b/basics/repository-layout/quasar/src/state/food.rs index e8569a4c7..a9f92591e 100644 --- a/basics/repository-layout/quasar/src/state/food.rs +++ b/basics/repository-layout/quasar/src/state/food.rs @@ -12,8 +12,20 @@ pub fn food_stand_name_matches(stand: &FoodStand, other: &str) -> bool { /// Static list of food stands. pub fn get_food_stands() -> &'static [FoodStand] { &[ - FoodStand { name: "Larry's Pizza", food_type: "pizza", tickets: 3 }, - FoodStand { name: "Taco Shack", food_type: "taco", tickets: 2 }, - FoodStand { name: "Dough Boy's", food_type: "fried dough", tickets: 1 }, + FoodStand { + name: "Larry's Pizza", + food_type: "pizza", + tickets: 3, + }, + FoodStand { + name: "Taco Shack", + food_type: "taco", + tickets: 2, + }, + FoodStand { + name: "Dough Boy's", + food_type: "fried dough", + tickets: 1, + }, ] } diff --git a/basics/repository-layout/quasar/src/state/game.rs b/basics/repository-layout/quasar/src/state/game.rs index ded4a7d55..b088c1570 100644 --- a/basics/repository-layout/quasar/src/state/game.rs +++ b/basics/repository-layout/quasar/src/state/game.rs @@ -15,8 +15,23 @@ pub fn game_name_matches(game: &Game, other: &str) -> bool { /// Static list of carnival games. pub fn get_games() -> &'static [Game] { &[ - Game { name: "Ring Toss", tickets: DEFAULT_TICKETS_TO_PLAY, tries: 5, prize: "teddy bear" }, - Game { name: "I Got It!", tickets: DEFAULT_TICKETS_TO_PLAY, tries: 12, prize: "goldfish" }, - Game { name: "Ladder Climb", tickets: DEFAULT_TICKETS_TO_PLAY, tries: 1, prize: "popcorn bucket" }, + Game { + name: "Ring Toss", + tickets: DEFAULT_TICKETS_TO_PLAY, + tries: 5, + prize: "teddy bear", + }, + Game { + name: "I Got It!", + tickets: DEFAULT_TICKETS_TO_PLAY, + tries: 12, + prize: "goldfish", + }, + Game { + name: "Ladder Climb", + tickets: DEFAULT_TICKETS_TO_PLAY, + tries: 1, + prize: "popcorn bucket", + }, ] } diff --git a/basics/repository-layout/quasar/src/state/ride.rs b/basics/repository-layout/quasar/src/state/ride.rs index 5759a9087..644bb40c3 100644 --- a/basics/repository-layout/quasar/src/state/ride.rs +++ b/basics/repository-layout/quasar/src/state/ride.rs @@ -15,9 +15,29 @@ pub fn ride_name_matches(ride: &Ride, other: &str) -> bool { /// Static list of carnival rides. pub fn get_rides() -> &'static [Ride] { &[ - Ride { name: "Tilt-a-Whirl", upside_down: false, tickets: 3, min_height: 48 }, - Ride { name: "Scrambler", upside_down: false, tickets: 3, min_height: 48 }, - Ride { name: "Ferris Wheel", upside_down: false, tickets: 5, min_height: 55 }, - Ride { name: "Zero Gravity", upside_down: true, tickets: 5, min_height: 60 }, + Ride { + name: "Tilt-a-Whirl", + upside_down: false, + tickets: 3, + min_height: 48, + }, + Ride { + name: "Scrambler", + upside_down: false, + tickets: 3, + min_height: 48, + }, + Ride { + name: "Ferris Wheel", + upside_down: false, + tickets: 5, + min_height: 55, + }, + Ride { + name: "Zero Gravity", + upside_down: true, + tickets: 5, + min_height: 60, + }, ] } diff --git a/basics/repository-layout/quasar/src/tests.rs b/basics/repository-layout/quasar/src/tests.rs index c25eeab62..04c7127a5 100644 --- a/basics/repository-layout/quasar/src/tests.rs +++ b/basics/repository-layout/quasar/src/tests.rs @@ -42,7 +42,10 @@ fn tall_rider_with_tickets_boards_the_ride(test: &mut Test) { outcome.succeeds(); let logs = outcome.logs().join("\n"); - assert!(logs.contains("about to go on a ride"), "should announce ride"); + assert!( + logs.contains("about to go on a ride"), + "should announce ride" + ); assert!(logs.contains("Welcome aboard"), "should welcome aboard"); } @@ -54,7 +57,10 @@ fn short_rider_is_turned_away(test: &mut Test) { outcome.succeeds(); let logs = outcome.logs().join("\n"); - assert!(logs.contains("not tall enough"), "should reject short rider"); + assert!( + logs.contains("not tall enough"), + "should reject short rider" + ); } #[quasar_test] @@ -65,7 +71,10 @@ fn rider_without_enough_tickets_is_turned_away(test: &mut Test) { outcome.succeeds(); let logs = outcome.logs().join("\n"); - assert!(logs.contains("enough tickets"), "should reject insufficient tickets"); + assert!( + logs.contains("enough tickets"), + "should reject insufficient tickets" + ); } #[quasar_test] @@ -76,7 +85,10 @@ fn upside_down_ride_warns_the_rider(test: &mut Test) { outcome.succeeds(); let logs = outcome.logs().join("\n"); - assert!(logs.contains("upside down"), "should warn about upside down"); + assert!( + logs.contains("upside down"), + "should warn about upside down" + ); } #[quasar_test] @@ -99,7 +111,10 @@ fn player_without_enough_tickets_is_turned_away(test: &mut Test) { outcome.succeeds(); let logs = outcome.logs().join("\n"); - assert!(logs.contains("enough tickets"), "should reject insufficient tickets"); + assert!( + logs.contains("enough tickets"), + "should reject insufficient tickets" + ); } #[quasar_test] @@ -122,7 +137,10 @@ fn visitor_without_enough_tickets_cannot_eat(test: &mut Test) { outcome.succeeds(); let logs = outcome.logs().join("\n"); - assert!(logs.contains("enough tickets"), "should reject insufficient tickets"); + assert!( + logs.contains("enough tickets"), + "should reject insufficient tickets" + ); } #[quasar_test] diff --git a/basics/transfer-sol/quasar/Cargo.toml b/basics/transfer-sol/quasar/Cargo.toml index c7f7f9044..1b66c9cab 100644 --- a/basics/transfer-sol/quasar/Cargo.toml +++ b/basics/transfer-sol/quasar/Cargo.toml @@ -30,6 +30,11 @@ idl-build = ["quasar-lang/idl-build"] # git. Keep this rev in lockstep with the CLI rev installed by # .github/workflows/quasar.yml. quasar-lang = { git = "https://github.com/blueshift-gg/quasar", rev = "be60fca" } +# Pinned: quasar-lang asks for zeropod "0.3.3" and wincode 0.4, but zeropod +# 0.3.4 moved to wincode 0.5, so a fresh resolve (no lockfile is committed) +# splits the graph across two wincode versions and every Pod* trait bound +# fails. Unpin when quasar-lang's pinned rev accepts zeropod 0.3.4+. +zeropod = "=0.3.3" solana-instruction = { version = "3.2.0" } [dev-dependencies] diff --git a/basics/transfer-sol/quasar/src/instructions/transfer_sol_with_cpi.rs b/basics/transfer-sol/quasar/src/instructions/transfer_sol_with_cpi.rs index 03aae2473..9dd6adff8 100644 --- a/basics/transfer-sol/quasar/src/instructions/transfer_sol_with_cpi.rs +++ b/basics/transfer-sol/quasar/src/instructions/transfer_sol_with_cpi.rs @@ -11,8 +11,12 @@ pub struct TransferSolWithCpiAccountConstraints { } #[inline(always)] -pub fn handle_transfer_sol_with_cpi(accounts: &mut TransferSolWithCpiAccountConstraints, amount: u64) -> Result<(), ProgramError> { - accounts.system_program +pub fn handle_transfer_sol_with_cpi( + accounts: &mut TransferSolWithCpiAccountConstraints, + amount: u64, +) -> Result<(), ProgramError> { + accounts + .system_program .transfer(&accounts.payer, &accounts.recipient, amount) .invoke() } diff --git a/basics/transfer-sol/quasar/src/lib.rs b/basics/transfer-sol/quasar/src/lib.rs index ca73e5adf..27d3e501a 100644 --- a/basics/transfer-sol/quasar/src/lib.rs +++ b/basics/transfer-sol/quasar/src/lib.rs @@ -2,7 +2,7 @@ use quasar_lang::prelude::*; -mod instructions; +pub mod instructions; use instructions::*; #[cfg(test)] mod tests; diff --git a/compression/cnft-burn/quasar/Cargo.toml b/compression/cnft-burn/quasar/Cargo.toml index b0885da91..2c11010f7 100644 --- a/compression/cnft-burn/quasar/Cargo.toml +++ b/compression/cnft-burn/quasar/Cargo.toml @@ -30,6 +30,11 @@ idl-build = ["quasar-lang/idl-build"] # git. Keep this rev in lockstep with the CLI rev installed by # .github/workflows/quasar.yml. quasar-lang = { git = "https://github.com/blueshift-gg/quasar", rev = "be60fca" } +# Pinned: quasar-lang asks for zeropod "0.3.3" and wincode 0.4, but zeropod +# 0.3.4 moved to wincode 0.5, so a fresh resolve (no lockfile is committed) +# splits the graph across two wincode versions and every Pod* trait bound +# fails. Unpin when quasar-lang's pinned rev accepts zeropod 0.3.4+. +zeropod = "=0.3.3" # Direct dependency for invoke_with_bounds - needed for raw CPI with variable # proof accounts. quasar-lang re-exports types but not the invoke functions. solana-instruction-view = { version = "2", features = ["cpi"] } diff --git a/compression/cnft-burn/quasar/src/instructions/burn_cnft.rs b/compression/cnft-burn/quasar/src/instructions/burn_cnft.rs index cdc543fc2..0d44625ab 100644 --- a/compression/cnft-burn/quasar/src/instructions/burn_cnft.rs +++ b/compression/cnft-burn/quasar/src/instructions/burn_cnft.rs @@ -1,5 +1,8 @@ use crate::*; -use quasar_lang::{cpi::{InstructionAccount, InstructionView}, remaining::RemainingAccounts}; +use quasar_lang::{ + cpi::{InstructionAccount, InstructionView}, + remaining::RemainingAccounts, +}; /// Maximum number of proof nodes for the merkle tree. /// Concurrent merkle trees support up to depth 30, but typical depth is 14-20. @@ -79,9 +82,8 @@ pub fn handle_burn_cnft( // leaf_delegate (= leaf_owner, not signer), merkle_tree, log_wrapper, // compression_program, system_program, then proof nodes. let sys_addr = accounts.system_program.address(); - let mut ix_accounts: [InstructionAccount; MAX_CPI_ACCOUNTS] = core::array::from_fn(|_| { - InstructionAccount::readonly(sys_addr) - }); + let mut ix_accounts: [InstructionAccount; MAX_CPI_ACCOUNTS] = + core::array::from_fn(|_| InstructionAccount::readonly(sys_addr)); ix_accounts[0] = InstructionAccount::readonly(accounts.tree_authority.address()); ix_accounts[1] = InstructionAccount::readonly_signer(accounts.leaf_owner.address()); @@ -108,9 +110,7 @@ pub fn handle_burn_cnft( views[5] = accounts.compression_program.to_account_view().clone(); views[6] = accounts.system_program.to_account_view().clone(); - for i in 0..proof_count { - views[7 + i] = proof_views[i].clone(); - } + views[7..7 + proof_count].clone_from_slice(&proof_views[..proof_count]); let instruction = InstructionView { program_id: &MPL_BUBBLEGUM_ID, diff --git a/compression/cnft-burn/quasar/src/lib.rs b/compression/cnft-burn/quasar/src/lib.rs index 358e79439..ecdabb74e 100644 --- a/compression/cnft-burn/quasar/src/lib.rs +++ b/compression/cnft-burn/quasar/src/lib.rs @@ -2,7 +2,7 @@ use quasar_lang::prelude::*; -mod instructions; +pub mod instructions; use instructions::*; #[cfg(test)] mod tests; @@ -12,16 +12,14 @@ const BURN_DISCRIMINATOR: [u8; 8] = [116, 110, 29, 56, 107, 219, 42, 93]; /// mpl-bubblegum program ID (BGUMAp9Gq7iTEuizy4pqaxsTyUCBK68MDfK752saRPUY). 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, + 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: 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, + 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, ]); declare_id!("C6qxH8n6mZxrrbtMtYWYSp8JR8vkQ55X1o4EBg7twnMv"); diff --git a/compression/cnft-vault/quasar/Cargo.toml b/compression/cnft-vault/quasar/Cargo.toml index 9d1859cae..e6f5e57c9 100644 --- a/compression/cnft-vault/quasar/Cargo.toml +++ b/compression/cnft-vault/quasar/Cargo.toml @@ -30,6 +30,11 @@ idl-build = ["quasar-lang/idl-build"] # git. Keep this rev in lockstep with the CLI rev installed by # .github/workflows/quasar.yml. quasar-lang = { git = "https://github.com/blueshift-gg/quasar", rev = "be60fca" } +# Pinned: quasar-lang asks for zeropod "0.3.3" and wincode 0.4, but zeropod +# 0.3.4 moved to wincode 0.5, so a fresh resolve (no lockfile is committed) +# splits the graph across two wincode versions and every Pod* trait bound +# fails. Unpin when quasar-lang's pinned rev accepts zeropod 0.3.4+. +zeropod = "=0.3.3" # Direct dependency for invoke_signed_with_bounds - needed for raw CPI with # variable proof accounts. quasar-lang re-exports types but not the invoke fns. solana-instruction-view = { version = "2", features = ["cpi"] } diff --git a/compression/cnft-vault/quasar/src/instructions/withdraw.rs b/compression/cnft-vault/quasar/src/instructions/withdraw.rs index b8528b276..0eb77e7b1 100644 --- a/compression/cnft-vault/quasar/src/instructions/withdraw.rs +++ b/compression/cnft-vault/quasar/src/instructions/withdraw.rs @@ -154,9 +154,7 @@ pub fn handle_withdraw_cnft( views[6] = accounts.compression_program.to_account_view().clone(); views[7] = accounts.system_program.to_account_view().clone(); - for i in 0..proof_count { - views[8 + i] = proof_views[i].clone(); - } + views[8..8 + proof_count].clone_from_slice(&proof_views[..proof_count]); let instruction = InstructionView { program_id: &MPL_BUBBLEGUM_ID, diff --git a/compression/cnft-vault/quasar/src/instructions/withdraw_two.rs b/compression/cnft-vault/quasar/src/instructions/withdraw_two.rs index 586dd61c1..94866cc65 100644 --- a/compression/cnft-vault/quasar/src/instructions/withdraw_two.rs +++ b/compression/cnft-vault/quasar/src/instructions/withdraw_two.rs @@ -153,9 +153,7 @@ pub fn handle_withdraw_two_cnfts( views[6] = accounts.compression_program.to_account_view().clone(); views[7] = accounts.system_program.to_account_view().clone(); - for i in 0..proof1_count { - views[8 + i] = all_proofs[i].clone(); - } + views[8..8 + proof1_count].clone_from_slice(&all_proofs[..proof1_count]); let instruction = InstructionView { program_id: &MPL_BUBBLEGUM_ID, @@ -166,7 +164,7 @@ pub fn handle_withdraw_two_cnfts( solana_instruction_view::cpi::invoke_signed_with_bounds::( &instruction, &views[..total_accounts], - &[signer.clone()], + core::slice::from_ref(&signer), )?; } @@ -209,9 +207,8 @@ pub fn handle_withdraw_two_cnfts( views[6] = accounts.compression_program.to_account_view().clone(); views[7] = accounts.system_program.to_account_view().clone(); - for i in 0..proof2_count { - views[8 + i] = all_proofs[proof2_start + i].clone(); - } + views[8..8 + proof2_count] + .clone_from_slice(&all_proofs[proof2_start..proof2_start + proof2_count]); let instruction = InstructionView { program_id: &MPL_BUBBLEGUM_ID, diff --git a/compression/cnft-vault/quasar/src/lib.rs b/compression/cnft-vault/quasar/src/lib.rs index 98b253e7f..db7151b3e 100644 --- a/compression/cnft-vault/quasar/src/lib.rs +++ b/compression/cnft-vault/quasar/src/lib.rs @@ -3,7 +3,7 @@ use quasar_lang::prelude::*; pub mod error; -mod instructions; +pub mod instructions; pub mod state; use instructions::*; #[cfg(test)] @@ -14,16 +14,14 @@ const TRANSFER_DISCRIMINATOR: [u8; 8] = [163, 52, 200, 231, 140, 3, 69, 186]; /// mpl-bubblegum program ID (BGUMAp9Gq7iTEuizy4pqaxsTyUCBK68MDfK752saRPUY). 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, + 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: 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, + 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, ]); declare_id!("Fd4iwpPWaCU8BNwGQGtvvrcvG4Tfizq3RgLm8YLBJX6D"); diff --git a/compression/cutils/quasar/Cargo.toml b/compression/cutils/quasar/Cargo.toml index 810366aac..92fb1f3b2 100644 --- a/compression/cutils/quasar/Cargo.toml +++ b/compression/cutils/quasar/Cargo.toml @@ -30,6 +30,11 @@ idl-build = ["quasar-lang/idl-build"] # git. Keep this rev in lockstep with the CLI rev installed by # .github/workflows/quasar.yml. quasar-lang = { git = "https://github.com/blueshift-gg/quasar", rev = "be60fca" } +# Pinned: quasar-lang asks for zeropod "0.3.3" and wincode 0.4, but zeropod +# 0.3.4 moved to wincode 0.5, so a fresh resolve (no lockfile is committed) +# splits the graph across two wincode versions and every Pod* trait bound +# fails. Unpin when quasar-lang's pinned rev accepts zeropod 0.3.4+. +zeropod = "=0.3.3" # Direct dependency for invoke_with_bounds - raw CPI with variable proof accounts. solana-instruction-view = { version = "2", features = ["cpi"] } solana-instruction = { version = "3.2.0" } diff --git a/compression/cutils/quasar/src/bubblegum_types.rs b/compression/cutils/quasar/src/bubblegum_types.rs index 8b10e03d5..aa8a07f9c 100644 --- a/compression/cutils/quasar/src/bubblegum_types.rs +++ b/compression/cutils/quasar/src/bubblegum_types.rs @@ -105,9 +105,8 @@ pub fn encode_mint_to_collection_v1( pub fn get_asset_id(tree: &Address, nonce: u64) -> Address { let nonce_bytes = nonce.to_le_bytes(); let seeds: &[&[u8]] = &[b"asset", tree.as_ref(), &nonce_bytes]; - let (pda, _bump) = - quasar_lang::pda::try_find_program_address(seeds, &crate::MPL_BUBBLEGUM_ID) - .expect("asset PDA derivation failed"); + let (pda, _bump) = quasar_lang::pda::try_find_program_address(seeds, &crate::MPL_BUBBLEGUM_ID) + .expect("asset PDA derivation failed"); pda } diff --git a/compression/cutils/quasar/src/instructions/mint.rs b/compression/cutils/quasar/src/instructions/mint.rs index 439322fad..835a07322 100644 --- a/compression/cutils/quasar/src/instructions/mint.rs +++ b/compression/cutils/quasar/src/instructions/mint.rs @@ -5,9 +5,6 @@ use quasar_lang::cpi::{InstructionAccount, InstructionView}; /// Maximum CPI accounts for MintToCollectionV1: 16 fixed accounts. const MINT_CPI_ACCOUNTS: usize = 16; -/// Maximum URI length for the instruction data buffer. -const MAX_URI_LEN: usize = 256; - /// Maximum instruction data buffer: discriminator(8) + metadata overhead(~120) + URI. const MAX_IX_DATA: usize = 400; @@ -54,7 +51,7 @@ pub struct MintAccountConstraints { } pub fn handle_mint(accounts: &mut MintAccountConstraints, uri: &str) -> Result<(), ProgramError> { - // The bounded String<256, 2> argument already enforces MAX_URI_LEN and + // The bounded String<256, 2> argument already caps the URI length and // UTF-8 at the decode boundary; the CPI encoder consumes the raw bytes. let uri = uri.as_bytes(); @@ -95,7 +92,10 @@ pub fn handle_mint(accounts: &mut MintAccountConstraints, uri: &str) -> Result<( accounts.payer.to_account_view().clone(), accounts.tree_delegate.to_account_view().clone(), accounts.collection_authority.to_account_view().clone(), - accounts.collection_authority_record_pda.to_account_view().clone(), + accounts + .collection_authority_record_pda + .to_account_view() + .clone(), accounts.collection_mint.to_account_view().clone(), accounts.collection_metadata.to_account_view().clone(), accounts.edition_account.to_account_view().clone(), @@ -112,8 +112,5 @@ pub fn handle_mint(accounts: &mut MintAccountConstraints, uri: &str) -> Result<( accounts: &ix_accounts, }; - solana_instruction_view::cpi::invoke::( - &instruction, - &views, - ) + solana_instruction_view::cpi::invoke::(&instruction, &views) } diff --git a/compression/cutils/quasar/src/instructions/verify.rs b/compression/cutils/quasar/src/instructions/verify.rs index ecc3ff198..bc64f0a3c 100644 --- a/compression/cutils/quasar/src/instructions/verify.rs +++ b/compression/cutils/quasar/src/instructions/verify.rs @@ -1,6 +1,9 @@ use crate::bubblegum_types::{get_asset_id, leaf_schema_v1_hash}; use crate::*; -use quasar_lang::{cpi::{InstructionAccount, InstructionView}, remaining::RemainingAccounts}; +use quasar_lang::{ + cpi::{InstructionAccount, InstructionView}, + remaining::RemainingAccounts, +}; /// Maximum proof nodes for the merkle tree. const MAX_PROOF_NODES: usize = 24; @@ -34,7 +37,6 @@ pub fn handle_verify( index: u32, remaining: RemainingAccounts<'_>, ) -> Result<(), ProgramError> { - // Compute asset ID and leaf hash let asset_id = get_asset_id(accounts.merkle_tree.address(), nonce); let leaf_hash = leaf_schema_v1_hash( @@ -87,13 +89,10 @@ pub fn handle_verify( // Build account views let tree_view = accounts.merkle_tree.to_account_view().clone(); - let mut views: [AccountView; MAX_CPI_ACCOUNTS] = - core::array::from_fn(|_| tree_view.clone()); + let mut views: [AccountView; MAX_CPI_ACCOUNTS] = core::array::from_fn(|_| tree_view.clone()); views[0] = accounts.merkle_tree.to_account_view().clone(); - for i in 0..proof_count { - views[1 + i] = proof_views[i].clone(); - } + views[1..1 + proof_count].clone_from_slice(&proof_views[..proof_count]); let instruction = InstructionView { program_id: accounts.compression_program.address(), diff --git a/compression/cutils/quasar/src/lib.rs b/compression/cutils/quasar/src/lib.rs index e1e263cda..e9f3cc8bd 100644 --- a/compression/cutils/quasar/src/lib.rs +++ b/compression/cutils/quasar/src/lib.rs @@ -3,23 +3,21 @@ use quasar_lang::prelude::*; mod bubblegum_types; -mod instructions; +pub mod instructions; use instructions::*; #[cfg(test)] mod tests; /// SPL Account Compression program ID (cmtDvXumGCrqC1Age74AVPhSRVXJMd8PJS91L8KbNCK). 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, + 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: 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, + 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, ]); declare_id!("BuFyrgRYzg2nPhqYrxZ7d9uYUs4VXtxH71U8EcoAfTQZ"); @@ -33,12 +31,9 @@ mod quasar_cutils { /// The URI arrives as a typed instruction argument: 0.1.0 clears /// `ctx.data` after decoding declared args (the instruction argument /// zero-copy boundary), so the pre-0.1.0 raw-tail pattern reads an empty - /// slice. `String<256, 2>` bounds it to MAX_URI_LEN with a u16 prefix. + /// slice. `String<256, 2>` bounds it to 256 bytes with a u16 prefix. #[instruction(discriminator = 0)] - pub fn mint( - ctx: Ctx, - uri: String<256, 2>, - ) -> Result<(), ProgramError> { + pub fn mint(ctx: Ctx, uri: String<256, 2>) -> Result<(), ProgramError> { instructions::handle_mint(&mut ctx.accounts, uri) } diff --git a/finance/betting-market/quasar/Cargo.toml b/finance/betting-market/quasar/Cargo.toml index fabf55fd8..11c4453fb 100644 --- a/finance/betting-market/quasar/Cargo.toml +++ b/finance/betting-market/quasar/Cargo.toml @@ -30,6 +30,11 @@ idl-build = ["quasar-lang/idl-build"] # git. Keep this rev in lockstep with the CLI rev installed by # .github/workflows/quasar.yml. quasar-lang = { git = "https://github.com/blueshift-gg/quasar", rev = "be60fca" } +# Pinned: quasar-lang asks for zeropod "0.3.3" and wincode 0.4, but zeropod +# 0.3.4 moved to wincode 0.5, so a fresh resolve (no lockfile is committed) +# splits the graph across two wincode versions and every Pod* trait bound +# fails. Unpin when quasar-lang's pinned rev accepts zeropod 0.3.4+. +zeropod = "=0.3.3" quasar-spl = { git = "https://github.com/blueshift-gg/quasar", rev = "be60fca" } solana-instruction = { version = "3.2.0" } diff --git a/finance/betting-market/quasar/src/instructions/add_outcome.rs b/finance/betting-market/quasar/src/instructions/add_outcome.rs index 465cfb051..335255f2a 100644 --- a/finance/betting-market/quasar/src/instructions/add_outcome.rs +++ b/finance/betting-market/quasar/src/instructions/add_outcome.rs @@ -34,7 +34,10 @@ pub fn handle_add_outcome( bumps: &AddOutcomeAccountConstraintsBumps, ) -> Result<(), ProgramError> { let label_bytes = label.as_bytes(); - require!(label_bytes.len() <= MAX_LABEL_LEN, BettingError::LabelTooLong); + require!( + label_bytes.len() <= MAX_LABEL_LEN, + BettingError::LabelTooLong + ); require!( accounts.event.status == EventStatus::Open as u8, BettingError::EventNotOpen diff --git a/finance/betting-market/quasar/src/instructions/claim_refund.rs b/finance/betting-market/quasar/src/instructions/claim_refund.rs index 46a7bf9d9..466daf74c 100644 --- a/finance/betting-market/quasar/src/instructions/claim_refund.rs +++ b/finance/betting-market/quasar/src/instructions/claim_refund.rs @@ -2,9 +2,7 @@ use quasar_lang::prelude::*; use quasar_spl::prelude::*; use crate::errors::BettingError; -use crate::state::{ - remove_bet, snapshot_user, Bet, Event, EventStatus, EventVaultPda, User, -}; +use crate::state::{remove_bet, snapshot_user, Bet, Event, EventStatus, EventVaultPda, User}; use super::transfer_from_vault; diff --git a/finance/betting-market/quasar/src/instructions/claim_winnings.rs b/finance/betting-market/quasar/src/instructions/claim_winnings.rs index 35a0db0fe..d3e994f53 100644 --- a/finance/betting-market/quasar/src/instructions/claim_winnings.rs +++ b/finance/betting-market/quasar/src/instructions/claim_winnings.rs @@ -2,9 +2,7 @@ use quasar_lang::prelude::*; use quasar_spl::prelude::*; use crate::errors::BettingError; -use crate::state::{ - remove_bet, snapshot_user, Bet, Event, EventStatus, EventVaultPda, User, -}; +use crate::state::{remove_bet, snapshot_user, Bet, Event, EventStatus, EventVaultPda, User}; use super::transfer_from_vault; @@ -84,7 +82,9 @@ pub fn handle_claim_winnings( .map_err(|_| BettingError::MathOverflow)?; // Winners always get their own stake back on top of their winnings. - let payout = stake.checked_add(winnings).ok_or(BettingError::MathOverflow)?; + let payout = stake + .checked_add(winnings) + .ok_or(BettingError::MathOverflow)?; // Drop the Bet from the bettor's index before the transfer (effects before // interactions); the Bet account itself closes when the instruction ends. diff --git a/finance/betting-market/quasar/src/instructions/mod.rs b/finance/betting-market/quasar/src/instructions/mod.rs index 29a4a92a8..9056cbadf 100644 --- a/finance/betting-market/quasar/src/instructions/mod.rs +++ b/finance/betting-market/quasar/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/quasar/src/instructions/settle_event.rs b/finance/betting-market/quasar/src/instructions/settle_event.rs index 3b761e15c..cb0986a30 100644 --- a/finance/betting-market/quasar/src/instructions/settle_event.rs +++ b/finance/betting-market/quasar/src/instructions/settle_event.rs @@ -2,9 +2,7 @@ use quasar_lang::prelude::*; use quasar_spl::prelude::*; use crate::errors::BettingError; -use crate::state::{ - snapshot_event, Config, Event, EventStatus, EventVaultPda, Outcome, -}; +use crate::state::{snapshot_event, Config, Event, EventStatus, EventVaultPda, Outcome}; use super::transfer_from_vault; diff --git a/finance/betting-market/quasar/src/lib.rs b/finance/betting-market/quasar/src/lib.rs index ca5187921..3fb44204d 100644 --- a/finance/betting-market/quasar/src/lib.rs +++ b/finance/betting-market/quasar/src/lib.rs @@ -64,7 +64,10 @@ mod quasar_betting_market { /// A bettor stakes tokens on one outcome. The stake joins the event's pool. #[instruction(discriminator = 3)] - pub fn place_bet(ctx: Ctx, amount: u64) -> Result<(), ProgramError> { + pub fn place_bet( + ctx: Ctx, + amount: u64, + ) -> Result<(), ProgramError> { instructions::place_bet::handle_place_bet(&mut ctx.accounts, amount, &ctx.bumps) } @@ -81,9 +84,7 @@ mod quasar_betting_market { /// 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. #[instruction(discriminator = 5)] - pub fn claim_winnings( - ctx: Ctx, - ) -> Result<(), ProgramError> { + pub fn claim_winnings(ctx: Ctx) -> Result<(), ProgramError> { instructions::claim_winnings::handle_claim_winnings(&mut ctx.accounts) } diff --git a/finance/betting-market/quasar/src/state/user.rs b/finance/betting-market/quasar/src/state/user.rs index 092102c9f..e37a95815 100644 --- a/finance/betting-market/quasar/src/state/user.rs +++ b/finance/betting-market/quasar/src/state/user.rs @@ -76,8 +76,7 @@ pub fn remove_bet( bet_count: &mut u8, bet_key: &Address, ) -> Result<(), ProgramError> { - let position = - position_of(bets, *bet_count, bet_key).ok_or(BettingError::BetNotInUserIndex)?; + let position = position_of(bets, *bet_count, bet_key).ok_or(BettingError::BetNotInUserIndex)?; let last = *bet_count as usize - 1; if position != last { let moved = read_bet(bets, last); diff --git a/finance/betting-market/quasar/src/tests.rs b/finance/betting-market/quasar/src/tests.rs index a706b499a..90ef882d0 100644 --- a/finance/betting-market/quasar/src/tests.rs +++ b/finance/betting-market/quasar/src/tests.rs @@ -6,8 +6,8 @@ use { crate::{ cpi::{ AddOutcomeInstruction, CancelEventInstruction, ClaimRefundInstruction, - ClaimWinningsInstruction, CloseLosingBetInstruction, InitializeEventInstruction, - InitializeConfigInstruction, PlaceBetInstruction, SettleEventInstruction, + ClaimWinningsInstruction, CloseLosingBetInstruction, InitializeConfigInstruction, + InitializeEventInstruction, PlaceBetInstruction, SettleEventInstruction, }, state::{Bet, Config, Event, EventStatus, EventVaultPda, Outcome, User}, }, @@ -160,7 +160,11 @@ fn full_lifecycle_settles_and_pays_the_winner(test: &mut Test) { // Event settled with the recorded figures. let event_state = test.read::(event); - assert_eq!(event_state.status, EventStatus::Settled as u8, "status settled"); + assert_eq!( + event_state.status, + EventStatus::Settled as u8, + "status settled" + ); assert_eq!(event_state.winning_outcome_index, 1, "winning index"); assert_eq!(u64::from(event_state.winning_pool), STAKE_B, "winning pool"); assert_eq!( diff --git a/finance/escrow/quasar/Cargo.toml b/finance/escrow/quasar/Cargo.toml index 50e78c92f..886e3af21 100644 --- a/finance/escrow/quasar/Cargo.toml +++ b/finance/escrow/quasar/Cargo.toml @@ -30,6 +30,11 @@ idl-build = ["quasar-lang/idl-build"] # git. Keep this rev in lockstep with the CLI rev installed by # .github/workflows/quasar.yml. quasar-lang = { git = "https://github.com/blueshift-gg/quasar", rev = "be60fca" } +# Pinned: quasar-lang asks for zeropod "0.3.3" and wincode 0.4, but zeropod +# 0.3.4 moved to wincode 0.5, so a fresh resolve (no lockfile is committed) +# splits the graph across two wincode versions and every Pod* trait bound +# fails. Unpin when quasar-lang's pinned rev accepts zeropod 0.3.4+. +zeropod = "=0.3.3" quasar-spl = { git = "https://github.com/blueshift-gg/quasar", rev = "be60fca" } solana-instruction = { version = "3.2.0" } diff --git a/finance/escrow/quasar/src/instructions/cancel_offer.rs b/finance/escrow/quasar/src/instructions/cancel_offer.rs index 633bf7584..228baf65f 100644 --- a/finance/escrow/quasar/src/instructions/cancel_offer.rs +++ b/finance/escrow/quasar/src/instructions/cancel_offer.rs @@ -1,8 +1,5 @@ use { - quasar_lang::cpi::Seed, - crate::state::Offer, - quasar_lang::prelude::*, - quasar_spl::prelude::*, + crate::state::Offer, quasar_lang::cpi::Seed, quasar_lang::prelude::*, quasar_spl::prelude::*, }; #[derive(Accounts)] diff --git a/finance/escrow/quasar/src/instructions/take_offer.rs b/finance/escrow/quasar/src/instructions/take_offer.rs index c818c75f5..730366125 100644 --- a/finance/escrow/quasar/src/instructions/take_offer.rs +++ b/finance/escrow/quasar/src/instructions/take_offer.rs @@ -1,8 +1,5 @@ use { - quasar_lang::cpi::Seed, - crate::state::Offer, - quasar_lang::prelude::*, - quasar_spl::prelude::*, + crate::state::Offer, quasar_lang::cpi::Seed, quasar_lang::prelude::*, quasar_spl::prelude::*, }; #[derive(Accounts)] diff --git a/finance/escrow/quasar/src/lib.rs b/finance/escrow/quasar/src/lib.rs index 6b92d6f38..ef09b8f7b 100644 --- a/finance/escrow/quasar/src/lib.rs +++ b/finance/escrow/quasar/src/lib.rs @@ -2,9 +2,9 @@ use quasar_lang::prelude::*; -mod instructions; +pub mod instructions; use instructions::*; -mod state; +pub mod state; #[cfg(test)] mod tests; @@ -31,11 +31,17 @@ mod quasar_escrow { #[instruction(discriminator = 1)] pub fn take_offer(ctx: Ctx) -> Result<(), ProgramError> { instructions::take_offer::handle_transfer_tokens(&mut ctx.accounts)?; - instructions::take_offer::handle_withdraw_tokens_and_close_take(&mut ctx.accounts, &ctx.bumps) + instructions::take_offer::handle_withdraw_tokens_and_close_take( + &mut ctx.accounts, + &ctx.bumps, + ) } #[instruction(discriminator = 2)] pub fn cancel_offer(ctx: Ctx) -> Result<(), ProgramError> { - instructions::cancel_offer::handle_withdraw_tokens_and_close_cancel_offer(&mut ctx.accounts, &ctx.bumps) + instructions::cancel_offer::handle_withdraw_tokens_and_close_cancel_offer( + &mut ctx.accounts, + &ctx.bumps, + ) } } diff --git a/finance/escrow/quasar/src/tests.rs b/finance/escrow/quasar/src/tests.rs index c1ffe3733..651a6c9c1 100644 --- a/finance/escrow/quasar/src/tests.rs +++ b/finance/escrow/quasar/src/tests.rs @@ -63,7 +63,11 @@ fn live_offer(test: &mut Test) -> Pubkey { bump, }, ); - test.add(TokenAccount::new(TOKEN_MINT_A, offer).at(VAULT).amount(DEPOSIT_AMOUNT)); + test.add( + TokenAccount::new(TOKEN_MINT_A, offer) + .at(VAULT) + .amount(DEPOSIT_AMOUNT), + ); offer } @@ -171,7 +175,12 @@ fn take_offer_rejects_a_mint_that_does_not_match_the_offer(test: &mut Test) { // The attacker substitutes a different mint for token_mint_a. The // has_one(token_mint_a) binding to the offer state must reject it. - test.add(Mint::new(MAKER).at(WRONG_MINT).supply(1_000_000_000).decimals(9)); + test.add( + Mint::new(MAKER) + .at(WRONG_MINT) + .supply(1_000_000_000) + .decimals(9), + ); let result = test.send(TakeOfferInstruction { taker: TAKER, diff --git a/finance/lending/quasar/src/instructions/admin.rs b/finance/lending/quasar/src/instructions/admin.rs index e91cc5f14..c93d0d281 100644 --- a/finance/lending/quasar/src/instructions/admin.rs +++ b/finance/lending/quasar/src/instructions/admin.rs @@ -32,7 +32,11 @@ pub struct InitializeLendingMarket { impl InitializeLendingMarket { #[inline(always)] - pub fn run(&mut self, market_id: u64, bumps: &InitializeLendingMarketBumps) -> Result<(), ProgramError> { + pub fn run( + &mut self, + market_id: u64, + bumps: &InitializeLendingMarketBumps, + ) -> Result<(), ProgramError> { self.lending_market.set_inner(LendingMarketInner { owner: *self.owner.address(), market_id, @@ -121,7 +125,11 @@ impl InitializeReserve { ) .invoke_signed(&vault_seeds)?; self.token_program - .initialize_account3(&self.liquidity_vault, &self.liquidity_mint, &reserve_address) + .initialize_account3( + &self.liquidity_vault, + &self.liquidity_mint, + &reserve_address, + ) .invoke()?; // Create the share-token mint PDA (authority = reserve, same decimals). @@ -254,7 +262,12 @@ pub struct CollectProtocolFees { pub owner: Signer, #[account(has_one(owner))] pub lending_market: Account, - #[account(mut, has_one(lending_market), has_one(liquidity_mint), has_one(liquidity_vault))] + #[account( + mut, + has_one(lending_market), + has_one(liquidity_mint), + has_one(liquidity_vault) + )] pub reserve: Account, pub liquidity_mint: Account, #[account(mut)] diff --git a/finance/lending/quasar/src/instructions/position.rs b/finance/lending/quasar/src/instructions/position.rs index 2c62f3163..5ebcf928c 100644 --- a/finance/lending/quasar/src/instructions/position.rs +++ b/finance/lending/quasar/src/instructions/position.rs @@ -4,7 +4,10 @@ use { error::LendingError, instructions::supply::reserve_seeds, logic::{accrue, now, price_scaled, snapshot_obligation, snapshot_reserve, SCALE}, - math::{current_debt, market_value, mul_div_ceil, mul_div_floor, net_total_liquidity, value_to_amount, Rounding}, + math::{ + current_debt, market_value, mul_div_ceil, mul_div_floor, net_total_liquidity, + value_to_amount, Rounding, + }, state::{ LendingMarket, Obligation, ObligationInner, ObligationVaultPda, PriceFeed, Reserve, }, @@ -94,7 +97,11 @@ impl DepositObligationCollateral { if obligation.collateral_reserve == Address::default() { obligation.collateral_reserve = reserve_address; } else { - require_keys_eq!(obligation.collateral_reserve, reserve_address, LendingError::WrongReserve); + require_keys_eq!( + obligation.collateral_reserve, + reserve_address, + LendingError::WrongReserve + ); } obligation.deposited_shares = obligation .deposited_shares @@ -130,7 +137,12 @@ pub struct BorrowObligationLiquidity { #[account(mut, has_one(lending_market))] pub collateral_reserve: Account, pub collateral_price: Account, - #[account(mut, has_one(lending_market), has_one(liquidity_mint), has_one(liquidity_vault))] + #[account( + mut, + has_one(lending_market), + has_one(liquidity_mint), + has_one(liquidity_vault) + )] pub borrow_reserve: Account, pub borrow_price: Account, pub liquidity_mint: Account, @@ -194,16 +206,38 @@ impl BorrowObligationLiquidity { price_scaled(&self.collateral_price, slot)?, Rounding::Down, )?; - let allowed = mul_div_floor(collateral_value, collateral.loan_to_value_bps as u128, BPS_DENOMINATOR)?; + let allowed = mul_div_floor( + collateral_value, + collateral.loan_to_value_bps as u128, + BPS_DENOMINATOR, + )?; // Existing debt value + the new borrow, both rounded up. let borrow_price = price_scaled(&self.borrow_price, slot)?; - let existing_debt = current_debt(obligation.borrowed_principal, borrow.borrow_accumulation_factor)?; - let existing_value = market_value(existing_debt, borrow.liquidity_decimals, borrow_price, Rounding::Up)?; - let new_value = market_value(amount, borrow.liquidity_decimals, borrow_price, Rounding::Up)?; - let projected = existing_value.checked_add(new_value).ok_or(LendingError::MathOverflow)?; + let existing_debt = current_debt( + obligation.borrowed_principal, + borrow.borrow_accumulation_factor, + )?; + let existing_value = market_value( + existing_debt, + borrow.liquidity_decimals, + borrow_price, + Rounding::Up, + )?; + let new_value = market_value( + amount, + borrow.liquidity_decimals, + borrow_price, + Rounding::Up, + )?; + let projected = existing_value + .checked_add(new_value) + .ok_or(LendingError::MathOverflow)?; require!(projected <= allowed, LendingError::BorrowTooLarge); - require!(amount <= borrow.available_liquidity, LendingError::InsufficientLiquidity); + require!( + amount <= borrow.available_liquidity, + LendingError::InsufficientLiquidity + ); let scaled_added = mul_div_ceil(amount as u128, SCALE, borrow.borrow_accumulation_factor)?; borrow.borrowed_principal = borrow @@ -278,11 +312,15 @@ impl RepayObligationLiquidity { accrue(&mut borrow, slot)?; let mut obligation = snapshot_obligation(&self.obligation); - let debt = current_debt(obligation.borrowed_principal, borrow.borrow_accumulation_factor)?; + let debt = current_debt( + obligation.borrowed_principal, + borrow.borrow_accumulation_factor, + )?; let repay = amount.min(debt); require!(repay > 0, LendingError::ZeroAmount); - let scaled_removed = mul_div_floor(repay as u128, SCALE, borrow.borrow_accumulation_factor)? - .min(obligation.borrowed_principal); + let scaled_removed = + mul_div_floor(repay as u128, SCALE, borrow.borrow_accumulation_factor)? + .min(obligation.borrowed_principal); borrow.borrowed_principal = borrow .borrowed_principal @@ -360,7 +398,10 @@ impl WithdrawObligationCollateral { let mut collateral = snapshot_reserve(&self.collateral_reserve); accrue(&mut collateral, slot)?; let mut obligation = snapshot_obligation(&self.obligation); - require!(obligation.deposited_shares >= shares, LendingError::WithdrawTooLarge); + require!( + obligation.deposited_shares >= shares, + LendingError::WithdrawTooLarge + ); // Remaining collateral value after withdrawing `shares`. let remaining_shares = obligation.deposited_shares - shares; @@ -381,7 +422,11 @@ impl WithdrawObligationCollateral { price_scaled(&self.collateral_price, slot)?, Rounding::Down, )?; - let allowed = mul_div_floor(remaining_value, collateral.loan_to_value_bps as u128, BPS_DENOMINATOR)?; + let allowed = mul_div_floor( + remaining_value, + collateral.loan_to_value_bps as u128, + BPS_DENOMINATOR, + )?; // Debt value (zero when the obligation has no borrow). let debt_value = if obligation.borrowed_principal > 0 { @@ -397,8 +442,16 @@ impl WithdrawObligationCollateral { ); let mut borrow = snapshot_reserve(&self.borrow_reserve); accrue(&mut borrow, slot)?; - let debt = current_debt(obligation.borrowed_principal, borrow.borrow_accumulation_factor)?; - market_value(debt, borrow.liquidity_decimals, price_scaled(&self.borrow_price, slot)?, Rounding::Up)? + let debt = current_debt( + obligation.borrowed_principal, + borrow.borrow_accumulation_factor, + )?; + market_value( + debt, + borrow.liquidity_decimals, + price_scaled(&self.borrow_price, slot)?, + Rounding::Up, + )? } else { 0 }; @@ -446,7 +499,12 @@ pub struct LiquidateObligation { pub obligation_vault: InterfaceAccount, #[account(mut)] pub liquidator_collateral: Account, - #[account(mut, has_one(lending_market), has_one(liquidity_mint), has_one(liquidity_vault))] + #[account( + mut, + has_one(lending_market), + has_one(liquidity_mint), + has_one(liquidity_vault) + )] pub borrow_reserve: Account, pub borrow_price: Account, pub liquidity_mint: Account, @@ -463,10 +521,26 @@ impl LiquidateObligation { require!(amount > 0, LendingError::ZeroAmount); let slot = now()?; - require_keys_eq!(self.obligation.collateral_reserve, *self.collateral_reserve.address(), LendingError::WrongReserve); - require_keys_eq!(self.obligation.borrow_reserve, *self.borrow_reserve.address(), LendingError::WrongReserve); - require_keys_eq!(self.collateral_reserve.price_feed, *self.collateral_price.address(), LendingError::WrongReserve); - require_keys_eq!(self.borrow_reserve.price_feed, *self.borrow_price.address(), LendingError::WrongReserve); + require_keys_eq!( + self.obligation.collateral_reserve, + *self.collateral_reserve.address(), + LendingError::WrongReserve + ); + require_keys_eq!( + self.obligation.borrow_reserve, + *self.borrow_reserve.address(), + LendingError::WrongReserve + ); + require_keys_eq!( + self.collateral_reserve.price_feed, + *self.collateral_price.address(), + LendingError::WrongReserve + ); + require_keys_eq!( + self.borrow_reserve.price_feed, + *self.borrow_price.address(), + LendingError::WrongReserve + ); let mut collateral = snapshot_reserve(&self.collateral_reserve); accrue(&mut collateral, slot)?; @@ -495,22 +569,52 @@ impl LiquidateObligation { collateral_price, Rounding::Down, )?; - let unhealthy_threshold = mul_div_floor(collateral_value, collateral.liquidation_threshold_bps as u128, BPS_DENOMINATOR)?; - let debt = current_debt(obligation.borrowed_principal, borrow.borrow_accumulation_factor)?; + let unhealthy_threshold = mul_div_floor( + collateral_value, + collateral.liquidation_threshold_bps as u128, + BPS_DENOMINATOR, + )?; + let debt = current_debt( + obligation.borrowed_principal, + borrow.borrow_accumulation_factor, + )?; let debt_value = market_value(debt, borrow.liquidity_decimals, borrow_price, Rounding::Up)?; - require!(debt_value > unhealthy_threshold, LendingError::ObligationHealthy); + require!( + debt_value > unhealthy_threshold, + LendingError::ObligationHealthy + ); // Repay capped by the close factor — taken from the borrow reserve // because it is a property of the debt being closed. - let max_repay = mul_div_floor(debt as u128, borrow.close_factor_bps as u128, BPS_DENOMINATOR)?; + let max_repay = mul_div_floor( + debt as u128, + borrow.close_factor_bps as u128, + BPS_DENOMINATOR, + )?; let repay = amount.min(u64::try_from(max_repay).map_err(|_| LendingError::MathOverflow)?); require!(repay > 0, LendingError::ZeroAmount); // Seize collateral worth repay value + bonus, converted to share tokens. - let repay_value = market_value(repay, borrow.liquidity_decimals, borrow_price, Rounding::Down)?; - let bonus = mul_div_floor(repay_value, collateral.liquidation_bonus_bps as u128, BPS_DENOMINATOR)?; - let seize_value = repay_value.checked_add(bonus).ok_or(LendingError::MathOverflow)?; - let seize_liquidity = value_to_amount(seize_value, collateral.liquidity_decimals, collateral_price, Rounding::Down)?; + let repay_value = market_value( + repay, + borrow.liquidity_decimals, + borrow_price, + Rounding::Down, + )?; + let bonus = mul_div_floor( + repay_value, + collateral.liquidation_bonus_bps as u128, + BPS_DENOMINATOR, + )?; + let seize_value = repay_value + .checked_add(bonus) + .ok_or(LendingError::MathOverflow)?; + let seize_liquidity = value_to_amount( + seize_value, + collateral.liquidity_decimals, + collateral_price, + Rounding::Down, + )?; let seize_shares = mul_div_floor( seize_liquidity as u128, collateral.share_mint_supply as u128, @@ -525,13 +629,26 @@ impl LiquidateObligation { LendingError::LiquidationTooLarge ); - let scaled_removed = mul_div_floor(repay as u128, SCALE, borrow.borrow_accumulation_factor)? - .min(obligation.borrowed_principal); + let scaled_removed = + mul_div_floor(repay as u128, SCALE, borrow.borrow_accumulation_factor)? + .min(obligation.borrowed_principal); - borrow.borrowed_principal = borrow.borrowed_principal.checked_sub(scaled_removed).ok_or(LendingError::MathOverflow)?; - borrow.available_liquidity = borrow.available_liquidity.checked_add(repay).ok_or(LendingError::MathOverflow)?; - obligation.borrowed_principal = obligation.borrowed_principal.checked_sub(scaled_removed).ok_or(LendingError::MathOverflow)?; - obligation.deposited_shares = obligation.deposited_shares.checked_sub(seize_shares).ok_or(LendingError::MathOverflow)?; + borrow.borrowed_principal = borrow + .borrowed_principal + .checked_sub(scaled_removed) + .ok_or(LendingError::MathOverflow)?; + borrow.available_liquidity = borrow + .available_liquidity + .checked_add(repay) + .ok_or(LendingError::MathOverflow)?; + obligation.borrowed_principal = obligation + .borrowed_principal + .checked_sub(scaled_removed) + .ok_or(LendingError::MathOverflow)?; + obligation.deposited_shares = obligation + .deposited_shares + .checked_sub(seize_shares) + .ok_or(LendingError::MathOverflow)?; let share_decimals = self.share_mint.decimals; let borrow_decimals = borrow.liquidity_decimals; diff --git a/finance/lending/quasar/src/instructions/supply.rs b/finance/lending/quasar/src/instructions/supply.rs index 029883e5a..c7bae1897 100644 --- a/finance/lending/quasar/src/instructions/supply.rs +++ b/finance/lending/quasar/src/instructions/supply.rs @@ -30,7 +30,12 @@ pub(crate) use reserve_seeds; pub struct DepositReserveLiquidity { #[account(mut)] pub supplier: Signer, - #[account(mut, has_one(liquidity_mint), has_one(liquidity_vault), has_one(share_mint))] + #[account( + mut, + has_one(liquidity_mint), + has_one(liquidity_vault), + has_one(share_mint) + )] pub reserve: Account, pub liquidity_mint: Account, #[account(mut)] @@ -95,7 +100,12 @@ impl DepositReserveLiquidity { let seeds = reserve_seeds!(lending_market, liquidity_mint, bump); self.token_program - .mint_to(&self.share_mint, &self.supplier_share, &self.reserve, shares) + .mint_to( + &self.share_mint, + &self.supplier_share, + &self.reserve, + shares, + ) .invoke_signed(&seeds) } } @@ -108,7 +118,12 @@ impl DepositReserveLiquidity { pub struct RedeemReserveCollateral { #[account(mut)] pub supplier: Signer, - #[account(mut, has_one(liquidity_mint), has_one(liquidity_vault), has_one(share_mint))] + #[account( + mut, + has_one(liquidity_mint), + has_one(liquidity_vault), + has_one(share_mint) + )] pub reserve: Account, pub liquidity_mint: Account, #[account(mut)] @@ -130,7 +145,10 @@ impl RedeemReserveCollateral { let mut reserve = snapshot_reserve(&self.reserve); accrue(&mut reserve, slot)?; - require!(reserve.share_mint_supply > 0, LendingError::InsufficientLiquidity); + require!( + reserve.share_mint_supply > 0, + LendingError::InsufficientLiquidity + ); let total = net_total_liquidity( reserve.available_liquidity, @@ -161,7 +179,12 @@ impl RedeemReserveCollateral { self.reserve.set_inner(reserve); self.token_program - .burn(&self.supplier_share, &self.share_mint, &self.supplier, shares) + .burn( + &self.supplier_share, + &self.share_mint, + &self.supplier, + shares, + ) .invoke()?; let seeds = reserve_seeds!(lending_market, liquidity_mint, bump); diff --git a/finance/lending/quasar/src/lib.rs b/finance/lending/quasar/src/lib.rs index e2f59b6c8..eaf93cd6e 100644 --- a/finance/lending/quasar/src/lib.rs +++ b/finance/lending/quasar/src/lib.rs @@ -17,11 +17,11 @@ use quasar_lang::prelude::*; mod constants; mod error; -mod instructions; +pub mod instructions; mod last_restart; mod logic; mod math; -mod state; +pub mod state; #[cfg(test)] mod tests; diff --git a/finance/lending/quasar/src/math.rs b/finance/lending/quasar/src/math.rs index 298fd51e3..17f9c58ea 100644 --- a/finance/lending/quasar/src/math.rs +++ b/finance/lending/quasar/src/math.rs @@ -16,19 +16,25 @@ pub enum Rounding { } pub fn ten_pow(exponent: u32) -> Result { - 10u128.checked_pow(exponent).ok_or(LendingError::MathOverflow.into()) + 10u128 + .checked_pow(exponent) + .ok_or(LendingError::MathOverflow.into()) } pub fn mul_div_floor(a: u128, b: u128, denominator: u128) -> Result { require!(denominator > 0, LendingError::MathOverflow); let product = a.checked_mul(b).ok_or(LendingError::MathOverflow)?; - Ok(product.checked_div(denominator).ok_or(LendingError::MathOverflow)?) + Ok(product + .checked_div(denominator) + .ok_or(LendingError::MathOverflow)?) } pub fn mul_div_ceil(a: u128, b: u128, denominator: u128) -> Result { require!(denominator > 0, LendingError::MathOverflow); let product = a.checked_mul(b).ok_or(LendingError::MathOverflow)?; - let rounding = denominator.checked_sub(1).ok_or(LendingError::MathOverflow)?; + let rounding = denominator + .checked_sub(1) + .ok_or(LendingError::MathOverflow)?; Ok(product .checked_add(rounding) .ok_or(LendingError::MathOverflow)? @@ -69,7 +75,12 @@ pub fn market_value( price_scaled: u128, rounding: Rounding, ) -> Result { - mul_div(amount as u128, price_scaled, ten_pow(decimals as u32)?, rounding) + mul_div( + amount as u128, + price_scaled, + ten_pow(decimals as u32)?, + rounding, + ) } /// Inverse of [`market_value`]: base units of a token worth `value_scaled`. @@ -79,7 +90,12 @@ pub fn value_to_amount( price_scaled: u128, rounding: Rounding, ) -> Result { - let amount = mul_div(value_scaled, ten_pow(decimals as u32)?, price_scaled, rounding)?; + let amount = mul_div( + value_scaled, + ten_pow(decimals as u32)?, + price_scaled, + rounding, + )?; u64::try_from(amount).map_err(|_| LendingError::MathOverflow.into()) } @@ -126,7 +142,11 @@ pub fn utilization_bps( if total == 0 { return Ok(0); } - mul_div_floor(current_debt(borrowed_principal, factor)? as u128, BPS_DENOMINATOR, total) + mul_div_floor( + current_debt(borrowed_principal, factor)? as u128, + BPS_DENOMINATOR, + total, + ) } /// Per-slot borrow rate (FIXED_POINT_SCALE-scaled) from the kinked curve. @@ -145,7 +165,11 @@ pub fn borrow_rate_per_slot( .checked_sub(min_rate_bps as u128) .ok_or(LendingError::MathOverflow)?; (min_rate_bps as u128) - .checked_add(mul_div_floor(range, utilization, optimal_utilization.max(1))?) + .checked_add(mul_div_floor( + range, + utilization, + optimal_utilization.max(1), + )?) .ok_or(LendingError::MathOverflow)? } else { let range = (max_rate_bps as u128) @@ -198,7 +222,10 @@ pub fn accrue_factor( slots_per_year, )?; let growth = FIXED_POINT_SCALE - .checked_add(rate.checked_mul(elapsed as u128).ok_or(LendingError::MathOverflow)?) + .checked_add( + rate.checked_mul(elapsed as u128) + .ok_or(LendingError::MathOverflow)?, + ) .ok_or(LendingError::MathOverflow)?; mul_div_floor(factor, growth, FIXED_POINT_SCALE) } diff --git a/finance/lending/quasar/src/tests.rs b/finance/lending/quasar/src/tests.rs index 4d80fd03b..4831fae46 100644 --- a/finance/lending/quasar/src/tests.rs +++ b/finance/lending/quasar/src/tests.rs @@ -8,9 +8,9 @@ use { cpi::{ BorrowObligationLiquidityInstruction, DepositObligationCollateralInstruction, DepositReserveLiquidityInstruction, InitializeLendingMarketInstruction, - InitializeObligationInstruction, InitializeReserveInstruction, LiquidateObligationInstruction, - RedeemReserveCollateralInstruction, RepayObligationLiquidityInstruction, - SetPriceInstruction, + InitializeObligationInstruction, InitializeReserveInstruction, + LiquidateObligationInstruction, RedeemReserveCollateralInstruction, + RepayObligationLiquidityInstruction, SetPriceInstruction, }, state::{LendingMarket, LiquidityVaultPda, Obligation, Reserve, ShareMintPda}, }, @@ -80,7 +80,8 @@ fn pdas(test: &Test) -> Pdas { collateral_share_mint: test.derive_pda(ShareMintPda::seeds(&collateral_reserve)), // Feed PDAs are seeded by (market, mint) — scoped to the market, not // to any individual. - collateral_price: test.derive_pda(crate::state::PriceFeed::seeds(&market, &COLLATERAL_MINT)), + collateral_price: test + .derive_pda(crate::state::PriceFeed::seeds(&market, &COLLATERAL_MINT)), borrow_reserve, borrow_vault: test.derive_pda(LiquidityVaultPda::seeds(&borrow_reserve)), borrow_share_mint: test.derive_pda(ShareMintPda::seeds(&borrow_reserve)), @@ -122,7 +123,9 @@ fn base_world(test: &mut Test) -> Pdas { .at(LIQUIDATOR_BORROW) .amount(1_000 * UNIT), ); - test.add(TokenAccount::new(w.collateral_share_mint, LIQUIDATOR).at(LIQUIDATOR_COLLATERAL_SHARE)); + test.add( + TokenAccount::new(w.collateral_share_mint, LIQUIDATOR).at(LIQUIDATOR_COLLATERAL_SHARE), + ); // Where the market owner receives collected protocol fees. test.add(TokenAccount::new(BORROW_MINT, OWNER).at(OWNER_BORROW)); w @@ -360,9 +363,9 @@ mod slot_warp { use { super::{dollars, EXP}, super::{ - BORROWER, BORROWER_BORROW, BORROWER_COLLATERAL, BORROWER_COLLATERAL_SHARE, - COLLATERAL_MINT, BORROW_MINT, DECIMALS, MARKET_ID, OWNER, OWNER_BORROW, QUOTE_MINT, - SUPPLIER, SUPPLIER_BORROW, SUPPLIER_BORROW_SHARE, UNIT, + BORROWER, BORROWER_BORROW, BORROWER_COLLATERAL, BORROWER_COLLATERAL_SHARE, BORROW_MINT, + COLLATERAL_MINT, DECIMALS, MARKET_ID, OWNER, OWNER_BORROW, QUOTE_MINT, SUPPLIER, + SUPPLIER_BORROW, SUPPLIER_BORROW_SHARE, UNIT, }, quasar_svm::{Account, AccountMeta, Instruction, Pubkey, QuasarSvm}, spl_token::state::{Account as SplToken, AccountState, Mint as SplMint}, @@ -489,7 +492,12 @@ mod slot_warp { token(SUPPLIER_BORROW, BORROW_MINT, SUPPLIER, 1_000 * UNIT), token(SUPPLIER_BORROW_SHARE, borrow_share_mint, SUPPLIER, 0), token(BORROWER_COLLATERAL, COLLATERAL_MINT, BORROWER, 1_000 * UNIT), - token(BORROWER_COLLATERAL_SHARE, collateral_share_mint, BORROWER, 0), + token( + BORROWER_COLLATERAL_SHARE, + collateral_share_mint, + BORROWER, + 0, + ), token(BORROWER_BORROW, BORROW_MINT, BORROWER, 0), // Where the market owner receives collected protocol fees. token(OWNER_BORROW, BORROW_MINT, OWNER, 0), @@ -788,9 +796,11 @@ mod slot_warp { world.svm.sysvars.warp_to_slot(restart_slot + 2); world.svm.sysvars.last_restart_slot.last_restart_slot = restart_slot; - world.borrow(100 * UNIT).assert_error(quasar_svm::ProgramError::Custom( - crate::error::LendingError::PricePredatesRestart as u32, - )); + world + .borrow(100 * UNIT) + .assert_error(quasar_svm::ProgramError::Custom( + crate::error::LendingError::PricePredatesRestart as u32, + )); // Publishing after the restart reopens the market. world.set_price(COLLATERAL_MINT, world.collateral_price, dollars(1)); diff --git a/finance/order-book/quasar/Cargo.toml b/finance/order-book/quasar/Cargo.toml index fe2b397b3..6d83d060a 100644 --- a/finance/order-book/quasar/Cargo.toml +++ b/finance/order-book/quasar/Cargo.toml @@ -30,6 +30,11 @@ idl-build = ["quasar-lang/idl-build"] # git. Keep this rev in lockstep with the CLI rev installed by # .github/workflows/quasar.yml. quasar-lang = { git = "https://github.com/blueshift-gg/quasar", rev = "be60fca" } +# Pinned: quasar-lang asks for zeropod "0.3.3" and wincode 0.4, but zeropod +# 0.3.4 moved to wincode 0.5, so a fresh resolve (no lockfile is committed) +# splits the graph across two wincode versions and every Pod* trait bound +# fails. Unpin when quasar-lang's pinned rev accepts zeropod 0.3.4+. +zeropod = "=0.3.3" quasar-spl = { git = "https://github.com/blueshift-gg/quasar", rev = "be60fca" } solana-instruction = { version = "3.2.0" } # The ported Openbook slab casts a contiguous byte region to fixed-layout node diff --git a/finance/order-book/quasar/src/instructions/cancel_order.rs b/finance/order-book/quasar/src/instructions/cancel_order.rs index aa4297032..a7a84c394 100644 --- a/finance/order-book/quasar/src/instructions/cancel_order.rs +++ b/finance/order-book/quasar/src/instructions/cancel_order.rs @@ -30,7 +30,11 @@ pub fn handle_cancel_order( ) -> Result<(), ProgramError> { let mut order = snapshot_order(&accounts.order); - require_keys_eq!(order.owner, *accounts.owner.address(), OrderBookError::Unauthorized); + require_keys_eq!( + order.owner, + *accounts.owner.address(), + OrderBookError::Unauthorized + ); require!( order.status == OrderStatus::Open as u8 diff --git a/finance/order-book/quasar/src/instructions/initialize_market.rs b/finance/order-book/quasar/src/instructions/initialize_market.rs index cbd7e1332..7d6f9477d 100644 --- a/finance/order-book/quasar/src/instructions/initialize_market.rs +++ b/finance/order-book/quasar/src/instructions/initialize_market.rs @@ -93,7 +93,10 @@ pub fn handle_initialize_market( // program-owned, zeroed account; verify ownership before casting. { let view = accounts.order_book.to_account_view(); - require!(view.owned_by(&crate::ID), OrderBookError::InvalidOrderBookOwner); + require!( + view.owned_by(&crate::ID), + OrderBookError::InvalidOrderBookOwner + ); // SAFETY: `order_book` is writable and not aliased elsewhere in this // instruction. The cast mirrors the read-only raw-slice pattern used // in the pyth example, extended to a mutable slice for initialization. diff --git a/finance/order-book/quasar/src/instructions/mod.rs b/finance/order-book/quasar/src/instructions/mod.rs index 1ed385342..5fc5f626c 100644 --- a/finance/order-book/quasar/src/instructions/mod.rs +++ b/finance/order-book/quasar/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/quasar/src/instructions/place_order.rs b/finance/order-book/quasar/src/instructions/place_order.rs index e8ab00e9b..04d1267af 100644 --- a/finance/order-book/quasar/src/instructions/place_order.rs +++ b/finance/order-book/quasar/src/instructions/place_order.rs @@ -130,14 +130,23 @@ pub fn handle_place_order( ) -> Result<(), ProgramError> { let side = OrderSide::from_u8(side_byte).ok_or(OrderBookError::InvalidSide)?; - require!(accounts.market.is_active.is_true(), OrderBookError::MarketPaused); + require!( + accounts.market.is_active.is_true(), + OrderBookError::MarketPaused + ); require!(price > 0, OrderBookError::InvalidPrice); let tick_size = u64::from(accounts.market.tick_size); - require!(price.is_multiple_of(tick_size), OrderBookError::InvalidTickSize); + require!( + price.is_multiple_of(tick_size), + OrderBookError::InvalidTickSize + ); let min_order_size = u64::from(accounts.market.min_order_size); - require!(quantity >= min_order_size, OrderBookError::BelowMinOrderSize); + require!( + quantity >= min_order_size, + OrderBookError::BelowMinOrderSize + ); require!( (accounts.market_user.open_orders_len as usize) < MAX_OPEN_ORDERS, @@ -201,7 +210,10 @@ pub fn handle_place_order( // to this account's data is live. let data = unsafe { core::slice::from_raw_parts(view.data_ptr(), view.data_len()) }; let order_book = load_order_book(data)?; - require!(order_book.next_order_id == order_id_arg, OrderBookError::OrderIdMismatch); + require!( + order_book.next_order_id == order_id_arg, + OrderBookError::OrderIdMismatch + ); plan_fills(order_book, side, price, quantity) }; @@ -231,7 +243,8 @@ pub fn handle_place_order( // mutable handles. let order_view = unsafe { order_ra.as_account_view_unchecked_mut() }; Account::::from_account_view(&*order_view)?; - let maker_order_acc = unsafe { Account::::from_account_view_unchecked_mut(order_view) }; + let maker_order_acc = + unsafe { Account::::from_account_view_unchecked_mut(order_view) }; let mut maker_order = snapshot_order(maker_order_acc); let user_view = unsafe { user_ra.as_account_view_unchecked_mut() }; @@ -244,9 +257,21 @@ pub fn handle_place_order( maker_order.order_id == fill.maker_order_id, OrderBookError::MakerAccountMismatch ); - require_keys_eq!(maker_order.market, market_key, OrderBookError::MakerAccountMismatch); - require_keys_eq!(maker_order.owner, maker_user.owner, OrderBookError::MakerOwnerMismatch); - require_keys_eq!(maker_user.market, market_key, OrderBookError::MakerAccountMismatch); + require_keys_eq!( + maker_order.market, + market_key, + OrderBookError::MakerAccountMismatch + ); + require_keys_eq!( + maker_order.owner, + maker_user.owner, + OrderBookError::MakerOwnerMismatch + ); + require_keys_eq!( + maker_user.market, + market_key, + OrderBookError::MakerAccountMismatch + ); // Fee model (maker-funded, no extra taker deposit): // gross = fill_price × fill_quantity × quote_lot_size @@ -385,8 +410,18 @@ pub fn handle_place_order( let order_book = load_order_book_mut(data)?; let id = order_book.allocate_order_id()?; if plan.taker_remaining > 0 { - require!(!order_book.is_side_full(side), OrderBookError::OrderBookFull); - order_book.place_resting(side, price, plan.taker_remaining, owner_bytes, id, timestamp)?; + require!( + !order_book.is_side_full(side), + OrderBookError::OrderBookFull + ); + order_book.place_resting( + side, + price, + plan.taker_remaining, + owner_bytes, + id, + timestamp, + )?; } id }; diff --git a/finance/order-book/quasar/src/lib.rs b/finance/order-book/quasar/src/lib.rs index ae178de93..d627c398e 100644 --- a/finance/order-book/quasar/src/lib.rs +++ b/finance/order-book/quasar/src/lib.rs @@ -51,7 +51,10 @@ mod quasar_order_book { pub fn initialize_market_user( ctx: Ctx, ) -> Result<(), ProgramError> { - instructions::initialize_market_user::handle_initialize_market_user(&mut ctx.accounts, &ctx.bumps) + instructions::initialize_market_user::handle_initialize_market_user( + &mut ctx.accounts, + &ctx.bumps, + ) } /// Place a bid or ask (`side`: 0 = Bid, 1 = Ask). Locks the required funds, diff --git a/finance/order-book/quasar/src/state/slab/nodes.rs b/finance/order-book/quasar/src/state/slab/nodes.rs index a88a3793a..86c1e1988 100644 --- a/finance/order-book/quasar/src/state/slab/nodes.rs +++ b/finance/order-book/quasar/src/state/slab/nodes.rs @@ -63,7 +63,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) } @@ -190,13 +194,7 @@ const_assert_eq!(size_of::(), NODE_SIZE); const_assert_eq!(size_of::() % 8, 0); impl LeafNode { - pub fn new( - key: u128, - owner: [u8; 32], - quantity: u64, - order_id: u64, - timestamp: i64, - ) -> Self { + pub fn new(key: u128, owner: [u8; 32], quantity: u64, order_id: u64, timestamp: i64) -> Self { Self { tag: NodeTag::LeafNode as u8, padding: [0; 7], diff --git a/finance/order-book/quasar/src/tests.rs b/finance/order-book/quasar/src/tests.rs index 97ba85ae8..45016c08d 100644 --- a/finance/order-book/quasar/src/tests.rs +++ b/finance/order-book/quasar/src/tests.rs @@ -6,7 +6,7 @@ use { crate::{ cpi::{ - CancelOrderInstruction, InitializeMarketUserInstruction, InitializeMarketInstruction, + CancelOrderInstruction, InitializeMarketInstruction, InitializeMarketUserInstruction, PlaceOrderInstruction, SettleFundsInstruction, WithdrawFeesInstruction, }, errors::OrderBookError, @@ -92,7 +92,8 @@ fn init_market(test: &mut Test) -> Pubkey { fn initialize_market_user(test: &mut Test, market: Pubkey, owner: Pubkey) -> Pubkey { test.add(Wallet::new().at(owner)); - test.send(InitializeMarketUserInstruction { owner, market }).succeeds(); + test.send(InitializeMarketUserInstruction { owner, market }) + .succeeds(); test.derive_pda(MarketUser::seeds(&market, &owner)) } @@ -173,7 +174,11 @@ fn initialize_market_stamps_market_and_order_book(test: &mut Test) { // point here (hand-rolled zero-copy slab): disc(8) then market(32), // bids_root(8), asks_root(8), next_order_id(8)... let order_book = test.account(ORDER_BOOK).unwrap(); - assert_eq!(&order_book.data[0..8], b"ORDRBOOK", "order-book discriminator"); + assert_eq!( + &order_book.data[0..8], + b"ORDRBOOK", + "order-book discriminator" + ); let next_order_id_offset = 8 + 32 + 8 + 8; let mut id_bytes = [0u8; 8]; id_bytes.copy_from_slice(&order_book.data[next_order_id_offset..next_order_id_offset + 8]); @@ -232,8 +237,19 @@ fn place_match_settle_withdraw_moves_tokens_and_fees(test: &mut Test) { let taker_order = test.derive_pda(Order::seeds(&market, 2)); // Maker ask (id 1) rests on the book. - place_order(test, market, MAKER, MAKER_BASE, MAKER_QUOTE, 1, PRICE, QUANTITY, 1, &[]) - .succeeds(); + place_order( + test, + market, + MAKER, + MAKER_BASE, + MAKER_QUOTE, + 1, + PRICE, + QUANTITY, + 1, + &[], + ) + .succeeds(); // Taker bid (id 2) crosses the maker ask; maker accounts supplied as // remaining accounts. place_order( @@ -270,7 +286,10 @@ fn place_match_settle_withdraw_moves_tokens_and_fees(test: &mut Test) { assert_eq!(u64::from(taker_state.filled_quantity), QUANTITY); // Maker's open-orders list emptied when its resting order fully filled. - assert_eq!(test.read::(maker_market_user).open_orders_len, 0); + assert_eq!( + test.read::(maker_market_user).open_orders_len, + 0 + ); let _ = taker_market_user; // Settlement moved tokens: maker received net quote, taker received base. @@ -305,8 +324,19 @@ fn cancel_order_credits_the_locked_base_back(test: &mut Test) { test.add(TokenAccount::new(QUOTE_MINT, MAKER).at(MAKER_QUOTE)); let maker_order = test.derive_pda(Order::seeds(&market, 1)); - place_order(test, market, MAKER, MAKER_BASE, MAKER_QUOTE, 1, PRICE, QUANTITY, 1, &[]) - .succeeds(); + place_order( + test, + market, + MAKER, + MAKER_BASE, + MAKER_QUOTE, + 1, + PRICE, + QUANTITY, + 1, + &[], + ) + .succeeds(); test.send(CancelOrderInstruction { market, diff --git a/finance/perpetual-futures/quasar/src/instructions/add_liquidity.rs b/finance/perpetual-futures/quasar/src/instructions/add_liquidity.rs index 741ae3ed4..9f4a9ff73 100644 --- a/finance/perpetual-futures/quasar/src/instructions/add_liquidity.rs +++ b/finance/perpetual-futures/quasar/src/instructions/add_liquidity.rs @@ -1,11 +1,11 @@ use { - quasar_lang::cpi::Seed, crate::{ constants::MINIMUM_LIQUIDITY, instructions::shared::{err, error, refresh_price_and_funding, traders_unrealized_pnl}, state::Pool, LpMintPda, PoolAuthorityPda, }, + quasar_lang::cpi::Seed, quasar_lang::{prelude::*, sysvars::clock::Clock}, quasar_spl::prelude::*, }; diff --git a/finance/perpetual-futures/quasar/src/instructions/close_position.rs b/finance/perpetual-futures/quasar/src/instructions/close_position.rs index 2d72b05d2..85106ddbc 100644 --- a/finance/perpetual-futures/quasar/src/instructions/close_position.rs +++ b/finance/perpetual-futures/quasar/src/instructions/close_position.rs @@ -1,5 +1,4 @@ use { - quasar_lang::cpi::Seed, crate::{ constants::SIDE_LONG, instructions::shared::{ @@ -8,6 +7,7 @@ use { state::{Pool, Position}, PoolAuthorityPda, }, + quasar_lang::cpi::Seed, quasar_lang::{prelude::*, sysvars::clock::Clock}, quasar_spl::prelude::*, }; diff --git a/finance/perpetual-futures/quasar/src/instructions/collect_fees.rs b/finance/perpetual-futures/quasar/src/instructions/collect_fees.rs index 60fcad469..a4eb264f5 100644 --- a/finance/perpetual-futures/quasar/src/instructions/collect_fees.rs +++ b/finance/perpetual-futures/quasar/src/instructions/collect_fees.rs @@ -1,10 +1,10 @@ use { - quasar_lang::cpi::Seed, crate::{ instructions::shared::{err, error}, state::Pool, PoolAuthorityPda, }, + quasar_lang::cpi::Seed, quasar_lang::prelude::*, quasar_spl::prelude::*, }; diff --git a/finance/perpetual-futures/quasar/src/instructions/liquidate_position.rs b/finance/perpetual-futures/quasar/src/instructions/liquidate_position.rs index 77856e148..c23b08dd1 100644 --- a/finance/perpetual-futures/quasar/src/instructions/liquidate_position.rs +++ b/finance/perpetual-futures/quasar/src/instructions/liquidate_position.rs @@ -1,5 +1,4 @@ use { - quasar_lang::cpi::Seed, crate::{ instructions::{ close_position::remove_open_interest, @@ -11,6 +10,7 @@ use { state::{Pool, Position}, PoolAuthorityPda, }, + quasar_lang::cpi::Seed, quasar_lang::{prelude::*, sysvars::clock::Clock}, quasar_spl::prelude::*, }; diff --git a/finance/perpetual-futures/quasar/src/instructions/open_position.rs b/finance/perpetual-futures/quasar/src/instructions/open_position.rs index 2c40adb25..d4f7ebe56 100644 --- a/finance/perpetual-futures/quasar/src/instructions/open_position.rs +++ b/finance/perpetual-futures/quasar/src/instructions/open_position.rs @@ -98,7 +98,7 @@ pub fn handle_open_position( .reserved_liquidity .get() .checked_add(size) - .ok_or_else(|| ProgramError::ArithmeticOverflow)?; + .ok_or(ProgramError::ArithmeticOverflow)?; if new_reserved > accounts.pool.liquidity.get() { return Err(err(error::INSUFFICIENT_LIQUIDITY)); } diff --git a/finance/perpetual-futures/quasar/src/instructions/remove_liquidity.rs b/finance/perpetual-futures/quasar/src/instructions/remove_liquidity.rs index 30428dfc4..60adeaaf6 100644 --- a/finance/perpetual-futures/quasar/src/instructions/remove_liquidity.rs +++ b/finance/perpetual-futures/quasar/src/instructions/remove_liquidity.rs @@ -1,10 +1,10 @@ use { - quasar_lang::cpi::Seed, crate::{ instructions::shared::{err, error, refresh_price_and_funding, traders_unrealized_pnl}, state::Pool, LpMintPda, PoolAuthorityPda, }, + quasar_lang::cpi::Seed, quasar_lang::{prelude::*, sysvars::clock::Clock}, quasar_spl::prelude::*, }; diff --git a/finance/perpetual-futures/quasar/src/lib.rs b/finance/perpetual-futures/quasar/src/lib.rs index 0a1015614..b3e59590a 100644 --- a/finance/perpetual-futures/quasar/src/lib.rs +++ b/finance/perpetual-futures/quasar/src/lib.rs @@ -8,7 +8,7 @@ use quasar_lang::prelude::*; mod constants; -mod instructions; +pub mod instructions; mod last_restart; pub mod state; #[cfg(test)] diff --git a/finance/perpetual-futures/quasar/src/tests.rs b/finance/perpetual-futures/quasar/src/tests.rs index 2eae4b6f0..9d7a5ccd2 100644 --- a/finance/perpetual-futures/quasar/src/tests.rs +++ b/finance/perpetual-futures/quasar/src/tests.rs @@ -420,7 +420,10 @@ fn set_funding_rate_settles_at_the_old_rate_first(test: &mut Test) { close_position(test, &env).succeeds(); let doubled = (before_doubled - test.tokens(TRADER_COLLATERAL)) - fees; - assert!(doubled > 0, "the doubled-rate window must charge some funding"); + assert!( + doubled > 0, + "the doubled-rate window must charge some funding" + ); assert_eq!( spanning * 2, doubled * 3, diff --git a/finance/prop-amm/quasar/src/instructions/swap.rs b/finance/prop-amm/quasar/src/instructions/swap.rs index aabf53daa..093515217 100644 --- a/finance/prop-amm/quasar/src/instructions/swap.rs +++ b/finance/prop-amm/quasar/src/instructions/swap.rs @@ -1,11 +1,11 @@ use { - quasar_lang::cpi::Seed, crate::{ constants::{DIRECTION_BUY_BASE, DIRECTION_SELL_BASE}, instructions::shared::{self, err, error}, state::Market, MarketAuthorityPda, }, + quasar_lang::cpi::Seed, quasar_lang::{prelude::*, sysvars::clock::Clock}, quasar_spl::prelude::*, }; diff --git a/finance/prop-amm/quasar/src/instructions/withdraw_inventory.rs b/finance/prop-amm/quasar/src/instructions/withdraw_inventory.rs index 038307a99..9e2cad442 100644 --- a/finance/prop-amm/quasar/src/instructions/withdraw_inventory.rs +++ b/finance/prop-amm/quasar/src/instructions/withdraw_inventory.rs @@ -1,10 +1,10 @@ use { - quasar_lang::cpi::Seed, crate::{ instructions::shared::{err, error}, state::Market, MarketAuthorityPda, }, + quasar_lang::cpi::Seed, quasar_lang::prelude::*, quasar_spl::prelude::*, }; diff --git a/finance/prop-amm/quasar/src/lib.rs b/finance/prop-amm/quasar/src/lib.rs index 548cfde68..a2858c15b 100644 --- a/finance/prop-amm/quasar/src/lib.rs +++ b/finance/prop-amm/quasar/src/lib.rs @@ -8,7 +8,7 @@ use quasar_lang::prelude::*; mod constants; -mod instructions; +pub mod instructions; mod last_restart; pub mod state; #[cfg(test)] @@ -72,11 +72,7 @@ mod quasar_prop_amm { } #[instruction(discriminator = 3)] - pub fn set_quote( - ctx: Ctx, - spread_bps: u16, - paused: u8, - ) -> Result<(), ProgramError> { + pub fn set_quote(ctx: Ctx, spread_bps: u16, paused: u8) -> Result<(), ProgramError> { instructions::handle_set_quote(&mut ctx.accounts, spread_bps, paused) } diff --git a/finance/prop-amm/quasar/src/tests.rs b/finance/prop-amm/quasar/src/tests.rs index 3dddac97c..b77ec7692 100644 --- a/finance/prop-amm/quasar/src/tests.rs +++ b/finance/prop-amm/quasar/src/tests.rs @@ -157,7 +157,15 @@ fn base_world(test: &mut Test, spread_bps: u16) -> (Env, Outcome) { ) } -fn deposit_inventory(test: &mut Test, env: &Env, signer: Pubkey, signer_base: Pubkey, signer_quote: Pubkey, base: u64, quote: u64) -> Outcome { +fn deposit_inventory( + test: &mut Test, + env: &Env, + signer: Pubkey, + signer_base: Pubkey, + signer_quote: Pubkey, + base: u64, + quote: u64, +) -> Outcome { test.send(DepositInventoryInstruction { operator: signer, base_mint: BASE_MINT, @@ -176,14 +184,38 @@ fn deposit_inventory(test: &mut Test, env: &Env, signer: Pubkey, signer_base: Pu fn setup(test: &mut Test) -> Env { let (env, outcome) = base_world(test, SPREAD_BPS); outcome.succeeds(); - deposit_inventory(test, &env, OPERATOR, OPERATOR_BASE, OPERATOR_QUOTE, 1_000 * ONE_TOKEN, 200_000 * ONE_TOKEN).succeeds(); + deposit_inventory( + test, + &env, + OPERATOR, + OPERATOR_BASE, + OPERATOR_QUOTE, + 1_000 * ONE_TOKEN, + 200_000 * ONE_TOKEN, + ) + .succeeds(); env } -fn fund_trader(test: &mut Test, wallet: Pubkey, base_account: Pubkey, quote_account: Pubkey, base: u64, quote: u64) { +fn fund_trader( + test: &mut Test, + wallet: Pubkey, + base_account: Pubkey, + quote_account: Pubkey, + base: u64, + quote: u64, +) { test.add(Wallet::new().at(wallet)); - test.add(TokenAccount::new(BASE_MINT, wallet).at(base_account).amount(base)); - test.add(TokenAccount::new(QUOTE_MINT, wallet).at(quote_account).amount(quote)); + test.add( + TokenAccount::new(BASE_MINT, wallet) + .at(base_account) + .amount(base), + ); + test.add( + TokenAccount::new(QUOTE_MINT, wallet) + .at(quote_account) + .amount(quote), + ); } fn set_quote(test: &mut Test, signer: Pubkey, spread_bps: u16, paused: u8) -> Outcome { @@ -196,7 +228,16 @@ fn set_quote(test: &mut Test, signer: Pubkey, spread_bps: u16, paused: u8) -> Ou }) } -fn swap(test: &mut Test, env: &Env, trader: Pubkey, trader_base: Pubkey, trader_quote: Pubkey, direction: u8, amount_in: u64, minimum_amount_out: u64) -> Outcome { +fn swap( + test: &mut Test, + env: &Env, + trader: Pubkey, + trader_base: Pubkey, + trader_quote: Pubkey, + direction: u8, + amount_in: u64, + minimum_amount_out: u64, +) -> Outcome { test.send(SwapInstruction { trader, oracle_feed: FEED, @@ -212,7 +253,15 @@ fn swap(test: &mut Test, env: &Env, trader: Pubkey, trader_base: Pubkey, trader_ }) } -fn withdraw_inventory(test: &mut Test, env: &Env, signer: Pubkey, signer_base: Pubkey, signer_quote: Pubkey, base: u64, quote: u64) -> Outcome { +fn withdraw_inventory( + test: &mut Test, + env: &Env, + signer: Pubkey, + signer_base: Pubkey, + signer_quote: Pubkey, + base: u64, + quote: u64, +) -> Outcome { test.send(WithdrawInventoryInstruction { operator: signer, base_mint: BASE_MINT, @@ -243,13 +292,22 @@ fn swap_buys_base_at_the_ask(test: &mut Test) { let quote_in = 825_825_000; fund_trader(test, TRADER, TRADER_BASE, TRADER_QUOTE, 0, quote_in); - swap(test, &env, TRADER, TRADER_BASE, TRADER_QUOTE, DIRECTION_BUY_BASE, quote_in, 5 * ONE_TOKEN) - .succeeds() - .has_tokens(TRADER_BASE, 5 * ONE_TOKEN) - .has_tokens(TRADER_QUOTE, 0) - // Conservation: the vaults moved by exactly the two legs of the fill. - .has_tokens(env.base_vault, 995 * ONE_TOKEN) - .has_tokens(env.quote_vault, 200_000 * ONE_TOKEN + quote_in); + swap( + test, + &env, + TRADER, + TRADER_BASE, + TRADER_QUOTE, + DIRECTION_BUY_BASE, + quote_in, + 5 * ONE_TOKEN, + ) + .succeeds() + .has_tokens(TRADER_BASE, 5 * ONE_TOKEN) + .has_tokens(TRADER_QUOTE, 0) + // Conservation: the vaults moved by exactly the two legs of the fill. + .has_tokens(env.base_vault, 995 * ONE_TOKEN) + .has_tokens(env.quote_vault, 200_000 * ONE_TOKEN + quote_in); } /// Bob sells 5 NVDAx. At $165 with a 10 bps spread the bid is $164.835, so @@ -259,10 +317,19 @@ fn swap_sells_base_at_the_bid(test: &mut Test) { let env = setup(test); fund_trader(test, TRADER, TRADER_BASE, TRADER_QUOTE, 5 * ONE_TOKEN, 0); - swap(test, &env, TRADER, TRADER_BASE, TRADER_QUOTE, DIRECTION_SELL_BASE, 5 * ONE_TOKEN, 824_175_000) - .succeeds() - .has_tokens(TRADER_BASE, 0) - .has_tokens(TRADER_QUOTE, 824_175_000); + swap( + test, + &env, + TRADER, + TRADER_BASE, + TRADER_QUOTE, + DIRECTION_SELL_BASE, + 5 * ONE_TOKEN, + 824_175_000, + ) + .succeeds() + .has_tokens(TRADER_BASE, 0) + .has_tokens(TRADER_QUOTE, 824_175_000); } /// A buy immediately followed by a sell of the same 5 NVDAx costs exactly @@ -273,12 +340,31 @@ fn round_trip_costs_exactly_the_spread(test: &mut Test) { let quote_in = 825_825_000; fund_trader(test, TRADER, TRADER_BASE, TRADER_QUOTE, 0, quote_in); - swap(test, &env, TRADER, TRADER_BASE, TRADER_QUOTE, DIRECTION_BUY_BASE, quote_in, 0).succeeds(); - swap(test, &env, TRADER, TRADER_BASE, TRADER_QUOTE, DIRECTION_SELL_BASE, 5 * ONE_TOKEN, 0) - .succeeds() - .has_tokens(TRADER_BASE, 0) - .has_tokens(TRADER_QUOTE, quote_in - 1_650_000) - .has_tokens(env.quote_vault, 200_000 * ONE_TOKEN + 1_650_000); + swap( + test, + &env, + TRADER, + TRADER_BASE, + TRADER_QUOTE, + DIRECTION_BUY_BASE, + quote_in, + 0, + ) + .succeeds(); + swap( + test, + &env, + TRADER, + TRADER_BASE, + TRADER_QUOTE, + DIRECTION_SELL_BASE, + 5 * ONE_TOKEN, + 0, + ) + .succeeds() + .has_tokens(TRADER_BASE, 0) + .has_tokens(TRADER_QUOTE, quote_in - 1_650_000) + .has_tokens(env.quote_vault, 200_000 * ONE_TOKEN + 1_650_000); } /// When the oracle reprices, the quote follows instantly. At $170 the ask is @@ -290,9 +376,18 @@ fn quote_follows_the_oracle(test: &mut Test) { let quote_in = 850_850_000; fund_trader(test, TRADER, TRADER_BASE, TRADER_QUOTE, 0, quote_in); - swap(test, &env, TRADER, TRADER_BASE, TRADER_QUOTE, DIRECTION_BUY_BASE, quote_in, 5 * ONE_TOKEN) - .succeeds() - .has_tokens(TRADER_BASE, 5 * ONE_TOKEN); + swap( + test, + &env, + TRADER, + TRADER_BASE, + TRADER_QUOTE, + DIRECTION_BUY_BASE, + quote_in, + 5 * ONE_TOKEN, + ) + .succeeds() + .has_tokens(TRADER_BASE, 5 * ONE_TOKEN); } /// The operator re-quotes to a 50 bps spread; the next fill prices at @@ -304,9 +399,18 @@ fn set_quote_changes_the_spread(test: &mut Test) { let quote_in = 829_125_000; fund_trader(test, TRADER, TRADER_BASE, TRADER_QUOTE, 0, quote_in); - swap(test, &env, TRADER, TRADER_BASE, TRADER_QUOTE, DIRECTION_BUY_BASE, quote_in, 5 * ONE_TOKEN) - .succeeds() - .has_tokens(TRADER_BASE, 5 * ONE_TOKEN); + swap( + test, + &env, + TRADER, + TRADER_BASE, + TRADER_QUOTE, + DIRECTION_BUY_BASE, + quote_in, + 5 * ONE_TOKEN, + ) + .succeeds() + .has_tokens(TRADER_BASE, 5 * ONE_TOKEN); } /// The operator can withdraw every token in both vaults at any time — its @@ -314,14 +418,32 @@ fn set_quote_changes_the_spread(test: &mut Test) { #[quasar_test] fn operator_can_withdraw_everything_and_swaps_then_fail(test: &mut Test) { let env = setup(test); - withdraw_inventory(test, &env, OPERATOR, OPERATOR_BASE, OPERATOR_QUOTE, 1_000 * ONE_TOKEN, 200_000 * ONE_TOKEN) - .succeeds() - .has_tokens(env.base_vault, 0) - .has_tokens(env.quote_vault, 0); + withdraw_inventory( + test, + &env, + OPERATOR, + OPERATOR_BASE, + OPERATOR_QUOTE, + 1_000 * ONE_TOKEN, + 200_000 * ONE_TOKEN, + ) + .succeeds() + .has_tokens(env.base_vault, 0) + .has_tokens(env.quote_vault, 0); fund_trader(test, TRADER, TRADER_BASE, TRADER_QUOTE, 0, 825_825_000); assert!( - swap(test, &env, TRADER, TRADER_BASE, TRADER_QUOTE, DIRECTION_BUY_BASE, 825_825_000, 0).is_err(), + swap( + test, + &env, + TRADER, + TRADER_BASE, + TRADER_QUOTE, + DIRECTION_BUY_BASE, + 825_825_000, + 0 + ) + .is_err(), "a swap against an empty inventory must fail" ); } @@ -330,8 +452,16 @@ fn operator_can_withdraw_everything_and_swaps_then_fail(test: &mut Test) { fn withdraw_more_than_inventory_fails(test: &mut Test) { let env = setup(test); assert!( - withdraw_inventory(test, &env, OPERATOR, OPERATOR_BASE, OPERATOR_QUOTE, 1_001 * ONE_TOKEN, 0) - .is_err(), + withdraw_inventory( + test, + &env, + OPERATOR, + OPERATOR_BASE, + OPERATOR_QUOTE, + 1_001 * ONE_TOKEN, + 0 + ) + .is_err(), "withdrawing more than the vault holds must fail" ); } @@ -339,9 +469,25 @@ fn withdraw_more_than_inventory_fails(test: &mut Test) { #[quasar_test] fn deposit_rejects_non_operator(test: &mut Test) { let env = setup(test); - fund_trader(test, MALLORY, MALLORY_BASE, MALLORY_QUOTE, ONE_TOKEN, ONE_TOKEN); + fund_trader( + test, + MALLORY, + MALLORY_BASE, + MALLORY_QUOTE, + ONE_TOKEN, + ONE_TOKEN, + ); assert!( - deposit_inventory(test, &env, MALLORY, MALLORY_BASE, MALLORY_QUOTE, ONE_TOKEN, 0).is_err(), + deposit_inventory( + test, + &env, + MALLORY, + MALLORY_BASE, + MALLORY_QUOTE, + ONE_TOKEN, + 0 + ) + .is_err(), "deposit_inventory must reject a non-operator signer" ); } @@ -351,7 +497,16 @@ fn withdraw_rejects_non_operator(test: &mut Test) { let env = setup(test); fund_trader(test, MALLORY, MALLORY_BASE, MALLORY_QUOTE, 0, 0); assert!( - withdraw_inventory(test, &env, MALLORY, MALLORY_BASE, MALLORY_QUOTE, ONE_TOKEN, 0).is_err(), + withdraw_inventory( + test, + &env, + MALLORY, + MALLORY_BASE, + MALLORY_QUOTE, + ONE_TOKEN, + 0 + ) + .is_err(), "withdraw_inventory must reject a non-operator signer" ); } @@ -374,8 +529,17 @@ fn swap_rejects_slippage(test: &mut Test) { fund_trader(test, TRADER, TRADER_BASE, TRADER_QUOTE, 0, quote_in); // The fill would be exactly 5 NVDAx; demand one minor unit more. assert!( - swap(test, &env, TRADER, TRADER_BASE, TRADER_QUOTE, DIRECTION_BUY_BASE, quote_in, 5 * ONE_TOKEN + 1) - .is_err(), + swap( + test, + &env, + TRADER, + TRADER_BASE, + TRADER_QUOTE, + DIRECTION_BUY_BASE, + quote_in, + 5 * ONE_TOKEN + 1 + ) + .is_err(), "a fill below minimum_amount_out must be rejected" ); } @@ -388,7 +552,17 @@ fn swap_rejects_stale_price(test: &mut Test) { make_price_stale(test); fund_trader(test, TRADER, TRADER_BASE, TRADER_QUOTE, 0, 825_825_000); assert!( - swap(test, &env, TRADER, TRADER_BASE, TRADER_QUOTE, DIRECTION_BUY_BASE, 825_825_000, 0).is_err(), + swap( + test, + &env, + TRADER, + TRADER_BASE, + TRADER_QUOTE, + DIRECTION_BUY_BASE, + 825_825_000, + 0 + ) + .is_err(), "a stale oracle price must be rejected" ); } @@ -407,14 +581,33 @@ fn swap_rejects_price_from_before_a_restart(test: &mut Test) { set_feed_at_slot(test, dollars(165), SLOT - 5, 0); set_last_restart_slot(test, SLOT - 3); assert!( - swap(test, &env, TRADER, TRADER_BASE, TRADER_QUOTE, DIRECTION_BUY_BASE, 825_825_000, 0).is_err(), + swap( + test, + &env, + TRADER, + TRADER_BASE, + TRADER_QUOTE, + DIRECTION_BUY_BASE, + 825_825_000, + 0 + ) + .is_err(), "a pre-restart price must be rejected even inside the staleness bound" ); // Publishing after the restart (at `SLOT`) reopens the market. set_feed(test, dollars(165), 0); - swap(test, &env, TRADER, TRADER_BASE, TRADER_QUOTE, DIRECTION_BUY_BASE, 825_825_000, 0) - .succeeds(); + swap( + test, + &env, + TRADER, + TRADER_BASE, + TRADER_QUOTE, + DIRECTION_BUY_BASE, + 825_825_000, + 0, + ) + .succeeds(); } /// A price the oracle itself is unsure about is rejected: the confidence band @@ -425,7 +618,17 @@ fn swap_rejects_wide_confidence(test: &mut Test) { set_feed(test, dollars(165), 200_000_000); fund_trader(test, TRADER, TRADER_BASE, TRADER_QUOTE, 0, 825_825_000); assert!( - swap(test, &env, TRADER, TRADER_BASE, TRADER_QUOTE, DIRECTION_BUY_BASE, 825_825_000, 0).is_err(), + swap( + test, + &env, + TRADER, + TRADER_BASE, + TRADER_QUOTE, + DIRECTION_BUY_BASE, + 825_825_000, + 0 + ) + .is_err(), "a confidence band wider than max_confidence_bps must be rejected" ); } @@ -439,13 +642,32 @@ fn swap_rejects_when_paused(test: &mut Test) { fund_trader(test, TRADER, TRADER_BASE, TRADER_QUOTE, 0, 825_825_000); assert!( - swap(test, &env, TRADER, TRADER_BASE, TRADER_QUOTE, DIRECTION_BUY_BASE, 825_825_000, 0).is_err(), + swap( + test, + &env, + TRADER, + TRADER_BASE, + TRADER_QUOTE, + DIRECTION_BUY_BASE, + 825_825_000, + 0 + ) + .is_err(), "a paused market must reject swaps" ); set_quote(test, OPERATOR, SPREAD_BPS, 0).succeeds(); - swap(test, &env, TRADER, TRADER_BASE, TRADER_QUOTE, DIRECTION_BUY_BASE, 825_825_000, 5 * ONE_TOKEN) - .succeeds(); + swap( + test, + &env, + TRADER, + TRADER_BASE, + TRADER_QUOTE, + DIRECTION_BUY_BASE, + 825_825_000, + 5 * ONE_TOKEN, + ) + .succeeds(); } #[quasar_test] @@ -453,7 +675,17 @@ fn swap_rejects_zero_amount(test: &mut Test) { let env = setup(test); fund_trader(test, TRADER, TRADER_BASE, TRADER_QUOTE, 0, ONE_TOKEN); assert!( - swap(test, &env, TRADER, TRADER_BASE, TRADER_QUOTE, DIRECTION_BUY_BASE, 0, 0).is_err(), + swap( + test, + &env, + TRADER, + TRADER_BASE, + TRADER_QUOTE, + DIRECTION_BUY_BASE, + 0, + 0 + ) + .is_err(), "a zero-amount swap must be rejected" ); } @@ -468,7 +700,17 @@ fn swap_rejects_insufficient_inventory(test: &mut Test) { let quote_in = 181_681_500_000; fund_trader(test, TRADER, TRADER_BASE, TRADER_QUOTE, 0, quote_in); assert!( - swap(test, &env, TRADER, TRADER_BASE, TRADER_QUOTE, DIRECTION_BUY_BASE, quote_in, 0).is_err(), + swap( + test, + &env, + TRADER, + TRADER_BASE, + TRADER_QUOTE, + DIRECTION_BUY_BASE, + quote_in, + 0 + ) + .is_err(), "a swap larger than the inventory must be rejected" ); } diff --git a/finance/token-fundraiser/quasar/Cargo.toml b/finance/token-fundraiser/quasar/Cargo.toml index f54e5d2b7..15f29e472 100644 --- a/finance/token-fundraiser/quasar/Cargo.toml +++ b/finance/token-fundraiser/quasar/Cargo.toml @@ -29,6 +29,11 @@ idl-build = ["quasar-lang/idl-build"] # git. Keep this rev in lockstep with the CLI rev installed by # .github/workflows/quasar.yml. quasar-lang = { git = "https://github.com/blueshift-gg/quasar", rev = "be60fca" } +# Pinned: quasar-lang asks for zeropod "0.3.3" and wincode 0.4, but zeropod +# 0.3.4 moved to wincode 0.5, so a fresh resolve (no lockfile is committed) +# splits the graph across two wincode versions and every Pod* trait bound +# fails. Unpin when quasar-lang's pinned rev accepts zeropod 0.3.4+. +zeropod = "=0.3.3" quasar-spl = { git = "https://github.com/blueshift-gg/quasar", rev = "be60fca" } solana-instruction = { version = "3.2.0" } diff --git a/finance/token-fundraiser/quasar/src/instructions/check_contributions.rs b/finance/token-fundraiser/quasar/src/instructions/check_contributions.rs index a9ebfe435..deee38dcc 100644 --- a/finance/token-fundraiser/quasar/src/instructions/check_contributions.rs +++ b/finance/token-fundraiser/quasar/src/instructions/check_contributions.rs @@ -68,7 +68,10 @@ pub fn handle_check_contributions( .invoke_signed(&seeds)?; // Token conservation: the vault was fully drained. - require!(accounts.vault.amount() == 0, FundraiserError::BalanceMismatch); + require!( + accounts.vault.amount() == 0, + FundraiserError::BalanceMismatch + ); // Close the vault token account, returning its rent to the maker. accounts diff --git a/finance/token-fundraiser/quasar/src/instructions/refund.rs b/finance/token-fundraiser/quasar/src/instructions/refund.rs index 29ec78f09..465fa4b17 100644 --- a/finance/token-fundraiser/quasar/src/instructions/refund.rs +++ b/finance/token-fundraiser/quasar/src/instructions/refund.rs @@ -45,7 +45,10 @@ pub struct RefundAccountConstraints { } #[inline(always)] -pub fn handle_refund(accounts: &mut RefundAccountConstraints, bumps: &RefundAccountConstraintsBumps) -> Result<(), ProgramError> { +pub fn handle_refund( + accounts: &mut RefundAccountConstraints, + bumps: &RefundAccountConstraintsBumps, +) -> Result<(), ProgramError> { // Refunds are allowed only after the deadline (now >= start + duration). let now: i64 = Clock::get()?.unix_timestamp.into(); let deadline = fundraiser_deadline( diff --git a/finance/token-fundraiser/quasar/src/lib.rs b/finance/token-fundraiser/quasar/src/lib.rs index 87862e5cb..40e027967 100644 --- a/finance/token-fundraiser/quasar/src/lib.rs +++ b/finance/token-fundraiser/quasar/src/lib.rs @@ -3,9 +3,9 @@ use quasar_lang::prelude::*; mod error; -mod instructions; +pub mod instructions; use instructions::*; -mod state; +pub mod state; #[cfg(test)] mod tests; @@ -25,19 +25,29 @@ mod quasar_token_fundraiser { amount_to_raise: u64, duration: u16, ) -> Result<(), ProgramError> { - instructions::handle_initialize_fundraiser(&mut ctx.accounts, amount_to_raise, duration, ctx.bumps.fundraiser) + instructions::handle_initialize_fundraiser( + &mut ctx.accounts, + amount_to_raise, + duration, + ctx.bumps.fundraiser, + ) } /// Contribute tokens to the fundraiser while its window is open. Creates /// the contributor's tracking account on first contribution. #[instruction(discriminator = 1)] - pub fn contribute(ctx: Ctx, amount: u64) -> Result<(), ProgramError> { + pub fn contribute( + ctx: Ctx, + amount: u64, + ) -> Result<(), ProgramError> { instructions::handle_contribute(&mut ctx.accounts, amount, &ctx.bumps) } /// Maker withdraws all funds once the target is met. #[instruction(discriminator = 2)] - pub fn check_contributions(ctx: Ctx) -> Result<(), ProgramError> { + pub fn check_contributions( + ctx: Ctx, + ) -> Result<(), ProgramError> { instructions::handle_check_contributions(&mut ctx.accounts, &ctx.bumps) } diff --git a/finance/token-fundraiser/quasar/src/tests.rs b/finance/token-fundraiser/quasar/src/tests.rs index f9ca64be4..4d85db60b 100644 --- a/finance/token-fundraiser/quasar/src/tests.rs +++ b/finance/token-fundraiser/quasar/src/tests.rs @@ -47,12 +47,7 @@ fn framework_error(error: QuasarError) -> ProgramError { /// Register the maker, the mint, and warp to the fixed start time. fn base_world(test: &mut Test) { test.add(Wallet::new().at(MAKER)); - test.add( - Mint::new(MAKER) - .at(MINT) - .supply(1_000_000_000) - .decimals(9), - ); + test.add(Mint::new(MAKER).at(MINT).supply(1_000_000_000).decimals(9)); test.warp_to_timestamp(START_TIME); } @@ -153,7 +148,10 @@ fn contribute_creates_contributor_account_and_moves_tokens(test: &mut Test) { ); let fundraiser_state = test.read::(fundraiser); - assert_eq!(u64::from(fundraiser_state.current_amount), PARTIAL_CONTRIBUTION); + assert_eq!( + u64::from(fundraiser_state.current_amount), + PARTIAL_CONTRIBUTION + ); let (contributor_account, expected_bump) = test.derive_pda_with_bump(Contributor::seeds(&fundraiser, &CONTRIBUTOR)); @@ -240,7 +238,10 @@ fn refund_returns_tokens_after_failed_fundraiser(test: &mut Test) { // The contributor account was closed and its rent returned. .is_closed(contributor_account); - assert_eq!(u64::from(test.read::(fundraiser).current_amount), 0); + assert_eq!( + u64::from(test.read::(fundraiser).current_amount), + 0 + ); } #[quasar_test] diff --git a/finance/token-swap/quasar/Cargo.toml b/finance/token-swap/quasar/Cargo.toml index ee56fadf7..d9b047a2f 100644 --- a/finance/token-swap/quasar/Cargo.toml +++ b/finance/token-swap/quasar/Cargo.toml @@ -30,6 +30,11 @@ idl-build = ["quasar-lang/idl-build"] # git. Keep this rev in lockstep with the CLI rev installed by # .github/workflows/quasar.yml. quasar-lang = { git = "https://github.com/blueshift-gg/quasar", rev = "be60fca" } +# Pinned: quasar-lang asks for zeropod "0.3.3" and wincode 0.4, but zeropod +# 0.3.4 moved to wincode 0.5, so a fresh resolve (no lockfile is committed) +# splits the graph across two wincode versions and every Pod* trait bound +# fails. Unpin when quasar-lang's pinned rev accepts zeropod 0.3.4+. +zeropod = "=0.3.3" quasar-spl = { git = "https://github.com/blueshift-gg/quasar", rev = "be60fca" } solana-instruction = { version = "3.2.0" } diff --git a/finance/token-swap/quasar/src/instructions/claim_admin_fees.rs b/finance/token-swap/quasar/src/instructions/claim_admin_fees.rs index a7ac07825..6310071c7 100644 --- a/finance/token-swap/quasar/src/instructions/claim_admin_fees.rs +++ b/finance/token-swap/quasar/src/instructions/claim_admin_fees.rs @@ -1,10 +1,10 @@ use { - quasar_lang::cpi::Seed, crate::{ error::AmmError, state::{Config, PoolConfig, PoolConfigInner}, ConfigPda, PoolAuthorityPda, PoolPda, }, + quasar_lang::cpi::Seed, quasar_lang::prelude::*, quasar_spl::prelude::*, }; diff --git a/finance/token-swap/quasar/src/instructions/deposit_liquidity.rs b/finance/token-swap/quasar/src/instructions/deposit_liquidity.rs index f218249dd..58ce39210 100644 --- a/finance/token-swap/quasar/src/instructions/deposit_liquidity.rs +++ b/finance/token-swap/quasar/src/instructions/deposit_liquidity.rs @@ -1,10 +1,10 @@ use { - quasar_lang::cpi::Seed, crate::{ error::AmmError, state::{Config, PoolConfig}, ConfigPda, LiquidityMintPda, PoolAuthorityPda, PoolPda, }, + quasar_lang::cpi::Seed, quasar_lang::prelude::*, quasar_spl::prelude::*, }; @@ -67,7 +67,7 @@ fn isqrt(n: u128) -> u128 { return 0; } let mut x = n; - let mut y = (x + 1) / 2; + let mut y = x.div_ceil(2); while y < x { x = y; y = (x + n / x) / 2; @@ -213,13 +213,29 @@ pub fn handle_deposit_liquidity( ); // Transfer token A to the pool. - accounts.token_program - .transfer_checked(&accounts.token_a, &accounts.mint_a, &accounts.pool_a, &accounts.depositor, amount_a, accounts.mint_a.decimals()) + accounts + .token_program + .transfer_checked( + &accounts.token_a, + &accounts.mint_a, + &accounts.pool_a, + &accounts.depositor, + amount_a, + accounts.mint_a.decimals(), + ) .invoke()?; // Transfer token B to the pool. - accounts.token_program - .transfer_checked(&accounts.token_b, &accounts.mint_b, &accounts.pool_b, &accounts.depositor, amount_b, accounts.mint_b.decimals()) + accounts + .token_program + .transfer_checked( + &accounts.token_b, + &accounts.mint_b, + &accounts.pool_b, + &accounts.depositor, + amount_b, + accounts.mint_b.decimals(), + ) .invoke()?; // Mint LP tokens to the depositor (signed by pool authority). @@ -233,7 +249,8 @@ pub fn handle_deposit_liquidity( Seed::from(&bump as &[u8]), ]; - accounts.token_program + accounts + .token_program .mint_to( &accounts.liquidity_provider_mint, &accounts.liquidity_provider_token, diff --git a/finance/token-swap/quasar/src/instructions/initialize_config.rs b/finance/token-swap/quasar/src/instructions/initialize_config.rs index 2b7dc768b..7dae94729 100644 --- a/finance/token-swap/quasar/src/instructions/initialize_config.rs +++ b/finance/token-swap/quasar/src/instructions/initialize_config.rs @@ -1,5 +1,9 @@ use { - crate::{error::AmmError, state::{Config, ConfigInner}, ConfigPda, BASIS_POINTS_DIVISOR}, + crate::{ + error::AmmError, + state::{Config, ConfigInner}, + ConfigPda, BASIS_POINTS_DIVISOR, + }, quasar_lang::prelude::*, }; @@ -33,8 +37,8 @@ pub fn handle_initialize_config( ); accounts.config.set_inner(ConfigInner { admin: *accounts.admin.address(), - fee: fee.into(), - admin_share_bps: admin_share_bps.into(), + fee, + admin_share_bps, }); Ok(()) } diff --git a/finance/token-swap/quasar/src/instructions/initialize_pool.rs b/finance/token-swap/quasar/src/instructions/initialize_pool.rs index 41f495b8a..fad526664 100644 --- a/finance/token-swap/quasar/src/instructions/initialize_pool.rs +++ b/finance/token-swap/quasar/src/instructions/initialize_pool.rs @@ -66,7 +66,9 @@ pub struct InitializePoolAccountConstraints { } #[inline(always)] -pub fn handle_initialize_pool(accounts: &mut InitializePoolAccountConstraints) -> Result<(), ProgramError> { +pub fn handle_initialize_pool( + accounts: &mut InitializePoolAccountConstraints, +) -> Result<(), ProgramError> { accounts.pool_config.set_inner(PoolConfigInner { config: *accounts.config.address(), mint_a: *accounts.mint_a.address(), diff --git a/finance/token-swap/quasar/src/instructions/mod.rs b/finance/token-swap/quasar/src/instructions/mod.rs index 8e143cedb..fa7c611a1 100644 --- a/finance/token-swap/quasar/src/instructions/mod.rs +++ b/finance/token-swap/quasar/src/instructions/mod.rs @@ -1,13 +1,13 @@ mod claim_admin_fees; +mod deposit_liquidity; mod initialize_config; mod initialize_pool; -mod deposit_liquidity; mod swap_tokens; mod withdraw_liquidity; pub use claim_admin_fees::*; +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/quasar/src/instructions/swap_tokens.rs b/finance/token-swap/quasar/src/instructions/swap_tokens.rs index 2804fb28d..a718627d7 100644 --- a/finance/token-swap/quasar/src/instructions/swap_tokens.rs +++ b/finance/token-swap/quasar/src/instructions/swap_tokens.rs @@ -1,10 +1,10 @@ use { - quasar_lang::cpi::Seed, crate::{ error::AmmError, state::{Config, PoolConfig, PoolConfigInner}, ConfigPda, PoolAuthorityPda, PoolPda, BASIS_POINTS_DIVISOR, }, + quasar_lang::cpi::Seed, quasar_lang::prelude::*, quasar_spl::prelude::*, }; @@ -157,13 +157,17 @@ pub fn handle_swap_tokens( // transfer. let (new_owed_a, new_owed_b) = if input_is_token_a { ( - owed_a.checked_add(admin_portion).ok_or(AmmError::MathOverflow)?, + owed_a + .checked_add(admin_portion) + .ok_or(AmmError::MathOverflow)?, owed_b, ) } else { ( owed_a, - owed_b.checked_add(admin_portion).ok_or(AmmError::MathOverflow)?, + owed_b + .checked_add(admin_portion) + .ok_or(AmmError::MathOverflow)?, ) }; let config_addr = *accounts.pool_config.config(); @@ -190,21 +194,53 @@ pub fn handle_swap_tokens( if input_is_token_a { // Trader sends token A to pool. - accounts.token_program - .transfer_checked(&accounts.token_a, &accounts.mint_a, &accounts.pool_a, &accounts.trader, input, accounts.mint_a.decimals()) + accounts + .token_program + .transfer_checked( + &accounts.token_a, + &accounts.mint_a, + &accounts.pool_a, + &accounts.trader, + input, + accounts.mint_a.decimals(), + ) .invoke()?; // Pool sends token B to trader (signed). - accounts.token_program - .transfer_checked(&accounts.pool_b, &accounts.mint_b, &accounts.token_b, &accounts.pool_authority, output, accounts.mint_b.decimals()) + accounts + .token_program + .transfer_checked( + &accounts.pool_b, + &accounts.mint_b, + &accounts.token_b, + &accounts.pool_authority, + output, + accounts.mint_b.decimals(), + ) .invoke_signed(seeds)?; } else { // Pool sends token A to trader (signed). - accounts.token_program - .transfer_checked(&accounts.pool_a, &accounts.mint_a, &accounts.token_a, &accounts.pool_authority, output, accounts.mint_a.decimals()) + accounts + .token_program + .transfer_checked( + &accounts.pool_a, + &accounts.mint_a, + &accounts.token_a, + &accounts.pool_authority, + output, + accounts.mint_a.decimals(), + ) .invoke_signed(seeds)?; // Trader sends token B to pool. - accounts.token_program - .transfer_checked(&accounts.token_b, &accounts.mint_b, &accounts.pool_b, &accounts.trader, input, accounts.mint_b.decimals()) + accounts + .token_program + .transfer_checked( + &accounts.token_b, + &accounts.mint_b, + &accounts.pool_b, + &accounts.trader, + input, + accounts.mint_b.decimals(), + ) .invoke()?; } diff --git a/finance/token-swap/quasar/src/instructions/withdraw_liquidity.rs b/finance/token-swap/quasar/src/instructions/withdraw_liquidity.rs index 4c34e15a3..1358acdbd 100644 --- a/finance/token-swap/quasar/src/instructions/withdraw_liquidity.rs +++ b/finance/token-swap/quasar/src/instructions/withdraw_liquidity.rs @@ -1,10 +1,10 @@ use { - quasar_lang::cpi::Seed, crate::{ error::AmmError, state::{Config, PoolConfig}, ConfigPda, LiquidityMintPda, PoolAuthorityPda, PoolPda, }, + quasar_lang::cpi::Seed, quasar_lang::prelude::*, quasar_spl::prelude::*, }; @@ -127,18 +127,40 @@ pub fn handle_withdraw_liquidity( ); // Transfer token A from pool to depositor. - accounts.token_program - .transfer_checked(&accounts.pool_a, &accounts.mint_a, &accounts.token_a, &accounts.pool_authority, amount_a, accounts.mint_a.decimals()) + accounts + .token_program + .transfer_checked( + &accounts.pool_a, + &accounts.mint_a, + &accounts.token_a, + &accounts.pool_authority, + amount_a, + accounts.mint_a.decimals(), + ) .invoke_signed(seeds)?; // Transfer token B from pool to depositor. - accounts.token_program - .transfer_checked(&accounts.pool_b, &accounts.mint_b, &accounts.token_b, &accounts.pool_authority, amount_b, accounts.mint_b.decimals()) + accounts + .token_program + .transfer_checked( + &accounts.pool_b, + &accounts.mint_b, + &accounts.token_b, + &accounts.pool_authority, + amount_b, + accounts.mint_b.decimals(), + ) .invoke_signed(seeds)?; // Burn LP tokens. - accounts.token_program - .burn(&accounts.liquidity_provider_token, &accounts.liquidity_provider_mint, &accounts.depositor, amount) + accounts + .token_program + .burn( + &accounts.liquidity_provider_token, + &accounts.liquidity_provider_mint, + &accounts.depositor, + amount, + ) .invoke()?; Ok(()) diff --git a/finance/token-swap/quasar/src/lib.rs b/finance/token-swap/quasar/src/lib.rs index 4760b2ed3..9435d6bab 100644 --- a/finance/token-swap/quasar/src/lib.rs +++ b/finance/token-swap/quasar/src/lib.rs @@ -3,7 +3,7 @@ use quasar_lang::prelude::*; pub mod error; -mod instructions; +pub mod instructions; use instructions::*; pub mod state; #[cfg(test)] diff --git a/finance/token-swap/quasar/src/tests.rs b/finance/token-swap/quasar/src/tests.rs index bf53e3396..8d0661fe1 100644 --- a/finance/token-swap/quasar/src/tests.rs +++ b/finance/token-swap/quasar/src/tests.rs @@ -5,8 +5,8 @@ use { crate::{ cpi::{ - ClaimAdminFeesInstruction, InitializeConfigInstruction, InitializePoolInstruction, - DepositLiquidityInstruction, SwapTokensInstruction, WithdrawLiquidityInstruction, + ClaimAdminFeesInstruction, DepositLiquidityInstruction, InitializeConfigInstruction, + InitializePoolInstruction, SwapTokensInstruction, WithdrawLiquidityInstruction, }, error::AmmError, state::Config, @@ -117,10 +117,25 @@ fn setup_pool(test: &mut Test) -> PoolEnv { } /// Fund a depositor wallet with token A/B accounts holding the given amounts. -fn fund(test: &mut Test, wallet: Pubkey, token_a: Pubkey, token_b: Pubkey, amount_a: u64, amount_b: u64) { +fn fund( + test: &mut Test, + wallet: Pubkey, + token_a: Pubkey, + token_b: Pubkey, + amount_a: u64, + amount_b: u64, +) { test.add(Wallet::new().at(wallet)); - test.add(TokenAccount::new(MINT_A, wallet).at(token_a).amount(amount_a)); - test.add(TokenAccount::new(MINT_B, wallet).at(token_b).amount(amount_b)); + test.add( + TokenAccount::new(MINT_A, wallet) + .at(token_a) + .amount(amount_a), + ); + test.add( + TokenAccount::new(MINT_B, wallet) + .at(token_b) + .amount(amount_b), + ); } #[allow(clippy::too_many_arguments)] @@ -153,9 +168,25 @@ fn deposit( /// Fund the seeding depositor and deposit `amount_a` / `amount_b`, with no LP /// floor (pool-setup helper, not a slippage test). Returns the LP balance. fn seed_pool(test: &mut Test, amount_a: u64, amount_b: u64) -> u64 { - fund(test, SEEDER, SEEDER_TOKEN_A, SEEDER_TOKEN_B, amount_a, amount_b); - deposit(test, SEEDER, SEEDER_TOKEN_A, SEEDER_TOKEN_B, SEEDER_LP, amount_a, amount_b, 0) - .succeeds(); + fund( + test, + SEEDER, + SEEDER_TOKEN_A, + SEEDER_TOKEN_B, + amount_a, + amount_b, + ); + deposit( + test, + SEEDER, + SEEDER_TOKEN_A, + SEEDER_TOKEN_B, + SEEDER_LP, + amount_a, + amount_b, + 0, + ) + .succeeds(); test.tokens(SEEDER_LP) } @@ -183,7 +214,12 @@ fn swap( }) } -fn claim_fees(test: &mut Test, admin: Pubkey, admin_token_a: Pubkey, admin_token_b: Pubkey) -> Outcome { +fn claim_fees( + test: &mut Test, + admin: Pubkey, + admin_token_a: Pubkey, + admin_token_b: Pubkey, +) -> Outcome { test.send(ClaimAdminFeesInstruction { mint_a: MINT_A, mint_b: MINT_B, @@ -234,7 +270,9 @@ fn initialize_config_rejects_invalid_admin_share(test: &mut Test) { fn initialize_pool_creates_pool_config_and_lp_mint(test: &mut Test) { let env = setup_pool(test); // The pool_config PDA must now exist and be owned by our program. - let pc = test.account(env.pool_config).expect("pool_config missing after initialize_pool"); + let pc = test + .account(env.pool_config) + .expect("pool_config missing after initialize_pool"); assert_eq!(pc.owner, test.program_id()); // LP mint PDA must be a valid SPL mint (82 bytes, owned by token program). let lp = test.account(env.lp_mint).expect("lp_mint missing"); @@ -266,10 +304,23 @@ fn deposit_liquidity_subsequent_proportional(test: &mut Test) { let lp1_bal = seed_pool(test, 1_000_000, 4_000_000); // Second depositor with the same 1:4 ratio gets proportional LP tokens. - fund(test, DEPOSITOR, DEPOSITOR_TOKEN_A, DEPOSITOR_TOKEN_B, 500_000, 2_000_000); + fund( + test, + DEPOSITOR, + DEPOSITOR_TOKEN_A, + DEPOSITOR_TOKEN_B, + 500_000, + 2_000_000, + ); deposit( - test, DEPOSITOR, DEPOSITOR_TOKEN_A, DEPOSITOR_TOKEN_B, DEPOSITOR_LP, - 500_000, 2_000_000, 0, + test, + DEPOSITOR, + DEPOSITOR_TOKEN_A, + DEPOSITOR_TOKEN_B, + DEPOSITOR_LP, + 500_000, + 2_000_000, + 0, ) .succeeds(); let lp2_bal = test.tokens(DEPOSITOR_LP); @@ -288,10 +339,23 @@ fn deposit_insufficient_funds_rejected(test: &mut Test) { setup_pool(test); // Fund with only 100 of each but request 1_000_000. - fund(test, DEPOSITOR, DEPOSITOR_TOKEN_A, DEPOSITOR_TOKEN_B, 100, 100); + fund( + test, + DEPOSITOR, + DEPOSITOR_TOKEN_A, + DEPOSITOR_TOKEN_B, + 100, + 100, + ); deposit( - test, DEPOSITOR, DEPOSITOR_TOKEN_A, DEPOSITOR_TOKEN_B, DEPOSITOR_LP, - 1_000_000, 1_000_000, 0, + test, + DEPOSITOR, + DEPOSITOR_TOKEN_A, + DEPOSITOR_TOKEN_B, + DEPOSITOR_LP, + 1_000_000, + 1_000_000, + 0, ) .fails_with(AmmError::InsufficientBalance); } @@ -313,14 +377,27 @@ fn deposit_clamps_down_never_up(test: &mut Test) { // old logic would try to pull 4_000_000 token A (scaling A UP); the // correct clamp uses all 1_000_000 A and scales B down to 250_000. let (stated_a, stated_b) = (1_000_000u64, 1_000_000u64); - fund(test, DEPOSITOR, DEPOSITOR_TOKEN_A, DEPOSITOR_TOKEN_B, stated_a, stated_b); + fund( + test, + DEPOSITOR, + DEPOSITOR_TOKEN_A, + DEPOSITOR_TOKEN_B, + stated_a, + stated_b, + ); let expected_b_pulled = mul_div(stated_a, pool_seed_b, pool_seed_a); let expected_lp = mul_div(stated_a, lp_supply, pool_seed_a); deposit( - test, DEPOSITOR, DEPOSITOR_TOKEN_A, DEPOSITOR_TOKEN_B, DEPOSITOR_LP, - stated_a, stated_b, expected_lp, + test, + DEPOSITOR, + DEPOSITOR_TOKEN_A, + DEPOSITOR_TOKEN_B, + DEPOSITOR_LP, + stated_a, + stated_b, + expected_lp, ) .succeeds() // Exact amounts pulled: all of A, ratio-clamped B, nothing more. @@ -344,7 +421,14 @@ fn deposit_clamps_down_other_side(test: &mut Test) { let lp_supply = seed_pool(test, pool_seed_a, pool_seed_b); let (stated_a, stated_b) = (1_000_000u64, 1_000_000u64); - fund(test, DEPOSITOR, DEPOSITOR_TOKEN_A, DEPOSITOR_TOKEN_B, stated_a, stated_b); + fund( + test, + DEPOSITOR, + DEPOSITOR_TOKEN_A, + DEPOSITOR_TOKEN_B, + stated_a, + stated_b, + ); // amount_b_required for the full stated_a would be 4_000_000 > stated_b, // so amount_b binds: all of B is used and A is clamped down. @@ -352,8 +436,14 @@ fn deposit_clamps_down_other_side(test: &mut Test) { let expected_lp = mul_div(stated_b, lp_supply, pool_seed_b); deposit( - test, DEPOSITOR, DEPOSITOR_TOKEN_A, DEPOSITOR_TOKEN_B, DEPOSITOR_LP, - stated_a, stated_b, expected_lp, + test, + DEPOSITOR, + DEPOSITOR_TOKEN_A, + DEPOSITOR_TOKEN_B, + DEPOSITOR_LP, + stated_a, + stated_b, + expected_lp, ) .succeeds() .has_tokens(DEPOSITOR_TOKEN_A, stated_a - expected_a_pulled) @@ -372,21 +462,50 @@ fn deposit_slippage_rejected(test: &mut Test) { let lp_supply = seed_pool(test, pool_seed_a, pool_seed_b); let (stated_a, stated_b) = (500_000u64, 500_000u64); - fund(test, DEPOSITOR, DEPOSITOR_TOKEN_A, DEPOSITOR_TOKEN_B, stated_a, stated_b); + fund( + test, + DEPOSITOR, + DEPOSITOR_TOKEN_A, + DEPOSITOR_TOKEN_B, + stated_a, + stated_b, + ); // The pool will mint exactly this much; ask for one more. let exact_lp = mul_div(stated_a, lp_supply, pool_seed_a); deposit( - test, DEPOSITOR, DEPOSITOR_TOKEN_A, DEPOSITOR_TOKEN_B, DEPOSITOR_LP, - stated_a, stated_b, exact_lp + 1, + test, + DEPOSITOR, + DEPOSITOR_TOKEN_A, + DEPOSITOR_TOKEN_B, + DEPOSITOR_LP, + stated_a, + stated_b, + exact_lp + 1, ) .fails_with(AmmError::DepositBelowMinimum); // Nothing moved: depositor balances and pool reserves are unchanged. - assert_eq!(test.tokens(DEPOSITOR_TOKEN_A), stated_a, "token A must be untouched after revert"); - assert_eq!(test.tokens(DEPOSITOR_TOKEN_B), stated_b, "token B must be untouched after revert"); - assert_eq!(test.tokens(POOL_A), pool_seed_a, "pool_a must be untouched after revert"); - assert_eq!(test.tokens(POOL_B), pool_seed_b, "pool_b must be untouched after revert"); + assert_eq!( + test.tokens(DEPOSITOR_TOKEN_A), + stated_a, + "token A must be untouched after revert" + ); + assert_eq!( + test.tokens(DEPOSITOR_TOKEN_B), + stated_b, + "token B must be untouched after revert" + ); + assert_eq!( + test.tokens(POOL_A), + pool_seed_a, + "pool_a must be untouched after revert" + ); + assert_eq!( + test.tokens(POOL_B), + pool_seed_b, + "pool_b must be untouched after revert" + ); } // ─── withdraw_liquidity ────────────────────────────────────────────────────── @@ -442,8 +561,14 @@ fn withdraw_liquidity_pays_the_proportional_share(test: &mut Test) { // expected amounts as the slippage floors: the pool hasn't moved since // the quote, so the floors must be met. withdraw( - test, SEEDER, SEEDER_LP, RECV_A, RECV_B, - withdraw_amount, expected_a, expected_b, + test, + SEEDER, + SEEDER_LP, + RECV_A, + RECV_B, + withdraw_amount, + expected_a, + expected_b, ) .succeeds() // The depositor received exactly the proportional share. @@ -466,15 +591,33 @@ fn withdraw_slippage_rejected(test: &mut Test) { // Floor on token A set just above what the pool will pay out. withdraw( - test, SEEDER, SEEDER_LP, RECV_A, RECV_B, - withdraw_amount, expected_a + 1, 0, + test, + SEEDER, + SEEDER_LP, + RECV_A, + RECV_B, + withdraw_amount, + expected_a + 1, + 0, ) .fails_with(AmmError::WithdrawalBelowMinimum); // Nothing moved: pool reserves and the LP balance are unchanged. - assert_eq!(test.tokens(POOL_A), 2_000_000, "pool_a must be untouched after revert"); - assert_eq!(test.tokens(POOL_B), 2_000_000, "pool_b must be untouched after revert"); - assert_eq!(test.tokens(SEEDER_LP), lp_balance, "LP balance must be untouched after revert"); + assert_eq!( + test.tokens(POOL_A), + 2_000_000, + "pool_a must be untouched after revert" + ); + assert_eq!( + test.tokens(POOL_B), + 2_000_000, + "pool_b must be untouched after revert" + ); + assert_eq!( + test.tokens(SEEDER_LP), + lp_balance, + "LP balance must be untouched after revert" + ); } // ─── swap_tokens ───────────────────────────────────────────────────────────── @@ -491,19 +634,31 @@ fn swap_a_to_b_conserves_balances(test: &mut Test) { // by init(idempotent)). let trader_funding = 1_000_000u64; test.add(Wallet::new().at(TRADER)); - test.add(TokenAccount::new(MINT_A, TRADER).at(TRADER_TOKEN_A).amount(trader_funding)); + test.add( + TokenAccount::new(MINT_A, TRADER) + .at(TRADER_TOKEN_A) + .amount(trader_funding), + ); let input = 100_000u64; let expected_output = expected_swap_output(input, POOL_FEE_BPS, pool_seed_a, pool_seed_b); // floor = exact quote; the pool hasn't moved. - swap(test, TRADER, TRADER_TOKEN_A, TRADER_TOKEN_B, true, input, expected_output) - .succeeds() - // Conservation: the trader pays exactly `input` and receives exactly - // what the pool sent; nothing is minted or lost in transit. - .has_tokens(TRADER_TOKEN_A, trader_funding - input) - .has_tokens(TRADER_TOKEN_B, expected_output) - .has_tokens(POOL_A, pool_seed_a + input) - .has_tokens(POOL_B, pool_seed_b - expected_output); + swap( + test, + TRADER, + TRADER_TOKEN_A, + TRADER_TOKEN_B, + true, + input, + expected_output, + ) + .succeeds() + // Conservation: the trader pays exactly `input` and receives exactly + // what the pool sent; nothing is minted or lost in transit. + .has_tokens(TRADER_TOKEN_A, trader_funding - input) + .has_tokens(TRADER_TOKEN_B, expected_output) + .has_tokens(POOL_A, pool_seed_a + input) + .has_tokens(POOL_B, pool_seed_b - expected_output); } #[quasar_test] @@ -514,17 +669,29 @@ fn swap_b_to_a_conserves_balances(test: &mut Test) { let trader_funding = 1_000_000u64; test.add(Wallet::new().at(TRADER)); - test.add(TokenAccount::new(MINT_B, TRADER).at(TRADER_TOKEN_B).amount(trader_funding)); + test.add( + TokenAccount::new(MINT_B, TRADER) + .at(TRADER_TOKEN_B) + .amount(trader_funding), + ); let input = 100_000u64; let expected_output = expected_swap_output(input, POOL_FEE_BPS, pool_seed_b, pool_seed_a); // input_is_token_a = false. - swap(test, TRADER, TRADER_TOKEN_A, TRADER_TOKEN_B, false, input, expected_output) - .succeeds() - .has_tokens(TRADER_TOKEN_B, trader_funding - input) - .has_tokens(TRADER_TOKEN_A, expected_output) - .has_tokens(POOL_B, pool_seed_b + input) - .has_tokens(POOL_A, pool_seed_a - expected_output); + swap( + test, + TRADER, + TRADER_TOKEN_A, + TRADER_TOKEN_B, + false, + input, + expected_output, + ) + .succeeds() + .has_tokens(TRADER_TOKEN_B, trader_funding - input) + .has_tokens(TRADER_TOKEN_A, expected_output) + .has_tokens(POOL_B, pool_seed_b + input) + .has_tokens(POOL_A, pool_seed_a - expected_output); } #[quasar_test] @@ -533,18 +700,42 @@ fn swap_slippage_rejected(test: &mut Test) { seed_pool(test, 10_000_000, 10_000_000); test.add(Wallet::new().at(TRADER)); - test.add(TokenAccount::new(MINT_A, TRADER).at(TRADER_TOKEN_A).amount(1_000_000)); + test.add( + TokenAccount::new(MINT_A, TRADER) + .at(TRADER_TOKEN_A) + .amount(1_000_000), + ); // min_output set one above the exact quote, so the floor cannot be met. let input = 100_000u64; let quote = expected_swap_output(input, POOL_FEE_BPS, 10_000_000, 10_000_000); - swap(test, TRADER, TRADER_TOKEN_A, TRADER_TOKEN_B, true, input, quote + 1) - .fails_with(AmmError::SlippageExceeded); + swap( + test, + TRADER, + TRADER_TOKEN_A, + TRADER_TOKEN_B, + true, + input, + quote + 1, + ) + .fails_with(AmmError::SlippageExceeded); // Nothing moved: the trader keeps their input and the pool is untouched. - assert_eq!(test.tokens(TRADER_TOKEN_A), 1_000_000, "trader balance must be untouched after revert"); - assert_eq!(test.tokens(POOL_A), 10_000_000, "pool_a must be untouched after revert"); - assert_eq!(test.tokens(POOL_B), 10_000_000, "pool_b must be untouched after revert"); + assert_eq!( + test.tokens(TRADER_TOKEN_A), + 1_000_000, + "trader balance must be untouched after revert" + ); + assert_eq!( + test.tokens(POOL_A), + 10_000_000, + "pool_a must be untouched after revert" + ); + assert_eq!( + test.tokens(POOL_B), + 10_000_000, + "pool_b must be untouched after revert" + ); } // ─── claim_admin_fees ──────────────────────────────────────────────────────── @@ -556,8 +747,21 @@ fn claim_admin_fees_pays_the_admin(test: &mut Test) { // Seed pool and do a swap so fees accumulate. seed_pool(test, 10_000_000, 10_000_000); test.add(Wallet::new().at(TRADER)); - test.add(TokenAccount::new(MINT_A, TRADER).at(TRADER_TOKEN_A).amount(1_000_000)); - swap(test, TRADER, TRADER_TOKEN_A, TRADER_TOKEN_B, true, 500_000, 1).succeeds(); + test.add( + TokenAccount::new(MINT_A, TRADER) + .at(TRADER_TOKEN_A) + .amount(1_000_000), + ); + swap( + test, + TRADER, + TRADER_TOKEN_A, + TRADER_TOKEN_B, + true, + 500_000, + 1, + ) + .succeeds(); // Admin claims accumulated fees. test.add(Wallet::new().at(ADMIN)); @@ -581,8 +785,21 @@ fn claim_admin_fees_rejects_non_admin(test: &mut Test) { // Swap to accumulate some fees. test.add(Wallet::new().at(TRADER)); - test.add(TokenAccount::new(MINT_A, TRADER).at(TRADER_TOKEN_A).amount(1_000_000)); - swap(test, TRADER, TRADER_TOKEN_A, TRADER_TOKEN_B, true, 100_000, 1).succeeds(); + test.add( + TokenAccount::new(MINT_A, TRADER) + .at(TRADER_TOKEN_A) + .amount(1_000_000), + ); + swap( + test, + TRADER, + TRADER_TOKEN_A, + TRADER_TOKEN_B, + true, + 100_000, + 1, + ) + .succeeds(); // Impersonator tries to claim with a wrong signer. test.add(Wallet::new().at(BAD_ACTOR)); @@ -590,5 +807,8 @@ fn claim_admin_fees_rejects_non_admin(test: &mut Test) { test.add(TokenAccount::new(MINT_B, BAD_ACTOR).at(BAD_TOKEN_B)); let outcome = claim_fees(test, BAD_ACTOR, BAD_TOKEN_A, BAD_TOKEN_B); - assert!(outcome.is_err(), "unauthorized claim_admin_fees should fail"); + assert!( + outcome.is_err(), + "unauthorized claim_admin_fees should fail" + ); } diff --git a/finance/vault-strategy/quasar/mock-swap-router/Cargo.toml b/finance/vault-strategy/quasar/mock-swap-router/Cargo.toml index 2a5c367fe..e652f3ece 100644 --- a/finance/vault-strategy/quasar/mock-swap-router/Cargo.toml +++ b/finance/vault-strategy/quasar/mock-swap-router/Cargo.toml @@ -29,6 +29,11 @@ idl-build = ["quasar-lang/idl-build"] # git. Keep this rev in lockstep with the CLI rev installed by # .github/workflows/quasar.yml. quasar-lang = { git = "https://github.com/blueshift-gg/quasar", rev = "be60fca" } +# Pinned: quasar-lang asks for zeropod "0.3.3" and wincode 0.4, but zeropod +# 0.3.4 moved to wincode 0.5, so a fresh resolve (no lockfile is committed) +# splits the graph across two wincode versions and every Pod* trait bound +# fails. Unpin when quasar-lang's pinned rev accepts zeropod 0.3.4+. +zeropod = "=0.3.3" quasar-spl = { git = "https://github.com/blueshift-gg/quasar", rev = "be60fca" } solana-instruction = { version = "3.2.0" } diff --git a/finance/vault-strategy/quasar/mock-swap-router/src/tests.rs b/finance/vault-strategy/quasar/mock-swap-router/src/tests.rs index b0c1748d4..17a697ecd 100644 --- a/finance/vault-strategy/quasar/mock-swap-router/src/tests.rs +++ b/finance/vault-strategy/quasar/mock-swap-router/src/tests.rs @@ -33,7 +33,11 @@ fn initialize_and_swap_usdc_for_asset(test: &mut Test) { test.add(Mint::new(AUTHORITY).at(USDC_MINT).decimals(DECIMALS)); // The asset mint's authority is the router-authority PDA, so the router // can mint it. - test.add(Mint::new(router_authority).at(ASSET_MINT).decimals(DECIMALS)); + test.add( + Mint::new(router_authority) + .at(ASSET_MINT) + .decimals(DECIMALS), + ); test.add( TokenAccount::new(USDC_MINT, AUTHORITY) .at(CALLER_USDC) diff --git a/finance/vault-strategy/quasar/vault-strategy/Cargo.toml b/finance/vault-strategy/quasar/vault-strategy/Cargo.toml index 121d27bef..597ceed17 100644 --- a/finance/vault-strategy/quasar/vault-strategy/Cargo.toml +++ b/finance/vault-strategy/quasar/vault-strategy/Cargo.toml @@ -29,6 +29,11 @@ idl-build = ["quasar-lang/idl-build"] # git. Keep this rev in lockstep with the CLI rev installed by # .github/workflows/quasar.yml. quasar-lang = { git = "https://github.com/blueshift-gg/quasar", rev = "be60fca" } +# Pinned: quasar-lang asks for zeropod "0.3.3" and wincode 0.4, but zeropod +# 0.3.4 moved to wincode 0.5, so a fresh resolve (no lockfile is committed) +# splits the graph across two wincode versions and every Pod* trait bound +# fails. Unpin when quasar-lang's pinned rev accepts zeropod 0.3.4+. +zeropod = "=0.3.3" quasar-spl = { git = "https://github.com/blueshift-gg/quasar", rev = "be60fca" } solana-instruction = { version = "3.2.0" } diff --git a/finance/vault-strategy/quasar/vault-strategy/src/tests.rs b/finance/vault-strategy/quasar/vault-strategy/src/tests.rs index fe8882c5f..14f1d4f93 100644 --- a/finance/vault-strategy/quasar/vault-strategy/src/tests.rs +++ b/finance/vault-strategy/quasar/vault-strategy/src/tests.rs @@ -11,9 +11,7 @@ use { AddAssetInstruction, ApproveAssetInstruction, DepositInstruction, InitializeRegistryInstruction, InitializeStrategyInstruction, }, - state::{ - AssetConfig, AssetVaultPda, Registry, ShareMintPda, Strategy, UsdcVaultPda, - }, + state::{AssetConfig, AssetVaultPda, Registry, ShareMintPda, Strategy, UsdcVaultPda}, }, quasar_test::prelude::*, }; @@ -99,7 +97,10 @@ fn setup_strategy(test: &mut Test, asset_mint_authority: Pubkey) { let registry = test.derive_pda(Registry::seeds(&AUTHORITY)); - test.send(InitializeRegistryInstruction { authority: AUTHORITY }).succeeds(); + test.send(InitializeRegistryInstruction { + authority: AUTHORITY, + }) + .succeeds(); test.send(ApproveAssetInstruction { authority: AUTHORITY, asset_mint: ASSET_MINT, @@ -134,12 +135,19 @@ fn strategy_setup_records_the_basket(test: &mut Test) { let strategy = test.read::(w.strategy); assert_eq!(strategy.asset_count, 1, "asset_count"); - assert_eq!(u16::from(strategy.total_weight_bps), 10_000, "total_weight_bps"); + assert_eq!( + u16::from(strategy.total_weight_bps), + 10_000, + "total_weight_bps" + ); let asset_config = test.read::(w.asset_config); assert_eq!(u16::from(asset_config.weight_bps), 10_000, "weight_bps"); assert_eq!(asset_config.mint, ASSET_MINT, "asset mint"); - assert_eq!(asset_config.price_feed, PRICE_FEED, "price feed copied from registry"); + assert_eq!( + asset_config.price_feed, PRICE_FEED, + "price feed copied from registry" + ); } /// Two-program deposit: set up the router + a single-asset strategy, then @@ -179,7 +187,9 @@ fn deposit_mints_shares_and_deploys_into_the_basket(test: &mut Test) { // Initialize the router and set the asset's rate (hand-built: the router's // builders live in the sibling crate). - let rent_id: Pubkey = "SysvarRent111111111111111111111111111111111".parse().unwrap(); + let rent_id: Pubkey = "SysvarRent111111111111111111111111111111111" + .parse() + .unwrap(); test.send(Instruction { program_id: router_id(), accounts: vec![ diff --git a/tokens/create-token/quasar/Cargo.toml b/tokens/create-token/quasar/Cargo.toml index 0f24b3bc5..5a77c0699 100644 --- a/tokens/create-token/quasar/Cargo.toml +++ b/tokens/create-token/quasar/Cargo.toml @@ -30,6 +30,11 @@ idl-build = ["quasar-lang/idl-build"] # git. Keep this rev in lockstep with the CLI rev installed by # .github/workflows/quasar.yml. quasar-lang = { git = "https://github.com/blueshift-gg/quasar", rev = "be60fca" } +# Pinned: quasar-lang asks for zeropod "0.3.3" and wincode 0.4, but zeropod +# 0.3.4 moved to wincode 0.5, so a fresh resolve (no lockfile is committed) +# splits the graph across two wincode versions and every Pod* trait bound +# fails. Unpin when quasar-lang's pinned rev accepts zeropod 0.3.4+. +zeropod = "=0.3.3" quasar-spl = { git = "https://github.com/blueshift-gg/quasar", rev = "be60fca" } solana-instruction = { version = "3.2.0" } diff --git a/tokens/external-delegate-token-master/quasar/Cargo.toml b/tokens/external-delegate-token-master/quasar/Cargo.toml index bf01ee8c9..db0e66738 100644 --- a/tokens/external-delegate-token-master/quasar/Cargo.toml +++ b/tokens/external-delegate-token-master/quasar/Cargo.toml @@ -30,6 +30,11 @@ idl-build = ["quasar-lang/idl-build"] # git. Keep this rev in lockstep with the CLI rev installed by # .github/workflows/quasar.yml. quasar-lang = { git = "https://github.com/blueshift-gg/quasar", rev = "be60fca" } +# Pinned: quasar-lang asks for zeropod "0.3.3" and wincode 0.4, but zeropod +# 0.3.4 moved to wincode 0.5, so a fresh resolve (no lockfile is committed) +# splits the graph across two wincode versions and every Pod* trait bound +# fails. Unpin when quasar-lang's pinned rev accepts zeropod 0.3.4+. +zeropod = "=0.3.3" quasar-spl = { git = "https://github.com/blueshift-gg/quasar", rev = "be60fca" } solana-instruction = { version = "3.2.0" } solana-define-syscall = "4.0" diff --git a/tokens/nft-minter/quasar/Cargo.toml b/tokens/nft-minter/quasar/Cargo.toml index a4cc9e9e0..323e2b7a1 100644 --- a/tokens/nft-minter/quasar/Cargo.toml +++ b/tokens/nft-minter/quasar/Cargo.toml @@ -30,6 +30,11 @@ idl-build = ["quasar-lang/idl-build"] # git. Keep this rev in lockstep with the CLI rev installed by # .github/workflows/quasar.yml. quasar-lang = { git = "https://github.com/blueshift-gg/quasar", rev = "be60fca" } +# Pinned: quasar-lang asks for zeropod "0.3.3" and wincode 0.4, but zeropod +# 0.3.4 moved to wincode 0.5, so a fresh resolve (no lockfile is committed) +# splits the graph across two wincode versions and every Pod* trait bound +# fails. Unpin when quasar-lang's pinned rev accepts zeropod 0.3.4+. +zeropod = "=0.3.3" quasar-spl = { git = "https://github.com/blueshift-gg/quasar", rev = "be60fca" } # Vendored: upstream removed the quasar-metadata crate before the 0.1.0 # release with no replacement. See tokens/quasar-metadata/README.md. diff --git a/tokens/nft-minter/quasar/src/lib.rs b/tokens/nft-minter/quasar/src/lib.rs index c288d5762..01749d558 100644 --- a/tokens/nft-minter/quasar/src/lib.rs +++ b/tokens/nft-minter/quasar/src/lib.rs @@ -24,7 +24,7 @@ mod quasar_nft_minter { nft_symbol: String<10>, nft_uri: String<200>, ) -> Result<(), ProgramError> { - handle_mint_nft(&mut ctx.accounts, &nft_name, &nft_symbol, &nft_uri) + handle_mint_nft(&mut ctx.accounts, nft_name, nft_symbol, nft_uri) } } @@ -83,7 +83,8 @@ fn handle_mint_nft( ) -> Result<(), ProgramError> { // 1. Mint one token to the associated token account. log("Minting token"); - accounts.token_program + accounts + .token_program .mint_to( &accounts.mint_account, &accounts.associated_token_account, @@ -94,7 +95,8 @@ fn handle_mint_nft( // 2. Create Metaplex metadata account. log("Creating metadata account"); - accounts.token_metadata_program + accounts + .token_metadata_program .create_metadata_accounts_v3( &accounts.metadata_account, &accounts.mint_account, @@ -114,7 +116,8 @@ fn handle_mint_nft( // 3. Create master edition (makes it a verified NFT). log("Creating master edition account"); - accounts.token_metadata_program + accounts + .token_metadata_program .create_master_edition_v3( &accounts.edition_account, &accounts.mint_account, diff --git a/tokens/nft-operations/quasar/Cargo.toml b/tokens/nft-operations/quasar/Cargo.toml index 9ee4eed1e..86a593b06 100644 --- a/tokens/nft-operations/quasar/Cargo.toml +++ b/tokens/nft-operations/quasar/Cargo.toml @@ -30,6 +30,11 @@ idl-build = ["quasar-lang/idl-build"] # git. Keep this rev in lockstep with the CLI rev installed by # .github/workflows/quasar.yml. quasar-lang = { git = "https://github.com/blueshift-gg/quasar", rev = "be60fca" } +# Pinned: quasar-lang asks for zeropod "0.3.3" and wincode 0.4, but zeropod +# 0.3.4 moved to wincode 0.5, so a fresh resolve (no lockfile is committed) +# splits the graph across two wincode versions and every Pod* trait bound +# fails. Unpin when quasar-lang's pinned rev accepts zeropod 0.3.4+. +zeropod = "=0.3.3" quasar-spl = { git = "https://github.com/blueshift-gg/quasar", rev = "be60fca" } # Vendored: upstream removed the quasar-metadata crate before the 0.1.0 # release with no replacement. See tokens/quasar-metadata/README.md. diff --git a/tokens/nft-operations/quasar/src/instructions/create_collection.rs b/tokens/nft-operations/quasar/src/instructions/create_collection.rs index 0827e8ad8..1baad0494 100644 --- a/tokens/nft-operations/quasar/src/instructions/create_collection.rs +++ b/tokens/nft-operations/quasar/src/instructions/create_collection.rs @@ -67,7 +67,12 @@ pub fn handle_create_collection( // Mint 1 token (the collection NFT) to the destination. accounts .token_program - .mint_to(&accounts.mint, &accounts.destination, &accounts.mint_authority, 1u64) + .mint_to( + &accounts.mint, + &accounts.destination, + &accounts.mint_authority, + 1u64, + ) .invoke_signed(seeds)?; log("Collection NFT minted!"); diff --git a/tokens/nft-operations/quasar/src/instructions/mint_nft.rs b/tokens/nft-operations/quasar/src/instructions/mint_nft.rs index 4eed5d8db..1a4f28c0f 100644 --- a/tokens/nft-operations/quasar/src/instructions/mint_nft.rs +++ b/tokens/nft-operations/quasar/src/instructions/mint_nft.rs @@ -68,7 +68,12 @@ pub fn handle_mint_nft( // Mint 1 token (the NFT) to the destination. accounts .token_program - .mint_to(&accounts.mint, &accounts.destination, &accounts.mint_authority, 1u64) + .mint_to( + &accounts.mint, + &accounts.destination, + &accounts.mint_authority, + 1u64, + ) .invoke_signed(seeds)?; log("NFT minted!"); diff --git a/tokens/nft-operations/quasar/src/lib.rs b/tokens/nft-operations/quasar/src/lib.rs index f68d4ad31..aa3e50353 100644 --- a/tokens/nft-operations/quasar/src/lib.rs +++ b/tokens/nft-operations/quasar/src/lib.rs @@ -2,7 +2,7 @@ use quasar_lang::prelude::*; -mod instructions; +pub mod instructions; use instructions::*; #[cfg(test)] mod tests; @@ -37,7 +37,7 @@ mod quasar_nft_operations { symbol: String<10>, uri: String<200>, ) -> Result<(), ProgramError> { - instructions::handle_create_collection(&mut ctx.accounts, &ctx.bumps, &name, &symbol, &uri) + instructions::handle_create_collection(&mut ctx.accounts, &ctx.bumps, name, symbol, uri) } /// Mint an individual NFT with an unverified reference to the collection. @@ -48,7 +48,7 @@ mod quasar_nft_operations { symbol: String<10>, uri: String<200>, ) -> Result<(), ProgramError> { - instructions::handle_mint_nft(&mut ctx.accounts, &ctx.bumps, &name, &symbol, &uri) + instructions::handle_mint_nft(&mut ctx.accounts, &ctx.bumps, name, symbol, uri) } /// Verify the NFT as a member of the collection. diff --git a/tokens/nft-operations/quasar/src/tests.rs b/tokens/nft-operations/quasar/src/tests.rs index 4f8e3bfa4..b46419496 100644 --- a/tokens/nft-operations/quasar/src/tests.rs +++ b/tokens/nft-operations/quasar/src/tests.rs @@ -105,9 +105,7 @@ fn create_collection_mints_the_collection_nft(test: &mut Test) { // The metadata account carries the caller-supplied name, and the master // edition exists. - let metadata_account = test - .account(derive_metadata_pda(&COLLECTION_MINT)) - .unwrap(); + let metadata_account = test.account(derive_metadata_pda(&COLLECTION_MINT)).unwrap(); assert!( contains_bytes(&metadata_account.data, COLLECTION_NAME.as_bytes()), "Metadata should contain the caller-supplied collection name" diff --git a/tokens/pda-mint-authority/quasar/Cargo.toml b/tokens/pda-mint-authority/quasar/Cargo.toml index 902be730d..b739f03de 100644 --- a/tokens/pda-mint-authority/quasar/Cargo.toml +++ b/tokens/pda-mint-authority/quasar/Cargo.toml @@ -29,6 +29,11 @@ idl-build = ["quasar-lang/idl-build"] # git. Keep this rev in lockstep with the CLI rev installed by # .github/workflows/quasar.yml. quasar-lang = { git = "https://github.com/blueshift-gg/quasar", rev = "be60fca" } +# Pinned: quasar-lang asks for zeropod "0.3.3" and wincode 0.4, but zeropod +# 0.3.4 moved to wincode 0.5, so a fresh resolve (no lockfile is committed) +# splits the graph across two wincode versions and every Pod* trait bound +# fails. Unpin when quasar-lang's pinned rev accepts zeropod 0.3.4+. +zeropod = "=0.3.3" quasar-spl = { git = "https://github.com/blueshift-gg/quasar", rev = "be60fca" } solana-instruction = { version = "3.2.0" } diff --git a/tokens/pda-mint-authority/quasar/src/lib.rs b/tokens/pda-mint-authority/quasar/src/lib.rs index f67971d93..cb2690235 100644 --- a/tokens/pda-mint-authority/quasar/src/lib.rs +++ b/tokens/pda-mint-authority/quasar/src/lib.rs @@ -75,7 +75,8 @@ fn handle_create_mint( let rent = Rent::get()?; let lamports = rent.minimum_balance_unchecked(MINT_SPACE); - accounts.system_program + accounts + .system_program .create_account( &accounts.payer, &accounts.mint, @@ -118,13 +119,11 @@ fn handle_mint_tokens( mint_bump: u8, ) -> Result<(), ProgramError> { let bump = [mint_bump]; - let seeds: &[Seed] = &[ - Seed::from(b"mint" as &[u8]), - Seed::from(&bump as &[u8]), - ]; + let seeds: &[Seed] = &[Seed::from(b"mint" as &[u8]), Seed::from(&bump as &[u8])]; let mint_view = accounts.mint.to_account_view().clone(); - accounts.token_program + accounts + .token_program .mint_to(&mint_view, &accounts.token_account, &mint_view, amount) .invoke_signed(seeds) } diff --git a/tokens/quasar-metadata/Cargo.toml b/tokens/quasar-metadata/Cargo.toml index da20eb6dc..fae608934 100644 --- a/tokens/quasar-metadata/Cargo.toml +++ b/tokens/quasar-metadata/Cargo.toml @@ -26,4 +26,9 @@ debug = [] # Pinned to the same 0.1.0-release rev as every Quasar example in this # repository. See README.md for why this crate is vendored here. quasar-lang = { git = "https://github.com/blueshift-gg/quasar", rev = "be60fca" } +# Pinned: quasar-lang asks for zeropod "0.3.3" and wincode 0.4, but zeropod +# 0.3.4 moved to wincode 0.5, so a fresh resolve (no lockfile is committed) +# splits the graph across two wincode versions and every Pod* trait bound +# fails. Unpin when quasar-lang's pinned rev accepts zeropod 0.3.4+. +zeropod = "=0.3.3" solana-address = { version = ">=2.2, <2.6", features = ["copy"] } diff --git a/tokens/quasar-metadata/src/lib.rs b/tokens/quasar-metadata/src/lib.rs index 4cf6d0c38..41d192b52 100644 --- a/tokens/quasar-metadata/src/lib.rs +++ b/tokens/quasar-metadata/src/lib.rs @@ -16,7 +16,7 @@ pub mod instructions; pub mod pda; pub mod prelude; mod program; -mod state; +pub mod state; pub mod validate; pub use { diff --git a/tokens/token-extensions/basics/quasar/Cargo.toml b/tokens/token-extensions/basics/quasar/Cargo.toml index 09294d5aa..ca4b02a7a 100644 --- a/tokens/token-extensions/basics/quasar/Cargo.toml +++ b/tokens/token-extensions/basics/quasar/Cargo.toml @@ -30,6 +30,11 @@ idl-build = ["quasar-lang/idl-build"] # git. Keep this rev in lockstep with the CLI rev installed by # .github/workflows/quasar.yml. quasar-lang = { git = "https://github.com/blueshift-gg/quasar", rev = "be60fca" } +# Pinned: quasar-lang asks for zeropod "0.3.3" and wincode 0.4, but zeropod +# 0.3.4 moved to wincode 0.5, so a fresh resolve (no lockfile is committed) +# splits the graph across two wincode versions and every Pod* trait bound +# fails. Unpin when quasar-lang's pinned rev accepts zeropod 0.3.4+. +zeropod = "=0.3.3" solana-instruction = { version = "3.2.0" } [dev-dependencies] diff --git a/tokens/token-extensions/basics/quasar/src/lib.rs b/tokens/token-extensions/basics/quasar/src/lib.rs index 2611ae08e..f6f5eda63 100644 --- a/tokens/token-extensions/basics/quasar/src/lib.rs +++ b/tokens/token-extensions/basics/quasar/src/lib.rs @@ -20,8 +20,8 @@ pub struct Token2022Program; impl Id for Token2022Program { const ID: Address = Address::new_from_array([ - 6, 221, 246, 225, 238, 117, 143, 222, 24, 66, 93, 188, 228, 108, 205, 218, - 182, 26, 252, 77, 131, 185, 13, 39, 254, 189, 249, 40, 216, 161, 139, 252, + 6, 221, 246, 225, 238, 117, 143, 222, 24, 66, 93, 188, 228, 108, 205, 218, 182, 26, 252, + 77, 131, 185, 13, 39, 254, 189, 249, 40, 216, 161, 139, 252, ]); } @@ -33,13 +33,19 @@ mod quasar_token_2022_basics { /// Mint tokens to a recipient's token account. #[instruction(discriminator = 0)] - pub fn mint_token(ctx: Ctx, amount: u64) -> Result<(), ProgramError> { + pub fn mint_token( + ctx: Ctx, + amount: u64, + ) -> Result<(), ProgramError> { handle_mint_token(&mut ctx.accounts, amount) } /// Transfer tokens using transfer_checked (required for Token Extensions). #[instruction(discriminator = 1)] - pub fn transfer_token(ctx: Ctx, amount: u64) -> Result<(), ProgramError> { + pub fn transfer_token( + ctx: Ctx, + amount: u64, + ) -> Result<(), ProgramError> { handle_transfer_token(&mut ctx.accounts, amount) } } @@ -57,7 +63,10 @@ pub struct MintTokenAccountConstraints { } #[inline(always)] -fn handle_mint_token(accounts: &mut MintTokenAccountConstraints, amount: u64) -> Result<(), ProgramError> { +fn handle_mint_token( + accounts: &mut MintTokenAccountConstraints, + amount: u64, +) -> Result<(), ProgramError> { // SPL Token MintTo instruction: opcode 7, amount as u64 LE. let data = build_u64_data(7, amount); CpiCall::new( @@ -91,7 +100,10 @@ pub struct TransferTokenAccountConstraints { } #[inline(always)] -fn handle_transfer_token(accounts: &mut TransferTokenAccountConstraints, amount: u64) -> Result<(), ProgramError> { +fn handle_transfer_token( + accounts: &mut TransferTokenAccountConstraints, + amount: u64, +) -> Result<(), ProgramError> { // SPL Token TransferChecked instruction: opcode 12, amount as u64 LE, decimals as u8. let data = build_transfer_checked_data(amount, 6); CpiCall::new( diff --git a/tokens/token-extensions/cpi-guard/quasar/Cargo.toml b/tokens/token-extensions/cpi-guard/quasar/Cargo.toml index 1ee9097ed..98a23a754 100644 --- a/tokens/token-extensions/cpi-guard/quasar/Cargo.toml +++ b/tokens/token-extensions/cpi-guard/quasar/Cargo.toml @@ -30,6 +30,11 @@ idl-build = ["quasar-lang/idl-build"] # git. Keep this rev in lockstep with the CLI rev installed by # .github/workflows/quasar.yml. quasar-lang = { git = "https://github.com/blueshift-gg/quasar", rev = "be60fca" } +# Pinned: quasar-lang asks for zeropod "0.3.3" and wincode 0.4, but zeropod +# 0.3.4 moved to wincode 0.5, so a fresh resolve (no lockfile is committed) +# splits the graph across two wincode versions and every Pod* trait bound +# fails. Unpin when quasar-lang's pinned rev accepts zeropod 0.3.4+. +zeropod = "=0.3.3" solana-instruction = { version = "3.2.0" } [dev-dependencies] diff --git a/tokens/token-extensions/cpi-guard/quasar/src/lib.rs b/tokens/token-extensions/cpi-guard/quasar/src/lib.rs index 29e935dc6..dfdd74899 100644 --- a/tokens/token-extensions/cpi-guard/quasar/src/lib.rs +++ b/tokens/token-extensions/cpi-guard/quasar/src/lib.rs @@ -14,8 +14,8 @@ declare_id!("6tU3MEowU6oxxeDZLSxEwzcEZsZrhBJsfUR6xECvShid"); pub struct Token2022Program; impl Id for Token2022Program { const ID: Address = Address::new_from_array([ - 6, 221, 246, 225, 238, 117, 143, 222, 24, 66, 93, 188, 228, 108, 205, 218, - 182, 26, 252, 77, 131, 185, 13, 39, 254, 189, 249, 40, 216, 161, 139, 252, + 6, 221, 246, 225, 238, 117, 143, 222, 24, 66, 93, 188, 228, 108, 205, 218, 182, 26, 252, + 77, 131, 185, 13, 39, 254, 189, 249, 40, 216, 161, 139, 252, ]); } @@ -59,7 +59,9 @@ fn handle_cpi_transfer(accounts: &mut CpiTransferAccountConstraints) -> Result<( [ InstructionAccount::writable(accounts.sender_token_account.to_account_view().address()), InstructionAccount::readonly(accounts.mint_account.to_account_view().address()), - InstructionAccount::writable(accounts.recipient_token_account.to_account_view().address()), + InstructionAccount::writable( + accounts.recipient_token_account.to_account_view().address(), + ), InstructionAccount::readonly_signer(accounts.sender.to_account_view().address()), ], [ diff --git a/tokens/token-extensions/default-account-state/quasar/Cargo.toml b/tokens/token-extensions/default-account-state/quasar/Cargo.toml index 0e03ed1ba..99747e96d 100644 --- a/tokens/token-extensions/default-account-state/quasar/Cargo.toml +++ b/tokens/token-extensions/default-account-state/quasar/Cargo.toml @@ -30,6 +30,11 @@ idl-build = ["quasar-lang/idl-build"] # git. Keep this rev in lockstep with the CLI rev installed by # .github/workflows/quasar.yml. quasar-lang = { git = "https://github.com/blueshift-gg/quasar", rev = "be60fca" } +# Pinned: quasar-lang asks for zeropod "0.3.3" and wincode 0.4, but zeropod +# 0.3.4 moved to wincode 0.5, so a fresh resolve (no lockfile is committed) +# splits the graph across two wincode versions and every Pod* trait bound +# fails. Unpin when quasar-lang's pinned rev accepts zeropod 0.3.4+. +zeropod = "=0.3.3" solana-instruction = { version = "3.2.0" } [dev-dependencies] diff --git a/tokens/token-extensions/default-account-state/quasar/src/lib.rs b/tokens/token-extensions/default-account-state/quasar/src/lib.rs index 866e8a4ea..ec574cc1e 100644 --- a/tokens/token-extensions/default-account-state/quasar/src/lib.rs +++ b/tokens/token-extensions/default-account-state/quasar/src/lib.rs @@ -15,8 +15,8 @@ declare_id!("5LdYbHiUsFxVG8bfqoeBkhBYMRmWZb3BoLuABgYW7coB"); pub struct Token2022Program; impl Id for Token2022Program { const ID: Address = Address::new_from_array([ - 6, 221, 246, 225, 238, 117, 143, 222, 24, 66, 93, 188, 228, 108, 205, 218, - 182, 26, 252, 77, 131, 185, 13, 39, 254, 189, 249, 40, 216, 161, 139, 252, + 6, 221, 246, 225, 238, 117, 143, 222, 24, 66, 93, 188, 228, 108, 205, 218, 182, 26, 252, + 77, 131, 185, 13, 39, 254, 189, 249, 40, 216, 161, 139, 252, ]); } @@ -126,7 +126,9 @@ fn handle_update_default_state( accounts.token_program.to_account_view().address(), [ InstructionAccount::writable(accounts.mint_account.to_account_view().address()), - InstructionAccount::readonly_signer(accounts.freeze_authority.to_account_view().address()), + InstructionAccount::readonly_signer( + accounts.freeze_authority.to_account_view().address(), + ), ], [ accounts.mint_account.to_account_view(), diff --git a/tokens/token-extensions/group/quasar/Cargo.toml b/tokens/token-extensions/group/quasar/Cargo.toml index e5f989c42..5766f404e 100644 --- a/tokens/token-extensions/group/quasar/Cargo.toml +++ b/tokens/token-extensions/group/quasar/Cargo.toml @@ -30,6 +30,11 @@ idl-build = ["quasar-lang/idl-build"] # git. Keep this rev in lockstep with the CLI rev installed by # .github/workflows/quasar.yml. quasar-lang = { git = "https://github.com/blueshift-gg/quasar", rev = "be60fca" } +# Pinned: quasar-lang asks for zeropod "0.3.3" and wincode 0.4, but zeropod +# 0.3.4 moved to wincode 0.5, so a fresh resolve (no lockfile is committed) +# splits the graph across two wincode versions and every Pod* trait bound +# fails. Unpin when quasar-lang's pinned rev accepts zeropod 0.3.4+. +zeropod = "=0.3.3" solana-instruction = { version = "3.2.0" } [dev-dependencies] diff --git a/tokens/token-extensions/group/quasar/src/lib.rs b/tokens/token-extensions/group/quasar/src/lib.rs index 3529fde30..5467d67c1 100644 --- a/tokens/token-extensions/group/quasar/src/lib.rs +++ b/tokens/token-extensions/group/quasar/src/lib.rs @@ -14,8 +14,8 @@ declare_id!("4XCDGMD8fsdjUzmYj6d9if8twFt1f23Ym52iDmWK8fFs"); pub struct Token2022Program; impl Id for Token2022Program { const ID: Address = Address::new_from_array([ - 6, 221, 246, 225, 238, 117, 143, 222, 24, 66, 93, 188, 228, 108, 205, 218, - 182, 26, 252, 77, 131, 185, 13, 39, 254, 189, 249, 40, 216, 161, 139, 252, + 6, 221, 246, 225, 238, 117, 143, 222, 24, 66, 93, 188, 228, 108, 205, 218, 182, 26, 252, + 77, 131, 185, 13, 39, 254, 189, 249, 40, 216, 161, 139, 252, ]); } @@ -30,7 +30,9 @@ mod quasar_group { use super::*; #[instruction(discriminator = 0)] - pub fn initialize_group(ctx: Ctx) -> Result<(), ProgramError> { + pub fn initialize_group( + ctx: Ctx, + ) -> Result<(), ProgramError> { handle_initialize_group(&mut ctx.accounts) } } @@ -46,7 +48,9 @@ pub struct InitializeGroupAccountConstraints { } #[inline(always)] -fn handle_initialize_group(accounts: &mut InitializeGroupAccountConstraints) -> Result<(), ProgramError> { +fn handle_initialize_group( + accounts: &mut InitializeGroupAccountConstraints, +) -> Result<(), ProgramError> { // Mint + GroupPointer extension = 234 bytes // (base mint padded to 165 + account_type byte + GroupPointer TLV [2 type + 2 len + 64 data]) let mint_size: u64 = 234; diff --git a/tokens/token-extensions/immutable-owner/quasar/Cargo.toml b/tokens/token-extensions/immutable-owner/quasar/Cargo.toml index a890838f3..480ab91f8 100644 --- a/tokens/token-extensions/immutable-owner/quasar/Cargo.toml +++ b/tokens/token-extensions/immutable-owner/quasar/Cargo.toml @@ -30,6 +30,11 @@ idl-build = ["quasar-lang/idl-build"] # git. Keep this rev in lockstep with the CLI rev installed by # .github/workflows/quasar.yml. quasar-lang = { git = "https://github.com/blueshift-gg/quasar", rev = "be60fca" } +# Pinned: quasar-lang asks for zeropod "0.3.3" and wincode 0.4, but zeropod +# 0.3.4 moved to wincode 0.5, so a fresh resolve (no lockfile is committed) +# splits the graph across two wincode versions and every Pod* trait bound +# fails. Unpin when quasar-lang's pinned rev accepts zeropod 0.3.4+. +zeropod = "=0.3.3" solana-instruction = { version = "3.2.0" } [dev-dependencies] diff --git a/tokens/token-extensions/immutable-owner/quasar/src/lib.rs b/tokens/token-extensions/immutable-owner/quasar/src/lib.rs index 6ec0b52b1..be97b034a 100644 --- a/tokens/token-extensions/immutable-owner/quasar/src/lib.rs +++ b/tokens/token-extensions/immutable-owner/quasar/src/lib.rs @@ -14,8 +14,8 @@ declare_id!("6g5URpqqurW8RbKjuGeRCVZBKky3J4kYcLeotQ6vj6UT"); pub struct Token2022Program; impl Id for Token2022Program { const ID: Address = Address::new_from_array([ - 6, 221, 246, 225, 238, 117, 143, 222, 24, 66, 93, 188, 228, 108, 205, 218, - 182, 26, 252, 77, 131, 185, 13, 39, 254, 189, 249, 40, 216, 161, 139, 252, + 6, 221, 246, 225, 238, 117, 143, 222, 24, 66, 93, 188, 228, 108, 205, 218, 182, 26, 252, + 77, 131, 185, 13, 39, 254, 189, 249, 40, 216, 161, 139, 252, ]); } diff --git a/tokens/token-extensions/interest-bearing/quasar/Cargo.toml b/tokens/token-extensions/interest-bearing/quasar/Cargo.toml index ca8236e64..b9db80d1e 100644 --- a/tokens/token-extensions/interest-bearing/quasar/Cargo.toml +++ b/tokens/token-extensions/interest-bearing/quasar/Cargo.toml @@ -30,6 +30,11 @@ idl-build = ["quasar-lang/idl-build"] # git. Keep this rev in lockstep with the CLI rev installed by # .github/workflows/quasar.yml. quasar-lang = { git = "https://github.com/blueshift-gg/quasar", rev = "be60fca" } +# Pinned: quasar-lang asks for zeropod "0.3.3" and wincode 0.4, but zeropod +# 0.3.4 moved to wincode 0.5, so a fresh resolve (no lockfile is committed) +# splits the graph across two wincode versions and every Pod* trait bound +# fails. Unpin when quasar-lang's pinned rev accepts zeropod 0.3.4+. +zeropod = "=0.3.3" solana-instruction = { version = "3.2.0" } [dev-dependencies] diff --git a/tokens/token-extensions/interest-bearing/quasar/src/lib.rs b/tokens/token-extensions/interest-bearing/quasar/src/lib.rs index 356dbcaaa..6305c3110 100644 --- a/tokens/token-extensions/interest-bearing/quasar/src/lib.rs +++ b/tokens/token-extensions/interest-bearing/quasar/src/lib.rs @@ -14,8 +14,8 @@ declare_id!("DMQdkzRJz8uQSN8Kx2QYmQJn6xLKhsu3LcPYxs314MgC"); pub struct Token2022Program; impl Id for Token2022Program { const ID: Address = Address::new_from_array([ - 6, 221, 246, 225, 238, 117, 143, 222, 24, 66, 93, 188, 228, 108, 205, 218, - 182, 26, 252, 77, 131, 185, 13, 39, 254, 189, 249, 40, 216, 161, 139, 252, + 6, 221, 246, 225, 238, 117, 143, 222, 24, 66, 93, 188, 228, 108, 205, 218, 182, 26, 252, + 77, 131, 185, 13, 39, 254, 189, 249, 40, 216, 161, 139, 252, ]); } @@ -26,12 +26,18 @@ mod quasar_interest_bearing { use super::*; #[instruction(discriminator = 0)] - pub fn initialize(ctx: Ctx, rate: i16) -> Result<(), ProgramError> { + pub fn initialize( + ctx: Ctx, + rate: i16, + ) -> Result<(), ProgramError> { handle_initialize(&mut ctx.accounts, rate) } #[instruction(discriminator = 1)] - pub fn update_rate(ctx: Ctx, rate: i16) -> Result<(), ProgramError> { + pub fn update_rate( + ctx: Ctx, + rate: i16, + ) -> Result<(), ProgramError> { handle_update_rate(&mut ctx.accounts, rate) } } @@ -47,7 +53,10 @@ pub struct InitializeAccountConstraints { } #[inline(always)] -fn handle_initialize(accounts: &mut InitializeAccountConstraints, rate: i16) -> Result<(), ProgramError> { +fn handle_initialize( + accounts: &mut InitializeAccountConstraints, + rate: i16, +) -> Result<(), ProgramError> { // 165 (base) + 1 (account type) + 4 (TLV header) + 52 (InterestBearingConfig data) = 222 bytes let mint_size: u64 = 222; let lamports = Rent::get()?.try_minimum_balance(mint_size as usize)?; @@ -110,7 +119,10 @@ pub struct UpdateRateAccountConstraints { } #[inline(always)] -fn handle_update_rate(accounts: &mut UpdateRateAccountConstraints, rate: i16) -> Result<(), ProgramError> { +fn handle_update_rate( + accounts: &mut UpdateRateAccountConstraints, + rate: i16, +) -> Result<(), ProgramError> { // InterestBearingMintUpdateRate: opcode 33, sub-opcode 1, rate (i16 LE) let mut data = [0u8; 4]; data[0] = 33; diff --git a/tokens/token-extensions/memo-transfer/quasar/Cargo.toml b/tokens/token-extensions/memo-transfer/quasar/Cargo.toml index a030adfbf..5d0311624 100644 --- a/tokens/token-extensions/memo-transfer/quasar/Cargo.toml +++ b/tokens/token-extensions/memo-transfer/quasar/Cargo.toml @@ -30,6 +30,11 @@ idl-build = ["quasar-lang/idl-build"] # git. Keep this rev in lockstep with the CLI rev installed by # .github/workflows/quasar.yml. quasar-lang = { git = "https://github.com/blueshift-gg/quasar", rev = "be60fca" } +# Pinned: quasar-lang asks for zeropod "0.3.3" and wincode 0.4, but zeropod +# 0.3.4 moved to wincode 0.5, so a fresh resolve (no lockfile is committed) +# splits the graph across two wincode versions and every Pod* trait bound +# fails. Unpin when quasar-lang's pinned rev accepts zeropod 0.3.4+. +zeropod = "=0.3.3" solana-instruction = { version = "3.2.0" } [dev-dependencies] diff --git a/tokens/token-extensions/memo-transfer/quasar/src/lib.rs b/tokens/token-extensions/memo-transfer/quasar/src/lib.rs index 9b214cd04..78b5f62ea 100644 --- a/tokens/token-extensions/memo-transfer/quasar/src/lib.rs +++ b/tokens/token-extensions/memo-transfer/quasar/src/lib.rs @@ -14,8 +14,8 @@ declare_id!("5BQyC7y2Pc283woThq11uZRqsgcRbBRLKz4yQ8BJadi2"); pub struct Token2022Program; impl Id for Token2022Program { const ID: Address = Address::new_from_array([ - 6, 221, 246, 225, 238, 117, 143, 222, 24, 66, 93, 188, 228, 108, 205, 218, - 182, 26, 252, 77, 131, 185, 13, 39, 254, 189, 249, 40, 216, 161, 139, 252, + 6, 221, 246, 225, 238, 117, 143, 222, 24, 66, 93, 188, 228, 108, 205, 218, 182, 26, 252, + 77, 131, 185, 13, 39, 254, 189, 249, 40, 216, 161, 139, 252, ]); } diff --git a/tokens/token-extensions/mint-close-authority/quasar/Cargo.toml b/tokens/token-extensions/mint-close-authority/quasar/Cargo.toml index 1b1a0b94b..12c8e413a 100644 --- a/tokens/token-extensions/mint-close-authority/quasar/Cargo.toml +++ b/tokens/token-extensions/mint-close-authority/quasar/Cargo.toml @@ -30,6 +30,11 @@ idl-build = ["quasar-lang/idl-build"] # git. Keep this rev in lockstep with the CLI rev installed by # .github/workflows/quasar.yml. quasar-lang = { git = "https://github.com/blueshift-gg/quasar", rev = "be60fca" } +# Pinned: quasar-lang asks for zeropod "0.3.3" and wincode 0.4, but zeropod +# 0.3.4 moved to wincode 0.5, so a fresh resolve (no lockfile is committed) +# splits the graph across two wincode versions and every Pod* trait bound +# fails. Unpin when quasar-lang's pinned rev accepts zeropod 0.3.4+. +zeropod = "=0.3.3" solana-instruction = { version = "3.2.0" } [dev-dependencies] diff --git a/tokens/token-extensions/mint-close-authority/quasar/src/lib.rs b/tokens/token-extensions/mint-close-authority/quasar/src/lib.rs index b02a8b387..d6642f385 100644 --- a/tokens/token-extensions/mint-close-authority/quasar/src/lib.rs +++ b/tokens/token-extensions/mint-close-authority/quasar/src/lib.rs @@ -14,8 +14,8 @@ declare_id!("AcfQLsYKuzprcCNH1n96pKKgAbAnZchwpbr3gbVN742n"); pub struct Token2022Program; impl Id for Token2022Program { const ID: Address = Address::new_from_array([ - 6, 221, 246, 225, 238, 117, 143, 222, 24, 66, 93, 188, 228, 108, 205, 218, - 182, 26, 252, 77, 131, 185, 13, 39, 254, 189, 249, 40, 216, 161, 139, 252, + 6, 221, 246, 225, 238, 117, 143, 222, 24, 66, 93, 188, 228, 108, 205, 218, 182, 26, 252, + 77, 131, 185, 13, 39, 254, 189, 249, 40, 216, 161, 139, 252, ]); } diff --git a/tokens/token-extensions/non-transferable/quasar/Cargo.toml b/tokens/token-extensions/non-transferable/quasar/Cargo.toml index ac1e56e9d..172afe344 100644 --- a/tokens/token-extensions/non-transferable/quasar/Cargo.toml +++ b/tokens/token-extensions/non-transferable/quasar/Cargo.toml @@ -30,6 +30,11 @@ idl-build = ["quasar-lang/idl-build"] # git. Keep this rev in lockstep with the CLI rev installed by # .github/workflows/quasar.yml. quasar-lang = { git = "https://github.com/blueshift-gg/quasar", rev = "be60fca" } +# Pinned: quasar-lang asks for zeropod "0.3.3" and wincode 0.4, but zeropod +# 0.3.4 moved to wincode 0.5, so a fresh resolve (no lockfile is committed) +# splits the graph across two wincode versions and every Pod* trait bound +# fails. Unpin when quasar-lang's pinned rev accepts zeropod 0.3.4+. +zeropod = "=0.3.3" solana-instruction = { version = "3.2.0" } [dev-dependencies] diff --git a/tokens/token-extensions/non-transferable/quasar/src/lib.rs b/tokens/token-extensions/non-transferable/quasar/src/lib.rs index 4e1e6221c..d29eeb4c8 100644 --- a/tokens/token-extensions/non-transferable/quasar/src/lib.rs +++ b/tokens/token-extensions/non-transferable/quasar/src/lib.rs @@ -14,8 +14,8 @@ declare_id!("8Bz4wpHaUckiC169Rg5ZfaBHFemp5S8RwTSDTKzhJ9W"); pub struct Token2022Program; impl Id for Token2022Program { const ID: Address = Address::new_from_array([ - 6, 221, 246, 225, 238, 117, 143, 222, 24, 66, 93, 188, 228, 108, 205, 218, - 182, 26, 252, 77, 131, 185, 13, 39, 254, 189, 249, 40, 216, 161, 139, 252, + 6, 221, 246, 225, 238, 117, 143, 222, 24, 66, 93, 188, 228, 108, 205, 218, 182, 26, 252, + 77, 131, 185, 13, 39, 254, 189, 249, 40, 216, 161, 139, 252, ]); } diff --git a/tokens/token-extensions/permanent-delegate/quasar/Cargo.toml b/tokens/token-extensions/permanent-delegate/quasar/Cargo.toml index 2866619a1..26e7adecf 100644 --- a/tokens/token-extensions/permanent-delegate/quasar/Cargo.toml +++ b/tokens/token-extensions/permanent-delegate/quasar/Cargo.toml @@ -30,6 +30,11 @@ idl-build = ["quasar-lang/idl-build"] # git. Keep this rev in lockstep with the CLI rev installed by # .github/workflows/quasar.yml. quasar-lang = { git = "https://github.com/blueshift-gg/quasar", rev = "be60fca" } +# Pinned: quasar-lang asks for zeropod "0.3.3" and wincode 0.4, but zeropod +# 0.3.4 moved to wincode 0.5, so a fresh resolve (no lockfile is committed) +# splits the graph across two wincode versions and every Pod* trait bound +# fails. Unpin when quasar-lang's pinned rev accepts zeropod 0.3.4+. +zeropod = "=0.3.3" solana-instruction = { version = "3.2.0" } [dev-dependencies] diff --git a/tokens/token-extensions/permanent-delegate/quasar/src/lib.rs b/tokens/token-extensions/permanent-delegate/quasar/src/lib.rs index b60702f25..3f2d9402f 100644 --- a/tokens/token-extensions/permanent-delegate/quasar/src/lib.rs +++ b/tokens/token-extensions/permanent-delegate/quasar/src/lib.rs @@ -14,8 +14,8 @@ declare_id!("A9rxKS84ZoJVyeTfQbCEfxME2vvAM4uwSMjkmhR5XWb1"); pub struct Token2022Program; impl Id for Token2022Program { const ID: Address = Address::new_from_array([ - 6, 221, 246, 225, 238, 117, 143, 222, 24, 66, 93, 188, 228, 108, 205, 218, - 182, 26, 252, 77, 131, 185, 13, 39, 254, 189, 249, 40, 216, 161, 139, 252, + 6, 221, 246, 225, 238, 117, 143, 222, 24, 66, 93, 188, 228, 108, 205, 218, 182, 26, 252, + 77, 131, 185, 13, 39, 254, 189, 249, 40, 216, 161, 139, 252, ]); } diff --git a/tokens/token-extensions/transfer-fee/quasar/Cargo.toml b/tokens/token-extensions/transfer-fee/quasar/Cargo.toml index 24c9e6292..6b80bdb4a 100644 --- a/tokens/token-extensions/transfer-fee/quasar/Cargo.toml +++ b/tokens/token-extensions/transfer-fee/quasar/Cargo.toml @@ -31,6 +31,7 @@ idl-build = ["quasar-lang/idl-build"] # .github/workflows/quasar.yml. quasar-lang = { git = "https://github.com/blueshift-gg/quasar", rev = "be60fca" } solana-instruction = { version = "3.2.0" } +zeropod = "=0.3.3" [dev-dependencies] quasar-test = { git = "https://github.com/blueshift-gg/quasar", rev = "be60fca" } diff --git a/tokens/token-extensions/transfer-fee/quasar/src/lib.rs b/tokens/token-extensions/transfer-fee/quasar/src/lib.rs index f74923a4d..77e57e664 100644 --- a/tokens/token-extensions/transfer-fee/quasar/src/lib.rs +++ b/tokens/token-extensions/transfer-fee/quasar/src/lib.rs @@ -14,8 +14,8 @@ declare_id!("4evptdGtALCNT8uTxJhbWBRZpBE8w5oNtmgfSyfQu7td"); pub struct Token2022Program; impl Id for Token2022Program { const ID: Address = Address::new_from_array([ - 6, 221, 246, 225, 238, 117, 143, 222, 24, 66, 93, 188, 228, 108, 205, 218, - 182, 26, 252, 77, 131, 185, 13, 39, 254, 189, 249, 40, 216, 161, 139, 252, + 6, 221, 246, 225, 238, 117, 143, 222, 24, 66, 93, 188, 228, 108, 205, 218, 182, 26, 252, + 77, 131, 185, 13, 39, 254, 189, 249, 40, 216, 161, 139, 252, ]); } @@ -37,7 +37,11 @@ mod quasar_transfer_fee { /// Transfer tokens with fee. #[instruction(discriminator = 1)] - pub fn transfer(ctx: Ctx, amount: u64, fee: u64) -> Result<(), ProgramError> { + pub fn transfer( + ctx: Ctx, + amount: u64, + fee: u64, + ) -> Result<(), ProgramError> { handle_transfer(&mut ctx.accounts, amount, fee) } @@ -69,61 +73,66 @@ pub struct InitializeAccountConstraints { } #[inline(always)] -fn handle_initialize(accounts: &mut InitializeAccountConstraints, basis_points: u16, max_fee: u64) -> Result<(), ProgramError> { - // 165 (base) + 1 (AccountType) + 4 (TLV header) + 108 (TransferFeeConfig data) = 278 bytes - let mint_size: u64 = 278; - let lamports = Rent::get()?.try_minimum_balance(mint_size as usize)?; - - accounts.system_program - .create_account( - &accounts.payer, - &accounts.mint_account, - lamports, - mint_size, - accounts.token_program.to_account_view().address(), - ) - .invoke()?; - - // TransferFeeExtension opcode 26, sub-instruction 0 = InitializeTransferFeeConfig - // Data: [26, 0, COption_flag(1), config_authority(32), COption_flag(1), withdraw_authority(32), - // basis_points(u16 LE), max_fee(u64 LE)] - let mut ext_data = [0u8; 78]; - ext_data[0] = 26; // TransferFeeExtension - ext_data[1] = 0; // InitializeTransferFeeConfig sub-instruction - ext_data[2] = 1; // COption::Some for config_authority - ext_data[3..35].copy_from_slice(accounts.payer.to_account_view().address().as_ref()); - ext_data[35] = 1; // COption::Some for withdraw_authority - ext_data[36..68].copy_from_slice(accounts.payer.to_account_view().address().as_ref()); - ext_data[68..70].copy_from_slice(&basis_points.to_le_bytes()); - ext_data[70..78].copy_from_slice(&max_fee.to_le_bytes()); - - CpiCall::new( +fn handle_initialize( + accounts: &mut InitializeAccountConstraints, + basis_points: u16, + max_fee: u64, +) -> Result<(), ProgramError> { + // 165 (base) + 1 (AccountType) + 4 (TLV header) + 108 (TransferFeeConfig data) = 278 bytes + let mint_size: u64 = 278; + let lamports = Rent::get()?.try_minimum_balance(mint_size as usize)?; + + accounts + .system_program + .create_account( + &accounts.payer, + &accounts.mint_account, + lamports, + mint_size, accounts.token_program.to_account_view().address(), - [InstructionAccount::writable( - accounts.mint_account.to_account_view().address(), - )], - [accounts.mint_account.to_account_view()], - ext_data, ) .invoke()?; - // InitializeMint2 - let mut mint_data = [0u8; 67]; - mint_data[0] = 20; - mint_data[1] = 2; - mint_data[2..34].copy_from_slice(accounts.payer.to_account_view().address().as_ref()); - mint_data[34] = 1; - mint_data[35..67].copy_from_slice(accounts.payer.to_account_view().address().as_ref()); - - CpiCall::new( - accounts.token_program.to_account_view().address(), - [InstructionAccount::writable( - accounts.mint_account.to_account_view().address(), - )], - [accounts.mint_account.to_account_view()], - mint_data, - ) - .invoke() + // TransferFeeExtension opcode 26, sub-instruction 0 = InitializeTransferFeeConfig + // Data: [26, 0, COption_flag(1), config_authority(32), COption_flag(1), withdraw_authority(32), + // basis_points(u16 LE), max_fee(u64 LE)] + let mut ext_data = [0u8; 78]; + ext_data[0] = 26; // TransferFeeExtension + ext_data[1] = 0; // InitializeTransferFeeConfig sub-instruction + ext_data[2] = 1; // COption::Some for config_authority + ext_data[3..35].copy_from_slice(accounts.payer.to_account_view().address().as_ref()); + ext_data[35] = 1; // COption::Some for withdraw_authority + ext_data[36..68].copy_from_slice(accounts.payer.to_account_view().address().as_ref()); + ext_data[68..70].copy_from_slice(&basis_points.to_le_bytes()); + ext_data[70..78].copy_from_slice(&max_fee.to_le_bytes()); + + CpiCall::new( + accounts.token_program.to_account_view().address(), + [InstructionAccount::writable( + accounts.mint_account.to_account_view().address(), + )], + [accounts.mint_account.to_account_view()], + ext_data, + ) + .invoke()?; + + // InitializeMint2 + let mut mint_data = [0u8; 67]; + mint_data[0] = 20; + mint_data[1] = 2; + mint_data[2..34].copy_from_slice(accounts.payer.to_account_view().address().as_ref()); + mint_data[34] = 1; + mint_data[35..67].copy_from_slice(accounts.payer.to_account_view().address().as_ref()); + + CpiCall::new( + accounts.token_program.to_account_view().address(), + [InstructionAccount::writable( + accounts.mint_account.to_account_view().address(), + )], + [accounts.mint_account.to_account_view()], + mint_data, + ) + .invoke() } #[derive(Accounts)] @@ -139,32 +148,36 @@ pub struct TransferAccountConstraints { } #[inline(always)] -fn handle_transfer(accounts: &mut TransferAccountConstraints, amount: u64, fee: u64) -> Result<(), ProgramError> { - // TransferCheckedWithFee: opcode 37 - // Data: [37, amount (u64 LE), decimals (u8), fee (u64 LE)] - let mut data = [0u8; 18]; - data[0] = 37; - data[1..9].copy_from_slice(&amount.to_le_bytes()); - data[9] = 2; // decimals - data[10..18].copy_from_slice(&fee.to_le_bytes()); - - CpiCall::new( - accounts.token_program.to_account_view().address(), - [ - InstructionAccount::writable(accounts.from.to_account_view().address()), - InstructionAccount::readonly(accounts.mint.to_account_view().address()), - InstructionAccount::writable(accounts.to.to_account_view().address()), - InstructionAccount::readonly_signer(accounts.sender.to_account_view().address()), - ], - [ - accounts.from.to_account_view(), - accounts.mint.to_account_view(), - accounts.to.to_account_view(), - accounts.sender.to_account_view(), - ], - data, - ) - .invoke() +fn handle_transfer( + accounts: &mut TransferAccountConstraints, + amount: u64, + fee: u64, +) -> Result<(), ProgramError> { + // TransferCheckedWithFee: opcode 37 + // Data: [37, amount (u64 LE), decimals (u8), fee (u64 LE)] + let mut data = [0u8; 18]; + data[0] = 37; + data[1..9].copy_from_slice(&amount.to_le_bytes()); + data[9] = 2; // decimals + data[10..18].copy_from_slice(&fee.to_le_bytes()); + + CpiCall::new( + accounts.token_program.to_account_view().address(), + [ + InstructionAccount::writable(accounts.from.to_account_view().address()), + InstructionAccount::readonly(accounts.mint.to_account_view().address()), + InstructionAccount::writable(accounts.to.to_account_view().address()), + InstructionAccount::readonly_signer(accounts.sender.to_account_view().address()), + ], + [ + accounts.from.to_account_view(), + accounts.mint.to_account_view(), + accounts.to.to_account_view(), + accounts.sender.to_account_view(), + ], + data, + ) + .invoke() } #[derive(Accounts)] @@ -176,29 +189,33 @@ pub struct UpdateFeeAccountConstraints { } #[inline(always)] -fn handle_update_fee(accounts: &mut UpdateFeeAccountConstraints, basis_points: u16, max_fee: u64) -> Result<(), ProgramError> { - // SetTransferFee: opcode 26, sub-opcode 4 - // Actually: extension instruction layout is different. - // TransferFeeInstruction::SetTransferFee = 4 within type 26 - let mut data = [0u8; 12]; - data[0] = 26; - data[1] = 4; // SetTransferFee sub-instruction - data[2..4].copy_from_slice(&basis_points.to_le_bytes()); - data[4..12].copy_from_slice(&max_fee.to_le_bytes()); - - CpiCall::new( - accounts.token_program.to_account_view().address(), - [ - InstructionAccount::writable(accounts.mint_account.to_account_view().address()), - InstructionAccount::readonly_signer(accounts.authority.to_account_view().address()), - ], - [ - accounts.mint_account.to_account_view(), - accounts.authority.to_account_view(), - ], - data, - ) - .invoke() +fn handle_update_fee( + accounts: &mut UpdateFeeAccountConstraints, + basis_points: u16, + max_fee: u64, +) -> Result<(), ProgramError> { + // SetTransferFee: opcode 26, sub-opcode 4 + // Actually: extension instruction layout is different. + // TransferFeeInstruction::SetTransferFee = 4 within type 26 + let mut data = [0u8; 12]; + data[0] = 26; + data[1] = 4; // SetTransferFee sub-instruction + data[2..4].copy_from_slice(&basis_points.to_le_bytes()); + data[4..12].copy_from_slice(&max_fee.to_le_bytes()); + + CpiCall::new( + accounts.token_program.to_account_view().address(), + [ + InstructionAccount::writable(accounts.mint_account.to_account_view().address()), + InstructionAccount::readonly_signer(accounts.authority.to_account_view().address()), + ], + [ + accounts.mint_account.to_account_view(), + accounts.authority.to_account_view(), + ], + data, + ) + .invoke() } #[derive(Accounts)] @@ -213,22 +230,22 @@ pub struct WithdrawAccountConstraints { #[inline(always)] fn handle_withdraw(accounts: &mut WithdrawAccountConstraints) -> Result<(), ProgramError> { - // WithdrawWithheldTokensFromMint: opcode 26, sub-opcode 3 - let data: [u8; 2] = [26, 3]; - - CpiCall::new( - accounts.token_program.to_account_view().address(), - [ - InstructionAccount::writable(accounts.mint_account.to_account_view().address()), - InstructionAccount::writable(accounts.destination.to_account_view().address()), - InstructionAccount::readonly_signer(accounts.authority.to_account_view().address()), - ], - [ - accounts.mint_account.to_account_view(), - accounts.destination.to_account_view(), - accounts.authority.to_account_view(), - ], - data, - ) - .invoke() + // WithdrawWithheldTokensFromMint: opcode 26, sub-opcode 3 + let data: [u8; 2] = [26, 3]; + + CpiCall::new( + accounts.token_program.to_account_view().address(), + [ + InstructionAccount::writable(accounts.mint_account.to_account_view().address()), + InstructionAccount::writable(accounts.destination.to_account_view().address()), + InstructionAccount::readonly_signer(accounts.authority.to_account_view().address()), + ], + [ + accounts.mint_account.to_account_view(), + accounts.destination.to_account_view(), + accounts.authority.to_account_view(), + ], + data, + ) + .invoke() } diff --git a/tokens/token-extensions/transfer-hook/account-data-as-seed/quasar/Cargo.toml b/tokens/token-extensions/transfer-hook/account-data-as-seed/quasar/Cargo.toml index ecbf82dfd..8075ac69a 100644 --- a/tokens/token-extensions/transfer-hook/account-data-as-seed/quasar/Cargo.toml +++ b/tokens/token-extensions/transfer-hook/account-data-as-seed/quasar/Cargo.toml @@ -30,6 +30,11 @@ idl-build = ["quasar-lang/idl-build"] # git. Keep this rev in lockstep with the CLI rev installed by # .github/workflows/quasar.yml. quasar-lang = { git = "https://github.com/blueshift-gg/quasar", rev = "be60fca" } +# Pinned: quasar-lang asks for zeropod "0.3.3" and wincode 0.4, but zeropod +# 0.3.4 moved to wincode 0.5, so a fresh resolve (no lockfile is committed) +# splits the graph across two wincode versions and every Pod* trait bound +# fails. Unpin when quasar-lang's pinned rev accepts zeropod 0.3.4+. +zeropod = "=0.3.3" quasar-spl = { git = "https://github.com/blueshift-gg/quasar", rev = "be60fca" } solana-instruction = { version = "3.2.0" } diff --git a/tokens/token-extensions/transfer-hook/account-data-as-seed/quasar/src/lib.rs b/tokens/token-extensions/transfer-hook/account-data-as-seed/quasar/src/lib.rs index 0b8ad54b0..9d7e2fe28 100644 --- a/tokens/token-extensions/transfer-hook/account-data-as-seed/quasar/src/lib.rs +++ b/tokens/token-extensions/transfer-hook/account-data-as-seed/quasar/src/lib.rs @@ -1,10 +1,7 @@ #![cfg_attr(not(test), no_std)] use quasar_lang::sysvars::Sysvar; -use quasar_lang::{ - cpi::Seed, - prelude::*, -}; +use quasar_lang::{cpi::Seed, prelude::*}; #[cfg(test)] mod tests; @@ -36,7 +33,10 @@ mod quasar_transfer_hook_account_data_as_seed { /// Transfer hook handler - increments a per-owner counter on each transfer. /// Discriminator = sha256("spl-transfer-hook-interface:execute")[:8] #[instruction(discriminator = [105, 37, 101, 197, 75, 251, 102, 26])] - pub fn transfer_hook(ctx: Ctx, _amount: u64) -> Result<(), ProgramError> { + pub fn transfer_hook( + ctx: Ctx, + _amount: u64, + ) -> Result<(), ProgramError> { handle_transfer_hook(&mut ctx.accounts) } } @@ -60,7 +60,9 @@ pub struct InitializeExtraAccountMetaListAccountConstraints { } #[inline(always)] -pub fn handle_initialize_extra_account_meta_list(accounts: &mut InitializeExtraAccountMetaListAccountConstraints) -> Result<(), ProgramError> { +pub fn handle_initialize_extra_account_meta_list( + accounts: &mut InitializeExtraAccountMetaListAccountConstraints, +) -> Result<(), ProgramError> { // ExtraAccountMetaList with 1 extra account. // ExtraAccountMeta for a PDA with seeds [Literal("counter"), AccountData(0, 32, 32)]: // The AccountData seed resolves the owner pubkey from account_index=0 @@ -92,7 +94,8 @@ pub fn handle_initialize_extra_account_meta_list(accounts: &mut InitializeExtraA Seed::from(&bump_bytes as &[u8]), ]; - accounts.system_program + accounts + .system_program .create_account( &accounts.payer, &accounts.extra_account_meta_list, @@ -104,8 +107,7 @@ pub fn handle_initialize_extra_account_meta_list(accounts: &mut InitializeExtraA // Write TLV data let view = unsafe { - &mut *(&mut accounts.extra_account_meta_list as *mut UncheckedAccount - as *mut AccountView) + &mut *(&mut accounts.extra_account_meta_list as *mut UncheckedAccount as *mut AccountView) }; let mut data = view.try_borrow_mut()?; @@ -117,7 +119,7 @@ pub fn handle_initialize_extra_account_meta_list(accounts: &mut InitializeExtraA data[16] = 1; // discriminator: PDA from seeds let mut config = [0u8; 32]; config[0] = 2; // number of seeds - // Seed 0: Literal "counter" + // Seed 0: Literal "counter" config[1] = 0; // seed type: literal config[2] = 7; // seed length config[3..10].copy_from_slice(b"counter"); @@ -135,8 +137,10 @@ pub fn handle_initialize_extra_account_meta_list(accounts: &mut InitializeExtraA let counter_size: u64 = 16; let counter_lamports = Rent::get()?.try_minimum_balance(counter_size as usize)?; - let (counter_pda, counter_bump) = - quasar_lang::pda::try_find_program_address(&[b"counter", payer_address.as_ref()], &crate::ID)?; + let (counter_pda, counter_bump) = quasar_lang::pda::try_find_program_address( + &[b"counter", payer_address.as_ref()], + &crate::ID, + )?; if accounts.counter_account.to_account_view().address() != &counter_pda { return Err(ProgramError::InvalidSeeds); @@ -149,7 +153,8 @@ pub fn handle_initialize_extra_account_meta_list(accounts: &mut InitializeExtraA Seed::from(&counter_bump_bytes as &[u8]), ]; - accounts.system_program + accounts + .system_program .create_account( &accounts.payer, &accounts.counter_account, @@ -180,10 +185,11 @@ pub struct TransferHookAccountConstraints { } #[inline(always)] -pub fn handle_transfer_hook(accounts: &mut TransferHookAccountConstraints) -> Result<(), ProgramError> { +pub fn handle_transfer_hook( + accounts: &mut TransferHookAccountConstraints, +) -> Result<(), ProgramError> { let view = unsafe { - &mut *(&mut accounts.counter_account as *mut UncheckedAccount - as *mut AccountView) + &mut *(&mut accounts.counter_account as *mut UncheckedAccount as *mut AccountView) }; let mut data = view.try_borrow_mut()?; diff --git a/tokens/token-extensions/transfer-hook/allow-block-list-token/quasar/Cargo.toml b/tokens/token-extensions/transfer-hook/allow-block-list-token/quasar/Cargo.toml index ecb788837..3d9575cdb 100644 --- a/tokens/token-extensions/transfer-hook/allow-block-list-token/quasar/Cargo.toml +++ b/tokens/token-extensions/transfer-hook/allow-block-list-token/quasar/Cargo.toml @@ -30,6 +30,11 @@ idl-build = ["quasar-lang/idl-build"] # git. Keep this rev in lockstep with the CLI rev installed by # .github/workflows/quasar.yml. quasar-lang = { git = "https://github.com/blueshift-gg/quasar", rev = "be60fca" } +# Pinned: quasar-lang asks for zeropod "0.3.3" and wincode 0.4, but zeropod +# 0.3.4 moved to wincode 0.5, so a fresh resolve (no lockfile is committed) +# splits the graph across two wincode versions and every Pod* trait bound +# fails. Unpin when quasar-lang's pinned rev accepts zeropod 0.3.4+. +zeropod = "=0.3.3" quasar-spl = { git = "https://github.com/blueshift-gg/quasar", rev = "be60fca" } solana-instruction = { version = "3.2.0" } diff --git a/tokens/token-extensions/transfer-hook/allow-block-list-token/quasar/src/errors.rs b/tokens/token-extensions/transfer-hook/allow-block-list-token/quasar/src/errors.rs index ef648b0c7..df447aec7 100644 --- a/tokens/token-extensions/transfer-hook/allow-block-list-token/quasar/src/errors.rs +++ b/tokens/token-extensions/transfer-hook/allow-block-list-token/quasar/src/errors.rs @@ -1,7 +1,7 @@ use quasar_lang::prelude::ProgramError; -/// Custom error codes for the allow/block list program. -/// Encoded as ProgramError::Custom(N). +// Custom error codes for the allow/block list program. +// Encoded as ProgramError::Custom(N). pub const ERROR_INVALID_METADATA: u32 = 6000; pub const ERROR_WALLET_NOT_ALLOWED: u32 = 6001; diff --git a/tokens/token-extensions/transfer-hook/allow-block-list-token/quasar/src/instructions/attach_to_mint.rs b/tokens/token-extensions/transfer-hook/allow-block-list-token/quasar/src/instructions/attach_to_mint.rs index 0f7f82551..78461e05f 100644 --- a/tokens/token-extensions/transfer-hook/allow-block-list-token/quasar/src/instructions/attach_to_mint.rs +++ b/tokens/token-extensions/transfer-hook/allow-block-list-token/quasar/src/instructions/attach_to_mint.rs @@ -18,7 +18,9 @@ pub struct AttachToMintAccountConstraints { } #[inline(always)] -pub fn handle_attach_to_mint(accounts: &mut AttachToMintAccountConstraints) -> Result<(), ProgramError> { +pub fn handle_attach_to_mint( + accounts: &mut AttachToMintAccountConstraints, +) -> Result<(), ProgramError> { let mint_key = accounts.mint.to_account_view().address(); let payer_key = accounts.payer.to_account_view().address(); let token_prog = accounts.token_program.to_account_view().address(); @@ -28,7 +30,7 @@ pub fn handle_attach_to_mint(accounts: &mut AttachToMintAccountConstraints) -> R let mut update_data = [0u8; 37]; update_data[0] = 36; update_data[1] = 1; // Update sub-instruction - // COption: 4 bytes discriminator (1 = Some) + 32 bytes pubkey + // COption: 4 bytes discriminator (1 = Some) + 32 bytes pubkey update_data[2..6].copy_from_slice(&1u32.to_le_bytes()); // Some update_data[6..38 - 1].copy_from_slice(&crate::ID.as_ref()[..31]); // Actually, COption encoding is: [1u8 if Some, 0 if None] but SPL uses 4 bytes @@ -39,8 +41,8 @@ pub fn handle_attach_to_mint(accounts: &mut AttachToMintAccountConstraints) -> R // Total ix data = 2 (opcode + sub) + 4 + 32 = 38 // Let me redo this properly. let mut update_data = [0u8; 38]; - update_data[0] = 36; // TransferHookExtension opcode - update_data[1] = 1; // Update sub-instruction + update_data[0] = 36; // TransferHookExtension opcode + update_data[1] = 1; // Update sub-instruction update_data[2..6].copy_from_slice(&1u32.to_le_bytes()); // COption::Some update_data[6..38].copy_from_slice(crate::ID.as_ref()); @@ -77,7 +79,8 @@ pub fn handle_attach_to_mint(accounts: &mut AttachToMintAccountConstraints) -> R Seed::from(&bump_bytes as &[u8]), ]; - accounts.system_program + accounts + .system_program .create_account( &accounts.payer, &accounts.extra_metas_account, @@ -89,8 +92,7 @@ pub fn handle_attach_to_mint(accounts: &mut AttachToMintAccountConstraints) -> R // Write ExtraAccountMeta TLV data let view = unsafe { - &mut *(&mut accounts.extra_metas_account as *mut UncheckedAccount - as *mut AccountView) + &mut *(&mut accounts.extra_metas_account as *mut UncheckedAccount as *mut AccountView) }; let mut data = view.try_borrow_mut()?; @@ -101,9 +103,9 @@ pub fn handle_attach_to_mint(accounts: &mut AttachToMintAccountConstraints) -> R // ABWallet PDA: seeds = [Literal("ab_wallet"), AccountData(2, 32, 32)] data[16] = 1; // PDA from seeds let mut config = [0u8; 32]; - config[0] = 2; // 2 seeds - config[1] = 0; // literal - config[2] = 9; // length + config[0] = 2; // 2 seeds + config[1] = 0; // literal + config[2] = 9; // length config[3..12].copy_from_slice(AB_WALLET_SEED); config[12] = 1; // account data config[13] = 2; // account_index (destination token account) diff --git a/tokens/token-extensions/transfer-hook/allow-block-list-token/quasar/src/instructions/change_mode.rs b/tokens/token-extensions/transfer-hook/allow-block-list-token/quasar/src/instructions/change_mode.rs index 09b5dae50..da5449fcf 100644 --- a/tokens/token-extensions/transfer-hook/allow-block-list-token/quasar/src/instructions/change_mode.rs +++ b/tokens/token-extensions/transfer-hook/allow-block-list-token/quasar/src/instructions/change_mode.rs @@ -17,7 +17,11 @@ pub struct ChangeModeAccountConstraints { } #[inline(always)] -pub fn handle_change_mode(accounts: &mut ChangeModeAccountConstraints, mode: u8, threshold: u64) -> Result<(), ProgramError> { +pub fn handle_change_mode( + accounts: &mut ChangeModeAccountConstraints, + mode: u8, + threshold: u64, +) -> Result<(), ProgramError> { let mode_value = mode_to_metadata_value(mode); let token_prog = accounts.token_program.to_account_view().address(); let mint_key = accounts.mint.to_account_view().address(); @@ -51,7 +55,8 @@ pub fn handle_change_mode(accounts: &mut ChangeModeAccountConstraints, mode: u8, let current_lamports = mint_view.lamports(); if min_balance > current_lamports { let diff = min_balance - current_lamports; - accounts.system_program + accounts + .system_program .transfer(&accounts.authority, &accounts.mint, diff) .invoke()?; } @@ -127,7 +132,8 @@ fn has_threshold_in_metadata(ctx: &ChangeModeAccountConstraints) -> Result md.len() { return Ok(false); } - let slen = u32::from_le_bytes([md[mpos], md[mpos+1], md[mpos+2], md[mpos+3]]) as usize; + let slen = u32::from_le_bytes([md[mpos], md[mpos + 1], md[mpos + 2], md[mpos + 3]]) + as usize; mpos += 4 + slen; } @@ -135,14 +141,17 @@ fn has_threshold_in_metadata(ctx: &ChangeModeAccountConstraints) -> Result md.len() { return Ok(false); } - let kv_count = u32::from_le_bytes([md[mpos], md[mpos+1], md[mpos+2], md[mpos+3]]) as usize; + let kv_count = + u32::from_le_bytes([md[mpos], md[mpos + 1], md[mpos + 2], md[mpos + 3]]) as usize; mpos += 4; for _ in 0..kv_count { if mpos + 4 > md.len() { break; } - let key_len = u32::from_le_bytes([md[mpos], md[mpos+1], md[mpos+2], md[mpos+3]]) as usize; + let key_len = + u32::from_le_bytes([md[mpos], md[mpos + 1], md[mpos + 2], md[mpos + 3]]) + as usize; mpos += 4; if mpos + key_len > md.len() { break; @@ -153,7 +162,9 @@ fn has_threshold_in_metadata(ctx: &ChangeModeAccountConstraints) -> Result md.len() { break; } - let val_len = u32::from_le_bytes([md[mpos], md[mpos+1], md[mpos+2], md[mpos+3]]) as usize; + let val_len = + u32::from_le_bytes([md[mpos], md[mpos + 1], md[mpos + 2], md[mpos + 3]]) + as usize; mpos += 4 + val_len; if key == b"threshold" { diff --git a/tokens/token-extensions/transfer-hook/allow-block-list-token/quasar/src/instructions/init_config.rs b/tokens/token-extensions/transfer-hook/allow-block-list-token/quasar/src/instructions/init_config.rs index afe2e30dc..27297455d 100644 --- a/tokens/token-extensions/transfer-hook/allow-block-list-token/quasar/src/instructions/init_config.rs +++ b/tokens/token-extensions/transfer-hook/allow-block-list-token/quasar/src/instructions/init_config.rs @@ -16,7 +16,8 @@ pub struct InitConfigAccountConstraints { #[inline(always)] pub fn handle_init_config(accounts: &mut InitConfigAccountConstraints) -> Result<(), ProgramError> { - let (config_pda, bump) = quasar_lang::pda::try_find_program_address(&[CONFIG_SEED], &crate::ID)?; + let (config_pda, bump) = + quasar_lang::pda::try_find_program_address(&[CONFIG_SEED], &crate::ID)?; if accounts.config.to_account_view().address() != &config_pda { return Err(ProgramError::InvalidSeeds); @@ -24,12 +25,10 @@ pub fn handle_init_config(accounts: &mut InitConfigAccountConstraints) -> Result let lamports = Rent::get()?.try_minimum_balance(CONFIG_SIZE as usize)?; let bump_bytes = [bump]; - let seeds = [ - Seed::from(CONFIG_SEED), - Seed::from(&bump_bytes as &[u8]), - ]; + let seeds = [Seed::from(CONFIG_SEED), Seed::from(&bump_bytes as &[u8])]; - accounts.system_program + accounts + .system_program .create_account( &accounts.payer, &accounts.config, @@ -39,10 +38,7 @@ pub fn handle_init_config(accounts: &mut InitConfigAccountConstraints) -> Result ) .invoke_signed(&seeds)?; - let view = unsafe { - &mut *(&mut accounts.config as *mut UncheckedAccount - as *mut AccountView) - }; + let view = unsafe { &mut *(&mut accounts.config as *mut UncheckedAccount as *mut AccountView) }; let mut data = view.try_borrow_mut()?; write_config(&mut data, accounts.payer.to_account_view().address(), bump); diff --git a/tokens/token-extensions/transfer-hook/allow-block-list-token/quasar/src/instructions/init_mint.rs b/tokens/token-extensions/transfer-hook/allow-block-list-token/quasar/src/instructions/init_mint.rs index ea6a20ed9..c0e8bdc86 100644 --- a/tokens/token-extensions/transfer-hook/allow-block-list-token/quasar/src/instructions/init_mint.rs +++ b/tokens/token-extensions/transfer-hook/allow-block-list-token/quasar/src/instructions/init_mint.rs @@ -9,8 +9,8 @@ use crate::state::mode_to_metadata_value; pub struct Token2022; impl Id for Token2022 { const ID: Address = Address::new_from_array([ - 6, 221, 246, 225, 238, 117, 143, 222, 24, 66, 93, 188, 228, 108, 205, 218, - 182, 26, 252, 77, 131, 185, 13, 39, 254, 189, 249, 40, 216, 161, 139, 252, + 6, 221, 246, 225, 238, 117, 143, 222, 24, 66, 93, 188, 228, 108, 205, 218, 182, 26, 252, + 77, 131, 185, 13, 39, 254, 189, 249, 40, 216, 161, 139, 252, ]); } @@ -28,18 +28,35 @@ pub struct InitMintAccountConstraints { pub token_program: Program, } +/// The authorities stamped onto the new mint's extensions. +pub struct MintAuthorities<'a> { + pub freeze: &'a Address, + pub permanent_delegate: &'a Address, + pub transfer_hook: &'a Address, +} + +/// The token metadata written into the mint's own metadata extension. +pub struct MintMetadata<'a> { + pub name: &'a [u8], + pub symbol: &'a [u8], + pub uri: &'a [u8], +} + #[inline(always)] pub fn handle_init_mint( - accounts: &mut InitMintAccountConstraints, decimals: u8, - freeze_authority: &Address, - permanent_delegate: &Address, - transfer_hook_authority: &Address, + accounts: &mut InitMintAccountConstraints, + decimals: u8, + authorities: &MintAuthorities, + metadata: &MintMetadata, mode: u8, threshold: u64, - name: &[u8], - symbol: &[u8], - uri: &[u8], ) -> Result<(), ProgramError> { + let MintAuthorities { + freeze: freeze_authority, + permanent_delegate, + transfer_hook: transfer_hook_authority, + } = *authorities; + let MintMetadata { name, symbol, uri } = *metadata; let payer_key = accounts.payer.to_account_view().address(); let mint_key = accounts.mint.to_account_view().address(); let token_prog = accounts.token_program.to_account_view().address(); @@ -62,16 +79,31 @@ pub fn handle_init_mint( } else { 0 }; - let metadata_data_len = 32 + 32 + 4 + name.len() + 4 + symbol.len() + 4 + uri.len() - + 4 + additional_len + threshold_additional; + let metadata_data_len = 32 + + 32 + + 4 + + name.len() + + 4 + + symbol.len() + + 4 + + uri.len() + + 4 + + additional_len + + threshold_additional; let total_ext_data = 4 + metadata_data_len; let mint_size = 82 + 82 + 1 + 68 + 36 + 68 + total_ext_data; let lamports = Rent::get()?.try_minimum_balance(mint_size)?; // Create the mint account owned by Token2022. - accounts.system_program + accounts + .system_program .create_account( - &accounts.payer, &accounts.mint, lamports, mint_size as u64, token_prog) + &accounts.payer, + &accounts.mint, + lamports, + mint_size as u64, + token_prog, + ) .invoke()?; // Initialize PermanentDelegate extension: opcode 35 @@ -191,7 +223,11 @@ pub fn handle_init_mint( /// Emit a Token Extensions TokenMetadataUpdateField CPI. /// Opcode 44, sub-opcode 1, followed by Field::Key (discriminator 2, then borsh /// string for key, then borsh string for value). -fn emit_update_field_cpi(ctx: &InitMintAccountConstraints, key: &[u8], value: &[u8]) -> Result<(), ProgramError> { +fn emit_update_field_cpi( + ctx: &InitMintAccountConstraints, + key: &[u8], + value: &[u8], +) -> Result<(), ProgramError> { let token_prog = ctx.token_program.to_account_view().address(); let mint_key = ctx.mint.to_account_view().address(); let payer_key = ctx.payer.to_account_view().address(); @@ -266,12 +302,16 @@ fn init_extra_metas(ctx: &mut InitMintAccountConstraints) -> Result<(), ProgramE ctx.system_program .create_account( - &ctx.payer, &ctx.extra_metas_account, lamports, meta_list_size, &crate::ID) + &ctx.payer, + &ctx.extra_metas_account, + lamports, + meta_list_size, + &crate::ID, + ) .invoke_signed(&seeds)?; let view = unsafe { - &mut *(&mut ctx.extra_metas_account as *mut UncheckedAccount - as *mut AccountView) + &mut *(&mut ctx.extra_metas_account as *mut UncheckedAccount as *mut AccountView) }; let mut data = view.try_borrow_mut()?; @@ -285,11 +325,11 @@ fn init_extra_metas(ctx: &mut InitMintAccountConstraints) -> Result<(), ProgramE // Account index 2 = destination token account; data_index 32 = owner field. data[16] = 1; // discriminator: PDA from seeds let mut config = [0u8; 32]; - config[0] = 2; // number of seeds + config[0] = 2; // number of seeds // Seed 0: Literal "ab_wallet" - config[1] = 0; // seed type: literal - config[2] = 9; // seed length + config[1] = 0; // seed type: literal + config[2] = 9; // seed length config[3..12].copy_from_slice(AB_WALLET_SEED); // Seed 1: AccountData(account_index=2, data_index=32, length=32) diff --git a/tokens/token-extensions/transfer-hook/allow-block-list-token/quasar/src/instructions/init_wallet.rs b/tokens/token-extensions/transfer-hook/allow-block-list-token/quasar/src/instructions/init_wallet.rs index 26361bf01..c733de805 100644 --- a/tokens/token-extensions/transfer-hook/allow-block-list-token/quasar/src/instructions/init_wallet.rs +++ b/tokens/token-extensions/transfer-hook/allow-block-list-token/quasar/src/instructions/init_wallet.rs @@ -18,7 +18,10 @@ pub struct InitWalletAccountConstraints { } #[inline(always)] -pub fn handle_init_wallet(accounts: &mut InitWalletAccountConstraints, allowed: bool) -> Result<(), ProgramError> { +pub fn handle_init_wallet( + accounts: &mut InitWalletAccountConstraints, + allowed: bool, +) -> Result<(), ProgramError> { // Verify config PDA let (config_pda, _) = quasar_lang::pda::try_find_program_address(&[CONFIG_SEED], &crate::ID)?; if accounts.config.to_account_view().address() != &config_pda { @@ -55,7 +58,8 @@ pub fn handle_init_wallet(accounts: &mut InitWalletAccountConstraints, allowed: Seed::from(&bump_bytes as &[u8]), ]; - accounts.system_program + accounts + .system_program .create_account( &accounts.authority, &accounts.ab_wallet, @@ -66,10 +70,8 @@ pub fn handle_init_wallet(accounts: &mut InitWalletAccountConstraints, allowed: .invoke_signed(&seeds)?; // Write wallet data - let view = unsafe { - &mut *(&mut accounts.ab_wallet as *mut UncheckedAccount - as *mut AccountView) - }; + let view = + unsafe { &mut *(&mut accounts.ab_wallet as *mut UncheckedAccount as *mut AccountView) }; let mut data = view.try_borrow_mut()?; write_ab_wallet(&mut data, wallet_key, allowed); diff --git a/tokens/token-extensions/transfer-hook/allow-block-list-token/quasar/src/instructions/remove_wallet.rs b/tokens/token-extensions/transfer-hook/allow-block-list-token/quasar/src/instructions/remove_wallet.rs index 3770bc5c8..9de026684 100644 --- a/tokens/token-extensions/transfer-hook/allow-block-list-token/quasar/src/instructions/remove_wallet.rs +++ b/tokens/token-extensions/transfer-hook/allow-block-list-token/quasar/src/instructions/remove_wallet.rs @@ -14,7 +14,9 @@ pub struct RemoveWalletAccountConstraints { } #[inline(always)] -pub fn handle_remove_wallet(accounts: &mut RemoveWalletAccountConstraints) -> Result<(), ProgramError> { +pub fn handle_remove_wallet( + accounts: &mut RemoveWalletAccountConstraints, +) -> Result<(), ProgramError> { // Verify config PDA let (config_pda, _) = quasar_lang::pda::try_find_program_address(&[CONFIG_SEED], &crate::ID)?; if accounts.config.to_account_view().address() != &config_pda { @@ -43,10 +45,8 @@ pub fn handle_remove_wallet(accounts: &mut RemoveWalletAccountConstraints) -> Re set_lamports(authority_view, authority_view.lamports() + wallet_lamports); // Zero the account data - let mview = unsafe { - &mut *(&mut accounts.ab_wallet as *mut UncheckedAccount - as *mut AccountView) - }; + let mview = + unsafe { &mut *(&mut accounts.ab_wallet as *mut UncheckedAccount as *mut AccountView) }; let mut data = mview.try_borrow_mut()?; for byte in data.iter_mut() { *byte = 0; diff --git a/tokens/token-extensions/transfer-hook/allow-block-list-token/quasar/src/instructions/tx_hook.rs b/tokens/token-extensions/transfer-hook/allow-block-list-token/quasar/src/instructions/tx_hook.rs index b2cc6c8a5..ca00bdaf4 100644 --- a/tokens/token-extensions/transfer-hook/allow-block-list-token/quasar/src/instructions/tx_hook.rs +++ b/tokens/token-extensions/transfer-hook/allow-block-list-token/quasar/src/instructions/tx_hook.rs @@ -1,7 +1,7 @@ use quasar_lang::prelude::*; use crate::errors; -use crate::state::{read_wallet_allowed, MODE_ALLOW, MODE_BLOCK, MODE_MIXED, AB_WALLET_SIZE}; +use crate::state::{read_wallet_allowed, AB_WALLET_SIZE, MODE_ALLOW, MODE_BLOCK, MODE_MIXED}; /// Transfer hook handler. Called by Token Extensions during transfers. /// @@ -23,7 +23,10 @@ pub struct TxHookAccountConstraints { } #[inline(always)] -pub fn handle_tx_hook(accounts: &mut TxHookAccountConstraints, amount: u64) -> Result<(), ProgramError> { +pub fn handle_tx_hook( + accounts: &mut TxHookAccountConstraints, + amount: u64, +) -> Result<(), ProgramError> { let mint_view = accounts.mint.to_account_view(); let mint_data = mint_view.try_borrow()?; @@ -42,16 +45,16 @@ pub fn handle_tx_hook(accounts: &mut TxHookAccountConstraints, amount: u64) -> R (DecodedMintMode::Block, _) => Ok(()), // Mixed/Threshold mode: check amount threshold - (DecodedMintMode::Threshold(threshold), DecodedWalletMode::None) - if amount >= threshold => - { + (DecodedMintMode::Threshold(threshold), DecodedWalletMode::None) if amount >= threshold => { Err(errors::amount_not_allowed()) } (DecodedMintMode::Threshold(_), _) => Ok(()), } } -fn decode_wallet_mode(accounts: &TxHookAccountConstraints) -> Result { +fn decode_wallet_mode( + accounts: &TxHookAccountConstraints, +) -> Result { let wallet_view = accounts.ab_wallet.to_account_view(); if wallet_view.data_len() == 0 { return Ok(DecodedWalletMode::None); @@ -134,28 +137,32 @@ fn decode_mint_mode(data: &[u8]) -> Result { if mpos + 4 > md.len() { return Err(errors::invalid_metadata()); } - let name_len = u32::from_le_bytes([md[mpos], md[mpos+1], md[mpos+2], md[mpos+3]]) as usize; + let name_len = + u32::from_le_bytes([md[mpos], md[mpos + 1], md[mpos + 2], md[mpos + 3]]) as usize; mpos += 4 + name_len; // Skip symbol if mpos + 4 > md.len() { return Err(errors::invalid_metadata()); } - let sym_len = u32::from_le_bytes([md[mpos], md[mpos+1], md[mpos+2], md[mpos+3]]) as usize; + let sym_len = + u32::from_le_bytes([md[mpos], md[mpos + 1], md[mpos + 2], md[mpos + 3]]) as usize; mpos += 4 + sym_len; // Skip uri if mpos + 4 > md.len() { return Err(errors::invalid_metadata()); } - let uri_len = u32::from_le_bytes([md[mpos], md[mpos+1], md[mpos+2], md[mpos+3]]) as usize; + let uri_len = + u32::from_le_bytes([md[mpos], md[mpos + 1], md[mpos + 2], md[mpos + 3]]) as usize; mpos += 4 + uri_len; // Read additional_metadata count if mpos + 4 > md.len() { return Err(errors::invalid_metadata()); } - let kv_count = u32::from_le_bytes([md[mpos], md[mpos+1], md[mpos+2], md[mpos+3]]) as usize; + let kv_count = + u32::from_le_bytes([md[mpos], md[mpos + 1], md[mpos + 2], md[mpos + 3]]) as usize; mpos += 4; for _ in 0..kv_count { @@ -163,7 +170,9 @@ fn decode_mint_mode(data: &[u8]) -> Result { if mpos + 4 > md.len() { break; } - let key_len = u32::from_le_bytes([md[mpos], md[mpos+1], md[mpos+2], md[mpos+3]]) as usize; + let key_len = + u32::from_le_bytes([md[mpos], md[mpos + 1], md[mpos + 2], md[mpos + 3]]) + as usize; mpos += 4; if mpos + key_len > md.len() { break; @@ -175,7 +184,9 @@ fn decode_mint_mode(data: &[u8]) -> Result { if mpos + 4 > md.len() { break; } - let val_len = u32::from_le_bytes([md[mpos], md[mpos+1], md[mpos+2], md[mpos+3]]) as usize; + let val_len = + u32::from_le_bytes([md[mpos], md[mpos + 1], md[mpos + 2], md[mpos + 3]]) + as usize; mpos += 4; if mpos + val_len > md.len() { break; @@ -217,10 +228,12 @@ fn decode_mint_mode(data: &[u8]) -> Result { fn parse_u64_from_bytes(bytes: &[u8]) -> u64 { let mut result: u64 = 0; for &byte in bytes { - if byte < b'0' || byte > b'9' { + if !byte.is_ascii_digit() { return 0; } - result = result.saturating_mul(10).saturating_add((byte - b'0') as u64); + result = result + .saturating_mul(10) + .saturating_add((byte - b'0') as u64); } result } diff --git a/tokens/token-extensions/transfer-hook/allow-block-list-token/quasar/src/lib.rs b/tokens/token-extensions/transfer-hook/allow-block-list-token/quasar/src/lib.rs index 363da21dd..69f35c3f8 100644 --- a/tokens/token-extensions/transfer-hook/allow-block-list-token/quasar/src/lib.rs +++ b/tokens/token-extensions/transfer-hook/allow-block-list-token/quasar/src/lib.rs @@ -61,15 +61,22 @@ mod quasar_abl_token { let freeze_addr = Address::new_from_array(freeze_authority); let delegate_addr = Address::new_from_array(permanent_delegate); let hook_auth_addr = Address::new_from_array(transfer_hook_authority); - instructions::handle_init_mint(&mut ctx.accounts, decimals, - &freeze_addr, - &delegate_addr, - &hook_auth_addr, + instructions::handle_init_mint( + &mut ctx.accounts, + decimals, + &instructions::MintAuthorities { + freeze: &freeze_addr, + permanent_delegate: &delegate_addr, + transfer_hook: &hook_auth_addr, + }, + &instructions::MintMetadata { + name: &name[..nl], + symbol: &symbol[..sl], + uri: &uri[..ul], + }, mode, threshold, - &name[..nl], - &symbol[..sl], - &uri[..ul],) + ) } /// Create the Config PDA with the payer as authority. @@ -95,7 +102,10 @@ mod quasar_abl_token { /// Create a per-wallet allow/block entry. #[instruction(discriminator = [0, 0, 0, 0, 0, 0, 0, 4])] - pub fn init_wallet(ctx: Ctx, allowed: bool) -> Result<(), ProgramError> { + pub fn init_wallet( + ctx: Ctx, + allowed: bool, + ) -> Result<(), ProgramError> { instructions::handle_init_wallet(&mut ctx.accounts, allowed) } @@ -107,7 +117,11 @@ mod quasar_abl_token { /// Change the allow/block mode on the mint's metadata. #[instruction(discriminator = [0, 0, 0, 0, 0, 0, 0, 6])] - pub fn change_mode(ctx: Ctx, mode: u8, threshold: u64) -> Result<(), ProgramError> { + pub fn change_mode( + ctx: Ctx, + mode: u8, + threshold: u64, + ) -> Result<(), ProgramError> { instructions::handle_change_mode(&mut ctx.accounts, mode, threshold) } } diff --git a/tokens/token-extensions/transfer-hook/counter/quasar/Cargo.toml b/tokens/token-extensions/transfer-hook/counter/quasar/Cargo.toml index 2d855dd00..50b2bb6f9 100644 --- a/tokens/token-extensions/transfer-hook/counter/quasar/Cargo.toml +++ b/tokens/token-extensions/transfer-hook/counter/quasar/Cargo.toml @@ -30,6 +30,11 @@ idl-build = ["quasar-lang/idl-build"] # git. Keep this rev in lockstep with the CLI rev installed by # .github/workflows/quasar.yml. quasar-lang = { git = "https://github.com/blueshift-gg/quasar", rev = "be60fca" } +# Pinned: quasar-lang asks for zeropod "0.3.3" and wincode 0.4, but zeropod +# 0.3.4 moved to wincode 0.5, so a fresh resolve (no lockfile is committed) +# splits the graph across two wincode versions and every Pod* trait bound +# fails. Unpin when quasar-lang's pinned rev accepts zeropod 0.3.4+. +zeropod = "=0.3.3" quasar-spl = { git = "https://github.com/blueshift-gg/quasar", rev = "be60fca" } solana-instruction = { version = "3.2.0" } diff --git a/tokens/token-extensions/transfer-hook/counter/quasar/src/lib.rs b/tokens/token-extensions/transfer-hook/counter/quasar/src/lib.rs index 71716ad8d..bc9f5d1fc 100644 --- a/tokens/token-extensions/transfer-hook/counter/quasar/src/lib.rs +++ b/tokens/token-extensions/transfer-hook/counter/quasar/src/lib.rs @@ -1,10 +1,7 @@ #![cfg_attr(not(test), no_std)] use quasar_lang::sysvars::Sysvar; -use quasar_lang::{ - cpi::Seed, - prelude::*, -}; +use quasar_lang::{cpi::Seed, prelude::*}; #[cfg(test)] mod tests; @@ -35,7 +32,10 @@ mod quasar_transfer_hook_counter { /// Transfer hook handler - increments the counter on each transfer. /// Discriminator = sha256("spl-transfer-hook-interface:execute")[:8] #[instruction(discriminator = [105, 37, 101, 197, 75, 251, 102, 26])] - pub fn transfer_hook(ctx: Ctx, _amount: u64) -> Result<(), ProgramError> { + pub fn transfer_hook( + ctx: Ctx, + _amount: u64, + ) -> Result<(), ProgramError> { handle_transfer_hook(&mut ctx.accounts) } } @@ -62,101 +62,100 @@ pub struct InitializeExtraAccountMetaListAccountConstraints { fn handle_initialize_extra_account_meta_list( accounts: &mut InitializeExtraAccountMetaListAccountConstraints, ) -> Result<(), ProgramError> { - // ExtraAccountMetaList with 1 extra account: - // [8 bytes: Execute discriminator] - // [4 bytes: data length] - // [4 bytes: PodSlice count = 1] - // [35 bytes: ExtraAccountMeta entry for the counter PDA] - // Total = 8 + 4 + 4 + 35 = 51 bytes - let meta_list_size: u64 = 51; - let lamports = Rent::get()?.try_minimum_balance(meta_list_size as usize)?; - - // Derive ExtraAccountMetaList PDA - let mint_address = accounts.mint.to_account_view().address(); - let (expected_pda, bump) = quasar_lang::pda::try_find_program_address( - &[b"extra-account-metas", mint_address.as_ref()], + // ExtraAccountMetaList with 1 extra account: + // [8 bytes: Execute discriminator] + // [4 bytes: data length] + // [4 bytes: PodSlice count = 1] + // [35 bytes: ExtraAccountMeta entry for the counter PDA] + // Total = 8 + 4 + 4 + 35 = 51 bytes + let meta_list_size: u64 = 51; + let lamports = Rent::get()?.try_minimum_balance(meta_list_size as usize)?; + + // Derive ExtraAccountMetaList PDA + let mint_address = accounts.mint.to_account_view().address(); + let (expected_pda, bump) = quasar_lang::pda::try_find_program_address( + &[b"extra-account-metas", mint_address.as_ref()], + &crate::ID, + )?; + + let meta_list_address = accounts.extra_account_meta_list.to_account_view().address(); + if meta_list_address != &expected_pda { + return Err(ProgramError::InvalidSeeds); + } + + // Create ExtraAccountMetaList PDA + let bump_bytes = [bump]; + let seeds = [ + Seed::from(b"extra-account-metas" as &[u8]), + Seed::from(mint_address.as_ref()), + Seed::from(&bump_bytes as &[u8]), + ]; + + accounts + .system_program + .create_account( + &accounts.payer, + &accounts.extra_account_meta_list, + lamports, + meta_list_size, + &crate::ID, + ) + .invoke_signed(&seeds)?; + + // Write TLV data with the counter PDA as an extra account + let view = unsafe { + &mut *(&mut accounts.extra_account_meta_list as *mut UncheckedAccount as *mut AccountView) + }; + let mut data = view.try_borrow_mut()?; + + // Execute discriminator (TLV type tag) + data[0..8].copy_from_slice(&EXECUTE_DISCRIMINATOR); + // Data length: 4 (count) + 35 (one ExtraAccountMeta) = 39 + data[8..12].copy_from_slice(&39u32.to_le_bytes()); + // PodSlice count: 1 entry + data[12..16].copy_from_slice(&1u32.to_le_bytes()); + + data[16] = 1; // discriminator: PDA from seeds + let mut config = [0u8; 32]; + config[0] = 1; // number of seeds + config[1] = 0; // seed type: literal + config[2] = 7; // seed length + config[3..10].copy_from_slice(b"counter"); + data[17..49].copy_from_slice(&config); + data[49] = 0; // is_signer = false + data[50] = 1; // is_writable = true + + // Also create the counter PDA (8 bytes for u64 counter + 8 bytes discriminator) + let counter_size: u64 = 16; + let counter_lamports = Rent::get()?.try_minimum_balance(counter_size as usize)?; + + let (counter_pda, counter_bump) = + quasar_lang::pda::try_find_program_address(&[b"counter"], &crate::ID)?; + + let counter_address = accounts.counter_account.to_account_view().address(); + if counter_address != &counter_pda { + return Err(ProgramError::InvalidSeeds); + } + + let counter_bump_bytes = [counter_bump]; + let counter_seeds = [ + Seed::from(b"counter" as &[u8]), + Seed::from(&counter_bump_bytes as &[u8]), + ]; + + accounts + .system_program + .create_account( + &accounts.payer, + &accounts.counter_account, + counter_lamports, + counter_size, &crate::ID, - )?; - - let meta_list_address = accounts.extra_account_meta_list.to_account_view().address(); - if meta_list_address != &expected_pda { - return Err(ProgramError::InvalidSeeds); - } - - // Create ExtraAccountMetaList PDA - let bump_bytes = [bump]; - let seeds = [ - Seed::from(b"extra-account-metas" as &[u8]), - Seed::from(mint_address.as_ref()), - Seed::from(&bump_bytes as &[u8]), - ]; - - accounts - .system_program - .create_account( - &accounts.payer, - &accounts.extra_account_meta_list, - lamports, - meta_list_size, - &crate::ID, - ) - .invoke_signed(&seeds)?; - - // Write TLV data with the counter PDA as an extra account - let view = unsafe { - &mut *(&mut accounts.extra_account_meta_list as *mut UncheckedAccount - as *mut AccountView) - }; - let mut data = view.try_borrow_mut()?; - - // Execute discriminator (TLV type tag) - data[0..8].copy_from_slice(&EXECUTE_DISCRIMINATOR); - // Data length: 4 (count) + 35 (one ExtraAccountMeta) = 39 - data[8..12].copy_from_slice(&39u32.to_le_bytes()); - // PodSlice count: 1 entry - data[12..16].copy_from_slice(&1u32.to_le_bytes()); - - data[16] = 1; // discriminator: PDA from seeds - let mut config = [0u8; 32]; - config[0] = 1; // number of seeds - config[1] = 0; // seed type: literal - config[2] = 7; // seed length - config[3..10].copy_from_slice(b"counter"); - data[17..49].copy_from_slice(&config); - data[49] = 0; // is_signer = false - data[50] = 1; // is_writable = true - - // Also create the counter PDA (8 bytes for u64 counter + 8 bytes discriminator) - let counter_size: u64 = 16; - let counter_lamports = Rent::get()?.try_minimum_balance(counter_size as usize)?; - - let (counter_pda, counter_bump) = - quasar_lang::pda::try_find_program_address(&[b"counter"], &crate::ID)?; - - let counter_address = accounts.counter_account.to_account_view().address(); - if counter_address != &counter_pda { - return Err(ProgramError::InvalidSeeds); - } - - let counter_bump_bytes = [counter_bump]; - let counter_seeds = [ - Seed::from(b"counter" as &[u8]), - Seed::from(&counter_bump_bytes as &[u8]), - ]; - - accounts - .system_program - .create_account( - &accounts.payer, - &accounts.counter_account, - counter_lamports, - counter_size, - &crate::ID, - ) - .invoke_signed(&counter_seeds)?; - - log("Extra account meta list and counter initialized"); - Ok(()) + ) + .invoke_signed(&counter_seeds)?; + + log("Extra account meta list and counter initialized"); + Ok(()) } // --------------------------------------------------------------------------- @@ -182,29 +181,28 @@ pub struct TransferHookAccountConstraints { #[inline(always)] fn handle_transfer_hook(accounts: &mut TransferHookAccountConstraints) -> Result<(), ProgramError> { - // Read the current counter from the account data - let view = unsafe { - &mut *(&mut accounts.counter_account as *mut UncheckedAccount - as *mut AccountView) - }; - let mut data = view.try_borrow_mut()?; - - // Counter is at offset 8 (after 8-byte Anchor-style discriminator) - // In our case we just use the first 8 bytes as the counter - if data.len() < 16 { - return Err(ProgramError::AccountDataTooSmall); - } - - let mut counter_bytes = [0u8; 8]; - counter_bytes.copy_from_slice(&data[8..16]); - let counter = u64::from_le_bytes(counter_bytes); - - let new_counter = counter - .checked_add(1) - .ok_or(ProgramError::ArithmeticOverflow)?; - - data[8..16].copy_from_slice(&new_counter.to_le_bytes()); - - log("Transfer hook: counter incremented"); - Ok(()) + // Read the current counter from the account data + let view = unsafe { + &mut *(&mut accounts.counter_account as *mut UncheckedAccount as *mut AccountView) + }; + let mut data = view.try_borrow_mut()?; + + // Counter is at offset 8 (after 8-byte Anchor-style discriminator) + // In our case we just use the first 8 bytes as the counter + if data.len() < 16 { + return Err(ProgramError::AccountDataTooSmall); + } + + let mut counter_bytes = [0u8; 8]; + counter_bytes.copy_from_slice(&data[8..16]); + let counter = u64::from_le_bytes(counter_bytes); + + let new_counter = counter + .checked_add(1) + .ok_or(ProgramError::ArithmeticOverflow)?; + + data[8..16].copy_from_slice(&new_counter.to_le_bytes()); + + log("Transfer hook: counter incremented"); + Ok(()) } diff --git a/tokens/token-extensions/transfer-hook/hello-world/quasar/Cargo.toml b/tokens/token-extensions/transfer-hook/hello-world/quasar/Cargo.toml index 2c97b87a7..9e56bf754 100644 --- a/tokens/token-extensions/transfer-hook/hello-world/quasar/Cargo.toml +++ b/tokens/token-extensions/transfer-hook/hello-world/quasar/Cargo.toml @@ -30,6 +30,11 @@ idl-build = ["quasar-lang/idl-build"] # git. Keep this rev in lockstep with the CLI rev installed by # .github/workflows/quasar.yml. quasar-lang = { git = "https://github.com/blueshift-gg/quasar", rev = "be60fca" } +# Pinned: quasar-lang asks for zeropod "0.3.3" and wincode 0.4, but zeropod +# 0.3.4 moved to wincode 0.5, so a fresh resolve (no lockfile is committed) +# splits the graph across two wincode versions and every Pod* trait bound +# fails. Unpin when quasar-lang's pinned rev accepts zeropod 0.3.4+. +zeropod = "=0.3.3" quasar-spl = { git = "https://github.com/blueshift-gg/quasar", rev = "be60fca" } solana-instruction = { version = "3.2.0" } diff --git a/tokens/token-extensions/transfer-hook/hello-world/quasar/src/lib.rs b/tokens/token-extensions/transfer-hook/hello-world/quasar/src/lib.rs index a0063fe79..54c671f2a 100644 --- a/tokens/token-extensions/transfer-hook/hello-world/quasar/src/lib.rs +++ b/tokens/token-extensions/transfer-hook/hello-world/quasar/src/lib.rs @@ -14,8 +14,8 @@ declare_id!("jY5DfVksJT8Le38LCaQhz5USeiGu4rUeVSS8QRAMoba"); pub struct Token2022Program; impl Id for Token2022Program { const ID: Address = Address::new_from_array([ - 6, 221, 246, 225, 238, 117, 143, 222, 24, 66, 93, 188, 228, 108, 205, 218, - 182, 26, 252, 77, 131, 185, 13, 39, 254, 189, 249, 40, 216, 161, 139, 252, + 6, 221, 246, 225, 238, 117, 143, 222, 24, 66, 93, 188, 228, 108, 205, 218, 182, 26, 252, + 77, 131, 185, 13, 39, 254, 189, 249, 40, 216, 161, 139, 252, ]); } @@ -36,7 +36,10 @@ mod quasar_transfer_hook_hello_world { /// Create a mint with the TransferHook extension pointing to this program. /// Custom discriminator (not part of the transfer hook interface). #[instruction(discriminator = [0, 0, 0, 0, 0, 0, 0, 1])] - pub fn initialize(ctx: Ctx, decimals: u8) -> Result<(), ProgramError> { + pub fn initialize( + ctx: Ctx, + decimals: u8, + ) -> Result<(), ProgramError> { handle_initialize(&mut ctx.accounts, decimals) } @@ -52,7 +55,10 @@ mod quasar_transfer_hook_hello_world { /// Transfer hook handler - called automatically by Token Extensions during transfers. /// Discriminator = sha256("spl-transfer-hook-interface:execute")[:8] #[instruction(discriminator = [105, 37, 101, 197, 75, 251, 102, 26])] - pub fn transfer_hook(ctx: Ctx, _amount: u64) -> Result<(), ProgramError> { + pub fn transfer_hook( + ctx: Ctx, + _amount: u64, + ) -> Result<(), ProgramError> { handle_transfer_hook(&mut ctx.accounts) } } @@ -72,59 +78,63 @@ pub struct InitializeAccountConstraints { } #[inline(always)] -fn handle_initialize(accounts: &mut InitializeAccountConstraints, decimals: u8) -> Result<(), ProgramError> { - // Mint with TransferHook extension: - // 165 (base account + padding) + 1 (account type) + 4 (TLV header) + 64 (extension) = 234 - let mint_size: u64 = 234; - let lamports = Rent::get()?.try_minimum_balance(mint_size as usize)?; - - // 1. Create account owned by Token Extensions - accounts.system_program - .create_account( - &accounts.payer, - &accounts.mint_account, - lamports, - mint_size, - accounts.token_program.to_account_view().address(), - ) - .invoke()?; - - // 2. InitializeTransferHook extension - // Layout: [36u8 (TransferHookExtension), 0u8 (Initialize), - // authority(32), program_id(32)] - let mut ext_data = [0u8; 66]; - ext_data[0] = 36; // TokenInstruction::TransferHookExtension - ext_data[1] = 0; // TransferHookInstruction::Initialize - ext_data[2..34].copy_from_slice(accounts.payer.to_account_view().address().as_ref()); - ext_data[34..66].copy_from_slice(crate::ID.as_ref()); - - CpiCall::new( +fn handle_initialize( + accounts: &mut InitializeAccountConstraints, + decimals: u8, +) -> Result<(), ProgramError> { + // Mint with TransferHook extension: + // 165 (base account + padding) + 1 (account type) + 4 (TLV header) + 64 (extension) = 234 + let mint_size: u64 = 234; + let lamports = Rent::get()?.try_minimum_balance(mint_size as usize)?; + + // 1. Create account owned by Token Extensions + accounts + .system_program + .create_account( + &accounts.payer, + &accounts.mint_account, + lamports, + mint_size, accounts.token_program.to_account_view().address(), - [InstructionAccount::writable( - accounts.mint_account.to_account_view().address(), - )], - [accounts.mint_account.to_account_view()], - ext_data, ) .invoke()?; - // 3. InitializeMint2: opcode 20 - let mut mint_data = [0u8; 67]; - mint_data[0] = 20; - mint_data[1] = decimals; - mint_data[2..34].copy_from_slice(accounts.payer.to_account_view().address().as_ref()); - mint_data[34] = 1; // has freeze authority - mint_data[35..67].copy_from_slice(accounts.payer.to_account_view().address().as_ref()); - - CpiCall::new( - accounts.token_program.to_account_view().address(), - [InstructionAccount::writable( - accounts.mint_account.to_account_view().address(), - )], - [accounts.mint_account.to_account_view()], - mint_data, - ) - .invoke() + // 2. InitializeTransferHook extension + // Layout: [36u8 (TransferHookExtension), 0u8 (Initialize), + // authority(32), program_id(32)] + let mut ext_data = [0u8; 66]; + ext_data[0] = 36; // TokenInstruction::TransferHookExtension + ext_data[1] = 0; // TransferHookInstruction::Initialize + ext_data[2..34].copy_from_slice(accounts.payer.to_account_view().address().as_ref()); + ext_data[34..66].copy_from_slice(crate::ID.as_ref()); + + CpiCall::new( + accounts.token_program.to_account_view().address(), + [InstructionAccount::writable( + accounts.mint_account.to_account_view().address(), + )], + [accounts.mint_account.to_account_view()], + ext_data, + ) + .invoke()?; + + // 3. InitializeMint2: opcode 20 + let mut mint_data = [0u8; 67]; + mint_data[0] = 20; + mint_data[1] = decimals; + mint_data[2..34].copy_from_slice(accounts.payer.to_account_view().address().as_ref()); + mint_data[34] = 1; // has freeze authority + mint_data[35..67].copy_from_slice(accounts.payer.to_account_view().address().as_ref()); + + CpiCall::new( + accounts.token_program.to_account_view().address(), + [InstructionAccount::writable( + accounts.mint_account.to_account_view().address(), + )], + [accounts.mint_account.to_account_view()], + mint_data, + ) + .invoke() } // --------------------------------------------------------------------------- @@ -146,65 +156,64 @@ pub struct InitializeExtraAccountMetaListAccountConstraints { fn handle_initialize_extra_account_meta_list( accounts: &mut InitializeExtraAccountMetaListAccountConstraints, ) -> Result<(), ProgramError> { - use quasar_lang::cpi::Seed; - - // ExtraAccountMetaList with 0 extra accounts: - // [8 bytes: Execute discriminator] - // [4 bytes: data length = 4] - // [4 bytes: PodSlice count = 0] - // Total = 16 bytes - let meta_list_size: u64 = 16; - let lamports = Rent::get()?.try_minimum_balance(meta_list_size as usize)?; - - // Derive PDA - let mint_address = accounts.mint.to_account_view().address(); - let (expected_pda, bump) = quasar_lang::pda::try_find_program_address( - &[b"extra-account-metas", mint_address.as_ref()], + use quasar_lang::cpi::Seed; + + // ExtraAccountMetaList with 0 extra accounts: + // [8 bytes: Execute discriminator] + // [4 bytes: data length = 4] + // [4 bytes: PodSlice count = 0] + // Total = 16 bytes + let meta_list_size: u64 = 16; + let lamports = Rent::get()?.try_minimum_balance(meta_list_size as usize)?; + + // Derive PDA + let mint_address = accounts.mint.to_account_view().address(); + let (expected_pda, bump) = quasar_lang::pda::try_find_program_address( + &[b"extra-account-metas", mint_address.as_ref()], + &crate::ID, + )?; + + let meta_list_address = accounts.extra_account_meta_list.to_account_view().address(); + if meta_list_address != &expected_pda { + return Err(ProgramError::InvalidSeeds); + } + + // Create PDA account owned by this program + let bump_bytes = [bump]; + let seeds = [ + Seed::from(b"extra-account-metas" as &[u8]), + Seed::from(mint_address.as_ref()), + Seed::from(&bump_bytes as &[u8]), + ]; + + accounts + .system_program + .create_account( + &accounts.payer, + &accounts.extra_account_meta_list, + lamports, + meta_list_size, &crate::ID, - )?; - - let meta_list_address = accounts.extra_account_meta_list.to_account_view().address(); - if meta_list_address != &expected_pda { - return Err(ProgramError::InvalidSeeds); - } - - // Create PDA account owned by this program - let bump_bytes = [bump]; - let seeds = [ - Seed::from(b"extra-account-metas" as &[u8]), - Seed::from(mint_address.as_ref()), - Seed::from(&bump_bytes as &[u8]), - ]; - - accounts - .system_program - .create_account( - &accounts.payer, - &accounts.extra_account_meta_list, - lamports, - meta_list_size, - &crate::ID, - ) - .invoke_signed(&seeds)?; - - // Write TLV data into the account. The account was just created - // (16 bytes) and is owned by this program, so the borrow is safe. - // SAFETY: `UncheckedAccount` is `#[repr(transparent)]` over - // `AccountView`, so the reference cast is sound. - let view: &mut AccountView = unsafe { - &mut *(&mut accounts.extra_account_meta_list as *mut UncheckedAccount - as *mut AccountView) - }; - let mut data = view.try_borrow_mut()?; - // Execute discriminator (type tag in TLV) - data[0..8].copy_from_slice(&EXECUTE_DISCRIMINATOR); - // Data length: 4 bytes for the PodSlice count field - data[8..12].copy_from_slice(&4u32.to_le_bytes()); - // PodSlice count: 0 entries - data[12..16].copy_from_slice(&0u32.to_le_bytes()); - - log("Extra account meta list initialized"); - Ok(()) + ) + .invoke_signed(&seeds)?; + + // Write TLV data into the account. The account was just created + // (16 bytes) and is owned by this program, so the borrow is safe. + // SAFETY: `UncheckedAccount` is `#[repr(transparent)]` over + // `AccountView`, so the reference cast is sound. + let view: &mut AccountView = unsafe { + &mut *(&mut accounts.extra_account_meta_list as *mut UncheckedAccount as *mut AccountView) + }; + let mut data = view.try_borrow_mut()?; + // Execute discriminator (type tag in TLV) + data[0..8].copy_from_slice(&EXECUTE_DISCRIMINATOR); + // Data length: 4 bytes for the PodSlice count field + data[8..12].copy_from_slice(&4u32.to_le_bytes()); + // PodSlice count: 0 entries + data[12..16].copy_from_slice(&0u32.to_le_bytes()); + + log("Extra account meta list initialized"); + Ok(()) } // --------------------------------------------------------------------------- @@ -226,12 +235,14 @@ pub struct TransferHookAccountConstraints { } #[inline(always)] -fn handle_transfer_hook(_accounts: &mut TransferHookAccountConstraints) -> Result<(), ProgramError> { - // In production, verify the source token's TransferHookAccount.transferring - // flag is set. The Token Extensions program sets this before invoking the hook - // and clears it after, preventing standalone invocation. - // - // For this hello-world example, we simply log a message. - log("Hello Transfer Hook!"); - Ok(()) +fn handle_transfer_hook( + _accounts: &mut TransferHookAccountConstraints, +) -> Result<(), ProgramError> { + // In production, verify the source token's TransferHookAccount.transferring + // flag is set. The Token Extensions program sets this before invoking the hook + // and clears it after, preventing standalone invocation. + // + // For this hello-world example, we simply log a message. + log("Hello Transfer Hook!"); + Ok(()) } diff --git a/tokens/token-extensions/transfer-hook/transfer-cost/quasar/Cargo.toml b/tokens/token-extensions/transfer-hook/transfer-cost/quasar/Cargo.toml index dd51d981a..555fed10b 100644 --- a/tokens/token-extensions/transfer-hook/transfer-cost/quasar/Cargo.toml +++ b/tokens/token-extensions/transfer-hook/transfer-cost/quasar/Cargo.toml @@ -30,6 +30,11 @@ idl-build = ["quasar-lang/idl-build"] # git. Keep this rev in lockstep with the CLI rev installed by # .github/workflows/quasar.yml. quasar-lang = { git = "https://github.com/blueshift-gg/quasar", rev = "be60fca" } +# Pinned: quasar-lang asks for zeropod "0.3.3" and wincode 0.4, but zeropod +# 0.3.4 moved to wincode 0.5, so a fresh resolve (no lockfile is committed) +# splits the graph across two wincode versions and every Pod* trait bound +# fails. Unpin when quasar-lang's pinned rev accepts zeropod 0.3.4+. +zeropod = "=0.3.3" quasar-spl = { git = "https://github.com/blueshift-gg/quasar", rev = "be60fca" } solana-instruction = { version = "3.2.0" } diff --git a/tokens/token-extensions/transfer-hook/transfer-cost/quasar/src/lib.rs b/tokens/token-extensions/transfer-hook/transfer-cost/quasar/src/lib.rs index 501e77783..8bcc2b347 100644 --- a/tokens/token-extensions/transfer-hook/transfer-cost/quasar/src/lib.rs +++ b/tokens/token-extensions/transfer-hook/transfer-cost/quasar/src/lib.rs @@ -1,10 +1,7 @@ #![cfg_attr(not(test), no_std)] use quasar_lang::sysvars::Sysvar; -use quasar_lang::{ - cpi::Seed, - prelude::*, -}; +use quasar_lang::{cpi::Seed, prelude::*}; #[cfg(test)] mod tests; @@ -40,7 +37,10 @@ mod quasar_transfer_hook_cost { /// In the full version, this would also charge a WSOL fee via delegate. /// Discriminator = sha256("spl-transfer-hook-interface:execute")[:8] #[instruction(discriminator = [105, 37, 101, 197, 75, 251, 102, 26])] - pub fn transfer_hook(ctx: Ctx, amount: u64) -> Result<(), ProgramError> { + pub fn transfer_hook( + ctx: Ctx, + amount: u64, + ) -> Result<(), ProgramError> { handle_transfer_hook(&mut ctx.accounts, amount) } } @@ -65,72 +65,84 @@ pub struct InitializeExtraAccountMetaListAccountConstraints { fn handle_initialize_extra_account_meta_list( accounts: &mut InitializeExtraAccountMetaListAccountConstraints, ) -> Result<(), ProgramError> { - // Create ExtraAccountMetaList PDA with 1 extra account: counter - let meta_list_size: u64 = 51; - let lamports = Rent::get()?.try_minimum_balance(meta_list_size as usize)?; + // Create ExtraAccountMetaList PDA with 1 extra account: counter + let meta_list_size: u64 = 51; + let lamports = Rent::get()?.try_minimum_balance(meta_list_size as usize)?; + + let mint_address = accounts.mint.to_account_view().address(); + let (expected_pda, bump) = quasar_lang::pda::try_find_program_address( + &[b"extra-account-metas", mint_address.as_ref()], + &crate::ID, + )?; + if accounts.extra_account_meta_list.to_account_view().address() != &expected_pda { + return Err(ProgramError::InvalidSeeds); + } + + let bump_bytes = [bump]; + let seeds = [ + Seed::from(b"extra-account-metas" as &[u8]), + Seed::from(mint_address.as_ref()), + Seed::from(&bump_bytes as &[u8]), + ]; + accounts + .system_program + .create_account( + &accounts.payer, + &accounts.extra_account_meta_list, + lamports, + meta_list_size, + &crate::ID, + ) + .invoke_signed(&seeds)?; + + // Write TLV data + let view = unsafe { + &mut *(&mut accounts.extra_account_meta_list as *mut UncheckedAccount as *mut AccountView) + }; + let mut data = view.try_borrow_mut()?; + data[0..8].copy_from_slice(&EXECUTE_DISCRIMINATOR); + data[8..12].copy_from_slice(&39u32.to_le_bytes()); + data[12..16].copy_from_slice(&1u32.to_le_bytes()); + + // ExtraAccountMeta: counter PDA with seeds = [Literal("counter")] + data[16] = 1; + let mut config = [0u8; 32]; + config[0] = 1; + config[1] = 0; // literal + config[2] = 7; + config[3..10].copy_from_slice(b"counter"); + data[17..49].copy_from_slice(&config); + data[49] = 0; + data[50] = 1; // writable + + // Create counter PDA: 1 byte for counter (u8) + let counter_size: u64 = 9; // 8 discriminator + 1 counter + let counter_lamports = Rent::get()?.try_minimum_balance(counter_size as usize)?; + + let (counter_pda, counter_bump) = + quasar_lang::pda::try_find_program_address(&[b"counter"], &crate::ID)?; + if accounts.counter_account.to_account_view().address() != &counter_pda { + return Err(ProgramError::InvalidSeeds); + } - let mint_address = accounts.mint.to_account_view().address(); - let (expected_pda, bump) = quasar_lang::pda::try_find_program_address( - &[b"extra-account-metas", mint_address.as_ref()], + let counter_bump_bytes = [counter_bump]; + let counter_seeds = [ + Seed::from(b"counter" as &[u8]), + Seed::from(&counter_bump_bytes as &[u8]), + ]; + accounts + .system_program + .create_account( + &accounts.payer, + &accounts.counter_account, + counter_lamports, + counter_size, &crate::ID, - )?; - if accounts.extra_account_meta_list.to_account_view().address() != &expected_pda { - return Err(ProgramError::InvalidSeeds); - } - - let bump_bytes = [bump]; - let seeds = [ - Seed::from(b"extra-account-metas" as &[u8]), - Seed::from(mint_address.as_ref()), - Seed::from(&bump_bytes as &[u8]), - ]; - accounts - .system_program - .create_account(&accounts.payer, &accounts.extra_account_meta_list, lamports, meta_list_size, &crate::ID) - .invoke_signed(&seeds)?; - - // Write TLV data - let view = unsafe { - &mut *(&mut accounts.extra_account_meta_list as *mut UncheckedAccount as *mut AccountView) - }; - let mut data = view.try_borrow_mut()?; - data[0..8].copy_from_slice(&EXECUTE_DISCRIMINATOR); - data[8..12].copy_from_slice(&39u32.to_le_bytes()); - data[12..16].copy_from_slice(&1u32.to_le_bytes()); - - // ExtraAccountMeta: counter PDA with seeds = [Literal("counter")] - data[16] = 1; - let mut config = [0u8; 32]; - config[0] = 1; - config[1] = 0; // literal - config[2] = 7; - config[3..10].copy_from_slice(b"counter"); - data[17..49].copy_from_slice(&config); - data[49] = 0; - data[50] = 1; // writable - - // Create counter PDA: 1 byte for counter (u8) - let counter_size: u64 = 9; // 8 discriminator + 1 counter - let counter_lamports = Rent::get()?.try_minimum_balance(counter_size as usize)?; - - let (counter_pda, counter_bump) = - quasar_lang::pda::try_find_program_address(&[b"counter"], &crate::ID)?; - if accounts.counter_account.to_account_view().address() != &counter_pda { - return Err(ProgramError::InvalidSeeds); - } - - let counter_bump_bytes = [counter_bump]; - let counter_seeds = [ - Seed::from(b"counter" as &[u8]), - Seed::from(&counter_bump_bytes as &[u8]), - ]; - accounts - .system_program - .create_account(&accounts.payer, &accounts.counter_account, counter_lamports, counter_size, &crate::ID) - .invoke_signed(&counter_seeds)?; - - log("Transfer cost hook initialized"); - Ok(()) + ) + .invoke_signed(&counter_seeds)?; + + log("Transfer cost hook initialized"); + Ok(()) } // --------------------------------------------------------------------------- @@ -149,36 +161,38 @@ pub struct TransferHookAccountConstraints { } #[inline(always)] -fn handle_transfer_hook(accounts: &mut TransferHookAccountConstraints, amount: u64) -> Result<(), ProgramError> { - // Validate amount - if amount > 50 { - log("Warning: large transfer amount"); - } - - // Increment transfer counter - let view = unsafe { - &mut *(&mut accounts.counter_account as *mut UncheckedAccount - as *mut AccountView) - }; - let mut data = view.try_borrow_mut()?; - - if data.len() < 9 { - return Err(ProgramError::AccountDataTooSmall); - } - - let counter = data[8]; - let new_counter = counter - .checked_add(1) - .ok_or(ProgramError::ArithmeticOverflow)?; - data[8] = new_counter; - - // In the full Anchor version, this would also: - // 1. Transfer WSOL from sender's ATA to delegate's ATA - // using the delegate PDA as the authority - // 2. The WSOL amount equals the token transfer amount - // This requires several additional accounts (WSOL mint, - // token program, ATA program, delegate PDA, and both ATAs). - - log("Transfer cost hook: counter incremented"); - Ok(()) +fn handle_transfer_hook( + accounts: &mut TransferHookAccountConstraints, + amount: u64, +) -> Result<(), ProgramError> { + // Validate amount + if amount > 50 { + log("Warning: large transfer amount"); + } + + // Increment transfer counter + let view = unsafe { + &mut *(&mut accounts.counter_account as *mut UncheckedAccount as *mut AccountView) + }; + let mut data = view.try_borrow_mut()?; + + if data.len() < 9 { + return Err(ProgramError::AccountDataTooSmall); + } + + let counter = data[8]; + let new_counter = counter + .checked_add(1) + .ok_or(ProgramError::ArithmeticOverflow)?; + data[8] = new_counter; + + // In the full Anchor version, this would also: + // 1. Transfer WSOL from sender's ATA to delegate's ATA + // using the delegate PDA as the authority + // 2. The WSOL amount equals the token transfer amount + // This requires several additional accounts (WSOL mint, + // token program, ATA program, delegate PDA, and both ATAs). + + log("Transfer cost hook: counter incremented"); + Ok(()) } diff --git a/tokens/token-extensions/transfer-hook/transfer-switch/quasar/Cargo.toml b/tokens/token-extensions/transfer-hook/transfer-switch/quasar/Cargo.toml index f9abccd31..d99ab88dc 100644 --- a/tokens/token-extensions/transfer-hook/transfer-switch/quasar/Cargo.toml +++ b/tokens/token-extensions/transfer-hook/transfer-switch/quasar/Cargo.toml @@ -30,6 +30,11 @@ idl-build = ["quasar-lang/idl-build"] # git. Keep this rev in lockstep with the CLI rev installed by # .github/workflows/quasar.yml. quasar-lang = { git = "https://github.com/blueshift-gg/quasar", rev = "be60fca" } +# Pinned: quasar-lang asks for zeropod "0.3.3" and wincode 0.4, but zeropod +# 0.3.4 moved to wincode 0.5, so a fresh resolve (no lockfile is committed) +# splits the graph across two wincode versions and every Pod* trait bound +# fails. Unpin when quasar-lang's pinned rev accepts zeropod 0.3.4+. +zeropod = "=0.3.3" quasar-spl = { git = "https://github.com/blueshift-gg/quasar", rev = "be60fca" } solana-instruction = { version = "3.2.0" } diff --git a/tokens/token-extensions/transfer-hook/transfer-switch/quasar/src/lib.rs b/tokens/token-extensions/transfer-hook/transfer-switch/quasar/src/lib.rs index 45462b56c..ff81dff87 100644 --- a/tokens/token-extensions/transfer-hook/transfer-switch/quasar/src/lib.rs +++ b/tokens/token-extensions/transfer-hook/transfer-switch/quasar/src/lib.rs @@ -1,10 +1,7 @@ #![cfg_attr(not(test), no_std)] use quasar_lang::sysvars::Sysvar; -use quasar_lang::{ - cpi::Seed, - prelude::*, -}; +use quasar_lang::{cpi::Seed, prelude::*}; #[cfg(test)] mod tests; @@ -46,7 +43,10 @@ mod quasar_transfer_hook_switch { /// Transfer hook handler - checks the sender's switch is on. /// Discriminator = sha256("spl-transfer-hook-interface:execute")[:8] #[instruction(discriminator = [105, 37, 101, 197, 75, 251, 102, 26])] - pub fn transfer_hook(ctx: Ctx, _amount: u64) -> Result<(), ProgramError> { + pub fn transfer_hook( + ctx: Ctx, + _amount: u64, + ) -> Result<(), ProgramError> { handle_transfer_hook(&mut ctx.accounts) } } @@ -71,7 +71,9 @@ pub struct ConfigureAdminAccountConstraints { } #[inline(always)] -fn handle_configure_admin(accounts: &mut ConfigureAdminAccountConstraints) -> Result<(), ProgramError> { +fn handle_configure_admin( + accounts: &mut ConfigureAdminAccountConstraints, +) -> Result<(), ProgramError> { let view = accounts.admin_config.to_account_view(); let data = view.try_borrow()?; @@ -103,15 +105,19 @@ fn handle_configure_admin(accounts: &mut ConfigureAdminAccountConstraints) -> Re ]; accounts .system_program - .create_account(&accounts.admin, &accounts.admin_config, lamports, size, &crate::ID) + .create_account( + &accounts.admin, + &accounts.admin_config, + lamports, + size, + &crate::ID, + ) .invoke_signed(&seeds)?; } // Write new admin - let mview = unsafe { - &mut *(&mut accounts.admin_config as *mut UncheckedAccount - as *mut AccountView) - }; + let mview = + unsafe { &mut *(&mut accounts.admin_config as *mut UncheckedAccount as *mut AccountView) }; let mut data = mview.try_borrow_mut()?; let new_admin_address = accounts.new_admin.to_account_view().address(); data[0..32].copy_from_slice(new_admin_address.as_ref()); @@ -139,50 +145,62 @@ pub struct InitializeExtraAccountMetasAccountConstraints { fn handle_initialize_extra_account_metas_list( accounts: &mut InitializeExtraAccountMetasAccountConstraints, ) -> Result<(), ProgramError> { - // 1 extra account: wallet switch PDA seeded by [AccountKey(index=3)] (sender/owner) - let meta_list_size: u64 = 51; // 8 + 4 + 4 + 35 - let lamports = Rent::get()?.try_minimum_balance(meta_list_size as usize)?; + // 1 extra account: wallet switch PDA seeded by [AccountKey(index=3)] (sender/owner) + let meta_list_size: u64 = 51; // 8 + 4 + 4 + 35 + let lamports = Rent::get()?.try_minimum_balance(meta_list_size as usize)?; + + let mint_address = accounts.token_mint.to_account_view().address(); + let (expected_pda, bump) = quasar_lang::pda::try_find_program_address( + &[b"extra-account-metas", mint_address.as_ref()], + &crate::ID, + )?; + if accounts + .extra_account_metas_list + .to_account_view() + .address() + != &expected_pda + { + return Err(ProgramError::InvalidSeeds); + } - let mint_address = accounts.token_mint.to_account_view().address(); - let (expected_pda, bump) = quasar_lang::pda::try_find_program_address( - &[b"extra-account-metas", mint_address.as_ref()], + let bump_bytes = [bump]; + let seeds = [ + Seed::from(b"extra-account-metas" as &[u8]), + Seed::from(mint_address.as_ref()), + Seed::from(&bump_bytes as &[u8]), + ]; + + accounts + .system_program + .create_account( + &accounts.payer, + &accounts.extra_account_metas_list, + lamports, + meta_list_size, &crate::ID, - )?; - if accounts.extra_account_metas_list.to_account_view().address() != &expected_pda { - return Err(ProgramError::InvalidSeeds); - } - - let bump_bytes = [bump]; - let seeds = [ - Seed::from(b"extra-account-metas" as &[u8]), - Seed::from(mint_address.as_ref()), - Seed::from(&bump_bytes as &[u8]), - ]; - - accounts.system_program - .create_account(&accounts.payer, &accounts.extra_account_metas_list, lamports, meta_list_size, &crate::ID) - .invoke_signed(&seeds)?; + ) + .invoke_signed(&seeds)?; - let view = unsafe { - &mut *(&mut accounts.extra_account_metas_list as *mut UncheckedAccount as *mut AccountView) - }; - let mut data = view.try_borrow_mut()?; - data[0..8].copy_from_slice(&EXECUTE_DISCRIMINATOR); - data[8..12].copy_from_slice(&39u32.to_le_bytes()); - data[12..16].copy_from_slice(&1u32.to_le_bytes()); - - // ExtraAccountMeta: PDA seeded by [AccountKey(index=3)] - the sender/owner - data[16] = 1; // PDA from seeds - let mut config = [0u8; 32]; - config[0] = 1; // 1 seed - config[1] = 2; // seed type: account key - config[2] = 3; // account index 3 (owner/sender) - data[17..49].copy_from_slice(&config); - data[49] = 0; // not signer - data[50] = 0; // not writable (just reading switch state) - - log("Extra account metas list initialized"); - Ok(()) + let view = unsafe { + &mut *(&mut accounts.extra_account_metas_list as *mut UncheckedAccount as *mut AccountView) + }; + let mut data = view.try_borrow_mut()?; + data[0..8].copy_from_slice(&EXECUTE_DISCRIMINATOR); + data[8..12].copy_from_slice(&39u32.to_le_bytes()); + data[12..16].copy_from_slice(&1u32.to_le_bytes()); + + // ExtraAccountMeta: PDA seeded by [AccountKey(index=3)] - the sender/owner + data[16] = 1; // PDA from seeds + let mut config = [0u8; 32]; + config[0] = 1; // 1 seed + config[1] = 2; // seed type: account key + config[2] = 3; // account index 3 (owner/sender) + data[17..49].copy_from_slice(&config); + data[49] = 0; // not signer + data[50] = 0; // not writable (just reading switch state) + + log("Extra account metas list initialized"); + Ok(()) } // --------------------------------------------------------------------------- @@ -233,14 +251,18 @@ fn handle_switch(accounts: &mut SwitchAccountConstraints, on: bool) -> Result<() ]; accounts .system_program - .create_account(&accounts.admin, &accounts.wallet_switch, lamports, size, &crate::ID) + .create_account( + &accounts.admin, + &accounts.wallet_switch, + lamports, + size, + &crate::ID, + ) .invoke_signed(&switch_seeds)?; } - let mview = unsafe { - &mut *(&mut accounts.wallet_switch as *mut UncheckedAccount - as *mut AccountView) - }; + let mview = + unsafe { &mut *(&mut accounts.wallet_switch as *mut UncheckedAccount as *mut AccountView) }; let mut data = mview.try_borrow_mut()?; data[0..32].copy_from_slice(wallet_address.as_ref()); data[32] = if on { 1 } else { 0 }; diff --git a/tokens/token-extensions/transfer-hook/transfer-switch/quasar/src/tests.rs b/tokens/token-extensions/transfer-hook/transfer-switch/quasar/src/tests.rs index f162f69b7..3e3a91cce 100644 --- a/tokens/token-extensions/transfer-hook/transfer-switch/quasar/src/tests.rs +++ b/tokens/token-extensions/transfer-hook/transfer-switch/quasar/src/tests.rs @@ -70,7 +70,8 @@ fn transfer_switch_gates_transfers_per_wallet(test: &mut Test) { .succeeds(); // 4. Transfer hook with the switch ON succeeds. - test.send(hook_instruction(meta_list, wallet_switch)).succeeds(); + test.send(hook_instruction(meta_list, wallet_switch)) + .succeeds(); // 5. Turn the switch OFF. test.send(SwitchInstruction { diff --git a/tokens/token-extensions/transfer-hook/whitelist/quasar/Cargo.toml b/tokens/token-extensions/transfer-hook/whitelist/quasar/Cargo.toml index 6a699b6f5..4ac00eabd 100644 --- a/tokens/token-extensions/transfer-hook/whitelist/quasar/Cargo.toml +++ b/tokens/token-extensions/transfer-hook/whitelist/quasar/Cargo.toml @@ -30,6 +30,11 @@ idl-build = ["quasar-lang/idl-build"] # git. Keep this rev in lockstep with the CLI rev installed by # .github/workflows/quasar.yml. quasar-lang = { git = "https://github.com/blueshift-gg/quasar", rev = "be60fca" } +# Pinned: quasar-lang asks for zeropod "0.3.3" and wincode 0.4, but zeropod +# 0.3.4 moved to wincode 0.5, so a fresh resolve (no lockfile is committed) +# splits the graph across two wincode versions and every Pod* trait bound +# fails. Unpin when quasar-lang's pinned rev accepts zeropod 0.3.4+. +zeropod = "=0.3.3" quasar-spl = { git = "https://github.com/blueshift-gg/quasar", rev = "be60fca" } solana-instruction = { version = "3.2.0" } diff --git a/tokens/token-extensions/transfer-hook/whitelist/quasar/src/lib.rs b/tokens/token-extensions/transfer-hook/whitelist/quasar/src/lib.rs index 50a71e7e7..f591ac971 100644 --- a/tokens/token-extensions/transfer-hook/whitelist/quasar/src/lib.rs +++ b/tokens/token-extensions/transfer-hook/whitelist/quasar/src/lib.rs @@ -1,10 +1,7 @@ #![cfg_attr(not(test), no_std)] use quasar_lang::sysvars::Sysvar; -use quasar_lang::{ - cpi::Seed, - prelude::*, -}; +use quasar_lang::{cpi::Seed, prelude::*}; #[cfg(test)] mod tests; @@ -33,13 +30,18 @@ mod quasar_transfer_hook_whitelist { /// Transfer hook handler - checks if the destination is in the whitelist. /// Discriminator = sha256("spl-transfer-hook-interface:execute")[:8] #[instruction(discriminator = [105, 37, 101, 197, 75, 251, 102, 26])] - pub fn transfer_hook(ctx: Ctx, _amount: u64) -> Result<(), ProgramError> { - handle_transfer_hook(&mut ctx.accounts) + pub fn transfer_hook( + ctx: Ctx, + _amount: u64, + ) -> Result<(), ProgramError> { + handle_transfer_hook(&ctx.accounts) } /// Add an address to the whitelist. Only callable by the authority. #[instruction(discriminator = [0, 0, 0, 0, 0, 0, 0, 2])] - pub fn add_to_whitelist(ctx: Ctx) -> Result<(), ProgramError> { + pub fn add_to_whitelist( + ctx: Ctx, + ) -> Result<(), ProgramError> { handle_add_to_whitelist(&mut ctx.accounts) } } @@ -62,7 +64,9 @@ pub struct InitializeExtraAccountMetaListAccountConstraints { } #[inline(always)] -pub fn handle_initialize(accounts: &mut InitializeExtraAccountMetaListAccountConstraints) -> Result<(), ProgramError> { +pub fn handle_initialize( + accounts: &mut InitializeExtraAccountMetaListAccountConstraints, +) -> Result<(), ProgramError> { // Create ExtraAccountMetaList PDA (1 extra account: whitelist) let meta_list_size: u64 = 51; // 8 + 4 + 4 + 35 let lamports = Rent::get()?.try_minimum_balance(meta_list_size as usize)?; @@ -84,8 +88,15 @@ pub fn handle_initialize(accounts: &mut InitializeExtraAccountMetaListAccountCon Seed::from(&bump_bytes as &[u8]), ]; - accounts.system_program - .create_account(&accounts.payer, &accounts.extra_account_meta_list, lamports, meta_list_size, &crate::ID) + accounts + .system_program + .create_account( + &accounts.payer, + &accounts.extra_account_meta_list, + lamports, + meta_list_size, + &crate::ID, + ) .invoke_signed(&seeds)?; // Write TLV data @@ -126,15 +137,20 @@ pub fn handle_initialize(accounts: &mut InitializeExtraAccountMetaListAccountCon Seed::from(&wl_bump_bytes as &[u8]), ]; - accounts.system_program - .create_account(&accounts.payer, &accounts.white_list, wl_lamports, wl_size, &crate::ID) + accounts + .system_program + .create_account( + &accounts.payer, + &accounts.white_list, + wl_lamports, + wl_size, + &crate::ID, + ) .invoke_signed(&wl_seeds)?; // Write authority (payer) to whitelist account - let wl_view = unsafe { - &mut *(&mut accounts.white_list as *mut UncheckedAccount - as *mut AccountView) - }; + let wl_view = + unsafe { &mut *(&mut accounts.white_list as *mut UncheckedAccount as *mut AccountView) }; let mut wl_data = wl_view.try_borrow_mut()?; wl_data[0..32].copy_from_slice(accounts.payer.to_account_view().address().as_ref()); // count = 0 (already zeroed) @@ -207,11 +223,11 @@ pub struct AddToWhitelistAccountConstraints { } #[inline(always)] -pub fn handle_add_to_whitelist(accounts: &mut AddToWhitelistAccountConstraints) -> Result<(), ProgramError> { - let view = unsafe { - &mut *(&mut accounts.white_list as *mut UncheckedAccount - as *mut AccountView) - }; +pub fn handle_add_to_whitelist( + accounts: &mut AddToWhitelistAccountConstraints, +) -> Result<(), ProgramError> { + let view = + unsafe { &mut *(&mut accounts.white_list as *mut UncheckedAccount as *mut AccountView) }; let mut data = view.try_borrow_mut()?; if data.len() < 36 { diff --git a/tokens/token-minter/quasar/Cargo.toml b/tokens/token-minter/quasar/Cargo.toml index f776e1955..60bf8f82f 100644 --- a/tokens/token-minter/quasar/Cargo.toml +++ b/tokens/token-minter/quasar/Cargo.toml @@ -30,6 +30,11 @@ idl-build = ["quasar-lang/idl-build"] # git. Keep this rev in lockstep with the CLI rev installed by # .github/workflows/quasar.yml. quasar-lang = { git = "https://github.com/blueshift-gg/quasar", rev = "be60fca" } +# Pinned: quasar-lang asks for zeropod "0.3.3" and wincode 0.4, but zeropod +# 0.3.4 moved to wincode 0.5, so a fresh resolve (no lockfile is committed) +# splits the graph across two wincode versions and every Pod* trait bound +# fails. Unpin when quasar-lang's pinned rev accepts zeropod 0.3.4+. +zeropod = "=0.3.3" quasar-spl = { git = "https://github.com/blueshift-gg/quasar", rev = "be60fca" } # Vendored: upstream removed the quasar-metadata crate before the 0.1.0 # release with no replacement. See tokens/quasar-metadata/README.md. diff --git a/tokens/token-minter/quasar/src/instructions/create.rs b/tokens/token-minter/quasar/src/instructions/create.rs index 5b787f7f7..bd6c489a7 100644 --- a/tokens/token-minter/quasar/src/instructions/create.rs +++ b/tokens/token-minter/quasar/src/instructions/create.rs @@ -1,8 +1,4 @@ -use { - quasar_lang::prelude::*, - quasar_metadata::prelude::*, - quasar_spl::prelude::*, -}; +use {quasar_lang::prelude::*, quasar_metadata::prelude::*, quasar_spl::prelude::*}; /// Accounts for creating a new token mint with Metaplex metadata. /// @@ -45,7 +41,8 @@ pub fn handle_create_token( ) -> Result<(), ProgramError> { log("Creating metadata account"); - accounts.token_metadata_program + accounts + .token_metadata_program .create_metadata_accounts_v3( &accounts.metadata_account, &accounts.mint_account, diff --git a/tokens/token-minter/quasar/src/instructions/mint.rs b/tokens/token-minter/quasar/src/instructions/mint.rs index 59e3e0543..4c1ac0189 100644 --- a/tokens/token-minter/quasar/src/instructions/mint.rs +++ b/tokens/token-minter/quasar/src/instructions/mint.rs @@ -32,7 +32,8 @@ pub fn handle_mint_token( ) -> Result<(), ProgramError> { log("Minting tokens to associated token account..."); - accounts.token_program + accounts + .token_program .mint_to( &accounts.mint_account, &accounts.associated_token_account, diff --git a/tokens/token-minter/quasar/src/lib.rs b/tokens/token-minter/quasar/src/lib.rs index 7d6e83a1a..9a1facba7 100644 --- a/tokens/token-minter/quasar/src/lib.rs +++ b/tokens/token-minter/quasar/src/lib.rs @@ -2,7 +2,7 @@ use quasar_lang::prelude::*; -mod instructions; +pub mod instructions; use instructions::*; #[cfg(test)] mod tests; @@ -28,12 +28,7 @@ mod quasar_token_minter { token_symbol: String<10>, token_uri: String<200>, ) -> Result<(), ProgramError> { - instructions::handle_create_token( - &mut ctx.accounts, - &token_name, - &token_symbol, - &token_uri, - ) + instructions::handle_create_token(&mut ctx.accounts, token_name, token_symbol, token_uri) } /// Mint `amount` minor units of the token to the recipient. diff --git a/tokens/transfer-tokens/quasar/Cargo.toml b/tokens/transfer-tokens/quasar/Cargo.toml index 64d6b6c3e..3b670beea 100644 --- a/tokens/transfer-tokens/quasar/Cargo.toml +++ b/tokens/transfer-tokens/quasar/Cargo.toml @@ -30,6 +30,11 @@ idl-build = ["quasar-lang/idl-build"] # git. Keep this rev in lockstep with the CLI rev installed by # .github/workflows/quasar.yml. quasar-lang = { git = "https://github.com/blueshift-gg/quasar", rev = "be60fca" } +# Pinned: quasar-lang asks for zeropod "0.3.3" and wincode 0.4, but zeropod +# 0.3.4 moved to wincode 0.5, so a fresh resolve (no lockfile is committed) +# splits the graph across two wincode versions and every Pod* trait bound +# fails. Unpin when quasar-lang's pinned rev accepts zeropod 0.3.4+. +zeropod = "=0.3.3" quasar-spl = { git = "https://github.com/blueshift-gg/quasar", rev = "be60fca" } solana-instruction = { version = "3.2.0" } diff --git a/tokens/transfer-tokens/quasar/src/lib.rs b/tokens/transfer-tokens/quasar/src/lib.rs index a76902227..7aacddadf 100644 --- a/tokens/transfer-tokens/quasar/src/lib.rs +++ b/tokens/transfer-tokens/quasar/src/lib.rs @@ -55,8 +55,14 @@ fn handle_mint_tokens( accounts: &mut MintTokensAccountConstraints, amount: u64, ) -> Result<(), ProgramError> { - accounts.token_program - .mint_to(&accounts.mint, &accounts.recipient_token_account, &accounts.mint_authority, amount) + accounts + .token_program + .mint_to( + &accounts.mint, + &accounts.recipient_token_account, + &accounts.mint_authority, + amount, + ) .invoke() } @@ -77,7 +83,13 @@ fn handle_transfer_tokens( accounts: &mut TransferTokensAccountConstraints, amount: u64, ) -> Result<(), ProgramError> { - accounts.token_program - .transfer(&accounts.sender_token_account, &accounts.recipient_token_account, &accounts.sender, amount) + accounts + .token_program + .transfer( + &accounts.sender_token_account, + &accounts.recipient_token_account, + &accounts.sender, + amount, + ) .invoke() }