Skip to content

Commit

Permalink
feat: added a test sale program demoing wen-distribution-program feat…
Browse files Browse the repository at this point in the history
…ures with tests
  • Loading branch information
sunguru98 committed Apr 26, 2024
1 parent cb5a007 commit 25772a0
Show file tree
Hide file tree
Showing 29 changed files with 2,634 additions and 223 deletions.
2 changes: 1 addition & 1 deletion Anchor.toml
Original file line number Diff line number Diff line change
Expand Up @@ -5,7 +5,7 @@ resolution = true
skip-lint = false

[programs.localnet]
test_sale = "7EhFwHKHPF4ffuFtsh1929XfsnbHKaA5QNbouX5PYpSj"
test_sale = "saLeHtY1jcSpuy5NKGX4pryocQ51WGUYqSSCKJNsgrP"
wen_new_standard = "wns1gDLt8fgLcGhWi5MqAqgXpwEP1JftKE9eZnXS1HM"
wen_royalty_distribution = "diste3nXmK7ddDTs1zb6uday6j4etCa9RChD8fJ1xay"

Expand Down
3 changes: 3 additions & 0 deletions programs/test_sale/src/constants.rs
Original file line number Diff line number Diff line change
@@ -0,0 +1,3 @@
pub const TEST_SALE: &[u8] = b"test_sale";
pub const SALE: &[u8] = b"sale";
pub const LISTING: &[u8] = b"listing";
13 changes: 13 additions & 0 deletions programs/test_sale/src/errors.rs
Original file line number Diff line number Diff line change
@@ -0,0 +1,13 @@
use anchor_lang::prelude::*;

#[error_code]
pub enum TestSaleError {
#[msg("Buy amount mismatch with listing amount")]
ListingAmountMismatch,
#[msg("SPL Payment token account required")]
PaymentTokenAccountNotExistant,
#[msg("Invalid SPL Payment token account")]
InvalidPaymentTokenAccount,
#[msg("Arithmetic error")]
ArithmeticError,
}
297 changes: 297 additions & 0 deletions programs/test_sale/src/instructions/listing/fulfill.rs
Original file line number Diff line number Diff line change
@@ -0,0 +1,297 @@
use anchor_lang::{
prelude::*,
system_program::{transfer, Transfer},
};
use anchor_spl::{
associated_token::AssociatedToken,
token_2022::spl_token_2022::{extension::StateWithExtensions, state::Mint as StateMint},
token_2022::{transfer_checked, Token2022, TransferChecked},
token_interface::{Mint, TokenAccount},
};
use wen_new_standard::{
cpi::{
accounts::{ApproveTransfer, ThawDelegatedAccount},
approve_transfer, thaw_mint_account,
},
program::WenNewStandard,
};
use wen_royalty_distribution::{program::WenRoyaltyDistribution, DistributionAccount};

use crate::{
constants::*,
utils::{
assert_right_associated_token_account, transfer_checked_with_hook, TransferCheckedWithHook,
},
};
use crate::{errors::*, utils::create_associated_token_account};
use crate::{state::*, utils::calculate_royalties};

#[derive(Accounts)]
#[instruction(args: FulfillListingArgs)]
pub struct FulfillListing<'info> {
#[account(mut)]
pub payer: Signer<'info>,

#[account(
mut,
seeds = [
TEST_SALE,
LISTING,
listing.seller.as_ref(),
listing.mint.as_ref(),
],
bump = listing.bump,
has_one = sale,
has_one = mint,
has_one = seller,
has_one = seller_token_account,
constraint = args.buy_amount.eq(&args.buy_amount) @ TestSaleError::ListingAmountMismatch
)]
pub listing: Account<'info, Listing>,

#[account(
mut,
seeds = [
TEST_SALE,
SALE,
sale.group.key().as_ref(),
sale.distribution.key().as_ref()
],
bump = sale.bump,
// has_one = distribution,
)]
pub sale: Account<'info, Sale>,

