From f8759721540ba66868c0551deb91550c1e8c7908 Mon Sep 17 00:00:00 2001 From: Samuel Vanderwaal Date: Sat, 23 Mar 2024 11:11:46 -0800 Subject: [PATCH 1/5] add nifty to monoswap --- Cargo.lock | 38 ++++ programs/libreplex_monoswap/Cargo.toml | 1 + programs/libreplex_monoswap/src/errors.rs | 35 +--- .../src/instructions/create_monoswap.rs | 46 +++-- .../src/instructions/create_nifty_swap.rs | 110 ++++++++++++ .../src/instructions/mod.rs | 8 +- .../src/instructions/nifty_swap.rs | 164 ++++++++++++++++++ .../src/instructions/swap.rs | 45 +++-- programs/libreplex_monoswap/src/lib.rs | 13 +- programs/libreplex_monoswap/src/state/mod.rs | 6 +- .../src/state/nifty_marker.rs | 24 +++ .../src/state/swap_marker.rs | 9 +- 12 files changed, 404 insertions(+), 95 deletions(-) create mode 100644 programs/libreplex_monoswap/src/instructions/create_nifty_swap.rs create mode 100644 programs/libreplex_monoswap/src/instructions/nifty_swap.rs create mode 100644 programs/libreplex_monoswap/src/state/nifty_marker.rs diff --git a/Cargo.lock b/Cargo.lock index 6a92fdce..24853007 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -2393,6 +2393,7 @@ dependencies = [ "libreplex_shared", "mocha", "mpl-token-metadata", + "nifty-asset", "solana-program", "solana-program-test", "solana-sdk", @@ -2690,6 +2691,34 @@ dependencies = [ "thiserror", ] +[[package]] +name = "nifty-asset" +version = "0.0.1-alpha.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "99238aae879eafc74679bfa512af21b5694ebfb0c485d5f709c4e33b2dd463a9" +dependencies = [ + "borsh 0.10.3", + "kaigan", + "nifty-asset-types", + "num-derive 0.3.3", + "num-traits", + "solana-program", + "thiserror", +] + +[[package]] +name = "nifty-asset-types" +version = "0.0.1-alpha.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "e0b94791938a72017c1cb3a95eef3b593d8871013d108463b2478cbaef5e6818" +dependencies = [ + "borsh 0.10.3", + "bytemuck", + "podded", + "solana-program", + "thiserror", +] + [[package]] name = "nix" version = "0.26.4" @@ -3091,6 +3120,15 @@ version = "0.2.3" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "b4596b6d070b27117e987119b4dac604f3c58cfb0b191112e24771b2faeac1a6" +[[package]] +name = "podded" +version = "0.5.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "e5ef03855395ea7838e038410cf2be5dd419fa65560c3c82dfcf1ee5b32cfb4e" +dependencies = [ + "bytemuck", +] + [[package]] name = "polyval" version = "0.5.3" diff --git a/programs/libreplex_monoswap/Cargo.toml b/programs/libreplex_monoswap/Cargo.toml index 20f97fb8..7be5d0b7 100644 --- a/programs/libreplex_monoswap/Cargo.toml +++ b/programs/libreplex_monoswap/Cargo.toml @@ -25,6 +25,7 @@ spl-associated-token-account = "2.2.0" solana-program = {version = "1.17.22"} libreplex_shared = {version = "*", path = "../libreplex_shared", features=["cpi"]} mpl-token-metadata = { version="~3" } +nifty-asset = "0.0.1-alpha.3" [dev-dependencies] mocha = "0.1.1" diff --git a/programs/libreplex_monoswap/src/errors.rs b/programs/libreplex_monoswap/src/errors.rs index c2d19db8..e28d4832 100644 --- a/programs/libreplex_monoswap/src/errors.rs +++ b/programs/libreplex_monoswap/src/errors.rs @@ -1,36 +1,7 @@ use anchor_lang::prelude::*; #[error_code] -pub enum LegacyInscriptionErrorCode { - - #[msg("Metadata has a bad mint")] - BadMint, - - #[msg("Cannot inscribe a fungible asset")] - CannotInscribeFungible, - - #[msg("Bad authority")] - BadAuthority, - - #[msg("Bad authority for holder inscription")] - BadAuthorityForHolderInscription, - - #[msg("Bad authority for update auth inscription")] - BadAuthorityForUpdateAuthInscription, - - #[msg("Multi Signature threshold must be one to create / edit inscriptions")] - MultiSigThresholdMustBeOne, - - #[msg("Not squads member")] - NotSquadsMember, - - #[msg("Inscription V2 key mismatch")] - Inscription2KeyMismatch, - - #[msg("Inscription V3 key mismatch")] - InscriptionV3KeyMismatch, - - #[msg("Metadata data missmatch")] - DataHashMismatch, - +pub enum MonoSwapError { + #[msg("Invalid Marker State")] + InvalidMarkerState, } diff --git a/programs/libreplex_monoswap/src/instructions/create_monoswap.rs b/programs/libreplex_monoswap/src/instructions/create_monoswap.rs index 6ba0e3f3..a0222e45 100644 --- a/programs/libreplex_monoswap/src/instructions/create_monoswap.rs +++ b/programs/libreplex_monoswap/src/instructions/create_monoswap.rs @@ -1,51 +1,51 @@ - - use anchor_lang::prelude::*; -use anchor_spl::{associated_token::AssociatedToken, token::spl_token, token_interface::{Mint,TokenAccount, spl_token_2022}}; +use anchor_spl::{ + associated_token::AssociatedToken, + token::spl_token, + token_interface::{spl_token_2022, Mint, TokenAccount}, +}; use libreplex_shared::operations::transfer_generic_spl; use crate::SwapMarker; - #[derive(Clone, AnchorDeserialize, AnchorSerialize)] pub struct CreateMonoSwapInput { pub mint_outgoing_amount: u64, - pub mint_incoming_amount: u64 + pub mint_incoming_amount: u64, } #[derive(Accounts)] pub struct CreateMonoSwapCtx<'info> { - #[account(init, - payer = payer, + payer = payer, space = SwapMarker::SIZE, - seeds = ["swap_marker".as_bytes(), + seeds = ["swap_marker".as_bytes(), namespace.key().as_ref(), mint_outgoing.key().as_ref(), mint_incoming.key().as_ref()], // always indexed by the incoming mint bump,)] pub swap_marker: Account<'info, SwapMarker>, - #[account(mut)] + #[account(mut)] pub mint_incoming: InterfaceAccount<'info, Mint>, - // each mint has to exist - there must be enough - pub mint_outgoing: InterfaceAccount<'info, Mint>, + // each mint has to exist - there must be enough + pub mint_outgoing: InterfaceAccount<'info, Mint>, // it is the responsibility of each swapper program to create enough - // of the outgoing mint so that the swap can happen. It is deposited + // of the outgoing mint so that the swap can happen. It is deposited // from this account #[account(mut, associated_token::mint = mint_outgoing, associated_token::authority = mint_outgoing_owner - )] + )] pub mint_outgoing_token_account_source: InterfaceAccount<'info, TokenAccount>, - - // escrow holders are organised by namespace + incoming mint - + + // escrow holders are organised by namespace + incoming mint - // that way you can get wallet contents to see what swaps are available to you /// CHECK: Checked in transfer logic #[account( - seeds = ["swap_escrow".as_bytes(), + seeds = ["swap_escrow".as_bytes(), namespace.key().as_ref(), mint_incoming.key().as_ref()], // always indexed by the incoming mint bump)] @@ -60,7 +60,6 @@ pub struct CreateMonoSwapCtx<'info> { )] pub mint_outgoing_token_account_escrow: InterfaceAccount<'info, TokenAccount>, - #[account(mut)] pub payer: Signer<'info>, @@ -68,7 +67,7 @@ pub struct CreateMonoSwapCtx<'info> { #[account(mut)] pub mint_outgoing_owner: Signer<'info>, - // any account that can sign this. this is useful for grouping swaps + // any account that can sign this. this is useful for grouping swaps pub namespace: Signer<'info>, /// CHECK: Checked in constraint @@ -81,14 +80,14 @@ pub struct CreateMonoSwapCtx<'info> { #[account()] pub system_program: Program<'info, System>, - } -pub fn create_swap(ctx: Context, input: CreateMonoSwapInput) -> Result<()> { - +pub fn process_create_swap( + ctx: Context, + input: CreateMonoSwapInput, +) -> Result<()> { let swap_marker = &mut ctx.accounts.swap_marker; let mint_outgoing = &mut ctx.accounts.mint_outgoing; - swap_marker.namespace = ctx.accounts.namespace.key(); swap_marker.mint_incoming = ctx.accounts.mint_incoming.key(); @@ -98,7 +97,7 @@ pub fn create_swap(ctx: Context, input: CreateMonoSwapInput) swap_marker.used = false; - // transfer the outgoing mint into escrow - + // transfer the outgoing mint into escrow - let token_program = &ctx.accounts.token_program; let mint_outgoing_token_account_source = &ctx.accounts.mint_outgoing_token_account_source; let mint_outgoing_token_account_escrow = &ctx.accounts.mint_outgoing_token_account_escrow; @@ -124,7 +123,6 @@ pub fn create_swap(ctx: Context, input: CreateMonoSwapInput) mint_outgoing.decimals, input.mint_outgoing_amount, )?; - Ok(()) } diff --git a/programs/libreplex_monoswap/src/instructions/create_nifty_swap.rs b/programs/libreplex_monoswap/src/instructions/create_nifty_swap.rs new file mode 100644 index 00000000..96ae03b5 --- /dev/null +++ b/programs/libreplex_monoswap/src/instructions/create_nifty_swap.rs @@ -0,0 +1,110 @@ +use anchor_lang::prelude::*; +use anchor_spl::{ + associated_token::AssociatedToken, + token_interface::{Mint, TokenAccount, TokenInterface}, +}; +use libreplex_shared::operations::transfer_generic_spl; + +use crate::NiftyMarker; + +// Swaps are created by transferring a token in. +#[derive(Accounts)] +pub struct CreateNiftySwapCtx<'info> { + // any account that can sign this. this is useful for grouping swaps + pub namespace: Signer<'info>, + + #[account(mut)] + pub payer: Signer<'info>, + + #[account(init, + payer = payer, + space = 8 + NiftyMarker::INIT_SPACE, + seeds = [ + "nifty_marker".as_bytes(), + namespace.key().as_ref(), + asset.key().as_ref(), + mint.key().as_ref() + ], + bump, + )] + pub nifty_marker: Account<'info, NiftyMarker>, + + #[account( + constraint = asset.owner == nifty_program.key + )] + pub asset: UncheckedAccount<'info>, + + // each mint has to exist - there must be enough + pub mint: InterfaceAccount<'info, Mint>, + + // escrow holders are organised by namespace + incoming mint - + // that way you can get wallet contents to see what swaps are available to you + /// CHECK: Checked in transfer logic + #[account( + seeds = [ + "nifty_escrow".as_bytes(), + namespace.key().as_ref(), + asset.key().as_ref(), + mint.key().as_ref(), + ], // always indexed by the incoming mint + bump + )] + pub escrow_owner: UncheckedAccount<'info>, + + #[account(init, + payer = payer, + associated_token::mint = mint, + associated_token::authority = escrow_owner, + )] + pub escrow_token_account: InterfaceAccount<'info, TokenAccount>, + + #[account(init, + payer = payer, + associated_token::mint = mint, + associated_token::authority = payer, + )] + pub source_token_account: InterfaceAccount<'info, TokenAccount>, + + // leave this here for integrations + #[account(mut)] + pub mint_outgoing_owner: Signer<'info>, + + pub token_program: Interface<'info, TokenInterface>, + pub associated_token_program: Program<'info, AssociatedToken>, + pub system_program: Program<'info, System>, + + #[account( + address = nifty_asset::ID, + )] + pub nifty_program: UncheckedAccount<'info>, +} + +pub fn create_nifty_swap(ctx: Context, amount: u64) -> Result<()> { + let swap_marker = &mut ctx.accounts.nifty_marker; + let mint = &ctx.accounts.mint; + + swap_marker.namespace = ctx.accounts.namespace.key(); + swap_marker.mint = ctx.accounts.mint.key(); + swap_marker.amount = amount; + + // transfer the outgoing mint into escrow - + let token_program = &ctx.accounts.token_program; + let associated_token_program = &ctx.accounts.associated_token_program; + + transfer_generic_spl( + &token_program.to_account_info(), + &ctx.accounts.source_token_account.to_account_info(), + &ctx.accounts.escrow_token_account.to_account_info(), + &ctx.accounts.payer.to_account_info(), + &mint.to_account_info(), + &ctx.accounts.escrow_owner.to_account_info(), + &associated_token_program.to_account_info(), + &ctx.accounts.system_program.to_account_info(), + None, // payer signs + &ctx.accounts.payer.to_account_info(), + mint.decimals, + amount, + )?; + + Ok(()) +} diff --git a/programs/libreplex_monoswap/src/instructions/mod.rs b/programs/libreplex_monoswap/src/instructions/mod.rs index d350e338..ce41c9c0 100644 --- a/programs/libreplex_monoswap/src/instructions/mod.rs +++ b/programs/libreplex_monoswap/src/instructions/mod.rs @@ -1,7 +1,11 @@ - pub mod create_monoswap; pub use create_monoswap::*; +pub mod create_nifty_swap; +pub use create_nifty_swap::*; pub mod swap; -pub use swap::*; \ No newline at end of file +pub use swap::*; + +pub mod nifty_swap; +pub use nifty_swap::*; diff --git a/programs/libreplex_monoswap/src/instructions/nifty_swap.rs b/programs/libreplex_monoswap/src/instructions/nifty_swap.rs new file mode 100644 index 00000000..e21eb20f --- /dev/null +++ b/programs/libreplex_monoswap/src/instructions/nifty_swap.rs @@ -0,0 +1,164 @@ +use anchor_lang::prelude::*; +use anchor_spl::{ + associated_token::AssociatedToken, + token::Token, + token_interface::{Mint, Token2022, TokenAccount}, +}; +use libreplex_shared::operations::transfer_generic_spl; +use nifty_asset::instructions::TransferCpi; + +use crate::{MarkerState, MonoSwapError, NiftyMarker, SwapDirection}; + +// the swap endpoint is symmetrical. +// it can be used to swap back and forth +#[derive(Accounts)] +pub struct NiftySwapCtx<'info> { + #[account(mut)] + pub payer: Signer<'info>, + + #[account(mut, + close = payer, + constraint = mint.key() == nifty_marker.mint, + constraint = asset.key() == nifty_marker.asset, + seeds = [ + "nifty_marker".as_bytes(), + nifty_marker.namespace.as_ref(), + asset.key().as_ref(), + mint.key().as_ref(), + ], + bump + )] + pub nifty_marker: Account<'info, NiftyMarker>, + + #[account( + constraint = asset.owner == nifty_program.key + )] + pub asset: UncheckedAccount<'info>, + + // each mint has to exist - there must be enough + pub mint: InterfaceAccount<'info, Mint>, + + /// CHECK: Check in pda derivation + #[account( + seeds = [ + "nifty_escrow".as_bytes(), + nifty_marker.namespace.key().as_ref(), + asset.key().as_ref(), + mint.key().as_ref(), + ], // always indexed by the incoming mint + bump + )] + pub escrow_owner: UncheckedAccount<'info>, + + #[account(mut, + associated_token::mint = mint, + associated_token::authority = escrow_owner + )] + pub escrow_token_account: InterfaceAccount<'info, TokenAccount>, + + // it is the responsibility of each swapper program to create enough + // of the outgoing mint so that the swap can happen. It is deposited + // from this account + #[account(mut, + associated_token::mint = mint, + associated_token::authority = payer + )] + pub external_token_account: InterfaceAccount<'info, TokenAccount>, + + pub token_program: Program<'info, Token>, + pub token_program_2022: Program<'info, Token2022>, + pub associated_token_program: Program<'info, AssociatedToken>, + pub system_program: Program<'info, System>, + + #[account( + address = nifty_asset::ID, + )] + pub nifty_program: UncheckedAccount<'info>, +} + +pub fn nifty_swap(ctx: Context, direction: SwapDirection) -> Result<()> { + let nifty_marker = &mut ctx.accounts.nifty_marker; + let asset_pubkey = ctx.accounts.asset.key(); + let mint_pubkey = ctx.accounts.mint.key(); + + let authority_seeds: &[&[u8]] = &[ + "nifty_escrow".as_bytes(), + nifty_marker.namespace.as_ref(), + asset_pubkey.as_ref(), + mint_pubkey.as_ref(), + &[ctx.bumps.escrow_owner], + ]; + + // Determine the direction of the swap + match direction { + // Nifty asset comes in, fungibles go out + SwapDirection::AssetIn => { + require!( + nifty_marker.state == MarkerState::FungibleEscrowed, + MonoSwapError::InvalidMarkerState + ); + + // Nifty Transfer from payer to escrow_owner + TransferCpi { + __program: &ctx.accounts.nifty_program.to_account_info(), + asset: &ctx.accounts.asset.to_account_info(), + signer: &ctx.accounts.payer.to_account_info(), + recipient: &ctx.accounts.escrow_owner.to_account_info(), + group_asset: None, + } + .invoke()?; + + // Transfer fungible from escrow to payer + transfer_generic_spl( + &ctx.accounts.token_program.to_account_info(), + &ctx.accounts.escrow_token_account.to_account_info(), + &ctx.accounts.external_token_account.to_account_info(), + &ctx.accounts.escrow_owner.to_account_info(), + &ctx.accounts.mint.to_account_info(), + &ctx.accounts.payer.to_account_info(), + &ctx.accounts.associated_token_program.to_account_info(), + &ctx.accounts.system_program.to_account_info(), + Some(&[&authority_seeds]), // payer signs + &ctx.accounts.payer.to_account_info(), + ctx.accounts.mint.decimals, + nifty_marker.amount, + )?; + + // Change nifty marker state + nifty_marker.state = MarkerState::AssetEscrowed; + } + // Fungibles come in, nifty asset goes out + SwapDirection::AssetOut => { + // Nifty Transfer from escrow_owner to payer + TransferCpi { + __program: &ctx.accounts.nifty_program.to_account_info(), + asset: &ctx.accounts.asset.to_account_info(), + signer: &ctx.accounts.escrow_owner.to_account_info(), + recipient: &ctx.accounts.payer.to_account_info(), + group_asset: None, + } + .invoke()?; + + // Transfer fungible from payer to escrow + transfer_generic_spl( + &ctx.accounts.token_program.to_account_info(), + &ctx.accounts.external_token_account.to_account_info(), + &ctx.accounts.escrow_token_account.to_account_info(), + &ctx.accounts.payer.to_account_info(), + &ctx.accounts.mint.to_account_info(), + &ctx.accounts.escrow_owner.to_account_info(), + &ctx.accounts.associated_token_program.to_account_info(), + &ctx.accounts.system_program.to_account_info(), + None, // payer signs + &ctx.accounts.payer.to_account_info(), + ctx.accounts.mint.decimals, + nifty_marker.amount, + )?; + + // Change nifty marker state + nifty_marker.state = MarkerState::FungibleEscrowed; + } + } + + Ok(()) +} diff --git a/programs/libreplex_monoswap/src/instructions/swap.rs b/programs/libreplex_monoswap/src/instructions/swap.rs index 5834fafb..37132737 100644 --- a/programs/libreplex_monoswap/src/instructions/swap.rs +++ b/programs/libreplex_monoswap/src/instructions/swap.rs @@ -12,12 +12,12 @@ use crate::SwapMarker; // it can be used to swap back and forth #[derive(Accounts)] pub struct SwapCtx<'info> { - #[account(mut, + #[account(mut, close = payer, constraint = mint_incoming.key() == swap_marker.mint_incoming, constraint = mint_outgoing.key() == swap_marker.mint_outgoing, seeds = [ - "swap_marker".as_bytes(), + "swap_marker".as_bytes(), swap_marker.namespace.as_ref(), mint_outgoing.key().as_ref(), mint_incoming.key().as_ref()], @@ -26,9 +26,9 @@ pub struct SwapCtx<'info> { /// swapping always creates a symmetrical swap marker that enables a swap back #[account(init, - payer = payer, + payer = payer, space = SwapMarker::SIZE, - seeds = ["swap_marker".as_bytes(), + seeds = ["swap_marker".as_bytes(), swap_marker.namespace.as_ref(), mint_incoming.key().as_ref(), mint_outgoing.key().as_ref()], // always indexed by the incoming mint @@ -44,7 +44,7 @@ pub struct SwapCtx<'info> { #[account()] pub mint_outgoing: InterfaceAccount<'info, Mint>, - // it is the responsibility of each swapper program to create enough + // it is the responsibility of each swapper program to create enough // of the outgoing mint so that the swap can happen. It is deposited // from this account #[account(mut, @@ -55,26 +55,25 @@ pub struct SwapCtx<'info> { /// CHECK: Check in pda derivation #[account( - seeds = ["swap_escrow".as_bytes(), + seeds = ["swap_escrow".as_bytes(), swap_marker.namespace.as_ref(), mint_incoming.key().as_ref()], // always indexed by the incoming mint bump)] pub escrow_holder: UncheckedAccount<'info>, - /// CHECK: Check in pda derivation - #[account( - seeds = ["swap_escrow".as_bytes(), + /// CHECK: Check in pda derivation + #[account( + seeds = ["swap_escrow".as_bytes(), swap_marker.namespace.as_ref(), mint_outgoing.key().as_ref()], // always indexed by the incoming mint bump)] - pub escrow_holder_reverse: UncheckedAccount<'info>, + pub escrow_holder_reverse: UncheckedAccount<'info>, // ... into this escrow account /// CHECK: Checked in transfer logic #[account(mut)] pub mint_incoming_token_account_target: UncheckedAccount<'info>, - // it is the responsibility of each swapper program to create enough // of the outgoing mint so that the swap can happen. It is deposited // from this account @@ -100,28 +99,26 @@ pub struct SwapCtx<'info> { pub associated_token_program: Program<'info, AssociatedToken>, - #[account()] pub system_program: Program<'info, System>, } -pub fn swap(ctx: Context) -> Result<()> { - - +pub fn process_swap(ctx: Context) -> Result<()> { let swap_marker_reverse = &mut ctx.accounts.swap_marker_reverse; let mint_incoming = &mut ctx.accounts.mint_incoming; let mint_outgoing = &mut ctx.accounts.mint_outgoing; let escrow_holder_reverse = &ctx.accounts.escrow_holder_reverse; let swap_marker = &ctx.accounts.swap_marker; - swap_marker_reverse.set_inner(SwapMarker { - namespace: swap_marker.namespace.key(), - mint_incoming: mint_outgoing.key(), - mint_outgoing: mint_incoming.key(), - mint_incoming_amount: swap_marker.mint_outgoing_amount, - mint_outgoing_amount: swap_marker.mint_incoming_amount, - used: true }); - + swap_marker_reverse.set_inner(SwapMarker { + namespace: swap_marker.namespace.key(), + mint_incoming: mint_outgoing.key(), + mint_outgoing: mint_incoming.key(), + mint_incoming_amount: swap_marker.mint_outgoing_amount, + mint_outgoing_amount: swap_marker.mint_incoming_amount, + used: true, + }); + // transfer the outgoing mint into escrow - let token_program = &ctx.accounts.token_program; let mint_outgoing_token_account_source = &ctx.accounts.mint_outgoing_token_account_source; @@ -142,7 +139,6 @@ pub fn swap(ctx: Context) -> Result<()> { &[ctx.bumps.escrow_holder], ]; - let payer = &ctx.accounts.payer; // outgoing is going to the payer @@ -182,6 +178,5 @@ pub fn swap(ctx: Context) -> Result<()> { swap_marker.mint_incoming_amount, )?; - Ok(()) } diff --git a/programs/libreplex_monoswap/src/lib.rs b/programs/libreplex_monoswap/src/lib.rs index 7a5185a4..69fe5e53 100644 --- a/programs/libreplex_monoswap/src/lib.rs +++ b/programs/libreplex_monoswap/src/lib.rs @@ -9,25 +9,22 @@ pub mod state; pub use errors::*; pub use constants::*; -pub use state::*; pub use instructions::*; +pub use state::*; #[program] pub mod libreplex_monoswap { + use super::*; pub fn create_monoswap( ctx: Context, input: CreateMonoSwapInput, ) -> Result<()> { - instructions::create_monoswap::create_swap(ctx, input) + process_create_swap(ctx, input) } - - pub fn swap( - ctx: Context, - ) -> Result<()> { - instructions::swap::swap(ctx) + pub fn swap(ctx: Context) -> Result<()> { + process_swap(ctx) } - } diff --git a/programs/libreplex_monoswap/src/state/mod.rs b/programs/libreplex_monoswap/src/state/mod.rs index ffd9f08b..5fa44982 100644 --- a/programs/libreplex_monoswap/src/state/mod.rs +++ b/programs/libreplex_monoswap/src/state/mod.rs @@ -1,3 +1,5 @@ - pub use swap_marker::*; -pub mod swap_marker; \ No newline at end of file +pub mod swap_marker; + +pub mod nifty_marker; +pub use nifty_marker::*; diff --git a/programs/libreplex_monoswap/src/state/nifty_marker.rs b/programs/libreplex_monoswap/src/state/nifty_marker.rs new file mode 100644 index 00000000..90ce43c3 --- /dev/null +++ b/programs/libreplex_monoswap/src/state/nifty_marker.rs @@ -0,0 +1,24 @@ +use anchor_lang::prelude::borsh::{BorshDeserialize, BorshSerialize}; +use anchor_lang::prelude::*; +use anchor_lang::{AnchorDeserialize, AnchorSerialize}; + +#[account] +#[derive(InitSpace)] +pub struct NiftyMarker { + pub namespace: Pubkey, + // The Nifty asset associated with this marker + pub asset: Pubkey, + // The mint associated with this marker + pub mint: Pubkey, + // The state of the marker indicating which type of asset is currently escrowed + pub state: MarkerState, + // The amount of the fungible token escrowed + pub amount: u64, +} + +#[derive(BorshSerialize, BorshDeserialize, Clone, Debug, Eq, PartialEq, InitSpace)] +pub enum MarkerState { + Uninitialized, + AssetEscrowed, + FungibleEscrowed, +} diff --git a/programs/libreplex_monoswap/src/state/swap_marker.rs b/programs/libreplex_monoswap/src/state/swap_marker.rs index 9fe00b68..01a0f7dd 100644 --- a/programs/libreplex_monoswap/src/state/swap_marker.rs +++ b/programs/libreplex_monoswap/src/state/swap_marker.rs @@ -11,7 +11,7 @@ pub struct SwapMarker { pub namespace: Pubkey, // allows slicing and dicing by incoming mint pub mint_incoming: Pubkey, - // allows slicing and dicing by outgoing mint + // allows slicing and dicing by outgoing mint pub mint_outgoing: Pubkey, pub mint_incoming_amount: u64, pub mint_outgoing_amount: u64, @@ -20,10 +20,15 @@ pub struct SwapMarker { // closed to avoid a situation where a // holder gets trapped into a crappy token // and cannot go back - pub used: bool + pub used: bool, } impl SwapMarker { pub const SIZE: usize = 8 + 32 + 32 + 32 + 8 + 8 + 1; } +#[derive(Clone, AnchorDeserialize, AnchorSerialize)] +pub enum SwapDirection { + AssetIn, + AssetOut, +} From 06b462e438b295d0e53f45ff3f0abe34cb0d764e Mon Sep 17 00:00:00 2001 From: Samuel Vanderwaal Date: Sun, 24 Mar 2024 11:27:12 -0800 Subject: [PATCH 2/5] add test --- Cargo.lock | 2 + programs/libreplex_monoswap/Cargo.toml | 22 +- .../src/instructions/create_nifty_swap.rs | 14 +- .../src/instructions/nifty_swap.rs | 2 +- programs/libreplex_monoswap/src/lib.rs | 8 + .../src/state/nifty_marker.rs | 3 +- .../tests/create_nifty_swap.rs | 242 ++++++++++++++++++ 7 files changed, 274 insertions(+), 19 deletions(-) create mode 100644 programs/libreplex_monoswap/tests/create_nifty_swap.rs diff --git a/Cargo.lock b/Cargo.lock index 24853007..98008133 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -2398,6 +2398,8 @@ dependencies = [ "solana-program-test", "solana-sdk", "spl-associated-token-account", + "spl-token", + "spl-token-2022 1.0.0", ] [[package]] diff --git a/programs/libreplex_monoswap/Cargo.toml b/programs/libreplex_monoswap/Cargo.toml index 7be5d0b7..6b7307a3 100644 --- a/programs/libreplex_monoswap/Cargo.toml +++ b/programs/libreplex_monoswap/Cargo.toml @@ -16,19 +16,27 @@ no-idl = [] no-log-ix-name = [] cpi = ["no-entrypoint"] default = [] +test-bpf = [] [dependencies] -anchor-lang = {version = "~0.29", features = ["init-if-needed"]} -anchor-spl = {version = "~0.29"} -libreplex_fair_launch = {version = "*", path="../libreplex_fair_launch", features =["cpi", "no-entrypoint"]} +anchor-lang = { version = "~0.29", features = ["init-if-needed"] } +anchor-spl = { version = "~0.29" } +libreplex_fair_launch = { version = "*", path = "../libreplex_fair_launch", features = [ + "cpi", + "no-entrypoint", +] } spl-associated-token-account = "2.2.0" -solana-program = {version = "1.17.22"} -libreplex_shared = {version = "*", path = "../libreplex_shared", features=["cpi"]} -mpl-token-metadata = { version="~3" } +solana-program = { version = "1.17.22" } +libreplex_shared = { version = "*", path = "../libreplex_shared", features = [ + "cpi", +] } +mpl-token-metadata = { version = "~3" } nifty-asset = "0.0.1-alpha.3" [dev-dependencies] -mocha = "0.1.1" assert_matches = "1.5.0" +mocha = "0.1.1" solana-program-test = "1.17.22" solana-sdk = "1.17.22" +spl-token-2022 = "1.0" +spl-token = "4" diff --git a/programs/libreplex_monoswap/src/instructions/create_nifty_swap.rs b/programs/libreplex_monoswap/src/instructions/create_nifty_swap.rs index 96ae03b5..e00a323e 100644 --- a/programs/libreplex_monoswap/src/instructions/create_nifty_swap.rs +++ b/programs/libreplex_monoswap/src/instructions/create_nifty_swap.rs @@ -34,7 +34,6 @@ pub struct CreateNiftySwapCtx<'info> { )] pub asset: UncheckedAccount<'info>, - // each mint has to exist - there must be enough pub mint: InterfaceAccount<'info, Mint>, // escrow holders are organised by namespace + incoming mint - @@ -51,24 +50,21 @@ pub struct CreateNiftySwapCtx<'info> { )] pub escrow_owner: UncheckedAccount<'info>, - #[account(init, + #[account( + init, payer = payer, associated_token::mint = mint, associated_token::authority = escrow_owner, )] pub escrow_token_account: InterfaceAccount<'info, TokenAccount>, - #[account(init, - payer = payer, + #[account( + mut, associated_token::mint = mint, associated_token::authority = payer, )] pub source_token_account: InterfaceAccount<'info, TokenAccount>, - // leave this here for integrations - #[account(mut)] - pub mint_outgoing_owner: Signer<'info>, - pub token_program: Interface<'info, TokenInterface>, pub associated_token_program: Program<'info, AssociatedToken>, pub system_program: Program<'info, System>, @@ -79,7 +75,7 @@ pub struct CreateNiftySwapCtx<'info> { pub nifty_program: UncheckedAccount<'info>, } -pub fn create_nifty_swap(ctx: Context, amount: u64) -> Result<()> { +pub fn process_create_nifty_swap(ctx: Context, amount: u64) -> Result<()> { let swap_marker = &mut ctx.accounts.nifty_marker; let mint = &ctx.accounts.mint; diff --git a/programs/libreplex_monoswap/src/instructions/nifty_swap.rs b/programs/libreplex_monoswap/src/instructions/nifty_swap.rs index e21eb20f..1858f557 100644 --- a/programs/libreplex_monoswap/src/instructions/nifty_swap.rs +++ b/programs/libreplex_monoswap/src/instructions/nifty_swap.rs @@ -76,7 +76,7 @@ pub struct NiftySwapCtx<'info> { pub nifty_program: UncheckedAccount<'info>, } -pub fn nifty_swap(ctx: Context, direction: SwapDirection) -> Result<()> { +pub fn process_nifty_swap(ctx: Context, direction: SwapDirection) -> Result<()> { let nifty_marker = &mut ctx.accounts.nifty_marker; let asset_pubkey = ctx.accounts.asset.key(); let mint_pubkey = ctx.accounts.mint.key(); diff --git a/programs/libreplex_monoswap/src/lib.rs b/programs/libreplex_monoswap/src/lib.rs index 69fe5e53..6013d9a0 100644 --- a/programs/libreplex_monoswap/src/lib.rs +++ b/programs/libreplex_monoswap/src/lib.rs @@ -27,4 +27,12 @@ pub mod libreplex_monoswap { pub fn swap(ctx: Context) -> Result<()> { process_swap(ctx) } + + pub fn create_nifty_swap(ctx: Context, amount: u64) -> Result<()> { + process_create_nifty_swap(ctx, amount) + } + + pub fn nifty_swap(ctx: Context, direction: SwapDirection) -> Result<()> { + process_nifty_swap(ctx, direction) + } } diff --git a/programs/libreplex_monoswap/src/state/nifty_marker.rs b/programs/libreplex_monoswap/src/state/nifty_marker.rs index 90ce43c3..e0ef2684 100644 --- a/programs/libreplex_monoswap/src/state/nifty_marker.rs +++ b/programs/libreplex_monoswap/src/state/nifty_marker.rs @@ -1,4 +1,3 @@ -use anchor_lang::prelude::borsh::{BorshDeserialize, BorshSerialize}; use anchor_lang::prelude::*; use anchor_lang::{AnchorDeserialize, AnchorSerialize}; @@ -16,7 +15,7 @@ pub struct NiftyMarker { pub amount: u64, } -#[derive(BorshSerialize, BorshDeserialize, Clone, Debug, Eq, PartialEq, InitSpace)] +#[derive(AnchorSerialize, AnchorDeserialize, Clone, Debug, Eq, PartialEq, InitSpace)] pub enum MarkerState { Uninitialized, AssetEscrowed, diff --git a/programs/libreplex_monoswap/tests/create_nifty_swap.rs b/programs/libreplex_monoswap/tests/create_nifty_swap.rs new file mode 100644 index 00000000..76eb0061 --- /dev/null +++ b/programs/libreplex_monoswap/tests/create_nifty_swap.rs @@ -0,0 +1,242 @@ +#![cfg(feature = "test-bpf")] + +use anchor_lang::{prelude::*, InstructionData}; +use libreplex_monoswap::{accounts::CreateNiftySwapCtx, instruction::CreateNiftySwap}; +use nifty_asset::{accounts::Asset, instructions::CreateBuilder, types::Standard}; +use solana_program::program_pack::Pack; +use solana_program_test::*; +use solana_sdk::{ + instruction::Instruction, pubkey::Pubkey, signature::Keypair, signer::Signer, + system_instruction, system_program, transaction::Transaction, +}; +use spl_associated_token_account::{ + get_associated_token_address, instruction::create_associated_token_account, +}; +use spl_token_2022::instruction::{initialize_mint2, mint_to}; + +const MINT_LAYOUT: u64 = 82; + +pub fn program_test() -> ProgramTest { + let mut test = ProgramTest::new("libreplex_monoswap", libreplex_monoswap::ID, None); + test.add_program("nifty_asset", nifty_asset::ID, None); + test.add_program("spl_token_2022", spl_token_2022::ID, None); + + test +} + +pub async fn airdrop( + context: &mut ProgramTestContext, + receiver: &Pubkey, + amount: u64, +) -> Result<()> { + let tx = Transaction::new_signed_with_payer( + &[system_instruction::transfer( + &context.payer.pubkey(), + receiver, + amount, + )], + Some(&context.payer.pubkey()), + &[&context.payer], + context.last_blockhash, + ); + + context.banks_client.process_transaction(tx).await.unwrap(); + Ok(()) +} + +mod nifty_swaps { + use libreplex_monoswap::NiftyMarker; + + use super::*; + + #[tokio::test] + async fn can_create_new_nifty_swap_with_legacy_token() { + let mut context = program_test().start_with_context().await; + + // **Set up keypairs and funding** + + // Authority creates the fungible and the nifty swap account + let authority_signer = Keypair::new(); + let authority = authority_signer.pubkey(); + + // User owns the Nifty asset and does the swapping + let user_signer = Keypair::new(); + let user = user_signer.pubkey(); + + // Mint account for the fungible token + let mint_signer = Keypair::new(); + let mint = mint_signer.pubkey(); + + // Keypair for the nifty asset + let asset_signer = Keypair::new(); + let asset = asset_signer.pubkey(); + + let nifty_marker = Pubkey::find_program_address( + &[ + b"nifty_marker", + authority.as_ref(), + asset.as_ref(), + mint.as_ref(), + ], + &libreplex_monoswap::ID, + ) + .0; + + // Monoswap nifty escrow owner pda + let escrow_owner = Pubkey::find_program_address( + &[ + b"nifty_escrow", + authority.as_ref(), + asset.as_ref(), + mint.as_ref(), + ], + &libreplex_monoswap::ID, + ) + .0; + + // Associated token accounts for the authority and escrow + let ata_pubkey = get_associated_token_address(&authority, &mint); + let ata_escrow_pubkey = get_associated_token_address(&escrow_owner, &mint); + + // Fund the authority and user + airdrop(&mut context, &authority, 1_000_000_000) + .await + .unwrap(); + airdrop(&mut context, &user, 1_000_000_000).await.unwrap(); + + // **Create Nifty and fungible tokens** + + // Create a simple Nifty asset with no extensions. + let nifty_ix = CreateBuilder::new() + .asset(asset) + .authority(user, true) + .owner(user) + .payer(Some(user)) + .system_program(Some(system_program::ID)) + .name("TestNifty".to_string()) + .standard(Standard::NonFungible) + .mutable(true) + .instruction(); + + let rent = context.banks_client.get_rent().await.unwrap(); + let min_rent = rent.minimum_balance(MINT_LAYOUT as usize); + + // Create mint account + let create_mint_account_ix = system_instruction::create_account( + &authority, + &mint, + min_rent, + MINT_LAYOUT, + &spl_token::ID, + ); + + // Initalize mint ix + let init_mint_ix = + initialize_mint2(&spl_token::ID, &mint, &authority, Some(&authority), 0).unwrap(); + + // Create associated account instruction + let create_assoc_account_ix = + create_associated_token_account(&authority, &authority, &mint, &spl_token::ID); + + // Mint to instruction + let mint_to_ix = mint_to(&spl_token::ID, &mint, &ata_pubkey, &authority, &[], 10).unwrap(); + + // **Compose tranasaction, send it and assert the results** + + let instructions = vec![ + nifty_ix, + create_mint_account_ix, + init_mint_ix, + create_assoc_account_ix, + mint_to_ix, + ]; + + let signers = vec![ + &context.payer, + &authority_signer, + &mint_signer, + &user_signer, + &asset_signer, + ]; + + let tx = Transaction::new_signed_with_payer( + &instructions, + Some(&context.payer.pubkey()), + &signers, + context.last_blockhash, + ); + context.banks_client.process_transaction(tx).await.unwrap(); + + let asset_account = context + .banks_client + .get_account(asset) + .await + .expect("get_account") + .expect("asset_account not found"); + + let asset_data = Asset::deserialize(&mut asset_account.data.as_slice()).unwrap(); + assert_eq!(asset_data.owner, user); + assert_eq!(asset_data.authority, user); + assert_eq!(asset_data.standard, Standard::NonFungible); + assert_eq!(asset_data.mutable, true); + + let authority_token_account = context + .banks_client + .get_account(ata_pubkey) + .await + .expect("get_account") + .expect("authority_token_account not found"); + let ata_data = spl_token::state::Account::unpack(&authority_token_account.data).unwrap(); + assert_eq!(ata_data.amount, 10); + assert_eq!(ata_data.owner, authority); + assert_eq!(ata_data.mint, mint); + + context.warp_to_slot(100).unwrap(); + + // **Create the Nifty swap account** + let create_swap_ix = Instruction { + program_id: libreplex_monoswap::ID, + accounts: CreateNiftySwapCtx { + namespace: authority, + payer: authority, + nifty_marker, + asset, + mint, + escrow_owner, + escrow_token_account: ata_escrow_pubkey, + source_token_account: ata_pubkey, + token_program: spl_token::ID, + associated_token_program: spl_associated_token_account::ID, + system_program: system_program::ID, + nifty_program: nifty_asset::ID, + } + .to_account_metas(None), + data: CreateNiftySwap { amount: 10 }.data(), + }; + + let blockhash = context.get_new_latest_blockhash().await.unwrap(); + + let tx = Transaction::new_signed_with_payer( + &[create_swap_ix], + Some(&context.payer.pubkey()), + &[&context.payer, &authority_signer], + blockhash, + ); + + context.banks_client.process_transaction(tx).await.unwrap(); + + let nifty_marker_account = context + .banks_client + .get_account(nifty_marker) + .await + .expect("get_account") + .expect("nifty_marker_account not found"); + + let nifty_marker_data = + NiftyMarker::deserialize(&mut &nifty_marker_account.data.as_slice()[8..]).unwrap(); + + assert_eq!(nifty_marker_data.namespace, authority); + assert_eq!(nifty_marker_data.mint, mint); + assert_eq!(nifty_marker_data.amount, 10); + } +} From 7f1196cfca33103db3ff8bb6ab56643da6a73d72 Mon Sep 17 00:00:00 2001 From: Samuel Vanderwaal Date: Sun, 24 Mar 2024 12:55:40 -0800 Subject: [PATCH 3/5] refactor out test helpers; add t2022 test --- .../src/instructions/create_nifty_swap.rs | 6 +- .../src/instructions/nifty_swap.rs | 9 +- .../tests/create_nifty_swap.rs | 242 -------------- .../tests/create_nifty_swap_test.rs | 294 ++++++++++++++++++ programs/libreplex_monoswap/tests/helpers.rs | 165 ++++++++++ 5 files changed, 469 insertions(+), 247 deletions(-) delete mode 100644 programs/libreplex_monoswap/tests/create_nifty_swap.rs create mode 100644 programs/libreplex_monoswap/tests/create_nifty_swap_test.rs create mode 100644 programs/libreplex_monoswap/tests/helpers.rs diff --git a/programs/libreplex_monoswap/src/instructions/create_nifty_swap.rs b/programs/libreplex_monoswap/src/instructions/create_nifty_swap.rs index e00a323e..434c3132 100644 --- a/programs/libreplex_monoswap/src/instructions/create_nifty_swap.rs +++ b/programs/libreplex_monoswap/src/instructions/create_nifty_swap.rs @@ -55,13 +55,15 @@ pub struct CreateNiftySwapCtx<'info> { payer = payer, associated_token::mint = mint, associated_token::authority = escrow_owner, + token::token_program = token_program, )] pub escrow_token_account: InterfaceAccount<'info, TokenAccount>, #[account( mut, - associated_token::mint = mint, - associated_token::authority = payer, + token::mint = mint, + token::authority = payer, + token::token_program = token_program, )] pub source_token_account: InterfaceAccount<'info, TokenAccount>, diff --git a/programs/libreplex_monoswap/src/instructions/nifty_swap.rs b/programs/libreplex_monoswap/src/instructions/nifty_swap.rs index 1858f557..9b84a1f5 100644 --- a/programs/libreplex_monoswap/src/instructions/nifty_swap.rs +++ b/programs/libreplex_monoswap/src/instructions/nifty_swap.rs @@ -2,7 +2,7 @@ use anchor_lang::prelude::*; use anchor_spl::{ associated_token::AssociatedToken, token::Token, - token_interface::{Mint, Token2022, TokenAccount}, + token_interface::{Mint, Token2022, TokenAccount, TokenInterface}, }; use libreplex_shared::operations::transfer_generic_spl; use nifty_asset::instructions::TransferCpi; @@ -53,6 +53,7 @@ pub struct NiftySwapCtx<'info> { #[account(mut, associated_token::mint = mint, associated_token::authority = escrow_owner + // token::token_program = token_program, )] pub escrow_token_account: InterfaceAccount<'info, TokenAccount>, @@ -62,11 +63,13 @@ pub struct NiftySwapCtx<'info> { #[account(mut, associated_token::mint = mint, associated_token::authority = payer + // token::token_program = token_program, )] pub external_token_account: InterfaceAccount<'info, TokenAccount>, - pub token_program: Program<'info, Token>, - pub token_program_2022: Program<'info, Token2022>, + pub token_program: Interface<'info, TokenInterface>, + // pub token_program: Program<'info, Token>, + // pub token_program_2022: Program<'info, Token2022>, pub associated_token_program: Program<'info, AssociatedToken>, pub system_program: Program<'info, System>, diff --git a/programs/libreplex_monoswap/tests/create_nifty_swap.rs b/programs/libreplex_monoswap/tests/create_nifty_swap.rs deleted file mode 100644 index 76eb0061..00000000 --- a/programs/libreplex_monoswap/tests/create_nifty_swap.rs +++ /dev/null @@ -1,242 +0,0 @@ -#![cfg(feature = "test-bpf")] - -use anchor_lang::{prelude::*, InstructionData}; -use libreplex_monoswap::{accounts::CreateNiftySwapCtx, instruction::CreateNiftySwap}; -use nifty_asset::{accounts::Asset, instructions::CreateBuilder, types::Standard}; -use solana_program::program_pack::Pack; -use solana_program_test::*; -use solana_sdk::{ - instruction::Instruction, pubkey::Pubkey, signature::Keypair, signer::Signer, - system_instruction, system_program, transaction::Transaction, -}; -use spl_associated_token_account::{ - get_associated_token_address, instruction::create_associated_token_account, -}; -use spl_token_2022::instruction::{initialize_mint2, mint_to}; - -const MINT_LAYOUT: u64 = 82; - -pub fn program_test() -> ProgramTest { - let mut test = ProgramTest::new("libreplex_monoswap", libreplex_monoswap::ID, None); - test.add_program("nifty_asset", nifty_asset::ID, None); - test.add_program("spl_token_2022", spl_token_2022::ID, None); - - test -} - -pub async fn airdrop( - context: &mut ProgramTestContext, - receiver: &Pubkey, - amount: u64, -) -> Result<()> { - let tx = Transaction::new_signed_with_payer( - &[system_instruction::transfer( - &context.payer.pubkey(), - receiver, - amount, - )], - Some(&context.payer.pubkey()), - &[&context.payer], - context.last_blockhash, - ); - - context.banks_client.process_transaction(tx).await.unwrap(); - Ok(()) -} - -mod nifty_swaps { - use libreplex_monoswap::NiftyMarker; - - use super::*; - - #[tokio::test] - async fn can_create_new_nifty_swap_with_legacy_token() { - let mut context = program_test().start_with_context().await; - - // **Set up keypairs and funding** - - // Authority creates the fungible and the nifty swap account - let authority_signer = Keypair::new(); - let authority = authority_signer.pubkey(); - - // User owns the Nifty asset and does the swapping - let user_signer = Keypair::new(); - let user = user_signer.pubkey(); - - // Mint account for the fungible token - let mint_signer = Keypair::new(); - let mint = mint_signer.pubkey(); - - // Keypair for the nifty asset - let asset_signer = Keypair::new(); - let asset = asset_signer.pubkey(); - - let nifty_marker = Pubkey::find_program_address( - &[ - b"nifty_marker", - authority.as_ref(), - asset.as_ref(), - mint.as_ref(), - ], - &libreplex_monoswap::ID, - ) - .0; - - // Monoswap nifty escrow owner pda - let escrow_owner = Pubkey::find_program_address( - &[ - b"nifty_escrow", - authority.as_ref(), - asset.as_ref(), - mint.as_ref(), - ], - &libreplex_monoswap::ID, - ) - .0; - - // Associated token accounts for the authority and escrow - let ata_pubkey = get_associated_token_address(&authority, &mint); - let ata_escrow_pubkey = get_associated_token_address(&escrow_owner, &mint); - - // Fund the authority and user - airdrop(&mut context, &authority, 1_000_000_000) - .await - .unwrap(); - airdrop(&mut context, &user, 1_000_000_000).await.unwrap(); - - // **Create Nifty and fungible tokens** - - // Create a simple Nifty asset with no extensions. - let nifty_ix = CreateBuilder::new() - .asset(asset) - .authority(user, true) - .owner(user) - .payer(Some(user)) - .system_program(Some(system_program::ID)) - .name("TestNifty".to_string()) - .standard(Standard::NonFungible) - .mutable(true) - .instruction(); - - let rent = context.banks_client.get_rent().await.unwrap(); - let min_rent = rent.minimum_balance(MINT_LAYOUT as usize); - - // Create mint account - let create_mint_account_ix = system_instruction::create_account( - &authority, - &mint, - min_rent, - MINT_LAYOUT, - &spl_token::ID, - ); - - // Initalize mint ix - let init_mint_ix = - initialize_mint2(&spl_token::ID, &mint, &authority, Some(&authority), 0).unwrap(); - - // Create associated account instruction - let create_assoc_account_ix = - create_associated_token_account(&authority, &authority, &mint, &spl_token::ID); - - // Mint to instruction - let mint_to_ix = mint_to(&spl_token::ID, &mint, &ata_pubkey, &authority, &[], 10).unwrap(); - - // **Compose tranasaction, send it and assert the results** - - let instructions = vec![ - nifty_ix, - create_mint_account_ix, - init_mint_ix, - create_assoc_account_ix, - mint_to_ix, - ]; - - let signers = vec![ - &context.payer, - &authority_signer, - &mint_signer, - &user_signer, - &asset_signer, - ]; - - let tx = Transaction::new_signed_with_payer( - &instructions, - Some(&context.payer.pubkey()), - &signers, - context.last_blockhash, - ); - context.banks_client.process_transaction(tx).await.unwrap(); - - let asset_account = context - .banks_client - .get_account(asset) - .await - .expect("get_account") - .expect("asset_account not found"); - - let asset_data = Asset::deserialize(&mut asset_account.data.as_slice()).unwrap(); - assert_eq!(asset_data.owner, user); - assert_eq!(asset_data.authority, user); - assert_eq!(asset_data.standard, Standard::NonFungible); - assert_eq!(asset_data.mutable, true); - - let authority_token_account = context - .banks_client - .get_account(ata_pubkey) - .await - .expect("get_account") - .expect("authority_token_account not found"); - let ata_data = spl_token::state::Account::unpack(&authority_token_account.data).unwrap(); - assert_eq!(ata_data.amount, 10); - assert_eq!(ata_data.owner, authority); - assert_eq!(ata_data.mint, mint); - - context.warp_to_slot(100).unwrap(); - - // **Create the Nifty swap account** - let create_swap_ix = Instruction { - program_id: libreplex_monoswap::ID, - accounts: CreateNiftySwapCtx { - namespace: authority, - payer: authority, - nifty_marker, - asset, - mint, - escrow_owner, - escrow_token_account: ata_escrow_pubkey, - source_token_account: ata_pubkey, - token_program: spl_token::ID, - associated_token_program: spl_associated_token_account::ID, - system_program: system_program::ID, - nifty_program: nifty_asset::ID, - } - .to_account_metas(None), - data: CreateNiftySwap { amount: 10 }.data(), - }; - - let blockhash = context.get_new_latest_blockhash().await.unwrap(); - - let tx = Transaction::new_signed_with_payer( - &[create_swap_ix], - Some(&context.payer.pubkey()), - &[&context.payer, &authority_signer], - blockhash, - ); - - context.banks_client.process_transaction(tx).await.unwrap(); - - let nifty_marker_account = context - .banks_client - .get_account(nifty_marker) - .await - .expect("get_account") - .expect("nifty_marker_account not found"); - - let nifty_marker_data = - NiftyMarker::deserialize(&mut &nifty_marker_account.data.as_slice()[8..]).unwrap(); - - assert_eq!(nifty_marker_data.namespace, authority); - assert_eq!(nifty_marker_data.mint, mint); - assert_eq!(nifty_marker_data.amount, 10); - } -} diff --git a/programs/libreplex_monoswap/tests/create_nifty_swap_test.rs b/programs/libreplex_monoswap/tests/create_nifty_swap_test.rs new file mode 100644 index 00000000..6754bced --- /dev/null +++ b/programs/libreplex_monoswap/tests/create_nifty_swap_test.rs @@ -0,0 +1,294 @@ +#![cfg(feature = "test-bpf")] + +use anchor_lang::{prelude::*, InstructionData}; +use libreplex_monoswap::{accounts::CreateNiftySwapCtx, instruction::CreateNiftySwap, NiftyMarker}; +use nifty_asset::{accounts::Asset, instructions::CreateBuilder, types::Standard}; +use solana_program::program_pack::Pack; +use solana_program_test::*; +use solana_sdk::{ + instruction::Instruction, pubkey::Pubkey, signature::Keypair, signer::Signer, + system_instruction, system_program, transaction::Transaction, +}; +use spl_associated_token_account::{ + get_associated_token_address_with_program_id, instruction::create_associated_token_account, +}; +use spl_token_2022::{ + instruction::{initialize_mint2, mint_to}, + state::Account as TokenAccount, +}; + +pub mod helpers; +use helpers::*; + +mod nifty_swaps { + use super::*; + + #[tokio::test] + async fn can_create_new_nifty_swap_with_legacy_token() { + let mut context = program_test().start_with_context().await; + + // **Set up keypairs and funding** + + // Authority creates the fungible and the nifty swap account + let authority_signer = Keypair::new(); + let authority = authority_signer.pubkey(); + + // User owns the Nifty asset and does the swapping + let user_signer = Keypair::new(); + let user = user_signer.pubkey(); + + // Fund the authority and user + airdrop(&mut context, &authority, 1_000_000_000) + .await + .unwrap(); + airdrop(&mut context, &user, 1_000_000_000).await.unwrap(); + + // **Create Nifty and fungible tokens** + + let FungibleTest { mint, ata } = + create_fungible_token(&mut context, &authority_signer, 10, TokenProgram::Legacy) + .await + .unwrap(); + + let AssetTest { asset } = create_nifty_asset(&mut context, &user_signer, user) + .await + .unwrap(); + + let asset_account = context + .banks_client + .get_account(asset) + .await + .expect("get_account") + .expect("asset_account not found"); + + let asset_data = Asset::deserialize(&mut asset_account.data.as_slice()).unwrap(); + assert_eq!(asset_data.owner, user); + assert_eq!(asset_data.authority, user); + assert_eq!(asset_data.standard, Standard::NonFungible); + assert_eq!(asset_data.mutable, true); + + let authority_token_account = context + .banks_client + .get_account(ata) + .await + .expect("get_account") + .expect("authority_token_account not found"); + + let ata_data = spl_token::state::Account::unpack(&authority_token_account.data).unwrap(); + assert_eq!(ata_data.amount, 10); + assert_eq!(ata_data.owner, authority); + assert_eq!(ata_data.mint, mint); + + context.warp_to_slot(100).unwrap(); + + let nifty_marker = Pubkey::find_program_address( + &[ + b"nifty_marker", + authority.as_ref(), + asset.as_ref(), + mint.as_ref(), + ], + &libreplex_monoswap::ID, + ) + .0; + + // Monoswap nifty escrow owner pda + let escrow_owner = Pubkey::find_program_address( + &[ + b"nifty_escrow", + authority.as_ref(), + asset.as_ref(), + mint.as_ref(), + ], + &libreplex_monoswap::ID, + ) + .0; + + // Associated token accounts for the authority and escrow + let ata_escrow = + get_associated_token_address_with_program_id(&escrow_owner, &mint, &spl_token::ID); + + // **Create the Nifty swap account** + let create_swap_ix = Instruction { + program_id: libreplex_monoswap::ID, + accounts: CreateNiftySwapCtx { + namespace: authority, + payer: authority, + nifty_marker, + asset, + mint, + escrow_owner, + escrow_token_account: ata_escrow, + source_token_account: ata, + token_program: spl_token::ID, + associated_token_program: spl_associated_token_account::ID, + system_program: system_program::ID, + nifty_program: nifty_asset::ID, + } + .to_account_metas(None), + data: CreateNiftySwap { amount: 10 }.data(), + }; + + let blockhash = context.get_new_latest_blockhash().await.unwrap(); + + let tx = Transaction::new_signed_with_payer( + &[create_swap_ix], + Some(&context.payer.pubkey()), + &[&context.payer, &authority_signer], + blockhash, + ); + + context.banks_client.process_transaction(tx).await.unwrap(); + + let nifty_marker_account = context + .banks_client + .get_account(nifty_marker) + .await + .expect("get_account") + .expect("nifty_marker_account not found"); + + let nifty_marker_data = + NiftyMarker::deserialize(&mut &nifty_marker_account.data.as_slice()[8..]).unwrap(); + + assert_eq!(nifty_marker_data.namespace, authority); + assert_eq!(nifty_marker_data.mint, mint); + assert_eq!(nifty_marker_data.amount, 10); + } + + #[tokio::test] + async fn can_create_new_nifty_swap_with_token_2022() { + let mut context = program_test().start_with_context().await; + + // **Set up keypairs and funding** + + // Authority creates the fungible and the nifty swap account + let authority_signer = Keypair::new(); + let authority = authority_signer.pubkey(); + + // User owns the Nifty asset and does the swapping + let user_signer = Keypair::new(); + let user = user_signer.pubkey(); + + // Fund the authority and user + airdrop(&mut context, &authority, 1_000_000_000) + .await + .unwrap(); + airdrop(&mut context, &user, 1_000_000_000).await.unwrap(); + + // **Create Nifty and fungible tokens** + + let FungibleTest { mint, ata } = + create_fungible_token(&mut context, &authority_signer, 10, TokenProgram::T22) + .await + .unwrap(); + + let AssetTest { asset } = create_nifty_asset(&mut context, &user_signer, user) + .await + .unwrap(); + + let asset_account = context + .banks_client + .get_account(asset) + .await + .expect("get_account") + .expect("asset_account not found"); + + let asset_data = Asset::deserialize(&mut asset_account.data.as_slice()).unwrap(); + assert_eq!(asset_data.owner, user); + assert_eq!(asset_data.authority, user); + assert_eq!(asset_data.standard, Standard::NonFungible); + assert_eq!(asset_data.mutable, true); + + let authority_token_account = context + .banks_client + .get_account(ata) + .await + .expect("get_account") + .expect("authority_token_account not found"); + + let ata_data = unpack::(&authority_token_account.data).unwrap(); + + assert_eq!(ata_data.amount, 10); + assert_eq!(ata_data.owner, authority); + assert_eq!(ata_data.mint, mint); + + context.warp_to_slot(100).unwrap(); + + let nifty_marker = Pubkey::find_program_address( + &[ + b"nifty_marker", + authority.as_ref(), + asset.as_ref(), + mint.as_ref(), + ], + &libreplex_monoswap::ID, + ) + .0; + + // Monoswap nifty escrow owner pda + let escrow_owner = Pubkey::find_program_address( + &[ + b"nifty_escrow", + authority.as_ref(), + asset.as_ref(), + mint.as_ref(), + ], + &libreplex_monoswap::ID, + ) + .0; + + // Associated token accounts for the authority and escrow + let ata_escrow = + get_associated_token_address_with_program_id(&escrow_owner, &mint, &spl_token_2022::ID); + + // **Create the Nifty swap account** + let create_swap_ix = Instruction { + program_id: libreplex_monoswap::ID, + accounts: CreateNiftySwapCtx { + namespace: authority, + payer: authority, + nifty_marker, + asset, + mint, + escrow_owner, + escrow_token_account: ata_escrow, + source_token_account: ata, + token_program: spl_token_2022::ID, + associated_token_program: spl_associated_token_account::ID, + system_program: system_program::ID, + nifty_program: nifty_asset::ID, + } + .to_account_metas(None), + data: CreateNiftySwap { amount: 10 }.data(), + }; + + println!("source ata: {:?}", ata); + println!("authority: {:?}", authority); + println!("mint: {:?}", mint); + + let blockhash = context.get_new_latest_blockhash().await.unwrap(); + + let tx = Transaction::new_signed_with_payer( + &[create_swap_ix], + Some(&context.payer.pubkey()), + &[&context.payer, &authority_signer], + blockhash, + ); + + context.banks_client.process_transaction(tx).await.unwrap(); + + let nifty_marker_account = context + .banks_client + .get_account(nifty_marker) + .await + .expect("get_account") + .expect("nifty_marker_account not found"); + + let nifty_marker_data = + NiftyMarker::deserialize(&mut &nifty_marker_account.data.as_slice()[8..]).unwrap(); + + assert_eq!(nifty_marker_data.namespace, authority); + assert_eq!(nifty_marker_data.mint, mint); + assert_eq!(nifty_marker_data.amount, 10); + } +} diff --git a/programs/libreplex_monoswap/tests/helpers.rs b/programs/libreplex_monoswap/tests/helpers.rs new file mode 100644 index 00000000..e5adafa2 --- /dev/null +++ b/programs/libreplex_monoswap/tests/helpers.rs @@ -0,0 +1,165 @@ +#![cfg(feature = "test-bpf")] + +use anchor_lang::{prelude::*, InstructionData}; +use anchor_spl::token; +use libreplex_monoswap::{accounts::CreateNiftySwapCtx, instruction::CreateNiftySwap}; +use nifty_asset::{accounts::Asset, instructions::CreateBuilder, types::Standard}; +use solana_program::program_pack::Pack; +use solana_program_test::*; +use solana_sdk::{ + instruction::Instruction, pubkey::Pubkey, signature::Keypair, signer::Signer, + system_instruction, system_program, transaction::Transaction, +}; +use spl_associated_token_account::{ + get_associated_token_address, get_associated_token_address_with_program_id, + instruction::create_associated_token_account, +}; +use spl_token_2022::{ + extension::{BaseState, StateWithExtensions}, + instruction::{initialize_mint2, mint_to}, +}; + +pub const MINT_LAYOUT: u64 = 82; + +pub fn unpack(account_data: &[u8]) -> Result { + Ok(StateWithExtensions::::unpack(account_data)?.base) +} + +pub fn program_test() -> ProgramTest { + let mut test = ProgramTest::new("libreplex_monoswap", libreplex_monoswap::ID, None); + test.add_program("nifty_asset", nifty_asset::ID, None); + test.add_program("spl_token_2022", spl_token_2022::ID, None); + + test +} + +pub async fn airdrop( + context: &mut ProgramTestContext, + receiver: &Pubkey, + amount: u64, +) -> Result<()> { + let tx = Transaction::new_signed_with_payer( + &[system_instruction::transfer( + &context.payer.pubkey(), + receiver, + amount, + )], + Some(&context.payer.pubkey()), + &[&context.payer], + context.last_blockhash, + ); + + context.banks_client.process_transaction(tx).await.unwrap(); + Ok(()) +} + +pub struct FungibleTest { + pub mint: Pubkey, + pub ata: Pubkey, +} + +pub struct AssetTest { + pub asset: Pubkey, +} + +pub enum TokenProgram { + Legacy, + T22, +} + +// Create a simple Nifty asset with no extensions. +pub async fn create_nifty_asset( + context: &mut ProgramTestContext, + authority_signer: &Keypair, + owner: Pubkey, +) -> Result { + let asset_signer = Keypair::new(); + let asset = asset_signer.pubkey(); + let authority = authority_signer.pubkey(); + + let nifty_ix = CreateBuilder::new() + .asset(asset) + .authority(authority, true) + .owner(owner) + .payer(Some(authority)) + .system_program(Some(system_program::ID)) + .name("TestNifty".to_string()) + .standard(Standard::NonFungible) + .mutable(true) + .instruction(); + + let signers = vec![&context.payer, &authority_signer, &asset_signer]; + + let tx = Transaction::new_signed_with_payer( + &[nifty_ix], + Some(&context.payer.pubkey()), + &signers, + context.last_blockhash, + ); + context.banks_client.process_transaction(tx).await.unwrap(); + + return Ok(AssetTest { asset }); +} + +pub async fn create_fungible_token( + context: &mut ProgramTestContext, + authority_signer: &Keypair, + amount: u64, + token_program: TokenProgram, +) -> Result { + let mint_signer = Keypair::new(); + let mint = mint_signer.pubkey(); + + let rent = context.banks_client.get_rent().await.unwrap(); + let min_rent = rent.minimum_balance(MINT_LAYOUT as usize); + + let authority = authority_signer.pubkey(); + + let token_program = match token_program { + TokenProgram::Legacy => spl_token::ID, + TokenProgram::T22 => spl_token_2022::ID, + }; + + let ata = get_associated_token_address_with_program_id(&authority, &mint, &token_program); + + // Create mint account + let create_mint_account_ix = system_instruction::create_account( + &authority, + &mint, + min_rent, + MINT_LAYOUT, + &token_program, + ); + + // Initalize mint ix + let init_mint_ix = + initialize_mint2(&token_program, &mint, &authority, Some(&authority), 0).unwrap(); + + // Create associated account instruction + let create_assoc_account_ix = + create_associated_token_account(&authority, &authority, &mint, &token_program); + + // Mint to instruction + let mint_to_ix = mint_to(&token_program, &mint, &ata, &authority, &[], 10).unwrap(); + + // **Compose tranasaction, send it and assert the results** + + let instructions = vec![ + create_mint_account_ix, + init_mint_ix, + create_assoc_account_ix, + mint_to_ix, + ]; + + let signers = vec![&context.payer, &authority_signer, &mint_signer]; + + let tx = Transaction::new_signed_with_payer( + &instructions, + Some(&context.payer.pubkey()), + &signers, + context.last_blockhash, + ); + context.banks_client.process_transaction(tx).await.unwrap(); + + return Ok(FungibleTest { mint, ata }); +} From 3ec72202577c4ae2a4862d7a7e05ea33373a0a36 Mon Sep 17 00:00:00 2001 From: Samuel Vanderwaal Date: Sun, 24 Mar 2024 16:36:46 -0800 Subject: [PATCH 4/5] fix bugs; add swap test --- .../src/instructions/create_nifty_swap.rs | 4 +- .../src/instructions/nifty_swap.rs | 16 +- .../src/state/nifty_marker.rs | 6 + .../src/state/swap_marker.rs | 6 - .../tests/create_nifty_swap_test.rs | 81 ++---- programs/libreplex_monoswap/tests/helpers.rs | 96 ++++++- .../tests/nifty_swap_test.rs | 271 ++++++++++++++++++ 7 files changed, 400 insertions(+), 80 deletions(-) create mode 100644 programs/libreplex_monoswap/tests/nifty_swap_test.rs diff --git a/programs/libreplex_monoswap/src/instructions/create_nifty_swap.rs b/programs/libreplex_monoswap/src/instructions/create_nifty_swap.rs index 434c3132..f6d2229d 100644 --- a/programs/libreplex_monoswap/src/instructions/create_nifty_swap.rs +++ b/programs/libreplex_monoswap/src/instructions/create_nifty_swap.rs @@ -5,7 +5,7 @@ use anchor_spl::{ }; use libreplex_shared::operations::transfer_generic_spl; -use crate::NiftyMarker; +use crate::{MarkerState, NiftyMarker}; // Swaps are created by transferring a token in. #[derive(Accounts)] @@ -83,7 +83,9 @@ pub fn process_create_nifty_swap(ctx: Context, amount: u64) swap_marker.namespace = ctx.accounts.namespace.key(); swap_marker.mint = ctx.accounts.mint.key(); + swap_marker.asset = ctx.accounts.asset.key(); swap_marker.amount = amount; + swap_marker.state = MarkerState::FungibleEscrowed; // transfer the outgoing mint into escrow - let token_program = &ctx.accounts.token_program; diff --git a/programs/libreplex_monoswap/src/instructions/nifty_swap.rs b/programs/libreplex_monoswap/src/instructions/nifty_swap.rs index 9b84a1f5..a507c4e9 100644 --- a/programs/libreplex_monoswap/src/instructions/nifty_swap.rs +++ b/programs/libreplex_monoswap/src/instructions/nifty_swap.rs @@ -17,20 +17,20 @@ pub struct NiftySwapCtx<'info> { pub payer: Signer<'info>, #[account(mut, - close = payer, - constraint = mint.key() == nifty_marker.mint, - constraint = asset.key() == nifty_marker.asset, seeds = [ "nifty_marker".as_bytes(), nifty_marker.namespace.as_ref(), asset.key().as_ref(), mint.key().as_ref(), ], - bump + bump, + has_one = mint, + has_one = asset, )] pub nifty_marker: Account<'info, NiftyMarker>, #[account( + mut, constraint = asset.owner == nifty_program.key )] pub asset: UncheckedAccount<'info>, @@ -50,7 +50,8 @@ pub struct NiftySwapCtx<'info> { )] pub escrow_owner: UncheckedAccount<'info>, - #[account(mut, + #[account( + mut, associated_token::mint = mint, associated_token::authority = escrow_owner // token::token_program = token_program, @@ -60,7 +61,8 @@ pub struct NiftySwapCtx<'info> { // it is the responsibility of each swapper program to create enough // of the outgoing mint so that the swap can happen. It is deposited // from this account - #[account(mut, + #[account( + mut, associated_token::mint = mint, associated_token::authority = payer // token::token_program = token_program, @@ -140,7 +142,7 @@ pub fn process_nifty_swap(ctx: Context, direction: SwapDirection) recipient: &ctx.accounts.payer.to_account_info(), group_asset: None, } - .invoke()?; + .invoke_signed(&[authority_seeds])?; // Transfer fungible from payer to escrow transfer_generic_spl( diff --git a/programs/libreplex_monoswap/src/state/nifty_marker.rs b/programs/libreplex_monoswap/src/state/nifty_marker.rs index e0ef2684..6fa4ac61 100644 --- a/programs/libreplex_monoswap/src/state/nifty_marker.rs +++ b/programs/libreplex_monoswap/src/state/nifty_marker.rs @@ -21,3 +21,9 @@ pub enum MarkerState { AssetEscrowed, FungibleEscrowed, } + +#[derive(Clone, AnchorDeserialize, AnchorSerialize)] +pub enum SwapDirection { + AssetIn, + AssetOut, +} diff --git a/programs/libreplex_monoswap/src/state/swap_marker.rs b/programs/libreplex_monoswap/src/state/swap_marker.rs index 01a0f7dd..c05cb1ee 100644 --- a/programs/libreplex_monoswap/src/state/swap_marker.rs +++ b/programs/libreplex_monoswap/src/state/swap_marker.rs @@ -26,9 +26,3 @@ pub struct SwapMarker { impl SwapMarker { pub const SIZE: usize = 8 + 32 + 32 + 32 + 8 + 8 + 1; } - -#[derive(Clone, AnchorDeserialize, AnchorSerialize)] -pub enum SwapDirection { - AssetIn, - AssetOut, -} diff --git a/programs/libreplex_monoswap/tests/create_nifty_swap_test.rs b/programs/libreplex_monoswap/tests/create_nifty_swap_test.rs index 6754bced..6e5f6588 100644 --- a/programs/libreplex_monoswap/tests/create_nifty_swap_test.rs +++ b/programs/libreplex_monoswap/tests/create_nifty_swap_test.rs @@ -2,16 +2,14 @@ use anchor_lang::{prelude::*, InstructionData}; use libreplex_monoswap::{accounts::CreateNiftySwapCtx, instruction::CreateNiftySwap, NiftyMarker}; -use nifty_asset::{accounts::Asset, instructions::CreateBuilder, types::Standard}; +use nifty_asset::{accounts::Asset, types::Standard}; use solana_program::program_pack::Pack; use solana_program_test::*; use solana_sdk::{ - instruction::Instruction, pubkey::Pubkey, signature::Keypair, signer::Signer, - system_instruction, system_program, transaction::Transaction, -}; -use spl_associated_token_account::{ - get_associated_token_address_with_program_id, instruction::create_associated_token_account, + instruction::Instruction, pubkey::Pubkey, signature::Keypair, signer::Signer, system_program, + transaction::Transaction, }; +use spl_associated_token_account::get_associated_token_address_with_program_id; use spl_token_2022::{ instruction::{initialize_mint2, mint_to}, state::Account as TokenAccount, @@ -20,7 +18,7 @@ use spl_token_2022::{ pub mod helpers; use helpers::*; -mod nifty_swaps { +mod create_nifty_swap_tests { use super::*; #[tokio::test] @@ -81,64 +79,21 @@ mod nifty_swaps { context.warp_to_slot(100).unwrap(); - let nifty_marker = Pubkey::find_program_address( - &[ - b"nifty_marker", - authority.as_ref(), - asset.as_ref(), - mint.as_ref(), - ], - &libreplex_monoswap::ID, - ) - .0; - - // Monoswap nifty escrow owner pda - let escrow_owner = Pubkey::find_program_address( - &[ - b"nifty_escrow", - authority.as_ref(), - asset.as_ref(), - mint.as_ref(), - ], - &libreplex_monoswap::ID, - ) - .0; - - // Associated token accounts for the authority and escrow - let ata_escrow = - get_associated_token_address_with_program_id(&escrow_owner, &mint, &spl_token::ID); - - // **Create the Nifty swap account** - let create_swap_ix = Instruction { - program_id: libreplex_monoswap::ID, - accounts: CreateNiftySwapCtx { - namespace: authority, - payer: authority, - nifty_marker, + let NiftySwapTest { + nifty_marker, + escrow_owner, + escrow_ata, + } = create_nifty_swap( + &mut context, + NiftySwapInput { + authority_signer: &authority_signer, asset, mint, - escrow_owner, - escrow_token_account: ata_escrow, - source_token_account: ata, - token_program: spl_token::ID, - associated_token_program: spl_associated_token_account::ID, - system_program: system_program::ID, - nifty_program: nifty_asset::ID, - } - .to_account_metas(None), - data: CreateNiftySwap { amount: 10 }.data(), - }; - - let blockhash = context.get_new_latest_blockhash().await.unwrap(); - - let tx = Transaction::new_signed_with_payer( - &[create_swap_ix], - Some(&context.payer.pubkey()), - &[&context.payer, &authority_signer], - blockhash, - ); - - context.banks_client.process_transaction(tx).await.unwrap(); + ata, + }, + ) + .await + .unwrap(); let nifty_marker_account = context .banks_client diff --git a/programs/libreplex_monoswap/tests/helpers.rs b/programs/libreplex_monoswap/tests/helpers.rs index e5adafa2..a0564b10 100644 --- a/programs/libreplex_monoswap/tests/helpers.rs +++ b/programs/libreplex_monoswap/tests/helpers.rs @@ -1,7 +1,6 @@ #![cfg(feature = "test-bpf")] use anchor_lang::{prelude::*, InstructionData}; -use anchor_spl::token; use libreplex_monoswap::{accounts::CreateNiftySwapCtx, instruction::CreateNiftySwap}; use nifty_asset::{accounts::Asset, instructions::CreateBuilder, types::Standard}; use solana_program::program_pack::Pack; @@ -11,8 +10,7 @@ use solana_sdk::{ system_instruction, system_program, transaction::Transaction, }; use spl_associated_token_account::{ - get_associated_token_address, get_associated_token_address_with_program_id, - instruction::create_associated_token_account, + get_associated_token_address_with_program_id, instruction::create_associated_token_account, }; use spl_token_2022::{ extension::{BaseState, StateWithExtensions}, @@ -163,3 +161,95 @@ pub async fn create_fungible_token( return Ok(FungibleTest { mint, ata }); } + +pub struct NiftySwapInput<'a> { + pub authority_signer: &'a Keypair, + pub asset: Pubkey, + pub mint: Pubkey, + pub ata: Pubkey, +} + +pub struct NiftySwapTest { + pub nifty_marker: Pubkey, + pub escrow_owner: Pubkey, + pub escrow_ata: Pubkey, +} + +pub async fn create_nifty_swap<'a>( + context: &mut ProgramTestContext, + input: NiftySwapInput<'a>, +) -> Result { + let NiftySwapInput { + authority_signer, + asset, + mint, + ata, + } = input; + + let authority = authority_signer.pubkey(); + + let nifty_marker = Pubkey::find_program_address( + &[ + b"nifty_marker", + authority.as_ref(), + asset.as_ref(), + mint.as_ref(), + ], + &libreplex_monoswap::ID, + ) + .0; + + // Monoswap nifty escrow owner pda + let escrow_owner = Pubkey::find_program_address( + &[ + b"nifty_escrow", + authority.as_ref(), + asset.as_ref(), + mint.as_ref(), + ], + &libreplex_monoswap::ID, + ) + .0; + + // Associated token accounts for the authority and escrow + let escrow_ata = + get_associated_token_address_with_program_id(&escrow_owner, &mint, &spl_token::ID); + + // **Create the Nifty swap account** + let create_swap_ix = Instruction { + program_id: libreplex_monoswap::ID, + accounts: CreateNiftySwapCtx { + namespace: authority, + payer: authority, + nifty_marker, + asset, + mint, + escrow_owner, + escrow_token_account: escrow_ata, + source_token_account: ata, + token_program: spl_token::ID, + associated_token_program: spl_associated_token_account::ID, + system_program: system_program::ID, + nifty_program: nifty_asset::ID, + } + .to_account_metas(None), + data: CreateNiftySwap { amount: 10 }.data(), + }; + + let blockhash = context.get_new_latest_blockhash().await.unwrap(); + + let tx = Transaction::new_signed_with_payer( + &[create_swap_ix], + Some(&context.payer.pubkey()), + &[&context.payer, &authority_signer], + blockhash, + ); + + context.banks_client.process_transaction(tx).await.unwrap(); + + Ok(NiftySwapTest { + nifty_marker, + escrow_owner, + escrow_ata, + }) +} diff --git a/programs/libreplex_monoswap/tests/nifty_swap_test.rs b/programs/libreplex_monoswap/tests/nifty_swap_test.rs new file mode 100644 index 00000000..c65e7d88 --- /dev/null +++ b/programs/libreplex_monoswap/tests/nifty_swap_test.rs @@ -0,0 +1,271 @@ +#![cfg(feature = "test-bpf")] + +use anchor_lang::{prelude::*, InstructionData}; +use libreplex_monoswap::{accounts::NiftySwapCtx, MarkerState, NiftyMarker, SwapDirection}; +use nifty_asset::{accounts::Asset, types::Standard}; +use solana_program::program_pack::Pack; +use solana_program_test::*; +use solana_sdk::{ + instruction::Instruction, pubkey::Pubkey, signature::Keypair, signer::Signer, + system_instruction, system_program, transaction::Transaction, +}; +use spl_associated_token_account::{ + get_associated_token_address_with_program_id, instruction::create_associated_token_account, +}; +use spl_token_2022::{ + instruction::{initialize_mint2, mint_to}, + state::Account as TokenAccount, +}; + +pub mod helpers; +use helpers::*; + +mod nifty_swap_tests { + use super::*; + + #[tokio::test] + async fn can_swap_both_directions() { + let mut context = program_test().start_with_context().await; + + // **Set up keypairs and funding** + + // Authority creates the fungible and the nifty swap account + let authority_signer = Keypair::new(); + let authority = authority_signer.pubkey(); + + // User owns the Nifty asset and does the swapping + let user_signer = Keypair::new(); + let user = user_signer.pubkey(); + + // Fund the authority and user + airdrop(&mut context, &authority, 1_000_000_000) + .await + .unwrap(); + airdrop(&mut context, &user, 1_000_000_000).await.unwrap(); + + // **Create Nifty and fungible tokens** + + let FungibleTest { mint, ata } = + create_fungible_token(&mut context, &authority_signer, 10, TokenProgram::Legacy) + .await + .unwrap(); + + let AssetTest { asset } = create_nifty_asset(&mut context, &user_signer, user) + .await + .unwrap(); + + let asset_account = context + .banks_client + .get_account(asset) + .await + .expect("get_account") + .expect("asset_account not found"); + + let asset_data = Asset::deserialize(&mut asset_account.data.as_slice()).unwrap(); + assert_eq!(asset_data.owner, user); + assert_eq!(asset_data.authority, user); + assert_eq!(asset_data.standard, Standard::NonFungible); + assert_eq!(asset_data.mutable, true); + + let authority_token_account = context + .banks_client + .get_account(ata) + .await + .expect("get_account") + .expect("authority_token_account not found"); + + let ata_data = spl_token::state::Account::unpack(&authority_token_account.data).unwrap(); + assert_eq!(ata_data.amount, 10); + assert_eq!(ata_data.owner, authority); + assert_eq!(ata_data.mint, mint); + + context.warp_to_slot(100).unwrap(); + + let NiftySwapTest { + nifty_marker, + escrow_owner, + escrow_ata, + } = create_nifty_swap( + &mut context, + NiftySwapInput { + authority_signer: &authority_signer, + asset, + mint, + ata, + }, + ) + .await + .unwrap(); + + let nifty_marker_account = context + .banks_client + .get_account(nifty_marker) + .await + .expect("get_account") + .expect("nifty_marker_account not found"); + + let nifty_marker_data = + NiftyMarker::deserialize(&mut &nifty_marker_account.data.as_slice()[8..]).unwrap(); + + assert_eq!(nifty_marker_data.namespace, authority); + assert_eq!(nifty_marker_data.mint, mint); + assert_eq!(nifty_marker_data.asset, asset); + assert_eq!(nifty_marker_data.amount, 10); + assert_eq!(nifty_marker_data.state, MarkerState::FungibleEscrowed); + + context.warp_to_slot(200).unwrap(); + + // **Perform the swap** + + let external_ata = + get_associated_token_address_with_program_id(&user, &mint, &spl_token::ID); + + let create_external_ata_ix = + create_associated_token_account(&user, &user, &mint, &spl_token::ID); + + let swap_ix = Instruction { + program_id: libreplex_monoswap::ID, + accounts: NiftySwapCtx { + nifty_marker, + asset, + mint, + escrow_owner, + escrow_token_account: escrow_ata, + external_token_account: external_ata, + payer: user, + token_program: spl_token::ID, + associated_token_program: spl_associated_token_account::ID, + system_program: system_program::ID, + nifty_program: nifty_asset::ID, + } + .to_account_metas(None), + data: libreplex_monoswap::instruction::NiftySwap { + direction: SwapDirection::AssetIn, + } + .data(), + }; + + let blockhash = context.get_new_latest_blockhash().await.unwrap(); + + let tx = Transaction::new_signed_with_payer( + &[create_external_ata_ix, swap_ix], + Some(&context.payer.pubkey()), + &[&context.payer, &user_signer], + blockhash, + ); + + context.banks_client.process_transaction(tx).await.unwrap(); + + // **Check the results** + + // The asset should be owned by escrow_owner. + let asset_account = context + .banks_client + .get_account(asset) + .await + .expect("get_account") + .expect("asset_account not found"); + + let asset_data = Asset::deserialize(&mut asset_account.data.as_slice()).unwrap(); + assert_eq!(asset_data.owner, escrow_owner); + + // The fungibles should be transfered to the user's external ATA. + let external_ata_account = context + .banks_client + .get_account(external_ata) + .await + .expect("get_account") + .expect("ata_account not found"); + + let external_ata_data = TokenAccount::unpack(&authority_token_account.data).unwrap(); + assert_eq!(external_ata_data.amount, 10); + assert_eq!(external_ata_data.owner, authority); + assert_eq!(external_ata_data.mint, mint); + + // The escrow ata should be empty. + let escrow_ata_account = context + .banks_client + .get_account(escrow_ata) + .await + .expect("get_account") + .expect("escrow_ata_account not found"); + + let escrow_ata_data = TokenAccount::unpack(&escrow_ata_account.data).unwrap(); + assert_eq!(escrow_ata_data.amount, 0); + + println!("escrow_ata_data: {:?}", escrow_ata); + println!("external_ata_data: {:?}", external_ata); + println!("asset_data: {:?}", asset); + println!("nifty marker: {:?}", nifty_marker); + println!("user: {:?}", user); + + // **Swap back** + let swap_ix = Instruction { + program_id: libreplex_monoswap::ID, + accounts: NiftySwapCtx { + nifty_marker, + asset, + mint, + escrow_owner, + escrow_token_account: escrow_ata, + external_token_account: external_ata, + payer: user, + token_program: spl_token::ID, + associated_token_program: spl_associated_token_account::ID, + system_program: system_program::ID, + nifty_program: nifty_asset::ID, + } + .to_account_metas(None), + data: libreplex_monoswap::instruction::NiftySwap { + direction: SwapDirection::AssetOut, + } + .data(), + }; + + let blockhash = context.get_new_latest_blockhash().await.unwrap(); + + let tx = Transaction::new_signed_with_payer( + &[swap_ix], + Some(&context.payer.pubkey()), + &[&context.payer, &user_signer], + blockhash, + ); + + context.banks_client.process_transaction(tx).await.unwrap(); + + // **Check the results** + + // The asset should be owned by user. + let asset_account = context + .banks_client + .get_account(asset) + .await + .expect("get_account") + .expect("asset_account not found"); + + let asset_data = Asset::deserialize(&mut asset_account.data.as_slice()).unwrap(); + assert_eq!(asset_data.owner, user); + + // The fungibles should be transfered back to the escrow ATA. + let escrow_ata_account = context + .banks_client + .get_account(escrow_ata) + .await + .expect("get_account") + .expect("ata_account not found"); + + let escrow_ata_data = TokenAccount::unpack(&authority_token_account.data).unwrap(); + assert_eq!(escrow_ata_data.amount, 10); + + // The user's external ata should be empty. + let external_ata_account = context + .banks_client + .get_account(external_ata) + .await + .expect("get_account") + .expect("escrow_ata_account not found"); + + let external_ata_data = TokenAccount::unpack(&external_ata_account.data).unwrap(); + assert_eq!(external_ata_data.amount, 0); + } +} From 58338eab50ed94054f1de8342401b1c5a84417f6 Mon Sep 17 00:00:00 2001 From: Samuel Vanderwaal Date: Sun, 24 Mar 2024 17:00:12 -0800 Subject: [PATCH 5/5] fix clippy lints in monoswap package --- programs/libreplex_monoswap/src/instructions/nifty_swap.rs | 3 +-- 1 file changed, 1 insertion(+), 2 deletions(-) diff --git a/programs/libreplex_monoswap/src/instructions/nifty_swap.rs b/programs/libreplex_monoswap/src/instructions/nifty_swap.rs index a507c4e9..c26e749d 100644 --- a/programs/libreplex_monoswap/src/instructions/nifty_swap.rs +++ b/programs/libreplex_monoswap/src/instructions/nifty_swap.rs @@ -1,8 +1,7 @@ use anchor_lang::prelude::*; use anchor_spl::{ associated_token::AssociatedToken, - token::Token, - token_interface::{Mint, Token2022, TokenAccount, TokenInterface}, + token_interface::{Mint, TokenAccount, TokenInterface}, }; use libreplex_shared::operations::transfer_generic_spl; use nifty_asset::instructions::TransferCpi;