solana-transaction-status 1.2.24

Solana transaction status types
Documentation
use crate::parse_instruction::{ParsableProgram, ParseInstructionError, ParsedInstructionEnum};
use serde_json::{json, Map, Value};
use solana_sdk::{instruction::CompiledInstruction, pubkey::Pubkey};
use spl_token_v1_0::instruction::TokenInstruction;

pub fn parse_token(
    instruction: &CompiledInstruction,
    account_keys: &[Pubkey],
) -> Result<ParsedInstructionEnum, ParseInstructionError> {
    let token_instruction = TokenInstruction::unpack(&instruction.data)
        .map_err(|_| ParseInstructionError::InstructionNotParsable(ParsableProgram::SplToken))?;
    if instruction.accounts.len() > account_keys.len() {
        // Runtime should prevent this from ever happening
        return Err(ParseInstructionError::InstructionKeyMismatch(
            ParsableProgram::SplToken,
        ));
    }
    match token_instruction {
        TokenInstruction::InitializeMint { amount, decimals } => {
            if instruction.accounts.len() < 2 {
                return Err(ParseInstructionError::InstructionKeyMismatch(
                    ParsableProgram::SplToken,
                ));
            }
            let mut value = json!({
                "mint": account_keys[instruction.accounts[0] as usize].to_string(),
                "amount": amount,
                "decimals":decimals,
            });
            let map = value.as_object_mut().unwrap();
            if amount == 0 {
                map.insert(
                    "owner".to_string(),
                    json!(account_keys[instruction.accounts[1] as usize].to_string()),
                );
            } else {
                map.insert(
                    "account".to_string(),
                    json!(account_keys[instruction.accounts[1] as usize].to_string()),
                );
                if let Some(i) = instruction.accounts.get(2) {
                    map.insert(
                        "owner".to_string(),
                        json!(account_keys[*i as usize].to_string()),
                    );
                }
            }
            Ok(ParsedInstructionEnum {
                instruction_type: "initializeMint".to_string(),
                info: value,
            })
        }
        TokenInstruction::InitializeAccount => {
            if instruction.accounts.len() < 3 {
                return Err(ParseInstructionError::InstructionKeyMismatch(
                    ParsableProgram::SplToken,
                ));
            }
            Ok(ParsedInstructionEnum {
                instruction_type: "initializeAccount".to_string(),
                info: json!({
                    "account": account_keys[instruction.accounts[0] as usize].to_string(),
                    "mint": account_keys[instruction.accounts[1] as usize].to_string(),
                    "owner": account_keys[instruction.accounts[2] as usize].to_string(),
                }),
            })
        }
        TokenInstruction::InitializeMultisig { m } => {
            if instruction.accounts.len() < 2 {
                return Err(ParseInstructionError::InstructionKeyMismatch(
                    ParsableProgram::SplToken,
                ));
            }
            let mut signers: Vec<String> = vec![];
            for i in instruction.accounts[1..].iter() {
                signers.push(account_keys[*i as usize].to_string());
            }
            Ok(ParsedInstructionEnum {
                instruction_type: "initializeMultisig".to_string(),
                info: json!({
                    "multisig": account_keys[instruction.accounts[0] as usize].to_string(),
                    "signers": signers,
                    "m": m,
                }),
            })
        }
        TokenInstruction::Transfer { amount } => {
            if instruction.accounts.len() < 3 {
                return Err(ParseInstructionError::InstructionKeyMismatch(
                    ParsableProgram::SplToken,
                ));
            }
            let mut value = json!({
                "source": account_keys[instruction.accounts[0] as usize].to_string(),
                "destination": account_keys[instruction.accounts[1] as usize].to_string(),
                "amount": amount,
            });
            let mut map = value.as_object_mut().unwrap();
            parse_signers(
                &mut map,
                2,
                account_keys,
                &instruction.accounts,
                "authority",
                "multisigAuthority",
            );
            Ok(ParsedInstructionEnum {
                instruction_type: "transfer".to_string(),
                info: value,
            })
        }
        TokenInstruction::Approve { amount } => {
            if instruction.accounts.len() < 3 {
                return Err(ParseInstructionError::InstructionKeyMismatch(
                    ParsableProgram::SplToken,
                ));
            }
            let mut value = json!({
                "source": account_keys[instruction.accounts[0] as usize].to_string(),
                "delegate": account_keys[instruction.accounts[1] as usize].to_string(),
                "amount": amount,
            });
            let mut map = value.as_object_mut().unwrap();
            parse_signers(
                &mut map,
                2,
                account_keys,
                &instruction.accounts,
                "owner",
                "multisigOwner",
            );
            Ok(ParsedInstructionEnum {
                instruction_type: "approve".to_string(),
                info: value,
            })
        }
        TokenInstruction::Revoke => {
            if instruction.accounts.len() < 2 {
                return Err(ParseInstructionError::InstructionKeyMismatch(
                    ParsableProgram::SplToken,
                ));
            }
            let mut value = json!({
                "source": account_keys[instruction.accounts[0] as usize].to_string(),
            });
            let mut map = value.as_object_mut().unwrap();
            parse_signers(
                &mut map,
                1,
                account_keys,
                &instruction.accounts,
                "owner",
                "multisigOwner",
            );
            Ok(ParsedInstructionEnum {
                instruction_type: "revoke".to_string(),
                info: value,
            })
        }
        TokenInstruction::SetOwner => {
            if instruction.accounts.len() < 3 {
                return Err(ParseInstructionError::InstructionKeyMismatch(
                    ParsableProgram::SplToken,
                ));
            }
            let mut value = json!({
                "owned": account_keys[instruction.accounts[0] as usize].to_string(),
                "newOwner": account_keys[instruction.accounts[1] as usize].to_string(),
            });
            let mut map = value.as_object_mut().unwrap();
            parse_signers(
                &mut map,
                2,
                account_keys,
                &instruction.accounts,
                "owner",
                "multisigOwner",
            );
            Ok(ParsedInstructionEnum {
                instruction_type: "setOwner".to_string(),
                info: value,
            })
        }
        TokenInstruction::MintTo { amount } => {
            if instruction.accounts.len() < 3 {
                return Err(ParseInstructionError::InstructionKeyMismatch(
                    ParsableProgram::SplToken,
                ));
            }
            let mut value = json!({
                "mint": account_keys[instruction.accounts[0] as usize].to_string(),
                "account": account_keys[instruction.accounts[1] as usize].to_string(),
                "amount": amount,
            });
            let mut map = value.as_object_mut().unwrap();
            parse_signers(
                &mut map,
                2,
                account_keys,
                &instruction.accounts,
                "owner",
                "multisigOwner",
            );
            Ok(ParsedInstructionEnum {
                instruction_type: "mintTo".to_string(),
                info: value,
            })
        }
        TokenInstruction::Burn { amount } => {
            if instruction.accounts.len() < 2 {
                return Err(ParseInstructionError::InstructionKeyMismatch(
                    ParsableProgram::SplToken,
                ));
            }
            let mut value = json!({
                "account": account_keys[instruction.accounts[0] as usize].to_string(),
                "amount": amount,
            });
            let mut map = value.as_object_mut().unwrap();
            parse_signers(
                &mut map,
                1,
                account_keys,
                &instruction.accounts,
                "authority",
                "multisigAuthority",
            );
            Ok(ParsedInstructionEnum {
                instruction_type: "burn".to_string(),
                info: value,
            })
        }
        TokenInstruction::CloseAccount => {
            if instruction.accounts.len() < 3 {
                return Err(ParseInstructionError::InstructionKeyMismatch(
                    ParsableProgram::SplToken,
                ));
            }
            let mut value = json!({
                "account": account_keys[instruction.accounts[0] as usize].to_string(),
                "destination": account_keys[instruction.accounts[1] as usize].to_string(),
            });
            let mut map = value.as_object_mut().unwrap();
            parse_signers(
                &mut map,
                2,
                account_keys,
                &instruction.accounts,
                "owner",
                "multisigOwner",
            );
            Ok(ParsedInstructionEnum {
                instruction_type: "closeAccount".to_string(),
                info: value,
            })
        }
    }
}

