# #36787 \[SC-Insight] The vault program don't support token2022 transfer

**Submitted on Nov 14th 2024 at 12:17:58 UTC by @Hoverfly9132 for** [**Audit Comp | Jito Restaking**](https://immunefi.com/audit-competition/jito-restaking-audit-competition)

* **Report ID:** #36787
* **Report Type:** Smart Contract
* **Report severity:** Insight
* **Target:** <https://github.com/jito-foundation/restaking/tree/master/vault\\_program>
* **Impacts:**
  * 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`](https://github.com/solana-labs/solana-program-library/blob/2d795f15287f6a0907bfe68f3fa252e30ee9572b/token/program/src/lib.rs#L85-L85) and [`spl_token_2022 program id`](https://github.com/solana-labs/solana-program-library/blob/2d795f15287f6a0907bfe68f3fa252e30ee9572b/token/program-2022/src/lib.rs#L95-L95).

```rust
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:

```rust
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`:

```rust
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);
} 
```


---

# Agent Instructions: Querying This Documentation

If you need additional information that is not directly available in this page, you can query the documentation dynamically by asking a question.

Perform an HTTP GET request on the current page URL with the `ask` query parameter:

```
GET https://reports.immunefi.com/jito-restaking/36787-sc-insight-the-vault-program-dont-support-token2022-transfer.md?ask=<question>
```

The question should be specific, self-contained, and written in natural language.
The response will contain a direct answer to the question and relevant excerpts and sources from the documentation.

Use this mechanism when the answer is not explicitly present in the current page, you need clarification or additional context, or you want to retrieve related documentation sections.
