Skip to content

Commit

Permalink
add creator verification and pay
Browse files Browse the repository at this point in the history
  • Loading branch information
JeremyLi28 committed Nov 8, 2024
1 parent c9dc16c commit 558bb62
Show file tree
Hide file tree
Showing 4 changed files with 168 additions and 2 deletions.
4 changes: 4 additions & 0 deletions programs/mmm/src/errors.rs
Original file line number Diff line number Diff line change
Expand Up @@ -72,4 +72,8 @@ pub enum MMMErrorCode {
InvalidTokenExtension, // 0x1791
#[msg("Unsupported asset plugin")]
UnsupportedAssetPlugin, // 0x1792
#[msg("Mismatched ceator data lengths")]
MismatchedCreatorDataLengths, // 0x1793
#[msg("Invalid creators")]
InvalidCreators, // 0x1794
}
24 changes: 23 additions & 1 deletion programs/mmm/src/instructions/cnft/sol_cnft_fulfill_buy.rs
Original file line number Diff line number Diff line change
Expand Up @@ -11,7 +11,8 @@ use crate::{
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,
hash_metadata, log_pool, pay_creator_fees_in_sol_cnft, transfer_compressed_nft,
try_close_pool, verify_creators, withdraw_m2,
},
verify_referral::verify_referral,
};
Expand Down Expand Up @@ -158,6 +159,12 @@ pub fn handler<'info>(

let data_hash = hash_metadata(&args.metadata_args)?;
let asset_mint = get_asset_id(&merkle_tree.key(), args.nonce);
let pool_key = pool.key();
let buyside_sol_escrow_account_seeds: &[&[&[u8]]] = &[&[
BUYSIDE_SOL_ESCROW_ACCOUNT_PREFIX.as_bytes(),
pool_key.as_ref(),
&[ctx.bumps.buyside_sol_escrow_account],
]];

// 1. Cacluate seller receives
let (total_price, next_price) =
Expand Down Expand Up @@ -212,6 +219,12 @@ pub fn handler<'info>(
} else {
remaining_accounts.split_at(creator_shares_length)
};
verify_creators(
creator_accounts.iter(),
args.creator_shares,
args.creator_verified,
args.creator_hash,
)?;

// 4. Transfer CNFT to buyer (pool or owner)
if pool.reinvest_fulfill_buy {
Expand Down Expand Up @@ -270,6 +283,15 @@ pub fn handler<'info>(
}

