Decode token instructions (#11281)
* Token->SplToken * Add spl_token instruction parsing * Rebase on master * Gracefully fail key len mismatches
This commit is contained in:
parent
1733f6c9e9
commit
0f551d4f75
|
@ -4057,10 +4057,13 @@ dependencies = [
|
|||
"serde",
|
||||
"serde_derive",
|
||||
"serde_json",
|
||||
"solana-account-decoder",
|
||||
"solana-sdk 1.3.0",
|
||||
"solana-stake-program",
|
||||
"solana-vote-program",
|
||||
"spl-memo",
|
||||
"spl-token",
|
||||
"thiserror",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
|
|
|
@ -16,7 +16,7 @@ lazy_static! {
|
|||
pub static ref PARSABLE_PROGRAM_IDS: HashMap<Pubkey, ParsableAccount> = {
|
||||
let mut m = HashMap::new();
|
||||
m.insert(*SYSTEM_PROGRAM_ID, ParsableAccount::Nonce);
|
||||
m.insert(*TOKEN_PROGRAM_ID, ParsableAccount::Token);
|
||||
m.insert(*TOKEN_PROGRAM_ID, ParsableAccount::SplToken);
|
||||
m.insert(*VOTE_PROGRAM_ID, ParsableAccount::Vote);
|
||||
m
|
||||
};
|
||||
|
@ -41,7 +41,7 @@ pub enum ParseAccountError {
|
|||
#[serde(rename_all = "camelCase")]
|
||||
pub enum ParsableAccount {
|
||||
Nonce,
|
||||
Token,
|
||||
SplToken,
|
||||
Vote,
|
||||
}
|
||||
|
||||
|
@ -51,7 +51,7 @@ pub fn parse_account_data(program_id: &Pubkey, data: &[u8]) -> Result<Value, Par
|
|||
.ok_or_else(|| ParseAccountError::ProgramNotParsable)?;
|
||||
let parsed_json = match program_name {
|
||||
ParsableAccount::Nonce => serde_json::to_value(parse_nonce(data)?)?,
|
||||
ParsableAccount::Token => serde_json::to_value(parse_token(data)?)?,
|
||||
ParsableAccount::SplToken => serde_json::to_value(parse_token(data)?)?,
|
||||
ParsableAccount::Vote => serde_json::to_value(parse_vote(data)?)?,
|
||||
};
|
||||
Ok(json!({
|
||||
|
|
|
@ -16,7 +16,7 @@ pub fn parse_token(data: &[u8]) -> Result<TokenAccountType, ParseAccountError> {
|
|||
let mut data = data.to_vec();
|
||||
if data.len() == size_of::<Account>() {
|
||||
let account: Account = *State::unpack(&mut data)
|
||||
.map_err(|_| ParseAccountError::AccountNotParsable(ParsableAccount::Token))?;
|
||||
.map_err(|_| ParseAccountError::AccountNotParsable(ParsableAccount::SplToken))?;
|
||||
Ok(TokenAccountType::Account(UiTokenAccount {
|
||||
mint: account.mint.to_string(),
|
||||
owner: account.owner.to_string(),
|
||||
|
@ -31,7 +31,7 @@ pub fn parse_token(data: &[u8]) -> Result<TokenAccountType, ParseAccountError> {
|
|||
}))
|
||||
} else if data.len() == size_of::<Mint>() {
|
||||
let mint: Mint = *State::unpack(&mut data)
|
||||
.map_err(|_| ParseAccountError::AccountNotParsable(ParsableAccount::Token))?;
|
||||
.map_err(|_| ParseAccountError::AccountNotParsable(ParsableAccount::SplToken))?;
|
||||
Ok(TokenAccountType::Mint(UiMint {
|
||||
owner: match mint.owner {
|
||||
COption::Some(pubkey) => Some(pubkey.to_string()),
|
||||
|
@ -42,7 +42,7 @@ pub fn parse_token(data: &[u8]) -> Result<TokenAccountType, ParseAccountError> {
|
|||
}))
|
||||
} else if data.len() == size_of::<Multisig>() {
|
||||
let multisig: Multisig = *State::unpack(&mut data)
|
||||
.map_err(|_| ParseAccountError::AccountNotParsable(ParsableAccount::Token))?;
|
||||
.map_err(|_| ParseAccountError::AccountNotParsable(ParsableAccount::SplToken))?;
|
||||
Ok(TokenAccountType::Multisig(UiMultisig {
|
||||
num_required_signers: multisig.m,
|
||||
num_valid_signers: multisig.n,
|
||||
|
@ -61,7 +61,7 @@ pub fn parse_token(data: &[u8]) -> Result<TokenAccountType, ParseAccountError> {
|
|||
}))
|
||||
} else {
|
||||
Err(ParseAccountError::AccountNotParsable(
|
||||
ParsableAccount::Token,
|
||||
ParsableAccount::SplToken,
|
||||
))
|
||||
}
|
||||
}
|
||||
|
|
|
@ -13,13 +13,16 @@ bincode = "1.3.1"
|
|||
bs58 = "0.3.1"
|
||||
Inflector = "0.11.4"
|
||||
lazy_static = "1.4.0"
|
||||
solana-account-decoder = { path = "../account-decoder", version = "1.3.0" }
|
||||
solana-sdk = { path = "../sdk", version = "1.3.0" }
|
||||
solana-stake-program = { path = "../programs/stake", version = "1.3.0" }
|
||||
solana-vote-program = { path = "../programs/vote", version = "1.3.0" }
|
||||
spl-memo-v1-0 = { package = "spl-memo", version = "1.0.4", features = ["skip-no-mangle"] }
|
||||
spl-token-v1-0 = { package = "spl-token", version = "1.0.2", features = ["skip-no-mangle"] }
|
||||
serde = "1.0.112"
|
||||
serde_derive = "1.0.103"
|
||||
serde_json = "1.0.56"
|
||||
thiserror = "1.0"
|
||||
|
||||
[package.metadata.docs.rs]
|
||||
targets = ["x86_64-unknown-linux-gnu"]
|
||||
|
|
|
@ -5,6 +5,7 @@ extern crate serde_derive;
|
|||
|
||||
pub mod parse_accounts;
|
||||
pub mod parse_instruction;
|
||||
pub mod parse_token;
|
||||
|
||||
use crate::{parse_accounts::parse_accounts, parse_instruction::parse};
|
||||
use serde_json::Value;
|
||||
|
@ -220,7 +221,11 @@ impl EncodedTransaction {
|
|||
.map(|instruction| {
|
||||
let program_id =
|
||||
instruction.program_id(&transaction.message.account_keys);
|
||||
if let Some(parsed_instruction) = parse(program_id, instruction) {
|
||||
if let Ok(parsed_instruction) = parse(
|
||||
program_id,
|
||||
instruction,
|
||||
&transaction.message.account_keys,
|
||||
) {
|
||||
UiInstruction::Parsed(parsed_instruction)
|
||||
} else {
|
||||
UiInstruction::Compiled(instruction.into())
|
||||
|
|
|
@ -1,34 +1,60 @@
|
|||
use crate::parse_token::parse_token;
|
||||
use inflector::Inflector;
|
||||
use serde_json::{json, Value};
|
||||
use solana_account_decoder::parse_token::spl_token_id_v1_0;
|
||||
use solana_sdk::{instruction::CompiledInstruction, pubkey::Pubkey};
|
||||
use std::{
|
||||
collections::HashMap,
|
||||
str::{from_utf8, FromStr},
|
||||
};
|
||||
use thiserror::Error;
|
||||
|
||||
lazy_static! {
|
||||
static ref MEMO_PROGRAM_ID: Pubkey =
|
||||
Pubkey::from_str(&spl_memo_v1_0::id().to_string()).unwrap();
|
||||
static ref TOKEN_PROGRAM_ID: Pubkey = spl_token_id_v1_0();
|
||||
static ref PARSABLE_PROGRAM_IDS: HashMap<Pubkey, ParsableProgram> = {
|
||||
let mut m = HashMap::new();
|
||||
m.insert(*MEMO_PROGRAM_ID, ParsableProgram::SplMemo);
|
||||
m.insert(*TOKEN_PROGRAM_ID, ParsableProgram::SplToken);
|
||||
m
|
||||
};
|
||||
}
|
||||
|
||||
#[derive(Debug, Serialize, Deserialize)]
|
||||
#[serde(rename_all = "camelCase")]
|
||||
enum ParsableProgram {
|
||||
SplMemo,
|
||||
#[derive(Error, Debug, PartialEq)]
|
||||
pub enum ParseInstructionError {
|
||||
#[error("{0:?} instruction not parsable")]
|
||||
InstructionNotParsable(ParsableProgram),
|
||||
|
||||
#[error("{0:?} instruction key mismatch")]
|
||||
InstructionKeyMismatch(ParsableProgram),
|
||||
|
||||
#[error("Program not parsable")]
|
||||
ProgramNotParsable,
|
||||
}
|
||||
|
||||
pub fn parse(program_id: &Pubkey, instruction: &CompiledInstruction) -> Option<Value> {
|
||||
PARSABLE_PROGRAM_IDS.get(program_id).map(|program_name| {
|
||||
let parsed_json = match program_name {
|
||||
ParsableProgram::SplMemo => parse_memo(instruction),
|
||||
};
|
||||
json!({ format!("{:?}", program_name).to_kebab_case(): parsed_json })
|
||||
})
|
||||
#[derive(Debug, Serialize, Deserialize, PartialEq)]
|
||||
#[serde(rename_all = "camelCase")]
|
||||
pub enum ParsableProgram {
|
||||
SplMemo,
|
||||
SplToken,
|
||||
}
|
||||
|
||||
pub fn parse(
|
||||
program_id: &Pubkey,
|
||||
instruction: &CompiledInstruction,
|
||||
account_keys: &[Pubkey],
|
||||
) -> Result<Value, ParseInstructionError> {
|
||||
let program_name = PARSABLE_PROGRAM_IDS
|
||||
.get(program_id)
|
||||
.ok_or_else(|| ParseInstructionError::ProgramNotParsable)?;
|
||||
let parsed_json = match program_name {
|
||||
ParsableProgram::SplMemo => parse_memo(instruction),
|
||||
ParsableProgram::SplToken => parse_token(instruction, account_keys)?,
|
||||
};
|
||||
Ok(json!({
|
||||
format!("{:?}", program_name).to_kebab_case(): parsed_json
|
||||
}))
|
||||
}
|
||||
|
||||
fn parse_memo(instruction: &CompiledInstruction) -> Value {
|
||||
|
@ -50,11 +76,14 @@ mod test {
|
|||
"spl-memo": "🦖"
|
||||
});
|
||||
assert_eq!(
|
||||
parse(&MEMO_PROGRAM_ID, &memo_instruction),
|
||||
Some(expected_json)
|
||||
parse(&MEMO_PROGRAM_ID, &memo_instruction, &[]).unwrap(),
|
||||
expected_json
|
||||
);
|
||||
|
||||
let non_parsable_program_id = Pubkey::new(&[1; 32]);
|
||||
assert_eq!(parse(&non_parsable_program_id, &memo_instruction), None);
|
||||
assert_eq!(
|
||||
parse(&non_parsable_program_id, &memo_instruction, &[]).unwrap_err(),
|
||||
ParseInstructionError::ProgramNotParsable
|
||||
);
|
||||
}
|
||||
}
|
||||
|
|
|
@ -0,0 +1,846 @@
|
|||
use crate::parse_instruction::{ParsableProgram, ParseInstructionError};
|
||||
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<Value, 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!({
|
||||
"type": "initializeMint",
|
||||
"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(value)
|
||||
}
|
||||
TokenInstruction::InitializeAccount => {
|
||||
if instruction.accounts.len() < 3 {
|
||||
return Err(ParseInstructionError::InstructionKeyMismatch(
|
||||
ParsableProgram::SplToken,
|
||||
));
|
||||
}
|
||||
Ok(json!({
|
||||
"type": "initializeAccount",
|
||||
"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(json!({
|
||||
"type": "initializeMultisig",
|
||||
"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!({
|
||||
"type": "transfer",
|
||||
"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(value)
|
||||
}
|
||||
TokenInstruction::Approve { amount } => {
|
||||
if instruction.accounts.len() < 3 {
|
||||
return Err(ParseInstructionError::InstructionKeyMismatch(
|
||||
ParsableProgram::SplToken,
|
||||
));
|
||||
}
|
||||
let mut value = json!({
|
||||
"type": "approve",
|
||||
"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(value)
|
||||
}
|
||||
TokenInstruction::Revoke => {
|
||||
if instruction.accounts.len() < 2 {
|
||||
return Err(ParseInstructionError::InstructionKeyMismatch(
|
||||
ParsableProgram::SplToken,
|
||||
));
|
||||
}
|
||||
let mut value = json!({
|
||||
"type": "revoke",
|
||||
"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(value)
|
||||
}
|
||||
TokenInstruction::SetOwner => {
|
||||
if instruction.accounts.len() < 3 {
|
||||
return Err(ParseInstructionError::InstructionKeyMismatch(
|
||||
ParsableProgram::SplToken,
|
||||
));
|
||||
}
|
||||
let mut value = json!({
|
||||
"type": "setOwner",
|
||||
"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(value)
|
||||
}
|
||||
TokenInstruction::MintTo { amount } => {
|
||||
if instruction.accounts.len() < 3 {
|
||||
return Err(ParseInstructionError::InstructionKeyMismatch(
|
||||
ParsableProgram::SplToken,
|
||||
));
|
||||
}
|
||||
let mut value = json!({
|
||||
"type": "mintTo",
|
||||
"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(value)
|
||||
}
|
||||
TokenInstruction::Burn { amount } => {
|
||||
if instruction.accounts.len() < 2 {
|
||||
return Err(ParseInstructionError::InstructionKeyMismatch(
|
||||
ParsableProgram::SplToken,
|
||||
));
|
||||
}
|
||||
let mut value = json!({
|
||||
"type": "burn",
|
||||
"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(value)
|
||||
}
|
||||
TokenInstruction::CloseAccount => {
|
||||
if instruction.accounts.len() < 3 {
|
||||
return Err(ParseInstructionError::InstructionKeyMismatch(
|
||||
ParsableProgram::SplToken,
|
||||
));
|
||||
}
|
||||
let mut value = json!({
|
||||
"type": "closeAccount",
|
||||
"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(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(),
|
||||
json!({
|
||||
"type": "initializeMint",
|
||||
"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(),
|
||||
json!({
|
||||
"type": "initializeMint",
|
||||
"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(),
|
||||
json!({
|
||||
"type": "initializeMint",
|
||||
"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(),
|
||||
json!({
|
||||
"type": "initializeAccount",
|
||||
"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(),
|
||||
json!({
|
||||
"type": "initializeMultisig",
|
||||
"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(),
|
||||
json!({
|
||||
"type": "transfer",
|
||||
"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(),
|
||||
json!({
|
||||
"type": "transfer",
|
||||
"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(),
|
||||
json!({
|
||||
"type": "approve",
|
||||
"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(),
|
||||
json!({
|
||||
"type": "approve",
|
||||
"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(),
|
||||
json!({
|
||||
"type": "revoke",
|
||||
"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(),
|
||||
json!({
|
||||
"type": "setOwner",
|
||||
"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(),
|
||||
json!({
|
||||
"type": "mintTo",
|
||||
"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(),
|
||||
json!({
|
||||
"type": "burn",
|
||||
"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(),
|
||||
json!({
|
||||
"type": "closeAccount",
|
||||
"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());
|
||||
}
|
||||
}
|
Loading…
Reference in New Issue