fn parse_signers(
    map: &mut Map<String, Value>,
    last_nonsigner_index: usize,
    account_keys: &[Pubkey],
    accounts: &[u8],
    owner_field_name: &str,
    multisig_field_name: &str,
) {
    if accounts.len() > last_nonsigner_index + 1 {
        let mut signers: Vec<String> = vec![];
        for i in accounts[last_nonsigner_index + 1..].iter() {
            signers.push(account_keys[*i as usize].to_string());
        }
        map.insert(
            multisig_field_name.to_string(),
            json!(account_keys[accounts[last_nonsigner_index] as usize].to_string()),
        );
        map.insert("signers".to_string(), json!(signers));
    } else {
        map.insert(
            owner_field_name.to_string(),
            json!(account_keys[accounts[last_nonsigner_index] as usize].to_string()),
        );
    }
}

#[cfg(test)]
mod test {
    use super::*;
    use solana_sdk::instruction::CompiledInstruction;
    use spl_token_v1_0::{
        instruction::*,
        solana_sdk::{
            instruction::CompiledInstruction as SplTokenCompiledInstruction, message::Message,
            pubkey::Pubkey as SplTokenPubkey,
        },
    };
    use std::str::FromStr;

    fn convert_pubkey(pubkey: Pubkey) -> SplTokenPubkey {
        SplTokenPubkey::from_str(&pubkey.to_string()).unwrap()
    }

