1use solana_program::msg;
2
3use crate::{
4 error::MetaplexError,
5 instruction::EmptyPaymentAccountArgs,
6 state::{
7 get_auction_manager, AuctionManager, Key, PayoutTicket, Store, MAX_PAYOUT_TICKET_SIZE,
8 PREFIX, TOTALS,
9 },
10 utils::{
11 assert_derivation, assert_initialized, assert_is_ata, assert_owned_by, assert_rent_exempt,
12 assert_safety_deposit_config_valid, create_or_allocate_account_raw, spl_token_transfer,
13 },
14};
15use borsh::BorshSerialize;
16use ywpl_auction::processor::AuctionData;
17use mpl_token_metadata::state::{MasterEditionV1, Metadata};
18use ywpl_token_vault::state::SafetyDepositBox;
19use solana_program::{
20 account_info::{next_account_info, AccountInfo},
21 entrypoint::ProgramResult,
22 program_error::ProgramError,
23 program_option::COption,
24 pubkey::Pubkey,
25 rent::Rent,
26 sysvar::Sysvar,
27};
28use spl_token::state::Account;
29
30fn assert_destination_ownership_validity(
31 auction_manager: &Box<dyn AuctionManager>,
32 metadata: &Metadata,
33 destination_info: &AccountInfo,
34 destination: &Account,
35 _store: &Store,
36 creator_index: Option<u8>,
37) -> ProgramResult {
38 if let Some(creators) = &metadata.data.creators {
39 if let Some(index) = creator_index {
40 if (index as usize) < creators.len() {
41 let creator = &creators[index as usize];
42 if destination.owner != creator.address {
43 return Err(MetaplexError::IncorrectOwner.into());
44 }
45
46 assert_is_ata(destination_info, &creator.address, &destination.mint)?;
49 } else {
50 return Err(MetaplexError::InvalidCreatorIndex.into());
51 }
52 } else if destination.owner != auction_manager.authority() {
53 return Err(MetaplexError::IncorrectOwner.into());
54 }
55 } else if destination.owner != auction_manager.authority() {
56 return Err(MetaplexError::IncorrectOwner.into());
57 }
58
59 if destination.delegate != COption::None {
60 return Err(MetaplexError::DelegateShouldBeNone.into());
61 }
62
63 if destination.close_authority != COption::None {
64 return Err(MetaplexError::CloseAuthorityShouldBeNone.into());
65 }
66
67 Ok(())
68}
69
70fn calculate_owed_amount(
71 auction_token_tracker_info: Option<&AccountInfo>,
72 safety_deposit_config_info: Option<&AccountInfo>,
73 auction_manager: &Box<dyn AuctionManager>,
74 auction: &AuctionData,
75 metadata: &Metadata,
76 winning_config_index: &Option<u8>,
77 winning_config_item_index: &Option<u8>,
78 creator_index: &Option<u8>,
79) -> Result<u64, ProgramError> {
80 let primary_sale_happened = auction_manager.get_primary_sale_happened(
81 metadata,
82 *winning_config_index,
83 *winning_config_item_index,
84 )?;
85
86 let mut amount_available_to_split: u128 = match winning_config_index {
87 Some(index) => auction.bid_state.amount(*index as usize) as u128,
88 None => {
89 auction_manager.get_collected_to_accept_payment(safety_deposit_config_info)?
91 }
92 };
93
94 if winning_config_index.is_some() {
95 msg!("Winning config index {:?}", winning_config_index.unwrap());
96 }
97 if winning_config_item_index.is_some() {
98 msg!(
99 "Winning config item index {:?}",
100 winning_config_item_index.unwrap()
101 );
102 }
103 if creator_index.is_some() {
104 msg!("Creator index {:?}", creator_index.unwrap());
105 }
106
107 msg!("Amount available to split {:?}", amount_available_to_split);
108 let numerator: u128 = match creator_index {
109 Some(_) => {
110 if primary_sale_happened {
111 metadata.data.seller_fee_basis_points as u128
113 } else {
114 10000
116 }
117 }
118 None => {
119 if primary_sale_happened {
120 (10000 - metadata.data.seller_fee_basis_points) as u128
122 } else {
123 0u128
126 }
127 }
128 };
129
130 msg!("Numerator {:?}", numerator);
131
132 let artist_further_multiplier = match creator_index {
136 Some(index) => match &metadata.data.creators {
137 Some(creators) => (creators[*index as usize].share as u128) * 100u128,
138 None => return Err(MetaplexError::CreatorIndexExpected.into()),
139 },
140 None => 10000,
141 };
142
143 msg!("Artist further multiplier {:?}", artist_further_multiplier);
144
145 amount_available_to_split = amount_available_to_split
150 .checked_mul(numerator)
151 .ok_or(MetaplexError::NumericalOverflowError)?;
152
153 msg!(
154 "Amount available to split after numerator mult {:?}",
155 amount_available_to_split,
156 );
157
158 amount_available_to_split = amount_available_to_split
163 .checked_mul(artist_further_multiplier)
164 .ok_or(MetaplexError::NumericalOverflowError)?;
165
166 msg!(
167 "Amount available to split after artist further multiplier mult {:?}",
168 amount_available_to_split,
169 );
170 if amount_available_to_split == 0 {
171 return Ok(0u64);
173 }
174
175 let proportion_divisor = match winning_config_index {
176 Some(val) => auction_manager.get_number_of_unique_token_types_for_this_winner(
177 *val as usize,
178 auction_token_tracker_info,
179 )?,
180 None => 1,
181 };
182
183 let proportional_amount_available_to_split = amount_available_to_split
185 .checked_div(proportion_divisor)
186 .ok_or(MetaplexError::NumericalOverflowError)?;
187
188 msg!(
189 "Divided the amount by {:?} to get {:?} due to sharing reward with other prizes",
190 proportion_divisor,
191 proportional_amount_available_to_split
192 );
193
194 let final_amount_available_to_split = proportional_amount_available_to_split
200 .checked_div(10000 * 10000)
201 .ok_or(MetaplexError::NumericalOverflowError)?;
202 msg!("Final amount mult {:?}", final_amount_available_to_split);
203
204 Ok(final_amount_available_to_split as u64)
205}
206
207pub fn process_empty_payment_account(
208 program_id: &Pubkey,
209 accounts: &[AccountInfo],
210 args: EmptyPaymentAccountArgs,
211) -> ProgramResult {
212 let account_info_iter = &mut accounts.iter();
213 let accept_payment_info = next_account_info(account_info_iter)?;
214 let destination_info = next_account_info(account_info_iter)?;
215 let auction_manager_info = next_account_info(account_info_iter)?;
216 let payout_ticket_info = next_account_info(account_info_iter)?;
217 let payer_info = next_account_info(account_info_iter)?;
218 let metadata_info = next_account_info(account_info_iter)?;
219 let master_edition_info = next_account_info(account_info_iter)?;
220 let safety_deposit_info = next_account_info(account_info_iter)?;
221 let store_info = next_account_info(account_info_iter)?;
222 let vault_info = next_account_info(account_info_iter)?;
223 let auction_info = next_account_info(account_info_iter)?;
224 let token_program_info = next_account_info(account_info_iter)?;
225 let system_info = next_account_info(account_info_iter)?;
226 let rent_info = next_account_info(account_info_iter)?;
227 let auction_token_tracker_info = next_account_info(account_info_iter).ok();
228 let safety_deposit_config_info = next_account_info(account_info_iter).ok();
229
230 if let Some(tracker_info) = auction_token_tracker_info {
231 assert_derivation(
232 program_id,
233 tracker_info,
234 &[
235 PREFIX.as_bytes(),
236 &program_id.as_ref(),
237 auction_manager_info.key.as_ref(),
238 TOTALS.as_bytes(),
239 ],
240 )?;
241 }
242
243 let rent = &Rent::from_account_info(&rent_info)?;
244
245 let auction_manager = get_auction_manager(auction_manager_info)?;
246 let store = Store::from_account_info(store_info)?;
247 let safety_deposit = SafetyDepositBox::from_account_info(safety_deposit_info)?;
248 let metadata = Metadata::from_account_info(metadata_info)?;
249 let auction = AuctionData::from_account_info(auction_info)?;
250 let destination: Account = assert_initialized(destination_info)?;
251 let accept_payment: Account = assert_initialized(accept_payment_info)?;
252
253 if auction_manager.store() != *store_info.key {
254 return Err(MetaplexError::AuctionManagerStoreMismatch.into());
255 }
256
257 msg!(
258 "At this point, accept payment has {:?} in it",
259 accept_payment.amount
260 );
261
262 auction_manager.assert_all_bids_claimed(&auction)?;
266
267 if *token_program_info.key != store.token_program {
268 return Err(MetaplexError::AuctionManagerTokenProgramMismatch.into());
269 }
270
271 assert_owned_by(auction_manager_info, program_id)?;
272 if !payout_ticket_info.data_is_empty() {
273 assert_owned_by(payout_ticket_info, program_id)?;
274 }
275 assert_owned_by(destination_info, token_program_info.key)?;
276 assert_owned_by(accept_payment_info, token_program_info.key)?;
277 assert_owned_by(metadata_info, &store.token_metadata_program)?;
278 if *master_edition_info.key != solana_program::system_program::id() {
279 assert_owned_by(master_edition_info, &store.token_metadata_program)?;
280 }
281 assert_owned_by(safety_deposit_info, &store.token_vault_program)?;
282 assert_owned_by(store_info, program_id)?;
283 assert_owned_by(vault_info, &store.token_vault_program)?;
284 assert_owned_by(auction_info, &store.auction_program)?;
285 assert_rent_exempt(rent, destination_info)?;
286
287 auction_manager.assert_winning_config_safety_deposit_validity(
289 &safety_deposit,
290 args.winning_config_index,
291 args.winning_config_item_index,
292 )?;
293
294 assert_safety_deposit_config_valid(
295 program_id,
296 auction_manager_info,
297 safety_deposit_info,
298 safety_deposit_config_info,
299 &auction_manager.key(),
300 )?;
301
302 assert_destination_ownership_validity(
305 &auction_manager,
306 &metadata,
307 destination_info,
308 &destination,
309 &store,
310 args.creator_index,
311 )?;
312
313 if auction_manager.vault() != *vault_info.key {
315 return Err(MetaplexError::AuctionManagerVaultMismatch.into());
316 }
317
318 if auction_manager.auction() != *auction_info.key {
319 return Err(MetaplexError::AuctionManagerAuctionMismatch.into());
320 }
321
322 if safety_deposit.vault != *vault_info.key {
323 return Err(MetaplexError::SafetyDepositBoxVaultMismatch.into());
324 }
325
326 if metadata.mint != safety_deposit.token_mint {
328 if master_edition_info.data.borrow()[0]
329 == mpl_token_metadata::state::Key::MasterEditionV1 as u8
330 {
331 let master_edition: MasterEditionV1 =
333 MasterEditionV1::from_account_info(master_edition_info)?;
334 if master_edition.printing_mint != safety_deposit.token_mint
335 && master_edition.one_time_printing_authorization_mint != safety_deposit.token_mint
336 {
337 return Err(MetaplexError::SafetyDepositBoxMetadataMismatch.into());
338 }
339 } else {
340 return Err(MetaplexError::SafetyDepositBoxMetadataMismatch.into());
341 }
342 }
343
344 if auction_manager.accept_payment() != *accept_payment_info.key {
346 return Err(MetaplexError::AcceptPaymentMismatch.into());
347 }
348
349 if destination.mint != accept_payment.mint {
350 return Err(MetaplexError::AcceptPaymentMintMismatch.into());
351 }
352
353 let winning_config_index_key: String = match args.winning_config_index {
354 Some(val) => val.to_string(),
355 None => "participation".to_owned(),
356 };
357
358 let winning_config_item_index_key: String = match args.winning_config_item_index {
359 Some(val) => val.to_string(),
360 None => "0".to_owned(),
361 };
362
363 let creator_index_key: String = match args.creator_index {
364 Some(val) => val.to_string(),
365 None => "auctioneer".to_owned(),
366 };
367
368 let payout_bump = assert_derivation(
369 program_id,
370 payout_ticket_info,
371 &[
372 PREFIX.as_bytes(),
373 auction_manager_info.key.as_ref(),
374 winning_config_index_key.as_bytes(),
375 winning_config_item_index_key.as_bytes(),
376 creator_index_key.as_bytes(),
377 &safety_deposit_info.key.as_ref(),
378 &destination.owner.as_ref(),
379 ],
380 )?;
381
382 let payout_seeds = &[
383 PREFIX.as_bytes(),
384 auction_manager_info.key.as_ref(),
385 winning_config_index_key.as_bytes(),
386 winning_config_item_index_key.as_bytes(),
387 creator_index_key.as_bytes(),
388 &safety_deposit_info.key.as_ref(),
389 &destination.owner.as_ref(),
390 &[payout_bump],
391 ];
392
393 if payout_ticket_info.data_is_empty() {
394 create_or_allocate_account_raw(
395 *program_id,
396 payout_ticket_info,
397 rent_info,
398 system_info,
399 payer_info,
400 MAX_PAYOUT_TICKET_SIZE,
401 payout_seeds,
402 )?;
403 }
404
405 let mut payout_ticket = PayoutTicket::from_account_info(payout_ticket_info)?;
406 payout_ticket.recipient = destination.owner;
407 payout_ticket.key = Key::PayoutTicketV1;
408
409 let amount = calculate_owed_amount(
410 auction_token_tracker_info,
411 safety_deposit_config_info,
412 &auction_manager,
413 &auction,
414 &metadata,
415 &args.winning_config_index,
416 &args.winning_config_item_index,
417 &args.creator_index,
418 )?;
419
420 let final_amount = amount
421 .checked_sub(payout_ticket.amount_paid)
422 .ok_or(MetaplexError::NumericalOverflowError)?;
423
424 if final_amount > 0 {
425 payout_ticket.amount_paid = payout_ticket
426 .amount_paid
427 .checked_add(final_amount)
428 .ok_or(MetaplexError::NumericalOverflowError)?;
429
430 let auction_key = auction_manager.auction();
431
432 let bump_seed = assert_derivation(
433 program_id,
434 auction_manager_info,
435 &[PREFIX.as_bytes(), auction_key.as_ref()],
436 )?;
437
438 let authority_seeds = &[PREFIX.as_bytes(), auction_key.as_ref(), &[bump_seed]];
439
440 spl_token_transfer(
441 accept_payment_info.clone(),
442 destination_info.clone(),
443 final_amount,
444 auction_manager_info.clone(),
445 authority_seeds,
446 token_program_info.clone(),
447 )?;
448 }
449
450 payout_ticket.serialize(&mut *payout_ticket_info.data.borrow_mut())?;
451
452 Ok(())
453}