Skip to content

Commit 558bb62

Browse files
committed
add creator verification and pay
1 parent c9dc16c commit 558bb62

File tree

4 files changed

+168
-2
lines changed

4 files changed

+168
-2
lines changed

programs/mmm/src/errors.rs

Lines changed: 4 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -72,4 +72,8 @@ pub enum MMMErrorCode {
7272
InvalidTokenExtension, // 0x1791
7373
#[msg("Unsupported asset plugin")]
7474
UnsupportedAssetPlugin, // 0x1792
75+
#[msg("Mismatched ceator data lengths")]
76+
MismatchedCreatorDataLengths, // 0x1793
77+
#[msg("Invalid creators")]
78+
InvalidCreators, // 0x1794
7579
}

programs/mmm/src/instructions/cnft/sol_cnft_fulfill_buy.rs

Lines changed: 23 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -11,7 +11,8 @@ use crate::{
1111
util::{
1212
assert_valid_fees_bp, check_remaining_accounts_for_m2, get_buyside_seller_receives,
1313
get_lp_fee_bp, get_sol_fee, get_sol_lp_fee, get_sol_total_price_and_next_price,
14-
hash_metadata, log_pool, transfer_compressed_nft, try_close_pool, withdraw_m2,
14+
hash_metadata, log_pool, pay_creator_fees_in_sol_cnft, transfer_compressed_nft,
15+
try_close_pool, verify_creators, withdraw_m2,
1516
},
1617
verify_referral::verify_referral,
1718
};
@@ -158,6 +159,12 @@ pub fn handler<'info>(
158159

159160
let data_hash = hash_metadata(&args.metadata_args)?;
160161
let asset_mint = get_asset_id(&merkle_tree.key(), args.nonce);
162+
let pool_key = pool.key();
163+
let buyside_sol_escrow_account_seeds: &[&[&[u8]]] = &[&[
164+
BUYSIDE_SOL_ESCROW_ACCOUNT_PREFIX.as_bytes(),
165+
pool_key.as_ref(),
166+
&[ctx.bumps.buyside_sol_escrow_account],
167+
]];
161168

162169
// 1. Cacluate seller receives
163170
let (total_price, next_price) =
@@ -212,6 +219,12 @@ pub fn handler<'info>(
212219
} else {
213220
remaining_accounts.split_at(creator_shares_length)
214221
};
222+
verify_creators(
223+
creator_accounts.iter(),
224+
args.creator_shares,
225+
args.creator_verified,
226+
args.creator_hash,
227+
)?;
215228

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

272285
// 5. Pool owner as buyer pay royalties to creators
286+
let royalty_paid = pay_creator_fees_in_sol_cnft(
287+
pool.buyside_creator_royalty_bp,
288+
seller_receives,
289+
&args.metadata_args,
290+
creator_accounts,
291+
buyside_sol_escrow_account.to_account_info(),
292+
buyside_sol_escrow_account_seeds,
293+
system_program.to_account_info(),
294+
)?;
273295
// 6. Prevent frontrun by pool config changes
274296
// 7. Close pool if all NFTs are sold
275297
// 8. Pool pay the sol to the seller

programs/mmm/src/util.rs