// 5. Pool owner as buyer pay royalties to creators
let royalty_paid = pay_creator_fees_in_sol_cnft(
pool.buyside_creator_royalty_bp,
seller_receives,
&args.metadata_args,
creator_accounts,
buyside_sol_escrow_account.to_account_info(),
buyside_sol_escrow_account_seeds,
system_program.to_account_info(),
)?;
// 6. Prevent frontrun by pool config changes
// 7. Close pool if all NFTs are sold
// 8. Pool pay the sol to the seller
Expand Down
122 changes: 121 additions & 1 deletion programs/mmm/src/util.rs
Original file line number Diff line number Diff line change
Expand Up @@ -14,6 +14,7 @@ use anchor_spl::token_interface::Mint;
use m2_interface::{
withdraw_by_mmm_ix_with_program_id, WithdrawByMMMArgs, WithdrawByMmmIxArgs, WithdrawByMmmKeys,
};
use mpl_bubblegum::hash::hash_creators;
use mpl_core::types::{Royalties, UpdateAuthority};
use mpl_token_metadata::{
accounts::{MasterEdition, Metadata},
Expand All @@ -30,7 +31,7 @@ use spl_token_2022::{
};
use spl_token_group_interface::state::TokenGroupMember;
use spl_token_metadata_interface::state::TokenMetadata;
use std::{convert::TryFrom, str::FromStr};
use std::{convert::TryFrom, slice::Iter, str::FromStr};

#[macro_export]
macro_rules! index_ra {
Expand Down Expand Up @@ -597,6 +598,83 @@ pub fn pay_creator_fees_in_sol<'info>(
Ok(total_royalty)
}

#[allow(clippy::too_many_arguments)]
pub fn pay_creator_fees_in_sol_cnft<'info>(
buyside_creator_royalty_bp: u16,
total_price: u64,
metadata_args: &MetadataArgs,
creator_accounts: &[AccountInfo<'info>],
payer: AccountInfo<'info>,
payer_seeds: &[&[&[u8]]],
system_program: AccountInfo<'info>,
) -> Result<u64> {
// Calculate the total royalty to be paid
let royalty = ((total_price as u128)
.checked_mul(metadata_args.seller_fee_basis_points as u128)
.ok_or(MMMErrorCode::NumericOverflow)?
.checked_div(10000)
.ok_or(MMMErrorCode::NumericOverflow)?
.checked_mul(buyside_creator_royalty_bp as u128)
.ok_or(MMMErrorCode::NumericOverflow)?
.checked_div(10000)
.ok_or(MMMErrorCode::NumericOverflow)?) as u64;

if royalty == 0 {
return Ok(0);
}

if payer.lamports() < royalty {
return Err(MMMErrorCode::NotEnoughBalance.into());
}

let min_rent = Rent::get()?.minimum_balance(0);
let mut total_royalty: u64 = 0;

let creator_accounts_iter = &mut creator_accounts.iter();
for (index, creator) in metadata_args.creators.iter().enumerate() {
let creator_fee = if index == metadata_args.creators.len() - 1 {
royalty
.checked_sub(total_royalty)
.ok_or(MMMErrorCode::NumericOverflow)?
} else {
(royalty as u128)
.checked_mul(creator.share as u128)
.ok_or(MMMErrorCode::NumericOverflow)?
.checked_div(100)
.ok_or(MMMErrorCode::NumericOverflow)? as u64
};
let current_creator_info = next_account_info(creator_accounts_iter)?;
if creator.address.ne(current_creator_info.key) {
return Err(MMMErrorCode::InvalidCreatorAddress.into());
}
let current_creator_lamports = current_creator_info.lamports();
if creator_fee > 0
&& current_creator_lamports
.checked_add(creator_fee)
.ok_or(MMMErrorCode::NumericOverflow)?
> min_rent
{
anchor_lang::solana_program::program::invoke_signed(
&anchor_lang::solana_program::system_instruction::transfer(
payer.key,
current_creator_info.key,
creator_fee,
),
&[
payer.to_account_info(),
current_creator_info.to_account_info(),
system_program.to_account_info(),
],
payer_seeds,
)?;
total_royalty = total_royalty
.checked_add(creator_fee)
.ok_or(MMMErrorCode::NumericOverflow)?;
}
}
Ok(total_royalty)
}

pub fn log_pool(prefix: &str, pool: &Pool) -> Result<()> {
msg!(prefix);
sol_log_data(&[&pool.try_to_vec()?]);
Expand Down Expand Up @@ -1228,6 +1306,48 @@ pub fn hash_metadata(metadata: &MetadataArgs) -> Result<[u8; 32]> {
.to_bytes())
}

pub fn verify_creators(
creator_accounts: Iter<AccountInfo>,
creator_shares: Vec<u16>,
creator_verified: Vec<bool>,
creator_hash: [u8; 32],
) -> Result<()> {
// Check that all input arrays/vectors are of the same length
if creator_accounts.len() != creator_shares.len()
|| creator_accounts.len() != creator_verified.len()
{
return Err(MMMErrorCode::MismatchedCreatorDataLengths.into());
}

// Convert input data to a vector of Creator structs
let creators: Vec<mpl_bubblegum::types::Creator> = creator_accounts
.zip(creator_shares.iter())
.zip(creator_verified.iter())
.map(
|((account, &share), &verified)| mpl_bubblegum::types::Creator {
address: *account.key,
verified,
share: share as u8, // Assuming the share is never more than 255. If it can be, this needs additional checks.
},
)
.collect();

// Compute the hash from the Creator vector
let computed_hash = hash_creators(&creators);

// Compare the computed hash with the provided hash
if computed_hash != creator_hash {
msg!(
"Computed hash does not match provided hash: {{\"computed\":{:?},\"provided\":{:?}}}",
computed_hash,
creator_hash
);
return Err(MMMErrorCode::InvalidCreators.into());
}

Ok(())
}

#[cfg(test)]
mod tests {
use anchor_spl::token_2022;
Expand Down
20 changes: 20 additions & 0 deletions sdk/src/idl/mmm.ts
Original file line number Diff line number Diff line change
Expand Up @@ -3294,6 +3294,16 @@ export type Mmm = {
"code": 6034,
"name": "UnsupportedAssetPlugin",
"msg": "Unsupported asset plugin"
},
{
"code": 6035,
"name": "MismatchedCreatorDataLengths",
"msg": "Mismatched ceator data lengths"
},
{
"code": 6036,
"name": "InvalidCreators",
"msg": "Invalid creators"
}
]
};
Expand Down Expand Up @@ -6594,6 +6604,16 @@ export const IDL: Mmm = {
"code": 6034,
"name": "UnsupportedAssetPlugin",
"msg": "Unsupported asset plugin"
},
{
"code": 6035,
"name": "MismatchedCreatorDataLengths",
"msg": "Mismatched ceator data lengths"
},
{
"code": 6036,
"name": "InvalidCreators",
"msg": "Invalid creators"
}
]
};

0 comments on commit 558bb62

Please sign in to comment.