#![allow(clippy::result_large_err)]
use anchor_lang::{
prelude::*,
solana_program::{program::invoke, system_instruction, system_program},
};
use anchor_spl::{associated_token::AssociatedToken, token_interface::TokenInterface};
use mpl_token_metadata::types::TokenStandard;
use solana_program::pubkey;
use std::slice::Iter;
use tensor_vipers::prelude::*;
use crate::TensorError;
pub const HUNDRED_PCT_BPS: u64 = 10000;
pub const HUNDRED_PCT: u64 = 100;
pub const GAMESHIFT_FEE_BPS: u64 = 200;
pub const GAMESHIFT_BROKER_PCT: u64 = 50; pub const BROKER_FEE_PCT: u64 = 50;
pub const TNSR_DISCOUNT_BP: u64 = 2500;
pub const SINGLETONS: [Pubkey; 3] = [
escrow::TSWAP_SINGLETON,
marketplace::TCOMP_SINGLETON,
price_lock::TLOCK_SINGLETON,
];
pub mod gameshift {
use anchor_lang::declare_id;
declare_id!("3g2nyraTXqEKke3sTtZw9JtfjCo8Hzw6qhKe8K2hrYuf");
}
pub mod escrow {
use super::*;
declare_id!("TSWAPaqyCSx2KABk68Shruf4rp7CxcNi8hAsbdwmHbN");
pub const TSWAP_SINGLETON: Pubkey = pubkey!("4zdNGgAtFsW1cQgHqkiWyRsxaAgxrSRRynnuunxzjxue");
}
pub mod fees {
use super::*;
declare_id!("TFEEgwDP6nn1s8mMX2tTNPPz8j2VomkphLUmyxKm17A");
}
pub mod marketplace {
use super::*;
declare_id!("TCMPhJdwDryooaGtiocG1u3xcYbRpiJzb283XfCZsDp");
pub const TCOMP_SINGLETON: Pubkey = pubkey!("q4s8z5dRAt2fKC2tLthBPatakZRXPMx1LfacckSXd4f");
}
pub mod mpl_token_auth_rules {
use super::*;
declare_id!("auth9SigNpDKz4sJJ1DfCTuZrZNSAgh9sFD3rboVmgg");
}
pub mod price_lock {
use super::*;
declare_id!("TLoCKic2wGJm7VhZKumih4Lc35fUhYqVMgA4j389Buk");
pub const TLOCK_SINGLETON: Pubkey = pubkey!("CdXA5Vpg4hqvsmLSKC2cygnJVvsQTrDrrn428nAZQaKz");
}
#[macro_export]
macro_rules! shard_num {
($account_info:expr) => {
&$account_info.key().as_ref()[31].to_le_bytes()
};
($pubkey:expr) => {
&$pubkey.as_ref()[31].to_le_bytes()
};
}
pub struct CalcFeesArgs {
pub amount: u64,
pub total_fee_bps: u64,
pub broker_fee_pct: u64,
pub maker_broker_pct: u64,
pub tnsr_discount: bool,
}
pub fn calc_fees(args: CalcFeesArgs) -> Result<(u64, u64, u64)> {
let CalcFeesArgs {
amount,
total_fee_bps,
broker_fee_pct,
maker_broker_pct,
tnsr_discount,
} = args;
let total_fee_bps = if tnsr_discount {
unwrap_checked!({
total_fee_bps
.checked_mul(HUNDRED_PCT_BPS - TNSR_DISCOUNT_BP)?
.checked_div(HUNDRED_PCT_BPS)
})
} else {
total_fee_bps
};
let total_fee = unwrap_checked!({
(amount)
.checked_mul(total_fee_bps)?
.checked_div(HUNDRED_PCT_BPS)
});
let broker_fees = unwrap_checked!({
total_fee
.checked_mul(broker_fee_pct)?
.checked_div(HUNDRED_PCT)
});
let protocol_fee = unwrap_checked!({ total_fee.checked_sub(broker_fees) });
let maker_broker_fee = unwrap_checked!({
broker_fees
.checked_mul(maker_broker_pct)?
.checked_div(HUNDRED_PCT)
});
let taker_broker_fee = unwrap_int!(broker_fees.checked_sub(maker_broker_fee));
Ok((protocol_fee, maker_broker_fee, taker_broker_fee))
}
pub fn calc_creators_fee(
seller_fee_basis_points: u16,
amount: u64,
token_standard: Option<TokenStandard>,
optional_royalty_pct: Option<u16>,
) -> Result<u64> {
let adj_optional_royalty_pct = match token_standard {
Some(TokenStandard::ProgrammableNonFungible)
| Some(TokenStandard::ProgrammableNonFungibleEdition) => Some(100),
_ => optional_royalty_pct,
};
let creator_fee_bps = if let Some(royalty_pct) = adj_optional_royalty_pct {
require!(royalty_pct <= 100, TensorError::BadRoyaltiesPct);
unwrap_checked!({
(seller_fee_basis_points as u64)
.checked_mul(royalty_pct as u64)?
.checked_div(100_u64)
})
} else {
0_u64
};
let fee = unwrap_checked!({
creator_fee_bps
.checked_mul(amount)?
.checked_div(HUNDRED_PCT_BPS)
});
Ok(fee)
}
pub fn transfer_all_lamports_from_pda<'info>(
from_pda: &AccountInfo<'info>,
to: &AccountInfo<'info>,
) -> Result<()> {
let rent = Rent::get()?.minimum_balance(from_pda.data_len());
let to_move = unwrap_int!(from_pda.lamports().checked_sub(rent));
transfer_lamports_from_pda(from_pda, to, to_move)
}
pub fn transfer_lamports_from_pda<'info>(
from_pda: &AccountInfo<'info>,
to: &AccountInfo<'info>,
lamports: u64,
) -> Result<()> {
let remaining_pda_lamports = unwrap_int!(from_pda.lamports().checked_sub(lamports));
let rent = Rent::get()?.minimum_balance(from_pda.data_len());
require!(
remaining_pda_lamports >= rent,
TensorError::InsufficientBalance
);
**from_pda.try_borrow_mut_lamports()? = remaining_pda_lamports;
let new_to = unwrap_int!(to.lamports.borrow().checked_add(lamports));
**to.lamports.borrow_mut() = new_to;
Ok(())
}
pub struct FromExternal<'b, 'info> {
pub from: &'b AccountInfo<'info>,
pub sys_prog: &'b AccountInfo<'info>,
}
pub enum FromAcc<'a, 'info> {
Pda(&'a AccountInfo<'info>),
External(&'a FromExternal<'a, 'info>),
}
#[derive(AnchorSerialize, AnchorDeserialize, PartialEq, Eq, Debug, Clone)]
pub struct TCreator {
pub address: Pubkey,
pub verified: bool,
pub share: u8,
}
impl From<TCreator> for mpl_token_metadata::types::Creator {
fn from(creator: TCreator) -> Self {
mpl_token_metadata::types::Creator {
address: creator.address,
verified: creator.verified,
share: creator.share,
}
}
}
impl From<mpl_token_metadata::types::Creator> for TCreator {
fn from(creator: mpl_token_metadata::types::Creator) -> Self {
TCreator {
address: creator.address,
verified: creator.verified,
share: creator.share,
}
}
}
#[cfg(feature = "mpl-core")]
impl From<mpl_core::types::Creator> for TCreator {
fn from(creator: mpl_core::types::Creator) -> Self {
TCreator {
address: creator.address,
share: creator.percentage,
verified: false,
}
}
}
#[repr(u8)]
pub enum CreatorFeeMode<'a, 'info> {
Sol {
from: &'a FromAcc<'a, 'info>,
},
Spl {
associated_token_program: &'a Program<'info, AssociatedToken>,
token_program: &'a Interface<'info, TokenInterface>,
system_program: &'a Program<'info, System>,
currency: &'a AccountInfo<'info>,
from: &'a AccountInfo<'info>,
from_token_acc: &'a AccountInfo<'info>,
rent_payer: &'a AccountInfo<'info>,
},
}
pub fn transfer_creators_fee<'a, 'info>(
creators: &'a Vec<TCreator>,
creator_accounts: &mut Iter<AccountInfo<'info>>,
creator_fee: u64,
mode: &'a CreatorFeeMode<'a, 'info>,
) -> Result<u64> {
let mut remaining_fee = creator_fee;
for creator in creators {
let current_creator_info = next_account_info(creator_accounts)?;
require!(
creator.address.eq(current_creator_info.key),
TensorError::CreatorMismatch
);
let pct = creator.share as u64;
let creator_fee = unwrap_checked!({ pct.checked_mul(creator_fee)?.checked_div(100) });
match mode {
CreatorFeeMode::Sol { from: _ } => {
let rent = Rent::get()?.minimum_balance(current_creator_info.data_len());
if unwrap_int!(current_creator_info.lamports().checked_add(creator_fee)) < rent {
continue;
}
}
CreatorFeeMode::Spl {
associated_token_program: _,
token_program: _,
system_program: _,
currency: _,
from: _,
from_token_acc: _,
rent_payer: _,
} => {}
}
remaining_fee = unwrap_int!(remaining_fee.checked_sub(creator_fee));
if creator_fee > 0 {
match mode {
CreatorFeeMode::Sol { from } => match from {
FromAcc::Pda(from_pda) => {
transfer_lamports_from_pda(from_pda, current_creator_info, creator_fee)?;
}
FromAcc::External(from_ext) => {
let FromExternal { from, sys_prog } = from_ext;
invoke(
&system_instruction::transfer(
from.key,
current_creator_info.key,
creator_fee,
),
&[
(*from).clone(),
current_creator_info.clone(),
(*sys_prog).clone(),
],
)?;
}
},
CreatorFeeMode::Spl {
associated_token_program,
token_program,
system_program,
currency,
from,
from_token_acc: from_ata,
rent_payer,
} => {
let current_creator_ata_info = next_account_info(creator_accounts)?;
anchor_spl::associated_token::create_idempotent(CpiContext::new(
associated_token_program.to_account_info(),
anchor_spl::associated_token::Create {
payer: rent_payer.to_account_info(),
associated_token: current_creator_ata_info.to_account_info(),
authority: current_creator_info.to_account_info(),
mint: currency.to_account_info(),
system_program: system_program.to_account_info(),
token_program: token_program.to_account_info(),
},
))?;
anchor_spl::token::transfer(
CpiContext::new(
token_program.to_account_info(),
anchor_spl::token::Transfer {
from: from_ata.to_account_info(),
to: current_creator_ata_info.to_account_info(),
authority: from.to_account_info(),
},
),
creator_fee,
)?;
}
}
}
}
Ok(unwrap_int!(creator_fee.checked_sub(remaining_fee)))
}
pub fn close_account(
pda_to_close: &mut AccountInfo,
sol_destination: &mut AccountInfo,
) -> Result<()> {
let dest_starting_lamports = sol_destination.lamports();
**sol_destination.lamports.borrow_mut() =
unwrap_int!(dest_starting_lamports.checked_add(pda_to_close.lamports()));
**pda_to_close.lamports.borrow_mut() = 0;
pda_to_close.assign(&system_program::ID);
pda_to_close.realloc(0, false).map_err(Into::into)
}
pub fn transfer_lamports<'info>(
from: &AccountInfo<'info>,
to: &AccountInfo<'info>,
lamports: u64,
) -> Result<()> {
if from.data_is_empty() && from.owner == &system_program::ID {
invoke(
&system_instruction::transfer(from.key, to.key, lamports),
&[from.clone(), to.clone()],
)
.map_err(Into::into)
} else {
transfer_lamports_from_pda(from, to, lamports)
}
}
pub fn transfer_lamports_checked<'info, 'b>(
from: &'b AccountInfo<'info>,
to: &'b AccountInfo<'info>,
lamports: u64,
) -> Result<()> {
let rent = Rent::get()?.minimum_balance(to.data_len());
if unwrap_int!(to.lamports().checked_add(lamports)) < rent {
msg!(
"Skipping transfer to {}: account would not be rent exempt",
to.key
);
Ok(())
} else {
transfer_lamports(from, to, lamports)
}
}
pub fn assert_fee_account(fee_vault_info: &AccountInfo, state_info: &AccountInfo) -> Result<()> {
let expected_fee_vault = Pubkey::find_program_address(
&[
b"fee_vault",
shard_num!(state_info),
],
&fees::ID,
)
.0;
require!(
fee_vault_info.key == &expected_fee_vault || SINGLETONS.contains(fee_vault_info.key),
TensorError::InvalidFeeAccount
);
Ok(())
}