/// CHECK: Could be SOL or SPL, checked in distribution program
pub payment_mint: UncheckedAccount<'info>,

#[account(mut)]
pub buyer: Signer<'info>,

/// CHECK: Checked based on payment_mint
#[account(mut)]
pub buyer_payment_token_account: UncheckedAccount<'info>,

#[account(
mut,
has_one = payment_mint,
constraint = listing.payment_mint.eq(&distribution.payment_mint)
)]
pub distribution: Account<'info, DistributionAccount>,

/// CHECK: Created and checked based on payment_mint
#[account(mut)]
pub distribution_payment_token_account: UncheckedAccount<'info>,

#[account(mut)]
pub mint: InterfaceAccount<'info, Mint>,

#[account(
mut,
token::mint = mint,
token::authority = seller,
)]
pub seller_token_account: InterfaceAccount<'info, TokenAccount>,

#[account(
init_if_needed,
payer = payer,
associated_token::mint = mint,
associated_token::authority = buyer,
)]
pub buyer_token_account: InterfaceAccount<'info, TokenAccount>,

/// CHECK: Constraint checked with listing. Could be any account
#[account(mut)]
pub seller: SystemAccount<'info>,

/// CHECK: Checked based on payment_mint
#[account(mut)]
pub seller_payment_token_account: UncheckedAccount<'info>,

/// CHECK: Checked inside WNS program
pub manager: UncheckedAccount<'info>,
/// CHECK: Checked inside Token extensions program
pub extra_metas_account: UncheckedAccount<'info>,
/// CHECK: Checked inside WNS program
#[account(mut)]
pub approve_account: UncheckedAccount<'info>,

pub wns_program: Program<'info, WenNewStandard>,
pub distribution_program: Program<'info, WenRoyaltyDistribution>,
pub associated_token_program: Program<'info, AssociatedToken>,
pub token_program: Program<'info, Token2022>,
pub system_program: Program<'info, System>,
}