Lines changed: 121 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -14,6 +14,7 @@ use anchor_spl::token_interface::Mint;
1414
use m2_interface::{
1515
withdraw_by_mmm_ix_with_program_id, WithdrawByMMMArgs, WithdrawByMmmIxArgs, WithdrawByMmmKeys,
1616
};
17+
use mpl_bubblegum::hash::hash_creators;
1718
use mpl_core::types::{Royalties, UpdateAuthority};
1819
use mpl_token_metadata::{
1920
accounts::{MasterEdition, Metadata},
@@ -30,7 +31,7 @@ use spl_token_2022::{
3031
};
3132
use spl_token_group_interface::state::TokenGroupMember;
3233
use spl_token_metadata_interface::state::TokenMetadata;
33-
use std::{convert::TryFrom, str::FromStr};
34+
use std::{convert::TryFrom, slice::Iter, str::FromStr};
3435

3536
#[macro_export]
3637
macro_rules! index_ra {
@@ -597,6 +598,83 @@ pub fn pay_creator_fees_in_sol<'info>(
597598
Ok(total_royalty)
598599
}
599600

601+
#[allow(clippy::too_many_arguments)]
602+
pub fn pay_creator_fees_in_sol_cnft<'info>(
603+
buyside_creator_royalty_bp: u16,
604+
total_price: u64,
605+
metadata_args: &MetadataArgs,
606+
creator_accounts: &[AccountInfo<'info>],
607+
payer: AccountInfo<'info>,
608+
payer_seeds: &[&[&[u8]]],
609+
system_program: AccountInfo<'info>,
610+
) -> Result<u64> {
611+
// Calculate the total royalty to be paid
612+
let royalty = ((total_price as u128)
613+
.checked_mul(metadata_args.seller_fee_basis_points as u128)
614+
.ok_or(MMMErrorCode::NumericOverflow)?
615+
.checked_div(10000)
616+
.ok_or(MMMErrorCode::NumericOverflow)?
617+
.checked_mul(buyside_creator_royalty_bp as u128)
618+
.ok_or(MMMErrorCode::NumericOverflow)?
619+
.checked_div(10000)
620+
.ok_or(MMMErrorCode::NumericOverflow)?) as u64;
621+
622+
if royalty == 0 {
623+
return Ok(0);
624+
}
625+
626+
if payer.lamports() < royalty {
627+
return Err(MMMErrorCode::NotEnoughBalance.into());
628+
}
629+
630+
let min_rent = Rent::get()?.minimum_balance(0);
631+
let mut total_royalty: u64 = 0;
632+
633+
let creator_accounts_iter = &mut creator_accounts.iter();
634+
for (index, creator) in metadata_args.creators.iter().enumerate() {
635+
let creator_fee = if index == metadata_args.creators.len() - 1 {
636+
royalty
637+
.checked_sub(total_royalty)
638+
.ok_or(MMMErrorCode::NumericOverflow)?
639+
} else {
640+
(royalty as u128)
641+
.checked_mul(creator.share as u128)
642+
.ok_or(MMMErrorCode::NumericOverflow)?
643+
.checked_div(100)
644+
.ok_or(MMMErrorCode::NumericOverflow)? as u64
645+
};
646+
let current_creator_info = next_account_info(creator_accounts_iter)?;
647+
if creator.address.ne(current_creator_info.key) {
648+
return Err(MMMErrorCode::InvalidCreatorAddress.into());
649+
}
650+
let current_creator_lamports = current_creator_info.lamports();
651+
if creator_fee > 0
652+
&& current_creator_lamports
653+
.checked_add(creator_fee)
654+
.ok_or(MMMErrorCode::NumericOverflow)?
655+
> min_rent
656+
{
657+
anchor_lang::solana_program::program::invoke_signed(
658+
&anchor_lang::solana_program::system_instruction::transfer(
659+
payer.key,
660+
current_creator_info.key,
661+
creator_fee,
662+
),
663+
&[
664+
payer.to_account_info(),
665+
current_creator_info.to_account_info(),
666+
system_program.to_account_info(),
667+
],
668+
payer_seeds,
669+
)?;
670+
total_royalty = total_royalty
671+
.checked_add(creator_fee)
672+
.ok_or(MMMErrorCode::NumericOverflow)?;
673+
}
674+
}
675+
Ok(total_royalty)
676+
}
677+
600678
pub fn log_pool(prefix: &str, pool: &Pool) -> Result<()> {
601679
msg!(prefix);
602680
sol_log_data(&[&pool.try_to_vec()?]);
@@ -1228,6 +1306,48 @@ pub fn hash_metadata(metadata: &MetadataArgs) -> Result<[u8; 32]> {
12281306
.to_bytes())
12291307
}
12301308

1309+
pub fn verify_creators(
1310+
creator_accounts: Iter<AccountInfo>,
1311+
creator_shares: Vec<u16>,
1312+
creator_verified: Vec<bool>,
1313+
creator_hash: [u8; 32],
1314+
) -> Result<()> {
1315+
// Check that all input arrays/vectors are of the same length
1316+
if creator_accounts.len() != creator_shares.len()
1317+
|| creator_accounts.len() != creator_verified.len()
1318+
{
1319+
return Err(MMMErrorCode::MismatchedCreatorDataLengths.into());
1320+
}
1321+
1322+
// Convert input data to a vector of Creator structs
1323+
let creators: Vec<mpl_bubblegum::types::Creator> = creator_accounts
1324+
.zip(creator_shares.iter())
1325+
.zip(creator_verified.iter())
1326+
.map(
1327+
|((account, &share), &verified)| mpl_bubblegum::types::Creator {
1328+
address: *account.key,
1329+
verified,
1330+
share: share as u8, // Assuming the share is never more than 255. If it can be, this needs additional checks.
1331+
},
1332+
)
1333+
.collect();
1334+
1335+
// Compute the hash from the Creator vector
1336+
let computed_hash = hash_creators(&creators);
1337+
1338+
// Compare the computed hash with the provided hash
1339+
if computed_hash != creator_hash {
1340+
msg!(
1341+
"Computed hash does not match provided hash: {{\"computed\":{:?},\"provided\":{:?}}}",
1342+
computed_hash,
1343+
creator_hash
1344+
);
1345+
return Err(MMMErrorCode::InvalidCreators.into());
1346+
}
1347+
1348+
Ok(())
1349+
}
1350+
12311351
#[cfg(test)]
12321352
mod tests {
12331353
use anchor_spl::token_2022;

sdk/src/idl/mmm.ts

Lines changed: 20 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -3294,6 +3294,16 @@ export type Mmm = {
32943294
"code": 6034,
32953295
"name": "UnsupportedAssetPlugin",
32963296
"msg": "Unsupported asset plugin"
3297+
},
3298+
{
3299+
"code": 6035,
3300+
"name": "MismatchedCreatorDataLengths",
3301+
"msg": "Mismatched ceator data lengths"
3302+
},
3303+
{
3304+
"code": 6036,
3305+
"name": "InvalidCreators",
3306+
"msg": "Invalid creators"
32973307
}
32983308
]
32993309
};
@@ -6594,6 +6604,16 @@ export const IDL: Mmm = {
65946604
"code": 6034,
65956605
"name": "UnsupportedAssetPlugin",
65966606
"msg": "Unsupported asset plugin"
6607+
},
6608+
{
6609+
"code": 6035,
6610+
"name": "MismatchedCreatorDataLengths",
6611+
"msg": "Mismatched ceator data lengths"
6612+
},
6613+
{
6614+
"code": 6036,
6615+
"name": "InvalidCreators",
6616+
"msg": "Invalid creators"
65976617
}
65986618
]
65996619
};

0 commit comments

Comments
 (0)