use {
bincode::{deserialize, serialized_size},
litesvm::LiteSVM,
serde::{Deserialize, Serialize},
solana_account::{Account, ReadableAccount},
solana_address::Address,
solana_config_interface::{
instruction::{create_account_with_max_config_space, store},
state::ConfigKeys,
},
solana_instruction::{error::InstructionError, AccountMeta},
solana_keypair::Keypair,
solana_rent::Rent,
solana_signer::Signer,
solana_transaction::Transaction,
solana_transaction_error::TransactionError,
};
#[derive(Serialize, Deserialize, Debug, PartialEq, Eq)]
struct MyConfig {
pub item: u64,
}
impl Default for MyConfig {
fn default() -> Self {
Self { item: 123_456_789 }
}
}
impl MyConfig {
pub fn new(item: u64) -> Self {
Self { item }
}
pub fn deserialize(input: &[u8]) -> Option<Self> {
deserialize(input).ok()
}
}
pub fn get_config_data(bytes: &[u8]) -> Result<&[u8], bincode::Error> {
bincode::deserialize::<ConfigKeys>(bytes)
.and_then(|keys| bincode::serialized_size(&keys))
.map(|offset| &bytes[offset as usize..])
}
struct TestContext {
svm: LiteSVM,
payer: Keypair,
}
fn setup_test_context() -> TestContext {
let payer = Keypair::new();
let mut svm = LiteSVM::new();
svm.airdrop(&payer.pubkey(), 1_000_000_000).unwrap();
TestContext { svm, payer }
}
fn get_config_space(key_len: usize) -> usize {
let entry_size = bincode::serialized_size(&(Address::default(), true)).unwrap() as usize;
bincode::serialized_size(&(ConfigKeys::default(), MyConfig::default())).unwrap() as usize
+ key_len * entry_size
}
fn create_config_account(
ctx: &mut TestContext,
config_keypair: &Keypair,
keys: Vec<(Address, bool)>,
) {
let payer = &ctx.payer;
let space = get_config_space(keys.len());
let max_space = serialized_size(&MyConfig::default()).unwrap();
let lamports = ctx.svm.get_sysvar::<Rent>().minimum_balance(space);
let instructions = create_account_with_max_config_space::<MyConfig>(
&payer.pubkey(),
&config_keypair.pubkey(),
lamports,
max_space,
keys,
);
ctx.svm
.send_transaction(Transaction::new_signed_with_payer(
&instructions,
Some(&payer.pubkey()),
&[payer, config_keypair],
ctx.svm.latest_blockhash(),
))
.unwrap();
}
#[test]
fn test_process_create_ok() {
let mut context = setup_test_context();
let config_keypair = Keypair::new();
create_config_account(&mut context, &config_keypair, vec![]);
let config_account = context.svm.get_account(&config_keypair.pubkey()).unwrap();
assert_eq!(
Some(MyConfig::default()),
deserialize(get_config_data(config_account.data()).unwrap()).ok()
);
}
#[test]
fn test_process_store_ok() {
let mut context = setup_test_context();
let config_keypair = Keypair::new();
let keys = vec![];
let my_config = MyConfig::new(42);
create_config_account(&mut context, &config_keypair, keys.clone());
let instruction = store(&config_keypair.pubkey(), true, keys, &my_config);
let payer = &context.payer;
context
.svm
.send_transaction(Transaction::new_signed_with_payer(
&[instruction],
Some(&payer.pubkey()),
&[payer, &config_keypair],
context.svm.latest_blockhash(),
))
.unwrap();
let config_account = context.svm.get_account(&config_keypair.pubkey()).unwrap();
assert_eq!(
Some(my_config),
deserialize(get_config_data(config_account.data()).unwrap()).ok()
);
}
#[test_log::test]
fn test_process_store_fail_instruction_data_too_large() {
let mut context = setup_test_context();
let config_keypair = Keypair::new();
let keys = vec![];
let my_config = MyConfig::new(42);
create_config_account(&mut context, &config_keypair, keys.clone());
let mut instruction = store(&config_keypair.pubkey(), true, keys, &my_config);
instruction.data = vec![0; 123]; let payer = &context.payer;
let err = context
.svm
.send_transaction(Transaction::new_signed_with_payer(
&[instruction],
Some(&payer.pubkey()),
&[payer, &config_keypair],
context.svm.latest_blockhash(),
))
.unwrap_err()
.err;
assert_eq!(
err,
TransactionError::InstructionError(0, InstructionError::InvalidInstructionData)
);
}
#[test]
fn test_process_store_fail_account0_not_signer() {
let mut context = setup_test_context();
let config_keypair = Keypair::new();
let keys = vec![];
let my_config = MyConfig::new(42);
create_config_account(&mut context, &config_keypair, keys.clone());
let mut instruction = store(&config_keypair.pubkey(), true, keys, &my_config);
let payer = &context.payer;
instruction.accounts[0].is_signer = false;
let err = context
.svm
.send_transaction(Transaction::new_signed_with_payer(
&[instruction],
Some(&payer.pubkey()),
&[&payer],
context.svm.latest_blockhash(),
))
.unwrap_err()
.err;
assert_eq!(
err,
TransactionError::InstructionError(0, InstructionError::MissingRequiredSignature)
);
}
#[test]
fn test_process_store_with_additional_signers() {
let mut context = setup_test_context();
let config_keypair = Keypair::new();
let pubkey = Address::new_unique();
let signer0 = Keypair::new();
let signer1 = Keypair::new();
let keys = vec![
(pubkey, false),
(signer0.pubkey(), true),
(signer1.pubkey(), true),
];
let my_config = MyConfig::new(42);
create_config_account(&mut context, &config_keypair, keys.clone());
let instruction = store(&config_keypair.pubkey(), true, keys.clone(), &my_config);
let payer = &context.payer;
context
.svm
.send_transaction(Transaction::new_signed_with_payer(
&[instruction],
Some(&payer.pubkey()),
&[payer, &config_keypair, &signer0, &signer1],
context.svm.latest_blockhash(),
))
.unwrap();
let config_account = context.svm.get_account(&config_keypair.pubkey()).unwrap();
let config_state: ConfigKeys = deserialize(config_account.data()).unwrap();
assert_eq!(config_state.keys, keys);
assert_eq!(
Some(my_config),
deserialize(get_config_data(config_account.data()).unwrap()).ok()
);
}
#[test]
fn test_process_store_bad_config_account() {
let mut context = setup_test_context();
let config_keypair = Keypair::new();
let pubkey = Address::new_unique();
let signer0 = Keypair::new();
let keys = vec![(pubkey, false), (signer0.pubkey(), true)];
let my_config = MyConfig::new(42);
context
.svm
.set_account(
signer0.pubkey(),
Account::new(100_000, 0, &solana_config_interface::id()),
)
.unwrap();
create_config_account(&mut context, &config_keypair, keys.clone());
let payer = &context.payer;
let mut instruction = store(&config_keypair.pubkey(), false, keys, &my_config);
instruction.accounts.remove(0);
let err = context
.svm
.send_transaction(Transaction::new_signed_with_payer(
&[instruction],
Some(&payer.pubkey()),
&[payer, &signer0],
context.svm.latest_blockhash(),
))
.unwrap_err()
.err;
assert_eq!(
err,
TransactionError::InstructionError(0, InstructionError::InvalidAccountData)
);
}
#[test]
fn test_process_store_with_bad_additional_signer() {
let mut context = setup_test_context();
let config_keypair = Keypair::new();
let bad_signer = Keypair::new();
let signer0 = Keypair::new();
let keys = vec![(signer0.pubkey(), true)];
let my_config = MyConfig::new(42);
create_config_account(&mut context, &config_keypair, keys.clone());
let payer = &context.payer;
let mut instruction = store(&config_keypair.pubkey(), true, keys.clone(), &my_config);
instruction.accounts[1] = AccountMeta::new(bad_signer.pubkey(), true);
let err = context
.svm
.send_transaction(Transaction::new_signed_with_payer(
&[instruction],
Some(&payer.pubkey()),
&[payer, &config_keypair, &bad_signer],
context.svm.latest_blockhash(),
))
.unwrap_err()
.err;
assert_eq!(
err,
TransactionError::InstructionError(0, InstructionError::MissingRequiredSignature)
);
let mut instruction = store(&config_keypair.pubkey(), true, keys, &my_config);
instruction.accounts[1].is_signer = false;
let err = context
.svm
.send_transaction(Transaction::new_signed_with_payer(
&[instruction],
Some(&payer.pubkey()),
&[payer, &config_keypair],
context.svm.latest_blockhash(),
))
.unwrap_err()
.err;
assert_eq!(
err,
TransactionError::InstructionError(0, InstructionError::MissingRequiredSignature)
);
}
#[test]
fn test_config_updates() {
let mut context = setup_test_context();
let config_keypair = Keypair::new();
let pubkey = Address::new_unique();
let signer0 = Keypair::new();
let signer1 = Keypair::new();
let signer2 = Keypair::new();
let keys = vec![
(pubkey, false),
(signer0.pubkey(), true),
(signer1.pubkey(), true),
];
let my_config = MyConfig::new(42);
create_config_account(&mut context, &config_keypair, keys.clone());
let payer = &context.payer;
let instruction = store(&config_keypair.pubkey(), true, keys.clone(), &my_config);
context
.svm
.send_transaction(Transaction::new_signed_with_payer(
&[instruction],
Some(&payer.pubkey()),
&[payer, &config_keypair, &signer0, &signer1],
context.svm.latest_blockhash(),
))
.unwrap();
let new_config = MyConfig::new(84);
let instruction = store(&config_keypair.pubkey(), false, keys.clone(), &new_config);
context
.svm
.send_transaction(Transaction::new_signed_with_payer(
&[instruction],
Some(&payer.pubkey()),
&[payer, &signer0, &signer1],
context.svm.latest_blockhash(),
))
.unwrap();
let config_account = context.svm.get_account(&config_keypair.pubkey()).unwrap();
let config_state: ConfigKeys = deserialize(config_account.data()).unwrap();
assert_eq!(config_state.keys, keys);
assert_eq!(
new_config,
MyConfig::deserialize(get_config_data(config_account.data()).unwrap()).unwrap()
);
let keys = vec![
(pubkey, false),
(signer0.pubkey(), true), ];
let instruction = store(&config_keypair.pubkey(), false, keys, &my_config);
let err = context
.svm
.send_transaction(Transaction::new_signed_with_payer(
&[instruction],
Some(&payer.pubkey()),
&[payer, &signer0], context.svm.latest_blockhash(),
))
.unwrap_err()
.err;
assert_eq!(
err,
TransactionError::InstructionError(0, InstructionError::MissingRequiredSignature)
);
let keys = vec![
(pubkey, false),
(signer0.pubkey(), true),
(signer2.pubkey(), true), ];
let instruction = store(&config_keypair.pubkey(), false, keys, &my_config);
let err = context
.svm
.send_transaction(Transaction::new_signed_with_payer(
&[instruction],
Some(&payer.pubkey()),
&[payer, &signer0, &signer2], context.svm.latest_blockhash(),
))
.unwrap_err()
.err;
assert_eq!(
err,
TransactionError::InstructionError(0, InstructionError::MissingRequiredSignature)
);
}
#[test]
fn test_config_initialize_contains_duplicates_fails() {
let mut context = setup_test_context();
let config_keypair = Keypair::new();
let pubkey = Address::new_unique();
let signer0 = Keypair::new();
let keys = vec![
(pubkey, false),
(signer0.pubkey(), true),
(signer0.pubkey(), true), ];
let my_config = MyConfig::new(42);
create_config_account(&mut context, &config_keypair, keys.clone());
let payer = &context.payer;
let instruction = store(&config_keypair.pubkey(), true, keys, &my_config);
let err = context
.svm
.send_transaction(Transaction::new_signed_with_payer(
&[instruction],
Some(&payer.pubkey()),
&[payer, &config_keypair, &signer0],
context.svm.latest_blockhash(),
))
.unwrap_err()
.err;
assert_eq!(
err,
TransactionError::InstructionError(0, InstructionError::InvalidArgument)
);
}
#[test]
fn test_config_update_contains_duplicates_fails() {
let mut context = setup_test_context();
let config_keypair = Keypair::new();
let pubkey = Address::new_unique();
let signer0 = Keypair::new();
let signer1 = Keypair::new();
let keys = vec![
(pubkey, false),
(signer0.pubkey(), true),
(signer1.pubkey(), true),
];
let my_config = MyConfig::new(42);
create_config_account(&mut context, &config_keypair, keys.clone());
let payer = &context.payer;
let instruction = store(&config_keypair.pubkey(), true, keys, &my_config);
context
.svm
.send_transaction(Transaction::new_signed_with_payer(
&[instruction],
Some(&payer.pubkey()),
&[payer, &config_keypair, &signer0, &signer1],
context.svm.latest_blockhash(),
))
.unwrap();
let new_config = MyConfig::new(84);
let dupe_keys = vec![
(pubkey, false),
(signer0.pubkey(), true),
(signer0.pubkey(), true), ];
let instruction = store(&config_keypair.pubkey(), true, dupe_keys, &new_config);
let err = context
.svm
.send_transaction(Transaction::new_signed_with_payer(
&[instruction],
Some(&payer.pubkey()),
&[payer, &config_keypair, &signer0],
context.svm.latest_blockhash(),
))
.unwrap_err()
.err;
assert_eq!(
err,
TransactionError::InstructionError(0, InstructionError::InvalidArgument)
);
}
#[test]
fn test_config_updates_requiring_config() {
let mut context = setup_test_context();
let config_keypair = Keypair::new();
let pubkey = Address::new_unique();
let signer0 = Keypair::new();
let keys = vec![
(pubkey, false),
(signer0.pubkey(), true),
(config_keypair.pubkey(), true),
];
let my_config = MyConfig::new(42);
create_config_account(
&mut context,
&config_keypair,
vec![(pubkey, false), (pubkey, false), (pubkey, false)], );
let payer = &context.payer;
let instruction = store(&config_keypair.pubkey(), true, keys.clone(), &my_config);
context
.svm
.send_transaction(Transaction::new_signed_with_payer(
&[instruction],
Some(&payer.pubkey()),
&[payer, &config_keypair, &signer0],
context.svm.latest_blockhash(),
))
.unwrap();
let new_config = MyConfig::new(84);
let instruction = store(&config_keypair.pubkey(), true, keys.clone(), &new_config);
context
.svm
.send_transaction(Transaction::new_signed_with_payer(
&[instruction],
Some(&payer.pubkey()),
&[payer, &config_keypair, &signer0],
context.svm.latest_blockhash(),
))
.unwrap();
let config_account = context.svm.get_account(&config_keypair.pubkey()).unwrap();
let config_state: ConfigKeys = deserialize(config_account.data()).unwrap();
assert_eq!(config_state.keys, keys);
assert_eq!(
Some(new_config),
deserialize(get_config_data(config_account.data()).unwrap()).ok()
);
let keys = vec![(pubkey, false), (config_keypair.pubkey(), true)]; let instruction = store(&config_keypair.pubkey(), true, keys, &my_config);
let err = context
.svm
.send_transaction(Transaction::new_signed_with_payer(
&[instruction],
Some(&payer.pubkey()),
&[payer, &config_keypair], context.svm.latest_blockhash(),
))
.unwrap_err()
.err;
assert_eq!(
err,
TransactionError::InstructionError(0, InstructionError::MissingRequiredSignature)
);
}
#[test]
#[allow(deprecated)]
fn test_config_initialize_no_panic() {
let mut context = setup_test_context();
let config_keypair = Keypair::new();
create_config_account(&mut context, &config_keypair, vec![]);
let payer = &context.payer;
let max_space = serialized_size(&MyConfig::default()).unwrap();
let instructions = create_account_with_max_config_space::<MyConfig>(
&payer.pubkey(),
&config_keypair.pubkey(),
1,
max_space,
vec![],
);
let mut instruction = instructions[1].clone();
instruction.accounts = vec![];
let err = context
.svm
.send_transaction(Transaction::new_signed_with_payer(
&[instruction],
Some(&payer.pubkey()),
&[&payer],
context.svm.latest_blockhash(),
))
.unwrap_err()
.err;
assert_eq!(
err,
TransactionError::InstructionError(0, InstructionError::NotEnoughAccountKeys)
);
}
#[test]
fn test_config_bad_owner() {
let mut context = setup_test_context();
let config_keypair = Keypair::new();
let pubkey = Address::new_unique();
let signer0 = Keypair::new();
let keys = vec![
(pubkey, false),
(signer0.pubkey(), true),
(config_keypair.pubkey(), true),
];
let my_config = MyConfig::new(42);
let space = get_config_space(keys.len());
let lamports = context.svm.get_sysvar::<Rent>().minimum_balance(space);
context
.svm
.set_account(
config_keypair.pubkey(),
Account::new(lamports, 0, &Address::new_unique()),
)
.unwrap();
let payer = &context.payer;
let instruction = store(&config_keypair.pubkey(), true, keys, &my_config);
let err = context
.svm
.send_transaction(Transaction::new_signed_with_payer(
&[instruction],
Some(&payer.pubkey()),
&[payer, &config_keypair, &signer0],
context.svm.latest_blockhash(),
))
.unwrap_err()
.err;
assert_eq!(
err,
TransactionError::InstructionError(0, InstructionError::InvalidAccountOwner)
);
}
#[test]
fn test_maximum_keys_input() {
let mut context = setup_test_context();
let config_keypair = Keypair::new();
let mut keys = vec![];
for _ in 0..37 {
keys.push((Address::new_unique(), false));
}
let my_config = MyConfig::new(42);
create_config_account(&mut context, &config_keypair, keys.clone());
let instruction = store(&config_keypair.pubkey(), true, keys.clone(), &my_config);
let payer = &context.payer;
context
.svm
.send_transaction(Transaction::new_signed_with_payer(
&[instruction],
Some(&payer.pubkey()),
&[payer, &config_keypair],
context.svm.latest_blockhash(),
))
.unwrap();
let config_account = context.svm.get_account(&config_keypair.pubkey()).unwrap();
assert_eq!(
Some(my_config),
deserialize(get_config_data(config_account.data()).unwrap()).ok()
);
let new_config = MyConfig::new(84);
let instruction = store(&config_keypair.pubkey(), true, keys.clone(), &new_config);
context
.svm
.send_transaction(Transaction::new_signed_with_payer(
&[instruction],
Some(&payer.pubkey()),
&[payer, &config_keypair],
context.svm.latest_blockhash(),
))
.unwrap();
keys.push((Address::new_unique(), false));
let my_config = MyConfig::new(42);
let instruction = store(&config_keypair.pubkey(), true, keys, &my_config);
let err = context
.svm
.send_transaction(Transaction::new_signed_with_payer(
&[instruction],
Some(&payer.pubkey()),
&[payer, &config_keypair],
context.svm.latest_blockhash(),
))
.unwrap_err()
.err;
assert_eq!(
err,
TransactionError::InstructionError(0, InstructionError::InvalidInstructionData)
);
}