Skip to content
Closed
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
53 changes: 53 additions & 0 deletions clients/rust-legacy/tests/cpi_guard.rs
Original file line number Diff line number Diff line change
Expand Up @@ -9,7 +9,9 @@ use {
solana_sdk::{
instruction::InstructionError, pubkey::Pubkey, rent::Rent, signature::Signer,
signer::keypair::Keypair, transaction::TransactionError, transport::TransportError,
transaction::Transaction,
},
solana_system_interface::instruction as system_instruction,
spl_instruction_padding_interface::instruction::wrap_instruction,
spl_token_2022_interface::{
error::TokenError,
Expand Down Expand Up @@ -685,6 +687,57 @@ async fn test_cpi_guard_unwrap_lamports() {
assert_eq!(alice_state.base.amount, amount);
}

#[tokio::test]
async fn test_cpi_guard_withdraw_excess_lamports() {
let context = make_context_with_new_mint().await;
let program_context = context.context.clone();
let TokenContext {
token, alice, bob, ..
} = context.token_context.unwrap();

let withdraw_excess_lamports = [wrap_instruction(
spl_instruction_padding_interface::id(),
instruction::withdraw_excess_lamports(
&spl_token_2022_interface::id(),
&alice.pubkey(),
&bob.pubkey(),
&alice.pubkey(),
&[],
)
.unwrap(),
vec![],
0,
)
.unwrap()];

token
.enable_cpi_guard(&alice.pubkey(), &alice.pubkey(), &[&alice])
.await
.unwrap();

{
let context = program_context.lock().await;
let instructions = vec![system_instruction::transfer(
&context.payer.pubkey(),
&alice.pubkey(),
1,
)];
let tx = Transaction::new_signed_with_payer(
&instructions,
Some(&context.payer.pubkey()),
&[&context.payer],
context.last_blockhash,
);
context.banks_client.process_transaction(tx).await.unwrap();
}

let error = token
.process_ixs(&withdraw_excess_lamports, &[&alice])
.await
.expect_err("expected CPI withdraw_excess_lamports to be blocked by CPI Guard");
assert_eq!(error, client_error(TokenError::CpiGuardTransferBlocked));
}

async fn make_close_test_account<S: Signer>(
token: &Token<ProgramBanksClientProcessTransaction>,
owner: &S,
Expand Down
54 changes: 53 additions & 1 deletion clients/rust-legacy/tests/token_metadata_initialize.rs
Original file line number Diff line number Diff line change
Expand Up @@ -9,7 +9,9 @@ use {
},
spl_token_2022_interface::{error::TokenError, extension::BaseStateWithExtensions},
spl_token_client::token::{ExtensionInitializationParams, TokenError as TokenClientError},
spl_token_metadata_interface::{error::TokenMetadataError, state::TokenMetadata},
spl_token_metadata_interface::{
error::TokenMetadataError, instruction::Initialize, state::TokenMetadata,
},
std::{convert::TryInto, sync::Arc},
};

Expand Down Expand Up @@ -282,3 +284,53 @@ async fn fail_without_signature() {
)))
);
}

#[tokio::test]
async fn fail_initialize_with_forged_name_length_returns_invalid_instruction_data() {
let authority = Pubkey::new_unique();
let mint_keypair = Keypair::new();
let mut test_context = setup(mint_keypair, &authority).await;

let token_context = test_context.token_context.take().unwrap();

let name = "Name".to_string();
let symbol = "SYM".to_string();
let uri = "uri".to_string();
let payload = borsh::to_vec(&Initialize {
name: name.clone(),
symbol: symbol.clone(),
uri: uri.clone(),
})
.unwrap();
let mut instruction = spl_token_metadata_interface::instruction::initialize(
&spl_token_2022_interface::id(),
token_context.token.get_address(),
&Pubkey::new_unique(),
token_context.token.get_address(),
&token_context.mint_authority.pubkey(),
name,
symbol,
uri,
);

let payload_offset = instruction.data.len().checked_sub(payload.len()).unwrap();
let length_prefix_len = std::mem::size_of::<u32>();
instruction.data[payload_offset..payload_offset + length_prefix_len]
.copy_from_slice(&u32::MAX.to_le_bytes());

let error = token_context
.token
.process_ixs(&[instruction], &[&token_context.mint_authority])
.await
.unwrap_err();

// Once the metadata decoder is hardened, malformed recognized payloads
// should reject as InvalidInstructionData instead of aborting or falling
// through to another error shape.
assert_eq!(
error,
TokenClientError::Client(Box::new(TransportError::TransactionError(
TransactionError::InstructionError(0, InstructionError::InvalidInstructionData)
)))
);
}
64 changes: 63 additions & 1 deletion clients/rust-legacy/tests/token_metadata_remove_key.rs
Original file line number Diff line number Diff line change
Expand Up @@ -14,7 +14,7 @@ use {
spl_token_client::token::{ExtensionInitializationParams, TokenError as TokenClientError},
spl_token_metadata_interface::{
error::TokenMetadataError,
instruction::remove_key,
instruction::{remove_key, RemoveKey},
state::{Field, TokenMetadata},
},
std::{convert::TryInto, sync::Arc},
Expand Down Expand Up @@ -250,3 +250,65 @@ async fn fail_authority_checks() {
TransactionError::InstructionError(0, InstructionError::MissingRequiredSignature,)
);
}

