spl_token_upgrade/
processor.rs

1//! Program state processor
2
3use {
4    crate::{
5        collect_token_upgrade_authority_signer_seeds, error::TokenUpgradeError,
6        get_token_upgrade_authority_address_and_bump_seed, instruction::TokenUpgradeInstruction,
7    },
8    solana_program::{
9        account_info::{next_account_info, AccountInfo},
10        entrypoint::ProgramResult,
11        msg,
12        program::{invoke, invoke_signed},
13        program_error::ProgramError,
14        pubkey::Pubkey,
15    },
16    spl_token_2022::{
17        extension::StateWithExtensions,
18        instruction::decode_instruction_type,
19        state::{Account, Mint},
20    },
21};
22
23fn check_owner(account_info: &AccountInfo, expected_owner: &Pubkey) -> ProgramResult {
24    if account_info.owner != expected_owner {
25        Err(ProgramError::IllegalOwner)
26    } else {
27        Ok(())
28    }
29}
30
31fn burn_original_tokens<'a>(
32    original_token_program: AccountInfo<'a>,
33    source: AccountInfo<'a>,
34    mint: AccountInfo<'a>,
35    authority: AccountInfo<'a>,
36    multisig_signers: &[AccountInfo<'a>],
37    amount: u64,
38    decimals: u8,
39) -> ProgramResult {
40    let multisig_pubkeys = multisig_signers.iter().map(|s| s.key).collect::<Vec<_>>();
41    let ix = spl_token_2022::instruction::burn_checked(
42        original_token_program.key,
43        source.key,
44        mint.key,
45        authority.key,
46        &multisig_pubkeys,
47        amount,
48        decimals,
49    )?;
50    let mut account_infos = vec![source, mint, authority];
51    account_infos.extend_from_slice(multisig_signers);
52    invoke(&ix, &account_infos)
53}
54
55#[allow(clippy::too_many_arguments)]
56fn transfer_new_tokens<'a>(
57    new_token_program: AccountInfo<'a>,
58    source: AccountInfo<'a>,
59    mint: AccountInfo<'a>,
60    destination: AccountInfo<'a>,
61    authority: AccountInfo<'a>,
62    authority_seeds: &[&[u8]],
63    amount: u64,
64    decimals: u8,
65) -> Result<(), ProgramError> {
66    let ix = spl_token_2022::instruction::transfer_checked(
67        new_token_program.key,
68        source.key,
69        mint.key,
70        destination.key,
71        authority.key,
72        &[],
73        amount,
74        decimals,
75    )?;
76    invoke_signed(
77        &ix,
78        &[source, mint, destination, authority],
79        &[authority_seeds],
80    )
81}
82
83fn process_exchange(program_id: &Pubkey, accounts: &[AccountInfo]) -> ProgramResult {
84    let account_info_iter = &mut accounts.iter();
85
86    let original_account_info = next_account_info(account_info_iter)?;
87    let original_mint_info = next_account_info(account_info_iter)?;
88    let new_escrow_info = next_account_info(account_info_iter)?;
89    let new_account_info = next_account_info(account_info_iter)?;
90    let new_mint_info = next_account_info(account_info_iter)?;
91    let new_transfer_authority_info = next_account_info(account_info_iter)?;
92    let original_token_program = next_account_info(account_info_iter)?;
93    let new_token_program = next_account_info(account_info_iter)?;
94    let original_transfer_authority_info = next_account_info(account_info_iter)?;
95
96    // owner checks
97    check_owner(original_account_info, original_token_program.key)?;
98    check_owner(original_mint_info, original_token_program.key)?;
99    check_owner(new_escrow_info, new_token_program.key)?;
100    check_owner(new_account_info, new_token_program.key)?;
101    check_owner(new_mint_info, new_token_program.key)?;
102
103    // PDA derivation check
104    let (expected_escrow_authority, bump_seed) = get_token_upgrade_authority_address_and_bump_seed(
105        original_mint_info.key,
106        new_mint_info.key,
107        program_id,
108    );
109    let bump_seed = [bump_seed];
110    let authority_seeds = collect_token_upgrade_authority_signer_seeds(
111        original_mint_info.key,
112        new_mint_info.key,
113        &bump_seed,
114    );
115    if expected_escrow_authority != *new_transfer_authority_info.key {
116        msg!(
117            "Expected escrow authority {}, received {}",
118            &expected_escrow_authority,
119            new_transfer_authority_info.key
120        );
121        return Err(TokenUpgradeError::InvalidOwner.into());
122    }
123
124    // pull out these values in a block to drop all data before performing CPIs
125    let (token_amount, decimals) = {
126        // check mints are actually mints
127        let original_mint_data = original_mint_info.try_borrow_data()?;
128        let original_mint = StateWithExtensions::<Mint>::unpack(&original_mint_data)?;
129        let new_mint_data = new_mint_info.try_borrow_data()?;
130        let new_mint = StateWithExtensions::<Mint>::unpack(&new_mint_data)?;
131
132        // check accounts are actually accounts
133        let original_account_data = original_account_info.try_borrow_data()?;
134        let original_account = StateWithExtensions::<Account>::unpack(&original_account_data)?;
135        let new_escrow_data = new_escrow_info.try_borrow_data()?;
136        let new_escrow = StateWithExtensions::<Account>::unpack(&new_escrow_data)?;
137        let new_account_data = new_account_info.try_borrow_data()?;
138        let _ = StateWithExtensions::<Account>::unpack(&new_account_data)?;
139
140        let token_amount = original_account.base.amount;
141        if new_escrow.base.amount < token_amount {
142            msg!(
143                "Escrow only has {} tokens, needs at least {}",
144                new_escrow.base.amount,
145                token_amount
146            );
147            return Err(ProgramError::InsufficientFunds);
148        }
149        if original_mint.base.decimals != new_mint.base.decimals {
150            msg!(
151                "Original and new token mint decimals mismatch: original has {} decimals, and new has {}",
152                original_mint.base.decimals,
153                new_mint.base.decimals,
154            );
155            return Err(TokenUpgradeError::DecimalsMismatch.into());
156        }
157
158        (original_account.base.amount, original_mint.base.decimals)
159    };
160
161    burn_original_tokens(
162        original_token_program.clone(),
163        original_account_info.clone(),
164        original_mint_info.clone(),
165        original_transfer_authority_info.clone(),
166        account_info_iter.as_slice(),
167        token_amount,
168        decimals,
169    )?;
170
171    transfer_new_tokens(
172        new_token_program.clone(),
173        new_escrow_info.clone(),
174        new_mint_info.clone(),
175        new_account_info.clone(),
176        new_transfer_authority_info.clone(),
177        &authority_seeds,
178        token_amount,
179        decimals,
180    )?;
181
182    Ok(())
183}
184
185/// Instruction processor
186pub fn process(program_id: &Pubkey, accounts: &[AccountInfo], input: &[u8]) -> ProgramResult {
187    match decode_instruction_type(input)? {
188        TokenUpgradeInstruction::Exchange => process_exchange(program_id, accounts),
189    }
190}