    fn convert_compiled_instruction(
        instruction: &SplTokenCompiledInstruction,
    ) -> CompiledInstruction {
        CompiledInstruction {
            program_id_index: instruction.program_id_index,
            accounts: instruction.accounts.clone(),
            data: instruction.data.clone(),
        }
    }

    #[test]
    fn test_parse_token() {
        let mut keys: Vec<Pubkey> = vec![];
        for _ in 0..10 {
            keys.push(Pubkey::new_rand());
        }

        // Test InitializeMint variations
        let initialize_mint_ix = initialize_mint(
            &spl_token_v1_0::id(),
            &convert_pubkey(keys[0]),
            Some(&convert_pubkey(keys[1])),
            Some(&convert_pubkey(keys[2])),
            42,
            2,
        )
        .unwrap();
        let message = Message::new(&[initialize_mint_ix], None);
        let compiled_instruction = convert_compiled_instruction(&message.instructions[0]);
        assert_eq!(
            parse_token(&compiled_instruction, &keys).unwrap(),
            ParsedInstructionEnum {
                instruction_type: "initializeMint".to_string(),
                info: json!({
                    "mint": keys[0].to_string(),
                    "amount": 42,
                    "decimals": 2,
                    "account": keys[1].to_string(),
                    "owner": keys[2].to_string(),
                })
            }
        );

        let initialize_mint_ix = initialize_mint(
            &spl_token_v1_0::id(),
            &convert_pubkey(keys[0]),
            Some(&convert_pubkey(keys[1])),
            None,
            42,
            2,
        )
        .unwrap();
        let message = Message::new(&[initialize_mint_ix], None);
        let compiled_instruction = convert_compiled_instruction(&message.instructions[0]);
        assert_eq!(
            parse_token(&compiled_instruction, &keys).unwrap(),
            ParsedInstructionEnum {
                instruction_type: "initializeMint".to_string(),
                info: json!({
                   "mint": keys[0].to_string(),
                   "amount": 42,
                   "decimals": 2,
                   "account": keys[1].to_string(),
                })
            }
        );

        let initialize_mint_ix = initialize_mint(
            &spl_token_v1_0::id(),
            &convert_pubkey(keys[0]),
            None,
            Some(&convert_pubkey(keys[1])),
            0,
            2,
        )
        .unwrap();
        let message = Message::new(&[initialize_mint_ix], None);
        let compiled_instruction = convert_compiled_instruction(&message.instructions[0]);
        assert_eq!(
            parse_token(&compiled_instruction, &keys).unwrap(),
            ParsedInstructionEnum {
                instruction_type: "initializeMint".to_string(),
                info: json!({
                   "mint": keys[0].to_string(),
                   "amount": 0,
                   "decimals": 2,
                   "owner": keys[1].to_string(),
                })
            }
        );

        // Test InitializeAccount
        let initialize_account_ix = initialize_account(
            &spl_token_v1_0::id(),
            &convert_pubkey(keys[0]),
            &convert_pubkey(keys[1]),
            &convert_pubkey(keys[2]),
        )
        .unwrap();
        let message = Message::new(&[initialize_account_ix], None);
        let compiled_instruction = convert_compiled_instruction(&message.instructions[0]);
        assert_eq!(
            parse_token(&compiled_instruction, &keys).unwrap(),
            ParsedInstructionEnum {
                instruction_type: "initializeAccount".to_string(),
                info: json!({
                   "account": keys[0].to_string(),
                   "mint": keys[1].to_string(),
                   "owner": keys[2].to_string(),
                })
            }
        );

        // Test InitializeMultisig
        let initialize_multisig_ix = initialize_multisig(
            &spl_token_v1_0::id(),
            &convert_pubkey(keys[0]),
            &[
                &convert_pubkey(keys[1]),
                &convert_pubkey(keys[2]),
                &convert_pubkey(keys[3]),
            ],
            2,
        )
        .unwrap();
        let message = Message::new(&[initialize_multisig_ix], None);
        let compiled_instruction = convert_compiled_instruction(&message.instructions[0]);
        assert_eq!(
            parse_token(&compiled_instruction, &keys).unwrap(),
            ParsedInstructionEnum {
                instruction_type: "initializeMultisig".to_string(),
                info: json!({
                   "multisig": keys[0].to_string(),
                   "m": 2,
                   "signers": keys[1..4].iter().map(|key| key.to_string()).collect::<Vec<String>>(),
                })
            }
        );

        // Test Transfer, incl multisig
        let transfer_ix = transfer(
            &spl_token_v1_0::id(),
            &convert_pubkey(keys[1]),
            &convert_pubkey(keys[2]),
            &convert_pubkey(keys[0]),
            &[],
            42,
        )
        .unwrap();
        let message = Message::new(&[transfer_ix], None);
        let compiled_instruction = convert_compiled_instruction(&message.instructions[0]);
        assert_eq!(
            parse_token(&compiled_instruction, &keys).unwrap(),
            ParsedInstructionEnum {
                instruction_type: "transfer".to_string(),
                info: json!({
                   "source": keys[1].to_string(),
                   "destination": keys[2].to_string(),
                   "authority": keys[0].to_string(),
                   "amount": 42,
                })
            }
        );

        let transfer_ix = transfer(
            &spl_token_v1_0::id(),
            &convert_pubkey(keys[2]),
            &convert_pubkey(keys[3]),
            &convert_pubkey(keys[4]),
            &[&convert_pubkey(keys[0]), &convert_pubkey(keys[1])],
            42,
        )
        .unwrap();
        let message = Message::new(&[transfer_ix], None);
        let compiled_instruction = convert_compiled_instruction(&message.instructions[0]);
        assert_eq!(
            parse_token(&compiled_instruction, &keys).unwrap(),
            ParsedInstructionEnum {
                instruction_type: "transfer".to_string(),
                info: json!({
                   "source": keys[2].to_string(),
                   "destination": keys[3].to_string(),
                   "multisigAuthority": keys[4].to_string(),
                   "signers": keys[0..2].iter().map(|key| key.to_string()).collect::<Vec<String>>(),
                   "amount": 42,
                })
            }
        );

        // Test Approve, incl multisig
        let approve_ix = approve(
            &spl_token_v1_0::id(),
            &convert_pubkey(keys[1]),
            &convert_pubkey(keys[2]),
            &convert_pubkey(keys[0]),
            &[],
            42,
        )
        .unwrap();
        let message = Message::new(&[approve_ix], None);
        let compiled_instruction = convert_compiled_instruction(&message.instructions[0]);
        assert_eq!(
            parse_token(&compiled_instruction, &keys).unwrap(),
            ParsedInstructionEnum {
                instruction_type: "approve".to_string(),
                info: json!({
                   "source": keys[1].to_string(),
                   "delegate": keys[2].to_string(),
                   "owner": keys[0].to_string(),
                   "amount": 42,
                })
            }
        );

        let approve_ix = approve(
            &spl_token_v1_0::id(),
            &convert_pubkey(keys[2]),
            &convert_pubkey(keys[3]),
            &convert_pubkey(keys[4]),
            &[&convert_pubkey(keys[0]), &convert_pubkey(keys[1])],
            42,
        )
        .unwrap();
        let message = Message::new(&[approve_ix], None);
        let compiled_instruction = convert_compiled_instruction(&message.instructions[0]);
        assert_eq!(
            parse_token(&compiled_instruction, &keys).unwrap(),
            ParsedInstructionEnum {
                instruction_type: "approve".to_string(),
                info: json!({
                   "source": keys[2].to_string(),
                   "delegate": keys[3].to_string(),
                   "multisigOwner": keys[4].to_string(),
                   "signers": keys[0..2].iter().map(|key| key.to_string()).collect::<Vec<String>>(),
                   "amount": 42,
                })
            }
        );

        // Test Revoke
        let revoke_ix = revoke(
            &spl_token_v1_0::id(),
            &convert_pubkey(keys[1]),
            &convert_pubkey(keys[0]),
            &[],
        )
        .unwrap();
        let message = Message::new(&[revoke_ix], None);
        let compiled_instruction = convert_compiled_instruction(&message.instructions[0]);
        assert_eq!(
            parse_token(&compiled_instruction, &keys).unwrap(),
            ParsedInstructionEnum {
                instruction_type: "revoke".to_string(),
                info: json!({
                   "source": keys[1].to_string(),
                   "owner": keys[0].to_string(),
                })
            }
        );

        // Test SetOwner
        let set_owner_ix = set_owner(
            &spl_token_v1_0::id(),
            &convert_pubkey(keys[1]),
            &convert_pubkey(keys[2]),
            &convert_pubkey(keys[0]),
            &[],
        )
        .unwrap();
        let message = Message::new(&[set_owner_ix], None);
        let compiled_instruction = convert_compiled_instruction(&message.instructions[0]);
        assert_eq!(
            parse_token(&compiled_instruction, &keys).unwrap(),
            ParsedInstructionEnum {
                instruction_type: "setOwner".to_string(),
                info: json!({
                   "owned": keys[1].to_string(),
                   "newOwner": keys[2].to_string(),
                   "owner": keys[0].to_string(),
                })
            }
        );

        // Test MintTo
        let mint_to_ix = mint_to(
            &spl_token_v1_0::id(),
            &convert_pubkey(keys[1]),
            &convert_pubkey(keys[2]),
            &convert_pubkey(keys[0]),
            &[],
            42,
        )
        .unwrap();
        let message = Message::new(&[mint_to_ix], None);
        let compiled_instruction = convert_compiled_instruction(&message.instructions[0]);
        assert_eq!(
            parse_token(&compiled_instruction, &keys).unwrap(),
            ParsedInstructionEnum {
                instruction_type: "mintTo".to_string(),
                info: json!({
                   "mint": keys[1].to_string(),
                   "account": keys[2].to_string(),
                   "owner": keys[0].to_string(),
                   "amount": 42,
                })
            }
        );

        // Test Burn
        let burn_ix = burn(
            &spl_token_v1_0::id(),
            &convert_pubkey(keys[1]),
            &convert_pubkey(keys[0]),
            &[],
            42,
        )
        .unwrap();
        let message = Message::new(&[burn_ix], None);
        let compiled_instruction = convert_compiled_instruction(&message.instructions[0]);
        assert_eq!(
            parse_token(&compiled_instruction, &keys).unwrap(),
            ParsedInstructionEnum {
                instruction_type: "burn".to_string(),
                info: json!({
                   "account": keys[1].to_string(),
                   "authority": keys[0].to_string(),
                   "amount": 42,
                })
            }
        );

        // Test CloseAccount
        let close_account_ix = close_account(
            &spl_token_v1_0::id(),
            &convert_pubkey(keys[1]),
            &convert_pubkey(keys[2]),
            &convert_pubkey(keys[0]),
            &[],
        )
        .unwrap();
        let message = Message::new(&[close_account_ix], None);
        let compiled_instruction = convert_compiled_instruction(&message.instructions[0]);
        assert_eq!(
            parse_token(&compiled_instruction, &keys).unwrap(),
            ParsedInstructionEnum {
                instruction_type: "closeAccount".to_string(),
                info: json!({
                   "account": keys[1].to_string(),
                   "destination": keys[2].to_string(),
                   "owner": keys[0].to_string(),
                })
            }
        );
    }

