Smart contract unable to operate due to lack of token funds
Description
Bug Description
From the competition page we can know the vault program should support SPL token 2022:
The Vault and Restaking programs support the SPL Token and SPL Token 2022 standards.
But when calling process_mint() instruction, the token program id is hardcoded to be spl_token and not spl_token_2022. The spl token and spl token 2022 have different program id, the SPL token program id is TokenkegQfeZyiNwAJbNbGKPFXCWuBvf9Ss623VQ5DA and SPL token 2022 program id is TokenzQdBNbLqP5VEhdkAS6EPFLC1PHnBqCXEpPxuEb, you can see the decalre for spl_token program id and spl_token_2022 program id.
pub fn process_mint(
program_id: &Pubkey,
accounts: &[AccountInfo],
amount_in: u64,
min_amount_out: u64,
) -> ProgramResult {
...
// transfer tokens from depositor to vault
{
invoke(
&transfer(
// @audit - hardcoded spl token program id
&spl_token::id(),
depositor_token_account.key,
vault_token_account.key,
depositor.key,
&[],
amount_in,
)?,
&[
depositor_token_account.clone(),
vault_token_account.clone(),
depositor.clone(),
],
)?;
}
...
}
If the users transfer SPL token2022 to the vault by calling the process_mint() instruction, the transfer will fail because wrong token program id.
And in the process_initialize_vault() instruction, the token program id is loaded by load_token_program(), this function will check the token program id is spl_token or not, if not, it will return an error:
pub fn load_token_program(info: &AccountInfo) -> Result<(), ProgramError> {
if info.key.ne(&spl_token::id()) {
msg!("Account is not the spl token program");
return Err(ProgramError::IncorrectProgramId);
}
Ok(())
}
So the vault program doesn't support SPL token2022 init.
Impact
The vault program doesn't support SPL token 2022 init and transfer.
Recommendation
Add the SPL token2022 feature to the vault program.
Proof of Concept
Proof of Concept
Place the case in the vault_program/src/ path, run it by cargo test --package jito-vault-program --lib -- test_token2022_basic:
use solana_program::{
pubkey::Pubkey,
system_instruction,
};
use spl_token_2022::{
instruction::{initialize_mint, initialize_account, mint_to, transfer_checked},
state::{Mint, Account},
extension::{ExtensionType, StateWithExtensions},
ID as TOKEN_2022_ID,
};
use solana_program_test::*;
use solana_sdk::{
signature::Keypair,
signer::Signer,
transaction::Transaction,
};
#[tokio::test]
async fn test_token2022_basic_transfer() {
let mut program_test = ProgramTest::new(
"spl_token_2022",
TOKEN_2022_ID,
processor!(spl_token_2022::processor::Processor::process),
);
program_test.prefer_bpf(false);
let mut context = program_test.start_with_context().await;
let payer = &context.payer;
let mint_authority = Keypair::new();
let mint = Keypair::new();
let owner = Keypair::new();
let recipient = Keypair::new();
// Create a regular Token-2022 mint
let mint_size = ExtensionType::try_calculate_account_len::<Mint>(&[]).unwrap();
let mint_rent = context.banks_client.get_rent().await.unwrap().minimum_balance(mint_size);
let transaction = Transaction::new_signed_with_payer(
&[
system_instruction::create_account(
&payer.pubkey(),
&mint.pubkey(),
mint_rent,
mint_size as u64,
&TOKEN_2022_ID,
),
initialize_mint(
&TOKEN_2022_ID,
&mint.pubkey(),
&mint_authority.pubkey(),
Some(&mint_authority.pubkey()),
9,
).unwrap(),
],
Some(&payer.pubkey()),
&[payer, &mint],
context.last_blockhash,
);
context.banks_client.process_transaction(transaction).await.unwrap();
// Create accounts and mint tokens
let account_size = ExtensionType::try_calculate_account_len::<Account>(&[]).unwrap();
let rent: u64 = context.banks_client.get_rent().await.unwrap().minimum_balance(account_size);
// Source account
let source_account = Keypair::new();
let transaction = Transaction::new_signed_with_payer(
&[
system_instruction::create_account(
&payer.pubkey(),
&source_account.pubkey(),
rent,
account_size as u64,
&TOKEN_2022_ID,
),
initialize_account(
&TOKEN_2022_ID,
&source_account.pubkey(),
&mint.pubkey(),
&owner.pubkey(),
).unwrap(),
],
Some(&payer.pubkey()),
&[payer, &source_account],
context.last_blockhash,
);
context.banks_client.process_transaction(transaction).await.unwrap();
// Mint tokens
const MINT_AMOUNT: u64 = 1_000_000;
let transaction = Transaction::new_signed_with_payer(
&[mint_to(
&TOKEN_2022_ID,
&mint.pubkey(),
&source_account.pubkey(),
&mint_authority.pubkey(),
&[],
MINT_AMOUNT,
).unwrap()],
Some(&payer.pubkey()),
&[payer, &mint_authority],
context.last_blockhash,
);
context.banks_client.process_transaction(transaction).await.unwrap();
// Destination account
let destination_account = Keypair::new();
let transaction = Transaction::new_signed_with_payer(
&[
system_instruction::create_account(
&payer.pubkey(),
&destination_account.pubkey(),
rent,
account_size as u64,
&TOKEN_2022_ID,
),
initialize_account(
&TOKEN_2022_ID,
&destination_account.pubkey(),
&mint.pubkey(),
&recipient.pubkey(),
).unwrap(),
],
Some(&payer.pubkey()),
&[payer, &destination_account],
context.last_blockhash,
);
context.banks_client.process_transaction(transaction).await.unwrap();
// Transfer using Token-2022
const TRANSFER_AMOUNT: u64 = 100_000;
let transaction = Transaction::new_signed_with_payer(
&[transfer_checked(
&TOKEN_2022_ID,
&source_account.pubkey(),
&mint.pubkey(),
&destination_account.pubkey(),
&owner.pubkey(),
&[],
TRANSFER_AMOUNT,
9,
).unwrap()],
Some(&payer.pubkey()),
&[payer, &owner],
context.last_blockhash,
);
context.banks_client.process_transaction(transaction).await.unwrap();
// Try SPL Token transfer (should fail)
let spl_transfer = Transaction::new_signed_with_payer(
&[spl_token::instruction::transfer(
&spl_token::id(),
&source_account.pubkey(),
&destination_account.pubkey(),
&owner.pubkey(),
&[],
TRANSFER_AMOUNT,
).unwrap()],
Some(&payer.pubkey()),
&[payer, &owner],
context.last_blockhash,
);
let result = context.banks_client.process_transaction(spl_transfer).await;
assert!(result.is_err());
// Verify balances
let source = context.banks_client.get_account(source_account.pubkey()).await.unwrap().unwrap();
let destination = context.banks_client.get_account(destination_account.pubkey()).await.unwrap().unwrap();
let source_token = StateWithExtensions::<Account>::unpack(&source.data).unwrap();
let destination_token = StateWithExtensions::<Account>::unpack(&destination.data).unwrap();
assert_eq!(source_token.base.amount, MINT_AMOUNT - TRANSFER_AMOUNT);
assert_eq!(destination_token.base.amount, TRANSFER_AMOUNT);
}