Skip to content
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
Original file line number Diff line number Diff line change
Expand Up @@ -17,4 +17,8 @@ cluster = "localnet"
wallet = "~/.config/solana/id.json"

[scripts]
test = "../node_modules/.bin/mocha --import=tsx -t 1000000 tests/**/*.ts"
# cargo test runs both the tx_hook.rs unit tests and the litesvm
# integration test - CI's Anchor workflow only runs this [scripts] test
# command (never a separate `cargo test`), so without this the Rust-side
# tests were never actually executed.
test = "cargo test -p abl-token && ../node_modules/.bin/mocha --import=tsx -t 1000000 tests/**/*.ts"
Original file line number Diff line number Diff line change
Expand Up @@ -34,6 +34,7 @@ spl-discriminator = "0.5.1"

[dev-dependencies]
litesvm = "0.11.0"
solana-account = "3.2.0"
solana-instruction = "3.0.0"
solana-keypair = "3.0.1"
solana-message = "3.1.0"
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -13,4 +13,7 @@ pub enum ABListError {

#[msg("Wallet blocked")]
WalletBlocked,

#[msg("Mint is not configured to use this transfer hook program")]
MintNotUsingThisHook,
}
Original file line number Diff line number Diff line change
Expand Up @@ -8,8 +8,8 @@ use anchor_spl::{
Token2022,
},
token_interface::{
spl_token_metadata_interface::state::Field, token_metadata_update_field,
Mint as MintAccount, TokenMetadataUpdateField,
spl_token_metadata_interface::state::Field, token_metadata_update_field, Mint as MintAccount,
TokenMetadataUpdateField,
},
};

Expand Down Expand Up @@ -50,11 +50,7 @@ impl ChangeMode<'_> {
token_metadata_update_field(cpi_ctx, Field::Key("AB".to_string()), args.mode.to_string())?;

if args.mode == Mode::Mixed || self.has_threshold()? {
let threshold = if args.mode == Mode::Mixed {
args.threshold
} else {
0
};
let threshold = if args.mode == Mode::Mixed { args.threshold } else { 0 };

let cpi_accounts = TokenMetadataUpdateField {
metadata: self.mint.to_account_info(),
Expand All @@ -64,11 +60,7 @@ impl ChangeMode<'_> {
let cpi_program = self.token_program.key();
let cpi_ctx = CpiContext::new(cpi_program, cpi_accounts);

token_metadata_update_field(
cpi_ctx,
Field::Key("threshold".to_string()),
threshold.to_string(),
)?;
token_metadata_update_field(cpi_ctx, Field::Key("threshold".to_string()), threshold.to_string())?;
}

let data = self.mint.to_account_info().data_len();
Expand All @@ -80,11 +72,7 @@ impl ChangeMode<'_> {
&self.mint.to_account_info().key(),
min_balance - self.mint.to_account_info().get_lamports(),
),
&[
self.authority.to_account_info(),
self.mint.to_account_info(),
self.system_program.to_account_info(),
],
&[self.authority.to_account_info(), self.mint.to_account_info(), self.system_program.to_account_info()],
)?;
}

Expand All @@ -96,11 +84,6 @@ impl ChangeMode<'_> {
let mint_data = mint_info.data.borrow();
let mint = StateWithExtensions::<Mint>::unpack(&mint_data)?;
let metadata = mint.get_variable_len_extension::<TokenMetadata>();
Ok(metadata.is_ok()
&& metadata
.unwrap()
.additional_metadata
.iter()
.any(|(key, _)| key == "threshold"))
Ok(metadata.is_ok() && metadata.unwrap().additional_metadata.iter().any(|(key, _)| key == "threshold"))
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -20,10 +20,7 @@ pub struct InitConfig<'info> {

impl InitConfig<'_> {
pub fn init_config(&mut self, config_bump: u8) -> Result<()> {
self.config.set_inner(Config {
authority: self.payer.key(),
bump: config_bump,
});
self.config.set_inner(Config { authority: self.payer.key(), bump: config_bump });

Ok(())
}
Expand Down
Original file line number Diff line number Diff line change
@@ -1,11 +1,9 @@
use anchor_lang::{
prelude::*, solana_program::program::invoke, solana_program::system_instruction::transfer,
};
use anchor_lang::{prelude::*, solana_program::program::invoke, solana_program::system_instruction::transfer};
use anchor_spl::{
token_2022::Token2022,
token_interface::{
spl_token_metadata_interface::state::Field, token_metadata_initialize,
token_metadata_update_field, Mint, TokenMetadataInitialize, TokenMetadataUpdateField,
spl_token_metadata_interface::state::Field, token_metadata_initialize, token_metadata_update_field, Mint,
TokenMetadataInitialize, TokenMetadataUpdateField,
},
};