pub fn handler(ctx: Context<FulfillListing>, args: FulfillListingArgs) -> Result<()> {
let listing = &mut ctx.accounts.listing;
let sale = &ctx.accounts.sale;

let is_payment_mint_spl = ctx.accounts.payment_mint.key.ne(&Pubkey::default());

let signer_seeds: &[&[&[u8]]] = &[&[
TEST_SALE,
SALE,
sale.group.as_ref(),
sale.distribution.as_ref(),
&[sale.bump],
]];

// Thaw NFT
thaw_mint_account(CpiContext::new_with_signer(
ctx.accounts.wns_program.to_account_info(),
ThawDelegatedAccount {
payer: ctx.accounts.payer.to_account_info(),
delegate_authority: ctx.accounts.sale.to_account_info(),
manager: ctx.accounts.manager.to_account_info(),
mint: ctx.accounts.mint.to_account_info(),
mint_token_account: ctx.accounts.seller_token_account.to_account_info(),
token_program: ctx.accounts.token_program.to_account_info(),
user: ctx.accounts.seller.to_account_info(),
},
signer_seeds,
))?;

// Transfer (listing_amount - royalty) to seller
// Checking payment mint and creating seller token account if necessary
let royalty_funds = calculate_royalties(&ctx.accounts.mint.to_account_info(), args.buy_amount)?;

let funds_to_send = listing
.listing_amount
.checked_sub(royalty_funds)
.ok_or(TestSaleError::ArithmeticError)?;

if is_payment_mint_spl {
let payment_mint = &ctx.accounts.payment_mint.try_borrow_data()?;
let payment_mint_data = StateWithExtensions::<StateMint>::unpack(payment_mint)?;

require_neq!(
ctx.accounts.buyer_payment_token_account.key,
&Pubkey::default(),
TestSaleError::PaymentTokenAccountNotExistant
);

require_neq!(
ctx.accounts.buyer_payment_token_account.key,
&Pubkey::default(),
TestSaleError::PaymentTokenAccountNotExistant
);

assert_right_associated_token_account(
ctx.accounts.buyer.key,
ctx.accounts.payment_mint.key,
ctx.accounts.buyer_payment_token_account.key,
)?;

if ctx.accounts.seller_payment_token_account.data_is_empty() {
create_associated_token_account(
ctx.accounts.payer.to_account_info(),
ctx.accounts.seller.to_account_info(),
ctx.accounts.payment_mint.to_account_info(),
ctx.accounts.seller_payment_token_account.to_account_info(),
ctx.accounts.associated_token_program.to_account_info(),
ctx.accounts.token_program.to_account_info(),
ctx.accounts.system_program.to_account_info(),
)?;
}

transfer_checked(
CpiContext::new(
ctx.accounts.token_program.to_account_info(),
TransferChecked {
authority: ctx.accounts.buyer.to_account_info(),
from: ctx.accounts.buyer_payment_token_account.to_account_info(),
to: ctx.accounts.seller_payment_token_account.to_account_info(),
mint: ctx.accounts.payment_mint.to_account_info(),
},
),
funds_to_send,
payment_mint_data.base.decimals,
)?;
} else {
transfer(
CpiContext::new(
ctx.accounts.system_program.to_account_info(),
Transfer {
from: ctx.accounts.buyer.to_account_info(),
to: ctx.accounts.seller.to_account_info(),
},
),
funds_to_send,
)?;
}

// Approve Transfer
if is_payment_mint_spl
&& ctx
.accounts
.distribution_payment_token_account
.data_is_empty()
{
create_associated_token_account(
ctx.accounts.payer.to_account_info(),
ctx.accounts.distribution.to_account_info(),
ctx.accounts.payment_mint.to_account_info(),
ctx.accounts
.distribution_payment_token_account
.to_account_info(),
ctx.accounts.associated_token_program.to_account_info(),
ctx.accounts.token_program.to_account_info(),
ctx.accounts.system_program.to_account_info(),
)?;
}

approve_transfer(
CpiContext::new(
ctx.accounts.wns_program.to_account_info(),
ApproveTransfer {
payer: ctx.accounts.payer.to_account_info(),
authority: ctx.accounts.buyer.to_account_info(),
payment_mint: ctx.accounts.payment_mint.to_account_info(),
mint: ctx.accounts.mint.to_account_info(),
distribution_account: ctx.accounts.distribution.to_account_info(),
authority_token_account: ctx.accounts.buyer_payment_token_account.to_account_info(),
distribution_token_account: ctx
.accounts
.distribution_payment_token_account
.to_account_info(),
approve_account: ctx.accounts.approve_account.to_account_info(),
distribution_program: ctx.accounts.distribution_program.to_account_info(),
associated_token_program: ctx.accounts.associated_token_program.to_account_info(),
token_program: ctx.accounts.token_program.to_account_info(),
system_program: ctx.accounts.system_program.to_account_info(),
},
),
args.buy_amount,
)?;

// // Transfer NFT to buyer
transfer_checked_with_hook(
CpiContext::new_with_signer(
ctx.accounts.token_program.to_account_info(),
TransferCheckedWithHook {
authority: ctx.accounts.sale.to_account_info(),
mint: ctx.accounts.mint.to_account_info(),
from: ctx.accounts.seller_token_account.to_account_info(),
to: ctx.accounts.buyer_token_account.to_account_info(),
extra_metas_account: ctx.accounts.extra_metas_account.to_account_info(),
approve_account: ctx.accounts.approve_account.to_account_info(),
wns_program: ctx.accounts.wns_program.to_account_info(),
},
signer_seeds,
),
1,
0,
)?;

// Close listing
listing.close(ctx.accounts.payer.to_account_info())?;

Ok(())
}

#[derive(AnchorSerialize, AnchorDeserialize)]
pub struct FulfillListingArgs {
pub buy_amount: u64,
}
Loading

0 comments on commit 25772a0

Please sign in to comment.