Skip to content

Commit

Permalink
add fee cacluation, shared escrow support, reinvest buy support
Browse files Browse the repository at this point in the history
  • Loading branch information
JeremyLi28 committed Nov 7, 2024
1 parent c58fdc8 commit c9dc16c
Show file tree
Hide file tree
Showing 2 changed files with 146 additions and 35 deletions.
167 changes: 139 additions & 28 deletions programs/mmm/src/instructions/cnft/sol_cnft_fulfill_buy.rs
Original file line number Diff line number Diff line change
@@ -1,12 +1,18 @@
use std::str::FromStr;

use anchor_lang::{prelude::*, AnchorDeserialize, AnchorSerialize};
use mpl_bubblegum::utils::get_asset_id;

use crate::{
constants::*,
errors::MMMErrorCode,
index_ra,
state::{BubblegumProgram, Pool, SellState, TreeConfigAnchor},
util::{hash_metadata, log_pool, transfer_compressed_nft, try_close_pool},
util::{
assert_valid_fees_bp, check_remaining_accounts_for_m2, get_buyside_seller_receives,
get_lp_fee_bp, get_sol_fee, get_sol_lp_fee, get_sol_total_price_and_next_price,
hash_metadata, log_pool, transfer_compressed_nft, try_close_pool, withdraw_m2,
},
verify_referral::verify_referral,
};

Expand Down Expand Up @@ -127,9 +133,11 @@ pub struct SolCnftFulfillBuy<'info> {
// Branch: using shared escrow accounts
// 0: m2_program
// 1: shared_escrow_account
// 2+: creator accounts
// 2-N: creator accounts
//. N+: proof accounts
// Branch: not using shared escrow accounts
// 0+: creator accounts
// 0-N: creator accounts
//. N+: proof accounts
}