Expand Down Expand Up @@ -80,11 +78,7 @@ impl InitMint<'_> {
};
let cpi_ctx = CpiContext::new(self.token_program.key(), cpi_accounts);

token_metadata_update_field(
cpi_ctx,
Field::Key("threshold".to_string()),
args.threshold.to_string(),
)?;
token_metadata_update_field(cpi_ctx, Field::Key("threshold".to_string()), args.threshold.to_string())?;
}

let data = self.mint.to_account_info().data_len();
Expand All @@ -96,11 +90,7 @@ impl InitMint<'_> {
&self.mint.to_account_info().key(),
min_balance - self.mint.to_account_info().get_lamports(),
),
&[
self.payer.to_account_info(),
self.mint.to_account_info(),
self.system_program.to_account_info(),
],
&[self.payer.to_account_info(), self.mint.to_account_info(), self.system_program.to_account_info()],
)?;
}

Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -4,6 +4,7 @@ pub mod init_config;
pub mod init_mint;
pub mod init_wallet;
pub mod remove_wallet;
pub mod resize_meta_list;
pub mod tx_hook;

pub use attach_to_mint::*;
Expand All @@ -12,4 +13,5 @@ pub use init_config::*;
pub use init_mint::*;
pub use init_wallet::*;
pub use remove_wallet::*;
pub use resize_meta_list::*;
pub use tx_hook::*;
Original file line number Diff line number Diff line change
@@ -0,0 +1,76 @@
use anchor_lang::{prelude::*, solana_program::program::invoke, solana_program::system_instruction::transfer};
use anchor_spl::{
token_2022::{
spl_token_2022::{
extension::{transfer_hook, StateWithExtensions},
state::Mint as MintState,
},
Token2022,
},
token_interface::Mint,
};

use spl_tlv_account_resolution::state::ExtraAccountMetaList;
use spl_transfer_hook_interface::instruction::ExecuteInstruction;

use crate::{get_extra_account_metas, get_meta_list_size, ABListError, META_LIST_ACCOUNT_SEED};

/// Rewrites an existing mint's extra-metas account to the current
/// `get_extra_account_metas()` layout, reallocating it if the size changed.
/// Permissionless: the content is fully determined by `mint` and this
/// program's own fixed extra-account list, so nothing needs a signature -
/// gating on the transfer-hook authority would strand mints whose authority
/// was revoked.
#[derive(Accounts)]
pub struct ResizeMetaList<'info> {
#[account(mut)]
pub payer: Signer<'info>,

#[account(mint::token_program = token_program)]
pub mint: Box<InterfaceAccount<'info, Mint>>,

#[account(
mut,
seeds = [META_LIST_ACCOUNT_SEED, mint.key().as_ref()],
bump,
)]
/// CHECK: extra metas account
pub extra_metas_account: UncheckedAccount<'info>,

pub system_program: Program<'info, System>,

pub token_program: Program<'info, Token2022>,
}

impl ResizeMetaList<'_> {
pub fn resize_meta_list(&mut self) -> Result<()> {
// Confirm the mint is actually configured to use this hook program -
// read directly from its TransferHook extension, no CPI needed.
let configured_program_id = {
let mint_info = self.mint.to_account_info();
let mint_data = mint_info.data.borrow();
let mint_state = StateWithExtensions::<MintState>::unpack(&mint_data)?;
transfer_hook::get_program_id(&mint_state)
};
require!(configured_program_id == Some(crate::ID_CONST), ABListError::MintNotUsingThisHook);

let account_info = self.extra_metas_account.to_account_info();
let new_size = get_meta_list_size()?;

let min_balance = Rent::get()?.minimum_balance(new_size);
if min_balance > account_info.lamports() {
invoke(
&transfer(&self.payer.key(), account_info.key, min_balance - account_info.lamports()),
&[self.payer.to_account_info(), account_info.clone(), self.system_program.to_account_info()],
)?;
}
account_info.resize(new_size)?;

let metas = get_extra_account_metas()?;
let mut data = account_info.try_borrow_mut_data()?;
ExtraAccountMetaList::update::<ExecuteInstruction>(&mut data, &metas)
.map_err(|_| ProgramError::InvalidAccountData)?;

Ok(())
}
}
Loading
Loading