Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Update fee destination address #149

Merged
merged 2 commits into from
Nov 1, 2024
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
2 changes: 1 addition & 1 deletion clients/js/test/close/fungible.test.ts
Original file line number Diff line number Diff line change
Expand Up @@ -22,7 +22,7 @@ import {
} from '../../src';

const closeDestination = publicKey(
'Levytx9LLPzAtDJJD7q813Zsm8zg9e1pb53mGxTKpD7'
'GxCXYtrnaU6JXeAza8Ugn4EE6QiFinpfn8t3Lo4UkBDX'
);

test.skip('it can close ownerless metadata for a fungible with zero supply and no mint authority', async (t) => {
Expand Down
2 changes: 1 addition & 1 deletion clients/js/test/close/nonFungible.test.ts
Original file line number Diff line number Diff line change
Expand Up @@ -20,7 +20,7 @@ import {
} from '../../src';

const closeDestination = publicKey(
'Levytx9LLPzAtDJJD7q813Zsm8zg9e1pb53mGxTKpD7'
'GxCXYtrnaU6JXeAza8Ugn4EE6QiFinpfn8t3Lo4UkBDX'
);

test.skip('it can close ownerless metadata for a non-fungible with zero supply', async (t) => {
Expand Down
1 change: 0 additions & 1 deletion programs/token-metadata/program/src/processor/close/mod.rs
Original file line number Diff line number Diff line change
Expand Up @@ -34,7 +34,6 @@ pub(crate) fn process_close_accounts<'a>(
}

// Assert the correct destination is set.
// TODO: This should be replaced by destination address.
if *ctx.accounts.destination_info.key != OWNERLESS_CLOSE_DESTINATION {
return Err(MetadataError::InvalidFeeAccount.into());
}
Expand Down
9 changes: 8 additions & 1 deletion programs/token-metadata/program/src/processor/fee/mod.rs
Original file line number Diff line number Diff line change
Expand Up @@ -3,7 +3,10 @@ use num_traits::FromPrimitive;
use solana_program::{account_info::next_account_info, rent::Rent, system_program, sysvar::Sysvar};

use super::*;
use crate::{state::fee::FEE_AUTHORITY, utils::fee::clear_fee_flag};
use crate::{
state::fee::{FEE_AUTHORITY, FEE_DESTINATION},
utils::fee::clear_fee_flag,
};

pub(crate) fn process_collect_fees(program_id: &Pubkey, accounts: &[AccountInfo]) -> ProgramResult {
let account_info_iter = &mut accounts.iter();
Expand All @@ -18,6 +21,10 @@ pub(crate) fn process_collect_fees(program_id: &Pubkey, accounts: &[AccountInfo]

let recipient_info = next_account_info(account_info_iter)?;

if *recipient_info.key != FEE_DESTINATION {
return Err(MetadataError::InvalidFeeAccount.into());
}

for account_info in account_info_iter {
if account_info.owner != program_id {
return Err(MetadataError::InvalidFeeAccount.into());
Expand Down
5 changes: 3 additions & 2 deletions programs/token-metadata/program/src/state/fee.rs
Original file line number Diff line number Diff line change
Expand Up @@ -2,10 +2,11 @@ use super::*;
use solana_program::{rent::Rent, sysvar::Sysvar};

pub(crate) const FEE_AUTHORITY: Pubkey = pubkey!("Levytx9LLPzAtDJJD7q813Zsm8zg9e1pb53mGxTKpD7");
pub const FEE_DESTINATION: Pubkey = pubkey!("2fb1TjRrJQLy9BkYfBjcYgibV7LUsr9cf6QxvyRZyuXn");
pub(crate) const OWNERLESS_CLOSE_AUTHORITY: Pubkey =
pubkey!("C1oseLQExhuEzeBhsVbLtseSpVgvpHDbBj3PTevBCEBh");
pub(crate) const OWNERLESS_CLOSE_DESTINATION: Pubkey =
pubkey!("E4ZJX8hYhz5tDbFsUo1DinxHqt33aUsFQpe8dYjASm2F");
pub const OWNERLESS_CLOSE_DESTINATION: Pubkey =
pubkey!("GxCXYtrnaU6JXeAza8Ugn4EE6QiFinpfn8t3Lo4UkBDX");
pub(crate) const RESIZE_AUTHORITY: Pubkey = pubkey!("ResizebfwTEZTLbHbctTByvXYECKTJQXnMWG8g9XLix");
pub(crate) const RESIZE_DESTINATION: Pubkey =
pubkey!("46mjNQBwXLCDCM7YiDQSPVdNZ4dLdZf79tTPRkT1wkF6");
Expand Down
112 changes: 93 additions & 19 deletions programs/token-metadata/program/tests/fees.rs
Original file line number Diff line number Diff line change
Expand Up @@ -5,19 +5,43 @@ use solana_program_test::*;
use utils::*;

mod fees {
use num_traits::FromPrimitive;
use solana_program::{native_token::LAMPORTS_PER_SOL, pubkey::Pubkey};
use solana_sdk::{
instruction::InstructionError,
pubkey,
signature::{read_keypair_file, Keypair},
signer::Signer,
transaction::Transaction,
transaction::TransactionError,
};
use token_metadata::{
error::MetadataError,
instruction::{collect_fees, BurnArgs, UpdateArgs},
state::{FEE_FLAG_CLEARED, METADATA_FEE_FLAG_OFFSET},
state::{
FEE_DESTINATION, FEE_FLAG_CLEARED, FEE_FLAG_SET, METADATA_FEE_FLAG_OFFSET,
OWNERLESS_CLOSE_DESTINATION,
},
};

use super::*;

#[tokio::test]
async fn fee_manager_pdas_are_correct() {
let fee_manager = pubkey!("mgrfTeJh5VgHt67LQQVZ7U2gPY88En94QMWz64cV2AY");

// Fee destination is correct PDA of the fee-manager program.
let (derived_fee_dest, _) =
Pubkey::find_program_address(&["fee_manager_treasury".as_bytes()], &fee_manager);
assert_eq!(derived_fee_dest, FEE_DESTINATION);

// Ownerless close destination is correct PDA of the fee-manager program.

let (derived_ownerless_close_dest, _) =
Pubkey::find_program_address(&["fee_manager_close_treasury".as_bytes()], &fee_manager);
assert_eq!(derived_ownerless_close_dest, OWNERLESS_CLOSE_DESTINATION);
}

#[tokio::test]
async fn charge_create_metadata_v3() {
let mut context = program_test().start_with_context().await;
Expand Down Expand Up @@ -94,16 +118,15 @@ mod fees {

let authority_funding = 10 * LAMPORTS_PER_SOL;

let authority =
read_keypair_file("/media/veracrypt1/Levytx9LLPzAtDJJD7q813Zsm8zg9e1pb53mGxTKpD7.json")
.unwrap();
let authority = read_keypair_file(
"/home/danenbm/keypairs/Levytx9LLPzAtDJJD7q813Zsm8zg9e1pb53mGxTKpD7.json",
)
.unwrap();
authority
.airdrop(&mut context, authority_funding)
.await
.unwrap();

let recipient = Keypair::new();

let num_accounts = 25;

let mut nfts = vec![];
Expand All @@ -122,7 +145,7 @@ mod fees {

let fee_accounts: Vec<Pubkey> = nfts.iter().map(|nft| nft.metadata).collect();

let ix = collect_fees(recipient.pubkey(), fee_accounts.clone());
let ix = collect_fees(FEE_DESTINATION, fee_accounts.clone());
let tx = Transaction::new_signed_with_payer(
&[ix],
Some(&authority.pubkey()),
Expand All @@ -134,9 +157,7 @@ mod fees {

let expected_balance = num_accounts * SOLANA_CREATE_FEE;

let recipient_balance = get_account(&mut context, &recipient.pubkey())
.await
.lamports;
let recipient_balance = get_account(&mut context, &FEE_DESTINATION).await.lamports;

assert_eq!(recipient_balance, expected_balance);

Expand All @@ -162,16 +183,15 @@ mod fees {

let fee_authority_funding = LAMPORTS_PER_SOL;

let fee_authority =
read_keypair_file("/media/veracrypt1/Levytx9LLPzAtDJJD7q813Zsm8zg9e1pb53mGxTKpD7.json")
.unwrap();
let fee_authority = read_keypair_file(
"/home/danenbm/keypairs/Levytx9LLPzAtDJJD7q813Zsm8zg9e1pb53mGxTKpD7.json",
)
.unwrap();
fee_authority
.airdrop(&mut context, fee_authority_funding)
.await
.unwrap();

let recipient = Keypair::new();

let mut nft = DigitalAsset::new();
nft.create_and_mint(
&mut context,
Expand All @@ -197,7 +217,7 @@ mod fees {
.await
.unwrap();

let ix = collect_fees(recipient.pubkey(), vec![nft.metadata]);
let ix = collect_fees(FEE_DESTINATION, vec![nft.metadata]);
let tx = Transaction::new_signed_with_payer(
&[ix],
Some(&fee_authority.pubkey()),
Expand All @@ -208,10 +228,64 @@ mod fees {

let expected_balance = SOLANA_CREATE_FEE;

let recipient_balance = get_account(&mut context, &recipient.pubkey())
.await
.lamports;
let recipient_balance = get_account(&mut context, &FEE_DESTINATION).await.lamports;

assert_eq!(recipient_balance, expected_balance);
}

#[test_case::test_case(spl_token::id() ; "Token Program")]
#[test_case::test_case(spl_token_2022::id() ; "Token-2022 Program")]
#[tokio::test]
// Used for local QA testing and requires a keypair so excluded from CI.
#[ignore]
async fn cannot_collect_fees_using_wrong_fee_destination(spl_token_program: Pubkey) {
// Create NFTs and then collect the fees from the metadata accounts.
let mut context = program_test().start_with_context().await;

let authority_funding = 10 * LAMPORTS_PER_SOL;

let authority = read_keypair_file(
"/home/danenbm/keypairs/Levytx9LLPzAtDJJD7q813Zsm8zg9e1pb53mGxTKpD7.json",
)
.unwrap();
authority
.airdrop(&mut context, authority_funding)
.await
.unwrap();

// Fee destination is meant to be a specific PDA of the fee-manager program.
let wrong_fee_destination = Keypair::new().pubkey();

let mut nft = DigitalAsset::new();
nft.create(
&mut context,
token_metadata::state::TokenStandard::NonFungible,
None,
spl_token_program,
)
.await
.unwrap();

let before_lamports = get_account(&mut context, &nft.metadata).await.lamports;

let ix = collect_fees(wrong_fee_destination, vec![nft.metadata]);
let tx = Transaction::new_signed_with_payer(
&[ix],
Some(&authority.pubkey()),
&[&authority],
context.last_blockhash,
);
let err = context
.banks_client
.process_transaction(tx)
.await
.unwrap_err();

assert_custom_error!(err, MetadataError::InvalidFeeAccount);

let account = get_account(&mut context, &nft.metadata).await;
let last_byte = account.data.len() - METADATA_FEE_FLAG_OFFSET;
assert_eq!(account.data[last_byte], FEE_FLAG_SET);
assert_eq!(account.lamports, before_lamports);
}
}
Loading