pub fn handler<'info>(
Expand All @@ -138,44 +146,147 @@ pub fn handler<'info>(
) -> Result<()> {
// let payer = &ctx.accounts.payer;
let owner = &ctx.accounts.owner;
let pool = &ctx.accounts.pool;
// let sell_state = &mut ctx.accounts.sell_state;
// let merkle_tree = &ctx.accounts.merkle_tree;
let pool = &mut ctx.accounts.pool;
let buyside_sol_escrow_account = &ctx.accounts.buyside_sol_escrow_account;
let sell_state = &mut ctx.accounts.sell_state;
let merkle_tree = &ctx.accounts.merkle_tree;
// Remaining accounts are 1. (Optional) creator addresses and 2. Merkle proof path.
let creator_shares_length = args.creator_shares.len();
let creator_shares_clone = args.creator_shares.clone();
let (creator_accounts, proof_path) = ctx.remaining_accounts.split_at(creator_shares_length);
let remaining_accounts = ctx.remaining_accounts;
let system_program = &ctx.accounts.system_program;

if pool.using_shared_escrow() {
return Err(MMMErrorCode::InvalidAccountState.into());
let data_hash = hash_metadata(&args.metadata_args)?;
let asset_mint = get_asset_id(&merkle_tree.key(), args.nonce);

// 1. Cacluate seller receives
let (total_price, next_price) =
get_sol_total_price_and_next_price(pool, args.asset_amount, true)?;
let metadata_royalty_bp = args.metadata_args.seller_fee_basis_points;
// TODO: update lp_fee_bp when shared escrow for both side is enabled
let seller_receives = {
let lp_fee_bp = get_lp_fee_bp(pool, buyside_sol_escrow_account.lamports());
get_buyside_seller_receives(
total_price,
lp_fee_bp,
metadata_royalty_bp,
pool.buyside_creator_royalty_bp,
)
}?;

// 2. Calculate fees
let lp_fee = get_sol_lp_fee(pool, buyside_sol_escrow_account.lamports(), seller_receives)?;
assert_valid_fees_bp(args.maker_fee_bp, args.taker_fee_bp)?;
let maker_fee = get_sol_fee(seller_receives, args.maker_fee_bp)?;
let taker_fee = get_sol_fee(seller_receives, args.taker_fee_bp)?;
let referral_fee = u64::try_from(
maker_fee
.checked_add(taker_fee)
.ok_or(MMMErrorCode::NumericOverflow)?,
)
.map_err(|_| MMMErrorCode::NumericOverflow)?;

// 3. Get creator accounts, verify creators
// check creator_accounts and verify the remaining accounts
// ... existing code ...
let (creator_accounts, proof_path) = if pool.using_shared_escrow() {
check_remaining_accounts_for_m2(remaining_accounts, &pool.owner.key())?;

let amount: u64 = (total_price as i64 + maker_fee) as u64;
withdraw_m2(
pool,
ctx.bumps.pool,
buyside_sol_escrow_account,
index_ra!(remaining_accounts, 1),
system_program,
index_ra!(remaining_accounts, 0),
pool.owner,
amount,
)?;
pool.shared_escrow_count = pool
.shared_escrow_count
.checked_sub(args.asset_amount)
.ok_or(MMMErrorCode::NumericOverflow)?;

remaining_accounts[2..].split_at(creator_shares_length + 2)
} else {
remaining_accounts.split_at(creator_shares_length)
};

// 4. Transfer CNFT to buyer (pool or owner)
if pool.reinvest_fulfill_buy {
if pool.using_shared_escrow() {
return Err(MMMErrorCode::InvalidAccountState.into());
}
transfer_compressed_nft(
&ctx.accounts.tree_authority.to_account_info(),
&pool.to_account_info(),
&ctx.accounts.payer.to_account_info(),
&ctx.accounts.owner.to_account_info(),
&ctx.accounts.merkle_tree,
&ctx.accounts.log_wrapper,
&ctx.accounts.compression_program,
&ctx.accounts.system_program, // Pass as Program<System> without calling to_account_info()
proof_path,
ctx.accounts.bubblegum_program.key(),
args.root,
data_hash,
args.creator_hash,
args.nonce,
args.index,
None, // signer passed through from ctx
)?;
pool.sellside_asset_amount = pool
.sellside_asset_amount
.checked_add(args.asset_amount)
.ok_or(MMMErrorCode::NumericOverflow)?;
sell_state.pool = pool.key();
sell_state.pool_owner = owner.key();
sell_state.asset_mint = asset_mint.key();
sell_state.cosigner_annotation = pool.cosigner_annotation;
sell_state.asset_amount = sell_state
.asset_amount
.checked_add(args.asset_amount)
.ok_or(MMMErrorCode::NumericOverflow)?;
} else {
transfer_compressed_nft(
&ctx.accounts.tree_authority.to_account_info(),
&ctx.accounts.payer.to_account_info(),
&ctx.accounts.payer.to_account_info(),
&ctx.accounts.owner.to_account_info(),
&ctx.accounts.merkle_tree,
&ctx.accounts.log_wrapper,
&ctx.accounts.compression_program,
&ctx.accounts.system_program, // Pass as Program<System> without calling to_account_info()
proof_path,
ctx.accounts.bubblegum_program.key(),
args.root,
data_hash,
args.creator_hash,
args.nonce,
args.index,
None, // signer passed through from ctx
)?;
}

let data_hash = hash_metadata(&args.metadata_args)?;
// 5. Pool owner as buyer pay royalties to creators
// 6. Prevent frontrun by pool config changes
// 7. Close pool if all NFTs are sold
// 8. Pool pay the sol to the seller
// 9. pay lp fee
// 10. pay referral fee
// 11. update pool state
// 12 try close buy side escrow account
// 13. try close sell state account
// 14. return the remaining per pool escrow balance to the shared escrow account
// 15. update pool and log pool and try close pool

msg!("seller fee basis points: {}", args.seller_fee_basis_points);
// Create data_hash from metadata_hash + seller_fee_basis_points (secures creator royalties)
// let data_hash = hash_metadata_data(args.metadata_hash, args.seller_fee_basis_points)?;

// Transfer CNFT from seller(payer) to buyer (pool owner)
// TODO: do I need to send to pool instead?
transfer_compressed_nft(
&ctx.accounts.tree_authority.to_account_info(),
&ctx.accounts.payer.to_account_info(),
&ctx.accounts.payer.to_account_info(),
&ctx.accounts.owner.to_account_info(),
&ctx.accounts.merkle_tree,
&ctx.accounts.log_wrapper,
&ctx.accounts.compression_program,
&ctx.accounts.system_program, // Pass as Program<System> without calling to_account_info()
proof_path,
ctx.accounts.bubblegum_program.key(),
args.root,
data_hash,
args.creator_hash,
args.nonce,
args.index,
None, // signer passed through from ctx
)?;

log_pool("post_sol_cnft_fulfill_buy", pool)?;
try_close_pool(pool, owner.to_account_info())?;
Expand Down
14 changes: 7 additions & 7 deletions tests/mmm-cnft.spec.ts
Original file line number Diff line number Diff line change
Expand Up @@ -53,6 +53,7 @@ async function createCNftCollectionOffer(
) {
const poolData = await createPool(program, {
...poolArgs,
reinvestFulfillBuy: false,
});

const poolKey = poolData.poolKey;
Expand Down Expand Up @@ -215,13 +216,12 @@ describe('cnft tests', () => {
)[0];

console.log(`metadataArgs: ${JSON.stringify(metadataArgs)}`);
console.log(
`${JSON.stringify(
convertToDecodeTokenProgramVersion(
metadataArgs.tokenProgramVersion,
) )}`,
);

console.log(
`${JSON.stringify(
convertToDecodeTokenProgramVersion(metadataArgs.tokenProgramVersion),
)}`,
);

const fulfillBuyTxnSig = await program.methods
.cnftFulfillBuy({
root: getByteArray(nft.tree.root),
Expand Down

0 comments on commit c9dc16c

Please sign in to comment.