#![cfg(feature = "program-test")]
use agave_feature_set::enable_tx_v1;
use light_client::rpc::{
build_v1_transaction, default_tx_config, with_defaults, RpcError, TransactionConfig,
DEFAULT_COMPUTE_UNIT_LIMIT, DEFAULT_LOADED_ACCOUNTS_DATA_SIZE_LIMIT, MAX_LEGACY_TX_SIZE,
MAX_TX_V1_SIZE,
};
use litesvm::LiteSVM;
use solana_instruction::Instruction;
use solana_keypair::Keypair;
use solana_message::VersionedMessage;
use solana_pubkey::Pubkey;
use solana_signer::Signer;
use solana_transaction::{versioned::VersionedTransaction, Transaction};
const TRANSFER_LAMPORTS: u64 = 1_000_000;
fn svm_with_tx_v1() -> LiteSVM {
let mut feature_set = LiteSVM::mainnet_feature_set();
feature_set.activate(&enable_tx_v1::id(), 0);
LiteSVM::new().with_feature_set(feature_set)
}
fn transfer_instructions(payer: &Pubkey, count: usize) -> (Vec<Instruction>, Vec<Pubkey>) {
let recipients: Vec<Pubkey> = (0..count).map(|_| Pubkey::new_unique()).collect();
let instructions = recipients
.iter()
.map(|to| solana_system_interface::instruction::transfer(payer, to, TRANSFER_LAMPORTS))
.collect();
(instructions, recipients)
}
fn serialized_len(transaction: &VersionedTransaction) -> usize {
bincode::serialize(transaction)
.expect("serialize versioned transaction")
.len()
}
#[test]
fn v1_transaction_above_legacy_limit_executes() {
let mut svm = svm_with_tx_v1();
let payer = Keypair::new();
svm.airdrop(&payer.pubkey(), 1_000_000_000)
.expect("airdrop");
let (instructions, recipients) = transfer_instructions(&payer.pubkey(), 30);
let blockhash = svm.latest_blockhash();
let legacy_len = bincode::serialize(&Transaction::new_signed_with_payer(
&instructions,
Some(&payer.pubkey()),
&[&payer],
blockhash,
))
.expect("serialize legacy transaction")
.len();
assert!(
legacy_len > MAX_LEGACY_TX_SIZE,
"test instructions must not fit a legacy transaction, got {legacy_len} bytes"
);
let transaction = build_v1_transaction(
&instructions,
&payer.pubkey(),
&[&payer],
blockhash,
TransactionConfig::default(),
)
.expect("build v1 transaction");
let size = serialized_len(&transaction);
assert!(
size > MAX_LEGACY_TX_SIZE && size <= MAX_TX_V1_SIZE,
"v1 transaction size {size} not in ({MAX_LEGACY_TX_SIZE}, {MAX_TX_V1_SIZE}]"
);
let result = svm.send_transaction(transaction);
assert!(result.is_ok(), "v1 transaction failed: {:?}", result.err());
let balances: Vec<u64> = recipients
.iter()
.map(|to| svm.get_account(to).map(|a| a.lamports).unwrap_or(0))
.collect();
assert_eq!(balances, vec![TRANSFER_LAMPORTS; recipients.len()]);
}
#[test]
fn v1_transaction_carries_config_in_message() {
let payer = Keypair::new();
let (instructions, _) = transfer_instructions(&payer.pubkey(), 1);
let config = TransactionConfig {
priority_fee: Some(5_000),
compute_unit_limit: Some(200_000),
loaded_accounts_data_size_limit: Some(1_000_000),
heap_size: None,
};
let transaction = build_v1_transaction(
&instructions,
&payer.pubkey(),
&[&payer],
LiteSVM::new().latest_blockhash(),
config,
)
.expect("build v1 transaction");
let message = match &transaction.message {
VersionedMessage::V1(message) => message,
other => panic!("expected a v1 message, got {other:?}"),
};
assert_eq!(message.config, config);
assert_eq!(
transaction.version(),
solana_transaction::versioned::TransactionVersion::Number(1)
);
assert_eq!(message.instructions.len(), instructions.len());
}
#[test]
fn v1_defaults_fill_zero_limits() {
let filled = with_defaults(TransactionConfig::default());
assert_eq!(
filled,
TransactionConfig {
priority_fee: None,
compute_unit_limit: Some(DEFAULT_COMPUTE_UNIT_LIMIT),
loaded_accounts_data_size_limit: Some(DEFAULT_LOADED_ACCOUNTS_DATA_SIZE_LIMIT),
heap_size: None,
}
);
assert_eq!(default_tx_config(), filled);
let explicit = TransactionConfig {
priority_fee: Some(1),
compute_unit_limit: Some(2),
loaded_accounts_data_size_limit: Some(3),
heap_size: Some(64 * 1024),
};
assert_eq!(with_defaults(explicit), explicit);
}
#[test]
fn v1_build_fails_without_payer_signature() {
let payer = Keypair::new();
let other = Keypair::new();
let (instructions, _) = transfer_instructions(&payer.pubkey(), 1);
let err = build_v1_transaction(
&instructions,
&payer.pubkey(),
&[&other],
LiteSVM::new().latest_blockhash(),
TransactionConfig::default(),
)
.expect_err("signing with the wrong key must fail");
assert!(matches!(err, RpcError::SigningError(_)), "{err:?}");
}