#[tokio::test]
async fn fail_remove_key_with_forged_key_length_returns_invalid_instruction_data() {
let authority = Keypair::new();
let mint_keypair = Keypair::new();
let mut test_context = setup(mint_keypair, &authority.pubkey()).await;
let payer_pubkey = test_context.context.lock().await.payer.pubkey();
let token_context = test_context.token_context.take().unwrap();

let update_authority = Keypair::new();
token_context
.token
.token_metadata_initialize_with_rent_transfer(
&payer_pubkey,
&update_authority.pubkey(),
&token_context.mint_authority.pubkey(),
"MySuperCoolToken".to_string(),
"MINE".to_string(),
"my.super.cool.token".to_string(),
&[&token_context.mint_authority],
)
.await
.unwrap();

let key = "new_name".to_string();
let idempotent = true;
let idempotent_prefix = borsh::to_vec(&idempotent).unwrap();
let payload = borsh::to_vec(&RemoveKey {
key: key.clone(),
idempotent,
})
.unwrap();
let mut instruction = remove_key(
&spl_token_2022_interface::id(),
token_context.token.get_address(),
&update_authority.pubkey(),
key,
idempotent,
);

let payload_offset = instruction.data.len().checked_sub(payload.len()).unwrap();
let key_length_offset = payload_offset + idempotent_prefix.len();
let length_prefix_len = std::mem::size_of::<u32>();
instruction.data[key_length_offset..key_length_offset + length_prefix_len]
.copy_from_slice(&u32::MAX.to_le_bytes());

let error = token_context
.token
.process_ixs(&[instruction], &[&update_authority])
.await
.unwrap_err();

// Once the metadata decoder is hardened, malformed recognized payloads
// should reject as InvalidInstructionData instead of aborting or falling
// through to another error shape.
assert_eq!(
error,
TokenClientError::Client(Box::new(TransportError::TransactionError(
TransactionError::InstructionError(0, InstructionError::InvalidInstructionData)
)))
);
}
64 changes: 63 additions & 1 deletion clients/rust-legacy/tests/token_metadata_update_field.rs
Original file line number Diff line number Diff line change
Expand Up @@ -11,7 +11,7 @@ use {
spl_token_client::token::{ExtensionInitializationParams, TokenError as TokenClientError},
spl_token_metadata_interface::{
error::TokenMetadataError,
instruction::update_field,
instruction::{update_field, UpdateField},
state::{Field, TokenMetadata},
},
std::{convert::TryInto, sync::Arc},
Expand Down Expand Up @@ -198,3 +198,65 @@ async fn fail_authority_checks() {
)))
);
}

#[tokio::test]
async fn fail_update_field_with_forged_value_length_returns_invalid_instruction_data() {
let authority = Keypair::new();
let mint_keypair = Keypair::new();
let mut test_context = setup(mint_keypair, &authority.pubkey()).await;
let payer_pubkey = test_context.context.lock().await.payer.pubkey();
let token_context = test_context.token_context.take().unwrap();

let update_authority = Keypair::new();
token_context
.token
.token_metadata_initialize_with_rent_transfer(
&payer_pubkey,
&update_authority.pubkey(),
&token_context.mint_authority.pubkey(),
"MySuperCoolToken".to_string(),
"MINE".to_string(),
"my.super.cool.token".to_string(),
&[&token_context.mint_authority],
)
.await
.unwrap();

let field = Field::Name;
let value = "new_name".to_string();
let field_prefix = borsh::to_vec(&field).unwrap();
let payload = borsh::to_vec(&UpdateField {
field: field.clone(),
value: value.clone(),
})
.unwrap();
let mut instruction = update_field(
&spl_token_2022_interface::id(),
token_context.token.get_address(),
&update_authority.pubkey(),
field,
value,
);

let payload_offset = instruction.data.len().checked_sub(payload.len()).unwrap();
let value_length_offset = payload_offset + field_prefix.len();
let length_prefix_len = std::mem::size_of::<u32>();
instruction.data[value_length_offset..value_length_offset + length_prefix_len]
.copy_from_slice(&u32::MAX.to_le_bytes());

let error = token_context
.token
.process_ixs(&[instruction], &[&update_authority])
.await
.unwrap_err();

// Once the metadata decoder is hardened, malformed recognized payloads
// should reject as InvalidInstructionData instead of aborting or falling
// through to another error shape.
assert_eq!(
error,
TokenClientError::Client(Box::new(TransportError::TransactionError(
TransactionError::InstructionError(0, InstructionError::InvalidInstructionData)
)))
);
}
Loading
Loading