// file: crates/ksp-onchain-transport-lib/unit_tests/rpc_transactions.rs // version: 1 #[test] fn transaction_encoding_strings_match_current_and_legacy_wire_labels() { assert_eq!(crate::SolanaTransactionBinaryEncoding::Base58.as_str(), "base58"); assert_eq!(crate::SolanaTransactionBinaryEncoding::Base64.as_str(), "base64"); assert_eq!(crate::SolanaTransactionEncoding::Binary.as_str(), "binary"); assert_eq!(crate::SolanaTransactionEncoding::Base58.as_str(), "base58"); assert_eq!(crate::SolanaTransactionEncoding::Base64.as_str(), "base64"); assert_eq!(crate::SolanaTransactionEncoding::Json.as_str(), "json"); assert_eq!(crate::SolanaTransactionEncoding::JsonParsed.as_str(), "jsonParsed"); } #[test] fn wire_field_preserves_omitted_null_and_value_states() { #[derive(serde::Deserialize)] struct Holder { #[serde(default)] field: crate::SolanaWireField, } let omitted = serde_json::from_value::(serde_json::json!({})).expect("omitted fixture must decode"); let null = serde_json::from_value::(serde_json::json!({"field": null})).expect("null fixture must decode"); let value = serde_json::from_value::(serde_json::json!({"field": 7})).expect("value fixture must decode"); assert!(omitted.field.is_omitted()); assert!(!omitted.field.is_null()); assert_eq!(omitted.field.value(), std::option::Option::None); assert!(null.field.is_null()); assert!(!null.field.is_omitted()); assert_eq!(null.field.value(), std::option::Option::None); assert_eq!(value.field.value(), std::option::Option::Some(&7)); } #[test] fn get_transaction_config_serializes_modern_object_without_legacy_overload() { let config = crate::SolanaGetTransactionConfig::new( std::option::Option::Some(crate::SolanaCommitment::Confirmed), std::option::Option::Some(crate::SolanaTransactionEncoding::JsonParsed), std::option::Option::Some(0), ); assert!(!config.is_empty()); assert_eq!(config.to_json_value(), serde_json::json!({"commitment":"confirmed","encoding":"jsonParsed","maxSupportedTransactionVersion":0})); assert!(crate::SolanaGetTransactionConfig::default().is_empty()); assert_eq!(crate::SolanaGetTransactionConfig::default().to_json_value(), serde_json::json!({})); } #[test] fn signatures_for_address_config_preserves_pagination_and_context_order_independently() { let config = crate::SolanaSignaturesForAddressConfig::new( std::option::Option::Some("before-signature".to_owned()), std::option::Option::Some("until-signature".to_owned()), std::option::Option::Some(25), std::option::Option::Some(crate::SolanaCommitment::Finalized), std::option::Option::Some(430_000_000), ); assert_eq!(config.before(), std::option::Option::Some("before-signature")); assert_eq!(config.until(), std::option::Option::Some("until-signature")); assert_eq!(config.limit(), std::option::Option::Some(25)); assert_eq!(config.commitment(), std::option::Option::Some(crate::SolanaCommitment::Finalized)); assert_eq!(config.min_context_slot(), std::option::Option::Some(430_000_000)); assert_eq!( config.to_json_value(), serde_json::json!({ "before":"before-signature", "until":"until-signature", "limit":25, "commitment":"finalized", "minContextSlot":430000000 }) ); assert!(crate::SolanaSignaturesForAddressConfig::default().is_empty()); } #[test] fn signature_status_and_airdrop_configs_preserve_explicit_options() { let status = crate::SolanaSignatureStatusesConfig::new(std::option::Option::Some(false)); assert_eq!(status.search_transaction_history(), std::option::Option::Some(false)); assert_eq!(status.to_json_value(), serde_json::json!({"searchTransactionHistory":false})); let airdrop = crate::SolanaRequestAirdropConfig::new( std::option::Option::Some("recent-blockhash".to_owned()), std::option::Option::Some(crate::SolanaCommitment::Confirmed), ); assert_eq!(airdrop.recent_blockhash(), std::option::Option::Some("recent-blockhash")); assert_eq!(airdrop.commitment(), std::option::Option::Some(crate::SolanaCommitment::Confirmed)); assert_eq!(airdrop.to_json_value(), serde_json::json!({"recentBlockhash":"recent-blockhash","commitment":"confirmed"})); } #[test] fn send_transaction_config_keeps_node_retries_distinct_from_transport_policy() { let config = crate::SolanaSendTransactionConfig::new( std::option::Option::Some(true), std::option::Option::Some(crate::SolanaCommitment::Processed), std::option::Option::Some(crate::SolanaTransactionBinaryEncoding::Base64), std::option::Option::Some(4), std::option::Option::Some(430_000_000), ); assert_eq!(config.skip_preflight(), std::option::Option::Some(true)); assert_eq!(config.max_retries(), std::option::Option::Some(4)); assert_eq!( config.to_json_value(), serde_json::json!({ "skipPreflight":true, "preflightCommitment":"processed", "encoding":"base64", "maxRetries":4, "minContextSlot":430000000 }) ); assert!(crate::SolanaSendTransactionConfig::default().is_empty()); } #[test] fn simulate_transaction_config_serializes_accounts_and_flags_without_decoding_transaction() { let address = "11111111111111111111111111111111".parse::().expect("fixture pubkey must parse"); let accounts = crate::SolanaSimulationAccountsConfig::new(std::option::Option::Some(crate::SolanaAccountEncoding::Base64), std::vec![address]); let config = crate::SolanaSimulateTransactionConfig::new( std::option::Option::Some(crate::SolanaCommitment::Confirmed), std::option::Option::Some(crate::SolanaTransactionBinaryEncoding::Base64), std::option::Option::Some(true), std::option::Option::Some(false), std::option::Option::Some(430_000_000), std::option::Option::Some(true), std::option::Option::Some(accounts), ); assert_eq!(config.encoding(), std::option::Option::Some(crate::SolanaTransactionBinaryEncoding::Base64)); assert_eq!(config.replace_recent_blockhash(), std::option::Option::Some(true)); assert_eq!(config.sig_verify(), std::option::Option::Some(false)); assert_eq!(config.accounts().expect("accounts config must be present").addresses(), &[address]); assert_eq!( config.to_json_value(), serde_json::json!({ "commitment":"confirmed", "encoding":"base64", "replaceRecentBlockhash":true, "sigVerify":false, "minContextSlot":430000000, "innerInstructions":true, "accounts":{"encoding":"base64","addresses":["11111111111111111111111111111111"]} }) ); assert!(crate::SolanaSimulateTransactionConfig::default().is_empty()); } #[test] fn latest_blockhash_fixture_decodes_shared_result() { let value = serde_json::from_str::(include_str!("../fixtures/http/latest_blockhash.v4_2_1.json")).expect("latest blockhash fixture must decode"); let blockhash = crate::SolanaLatestBlockhash::decode_wire("getLatestBlockhash", value).expect("latest blockhash must decode"); assert_eq!(blockhash.blockhash(), "11111111111111111111111111111111"); assert_eq!(blockhash.last_valid_block_height(), 430_123_456); } #[test] fn latest_blockhash_rejects_empty_wire_value() { let result = crate::SolanaLatestBlockhash::decode_wire("getLatestBlockhash", serde_json::json!({"blockhash":"","lastValidBlockHeight":430123456})); assert_eq!(result.expect_err("empty blockhash must fail closed").code(), crate::ERROR_CODE_INVALID_RESPONSE); } #[test] fn prioritization_fee_fixture_preserves_server_order() { let values = serde_json::from_str::>(include_str!("../fixtures/http/prioritization_fee.v4_2_1.json")) .expect("prioritization fee fixture must decode"); let first = crate::SolanaPrioritizationFee::decode_wire("getRecentPrioritizationFees", values[0].clone()).expect("first fee must decode"); let second = crate::SolanaPrioritizationFee::decode_wire("getRecentPrioritizationFees", values[1].clone()).expect("second fee must decode"); assert_eq!(first.slot(), 430_000_123); assert_eq!(first.prioritization_fee(), 1_000); assert_eq!(second.slot(), 430_000_122); assert_eq!(second.prioritization_fee(), 0); } #[test] fn signature_info_fixture_accepts_current_transaction_index_and_older_omission() { let values = serde_json::from_str::>(include_str!("../fixtures/http/signature_info.v4_2_1.json")) .expect("signature info fixture must decode"); let current = crate::SolanaSignatureInfo::decode_wire("getSignaturesForAddress", values[0].clone()).expect("current signature info must decode"); let older = crate::SolanaSignatureInfo::decode_wire("getSignaturesForAddress", values[1].clone()).expect("older signature info must decode"); assert_eq!(current.slot(), 430_000_123); assert_eq!(current.memo(), std::option::Option::Some("fixture memo")); assert_eq!(current.transaction_index(), std::option::Option::Some(7)); assert_eq!(current.confirmation_status(), std::option::Option::Some(crate::SolanaTransactionConfirmationStatus::Finalized)); assert_eq!(older.transaction_index(), std::option::Option::None); assert!(older.err().is_some()); assert_eq!(older.confirmation_status(), std::option::Option::Some(crate::SolanaTransactionConfirmationStatus::Confirmed)); } #[test] fn signature_status_fixture_preserves_position_null_and_legacy_status_value() { let values = serde_json::from_str::>(include_str!("../fixtures/http/signature_status.variants.json")) .expect("signature status fixture must decode"); assert!(values[0].is_null()); let rooted = crate::SolanaSignatureStatus::decode_wire("getSignatureStatuses", values[1].clone()).expect("rooted signature status must decode"); let errored = crate::SolanaSignatureStatus::decode_wire("getSignatureStatuses", values[2].clone()).expect("errored signature status must decode"); assert_eq!(rooted.confirmations(), std::option::Option::None); assert_eq!(rooted.status(), &serde_json::json!({"Ok":null})); assert_eq!(rooted.confirmation_status(), std::option::Option::Some(crate::SolanaTransactionConfirmationStatus::Finalized)); assert_eq!(errored.confirmations(), std::option::Option::Some(3)); assert!(errored.err().is_some()); } #[test] fn unknown_confirmation_status_is_rejected_as_invalid_response() { let result = crate::SolanaSignatureStatus::decode_wire( "getSignatureStatuses", serde_json::json!({"slot":1,"confirmations":0,"status":{"Ok":null},"err":null,"confirmationStatus":"future-status"}), ); let error = result.expect_err("unknown confirmation status must fail closed"); assert_eq!(error.code(), crate::ERROR_CODE_INVALID_RESPONSE); } #[test] fn encoded_transaction_fixture_preserves_legacy_binary_tuple_and_json_shapes() { let values = serde_json::from_str::>(include_str!("../fixtures/http/encoded_transaction.variants.json")) .expect("encoded transaction fixture must decode"); let legacy = crate::SolanaEncodedTransaction::decode_wire("getTransaction", values[0].clone()).expect("legacy transaction must decode"); let binary = crate::SolanaEncodedTransaction::decode_wire("getTransaction", values[1].clone()).expect("binary transaction must decode"); let json = crate::SolanaEncodedTransaction::decode_wire("getTransaction", values[2].clone()).expect("JSON transaction must decode"); assert!(matches!(legacy, crate::SolanaEncodedTransaction::LegacyBinary(_))); assert!(matches!(binary, crate::SolanaEncodedTransaction::Binary { encoding: crate::SolanaTransactionBinaryEncoding::Base64, .. })); assert!(matches!(json, crate::SolanaEncodedTransaction::Json(_))); let invalid = crate::SolanaEncodedTransaction::decode_wire("getTransaction", serde_json::json!(["payload", "json"])); assert_eq!(invalid.expect_err("non-binary tuple encoding must fail").code(), crate::ERROR_CODE_INVALID_RESPONSE); } #[test] fn confirmed_transaction_fixture_preserves_meta_version_and_transaction_index_states() { let values = serde_json::from_str::>(include_str!("../fixtures/http/confirmed_transaction.variants.json")) .expect("confirmed transaction fixture must decode"); let current = crate::SolanaConfirmedTransaction::decode_wire("getTransaction", values[0].clone()).expect("current transaction must decode"); let legacy = crate::SolanaConfirmedTransaction::decode_wire("getTransaction", values[1].clone()).expect("legacy transaction must decode"); let omitted = crate::SolanaConfirmedTransaction::decode_wire("getTransaction", values[2].clone()).expect("omitted-field transaction must decode"); assert_eq!(current.slot(), 430_000_123); assert_eq!(current.version().value(), std::option::Option::Some(&crate::SolanaTransactionVersion::Number(0))); assert_eq!(current.transaction_index().value(), std::option::Option::Some(&7)); assert!(current.meta().value().is_some()); assert_eq!(legacy.version().value(), std::option::Option::Some(&crate::SolanaTransactionVersion::Legacy)); assert!(legacy.meta().is_null()); assert!(legacy.transaction_index().is_null()); assert!(omitted.meta().is_omitted()); assert!(omitted.version().is_omitted()); assert!(omitted.transaction_index().is_omitted()); } #[test] fn confirmed_transaction_rejects_unknown_version_shape() { let result = crate::SolanaConfirmedTransaction::decode_wire( "getTransaction", serde_json::json!({"slot":1,"blockTime":null,"transaction":"legacy","meta":null,"version":"v0"}), ); assert_eq!(result.expect_err("unknown version label must fail").code(), crate::ERROR_CODE_INVALID_RESPONSE); } #[test] fn simulation_result_fixture_preserves_rich_optional_fields_and_positional_null_accounts() { let value = serde_json::from_str::(include_str!("../fixtures/http/simulate_transaction_result.v4_2_1.json")) .expect("simulation result fixture must decode"); let result = crate::SolanaSimulateTransactionResult::decode_wire("simulateTransaction", value).expect("simulation result must decode"); assert!(result.err().is_null()); assert_eq!(result.logs().value().expect("logs must be present").len(), 2); let accounts = result.accounts().value().expect("accounts must be present"); assert_eq!(accounts.len(), 2); assert_eq!(accounts[0].as_ref().expect("first account must be present").lamports(), 1_000); assert!(accounts[1].is_none()); assert_eq!(result.units_consumed().value(), std::option::Option::Some(&150)); assert_eq!(result.loaded_accounts_data_size().value(), std::option::Option::Some(&64)); assert_eq!(result.fee().value(), std::option::Option::Some(&5_000)); let replacement = result.replacement_blockhash().value().expect("replacement blockhash must be present"); assert_eq!(replacement.blockhash(), "ComputeBudget111111111111111111111111111111"); assert!(result.return_data().value().is_some()); assert!(result.inner_instructions().value().is_some()); assert!(result.pre_token_balances().value().is_some()); assert!(result.post_token_balances().value().is_some()); assert!(result.loaded_addresses().value().is_some()); } #[test] fn simulation_result_distinguishes_omitted_from_explicit_null_fields() { let result = crate::SolanaSimulateTransactionResult::decode_wire("simulateTransaction", serde_json::json!({"err":null,"logs":null,"unitsConsumed":1})) .expect("partial simulation result must decode"); assert!(result.err().is_null()); assert!(result.logs().is_null()); assert_eq!(result.units_consumed().value(), std::option::Option::Some(&1)); assert!(result.accounts().is_omitted()); assert!(result.replacement_blockhash().is_omitted()); assert!(result.loaded_addresses().is_omitted()); }