    #[test]
    fn test_token_ix_not_enough_keys() {
        let mut keys: Vec<Pubkey> = vec![];
        for _ in 0..10 {
            keys.push(Pubkey::new_rand());
        }

        // Test InitializeMint variations
        let initialize_mint_ix = initialize_mint(
            &spl_token_v1_0::id(),
            &convert_pubkey(keys[0]),
            Some(&convert_pubkey(keys[1])),
            Some(&convert_pubkey(keys[2])),
            42,
            2,
        )
        .unwrap();
        let message = Message::new(&[initialize_mint_ix], None);
        let mut compiled_instruction = convert_compiled_instruction(&message.instructions[0]);
        assert!(parse_token(&compiled_instruction, &keys[0..2]).is_err());
        compiled_instruction.accounts =
            compiled_instruction.accounts[0..compiled_instruction.accounts.len() - 2].to_vec();
        assert!(parse_token(&compiled_instruction, &keys).is_err());

        let initialize_mint_ix = initialize_mint(
            &spl_token_v1_0::id(),
            &convert_pubkey(keys[0]),
            Some(&convert_pubkey(keys[1])),
            None,
            42,
            2,
        )
        .unwrap();
        let message = Message::new(&[initialize_mint_ix], None);
        let mut compiled_instruction = convert_compiled_instruction(&message.instructions[0]);
        assert!(parse_token(&compiled_instruction, &keys[0..1]).is_err());
        compiled_instruction.accounts =
            compiled_instruction.accounts[0..compiled_instruction.accounts.len() - 1].to_vec();
        assert!(parse_token(&compiled_instruction, &keys).is_err());

        let initialize_mint_ix = initialize_mint(
            &spl_token_v1_0::id(),
            &convert_pubkey(keys[0]),
            None,
            Some(&convert_pubkey(keys[1])),
            0,
            2,
        )
        .unwrap();
        let message = Message::new(&[initialize_mint_ix], None);
        let mut compiled_instruction = convert_compiled_instruction(&message.instructions[0]);
        assert!(parse_token(&compiled_instruction, &keys[0..1]).is_err());
        compiled_instruction.accounts =
            compiled_instruction.accounts[0..compiled_instruction.accounts.len() - 1].to_vec();
        assert!(parse_token(&compiled_instruction, &keys).is_err());

        // Test InitializeAccount
        let initialize_account_ix = initialize_account(
            &spl_token_v1_0::id(),
            &convert_pubkey(keys[0]),
            &convert_pubkey(keys[1]),
            &convert_pubkey(keys[2]),
        )
        .unwrap();
        let message = Message::new(&[initialize_account_ix], None);
        let mut compiled_instruction = convert_compiled_instruction(&message.instructions[0]);
        assert!(parse_token(&compiled_instruction, &keys[0..2]).is_err());
        compiled_instruction.accounts =
            compiled_instruction.accounts[0..compiled_instruction.accounts.len() - 1].to_vec();
        assert!(parse_token(&compiled_instruction, &keys).is_err());

        // Test InitializeMultisig
        let initialize_multisig_ix = initialize_multisig(
            &spl_token_v1_0::id(),
            &convert_pubkey(keys[0]),
            &[
                &convert_pubkey(keys[1]),
                &convert_pubkey(keys[2]),
                &convert_pubkey(keys[3]),
            ],
            2,
        )
        .unwrap();
        let message = Message::new(&[initialize_multisig_ix], None);
        let mut compiled_instruction = convert_compiled_instruction(&message.instructions[0]);
        assert!(parse_token(&compiled_instruction, &keys[0..3]).is_err());
        compiled_instruction.accounts =
            compiled_instruction.accounts[0..compiled_instruction.accounts.len() - 3].to_vec();
        assert!(parse_token(&compiled_instruction, &keys).is_err());

        // Test Transfer, incl multisig
        let transfer_ix = transfer(
            &spl_token_v1_0::id(),
            &convert_pubkey(keys[1]),
            &convert_pubkey(keys[2]),
            &convert_pubkey(keys[0]),
            &[],
            42,
        )
        .unwrap();
        let message = Message::new(&[transfer_ix], None);
        let mut compiled_instruction = convert_compiled_instruction(&message.instructions[0]);
        assert!(parse_token(&compiled_instruction, &keys[0..2]).is_err());
        compiled_instruction.accounts =
            compiled_instruction.accounts[0..compiled_instruction.accounts.len() - 1].to_vec();
        assert!(parse_token(&compiled_instruction, &keys).is_err());

        let transfer_ix = transfer(
            &spl_token_v1_0::id(),
            &convert_pubkey(keys[2]),
            &convert_pubkey(keys[3]),
            &convert_pubkey(keys[4]),
            &[&convert_pubkey(keys[0]), &convert_pubkey(keys[1])],
            42,
        )
        .unwrap();
        let message = Message::new(&[transfer_ix], None);
        let mut compiled_instruction = convert_compiled_instruction(&message.instructions[0]);
        assert!(parse_token(&compiled_instruction, &keys[0..4]).is_err());
        compiled_instruction.accounts =
            compiled_instruction.accounts[0..compiled_instruction.accounts.len() - 3].to_vec();
        assert!(parse_token(&compiled_instruction, &keys).is_err());

        // Test Approve, incl multisig
        let approve_ix = approve(
            &spl_token_v1_0::id(),
            &convert_pubkey(keys[1]),
            &convert_pubkey(keys[2]),
            &convert_pubkey(keys[0]),
            &[],
            42,
        )
        .unwrap();
        let message = Message::new(&[approve_ix], None);
        let mut compiled_instruction = convert_compiled_instruction(&message.instructions[0]);
        assert!(parse_token(&compiled_instruction, &keys[0..2]).is_err());
        compiled_instruction.accounts =
            compiled_instruction.accounts[0..compiled_instruction.accounts.len() - 1].to_vec();
        assert!(parse_token(&compiled_instruction, &keys).is_err());

        let approve_ix = approve(
            &spl_token_v1_0::id(),
            &convert_pubkey(keys[2]),
            &convert_pubkey(keys[3]),
            &convert_pubkey(keys[4]),
            &[&convert_pubkey(keys[0]), &convert_pubkey(keys[1])],
            42,
        )
        .unwrap();
        let message = Message::new(&[approve_ix], None);
        let mut compiled_instruction = convert_compiled_instruction(&message.instructions[0]);
        assert!(parse_token(&compiled_instruction, &keys[0..4]).is_err());
        compiled_instruction.accounts =
            compiled_instruction.accounts[0..compiled_instruction.accounts.len() - 3].to_vec();
        assert!(parse_token(&compiled_instruction, &keys).is_err());

        // Test Revoke
        let revoke_ix = revoke(
            &spl_token_v1_0::id(),
            &convert_pubkey(keys[1]),
            &convert_pubkey(keys[0]),
            &[],
        )
        .unwrap();
        let message = Message::new(&[revoke_ix], None);
        let mut compiled_instruction = convert_compiled_instruction(&message.instructions[0]);
        assert!(parse_token(&compiled_instruction, &keys[0..1]).is_err());
        compiled_instruction.accounts =
            compiled_instruction.accounts[0..compiled_instruction.accounts.len() - 1].to_vec();
        assert!(parse_token(&compiled_instruction, &keys).is_err());

        // Test SetOwner
        let set_owner_ix = set_owner(
            &spl_token_v1_0::id(),
            &convert_pubkey(keys[1]),
            &convert_pubkey(keys[2]),
            &convert_pubkey(keys[0]),
            &[],
        )
        .unwrap();
        let message = Message::new(&[set_owner_ix], None);
        let mut compiled_instruction = convert_compiled_instruction(&message.instructions[0]);
        assert!(parse_token(&compiled_instruction, &keys[0..2]).is_err());
        compiled_instruction.accounts =
            compiled_instruction.accounts[0..compiled_instruction.accounts.len() - 1].to_vec();
        assert!(parse_token(&compiled_instruction, &keys).is_err());

        // Test MintTo
        let mint_to_ix = mint_to(
            &spl_token_v1_0::id(),
            &convert_pubkey(keys[1]),
            &convert_pubkey(keys[2]),
            &convert_pubkey(keys[0]),
            &[],
            42,
        )
        .unwrap();
        let message = Message::new(&[mint_to_ix], None);
        let mut compiled_instruction = convert_compiled_instruction(&message.instructions[0]);
        assert!(parse_token(&compiled_instruction, &keys[0..2]).is_err());
        compiled_instruction.accounts =
            compiled_instruction.accounts[0..compiled_instruction.accounts.len() - 1].to_vec();
        assert!(parse_token(&compiled_instruction, &keys).is_err());

        // Test Burn
        let burn_ix = burn(
            &spl_token_v1_0::id(),
            &convert_pubkey(keys[1]),
            &convert_pubkey(keys[0]),
            &[],
            42,
        )
        .unwrap();
        let message = Message::new(&[burn_ix], None);
        let mut compiled_instruction = convert_compiled_instruction(&message.instructions[0]);
        assert!(parse_token(&compiled_instruction, &keys[0..1]).is_err());
        compiled_instruction.accounts =
            compiled_instruction.accounts[0..compiled_instruction.accounts.len() - 1].to_vec();
        assert!(parse_token(&compiled_instruction, &keys).is_err());

        // Test CloseAccount
        let close_account_ix = close_account(
            &spl_token_v1_0::id(),
            &convert_pubkey(keys[1]),
            &convert_pubkey(keys[2]),
            &convert_pubkey(keys[0]),
            &[],
        )
        .unwrap();
        let message = Message::new(&[close_account_ix], None);
        let mut compiled_instruction = convert_compiled_instruction(&message.instructions[0]);
        assert!(parse_token(&compiled_instruction, &keys[0..2]).is_err());
        compiled_instruction.accounts =
            compiled_instruction.accounts[0..compiled_instruction.accounts.len() - 1].to_vec();
        assert!(parse_token(&compiled_instruction, &keys).is_err());
    }
}