// file: crates/ksp-interface-lib/unit_tests/program_instruction.rs // version: 2 fn meta(byte: u8, writable: bool) -> crate::ProgramAccountMeta { let pubkey = crate::Pubkey::new_from_array([byte; 32]); if writable { return crate::ProgramAccountMeta::writable(pubkey, false); } return crate::ProgramAccountMeta::readonly(pubkey, false); } #[test] fn instruction_accepts_empty_accounts_and_data_with_opaque_program_id() { let program_id = crate::Pubkey::new_from_array([0xD3_u8; 32]); let instruction = crate::ProgramInstruction::try_new(program_id, std::vec::Vec::new(), std::vec::Vec::new()); assert!(instruction.is_ok()); let instruction = match instruction { std::result::Result::Ok(value) => value, std::result::Result::Err(_) => return, }; assert_eq!(instruction.program_id(), &program_id); assert!(instruction.accounts().is_empty()); assert!(instruction.data().is_empty()); return; } #[test] fn instruction_preserves_account_order_duplicates_and_opaque_data() { let program_id = crate::Pubkey::new_from_array([0x41_u8; 32]); let first = meta(1, false); let duplicate = meta(2, true); let accounts = std::vec![first, duplicate, first]; let data = std::vec![0_u8, 1, 2, 0xFF]; let instruction = crate::ProgramInstruction::try_new(program_id, accounts, data.clone()); assert!(instruction.is_ok()); let instruction = match instruction { std::result::Result::Ok(value) => value, std::result::Result::Err(_) => return, }; assert_eq!(instruction.accounts(), &[first, duplicate, first]); assert_eq!(instruction.data(), data.as_slice()); return; } #[test] fn instruction_accepts_account_limit_and_rejects_one_above_it() { let program_id = crate::Pubkey::new_from_array([0x51_u8; 32]); let account = meta(7, false); let accepted = crate::ProgramInstruction::try_new(program_id, std::vec![account; crate::MAX_PROGRAM_INSTRUCTION_ACCOUNTS], std::vec::Vec::new()); assert!(accepted.is_ok()); let rejected = crate::ProgramInstruction::try_new(program_id, std::vec![account; crate::MAX_PROGRAM_INSTRUCTION_ACCOUNTS + 1], std::vec::Vec::new()); assert!(rejected.is_err()); let error = match rejected { std::result::Result::Err(value) => value, std::result::Result::Ok(_) => return, }; assert_eq!(error.code(), crate::ERROR_CODE_PROGRAM_INSTRUCTION_LIMIT_EXCEEDED); assert_eq!(error.message(), "Program instruction account count exceeds the Interface admission limit"); assert_eq!(error.context().len(), 3); assert_eq!(error.context()[0].key(), "field"); assert_eq!(error.context()[0].value(), "accounts"); assert_eq!(error.context()[1].key(), "actual_len"); assert_eq!(error.context()[1].value(), "256"); assert_eq!(error.context()[2].key(), "maximum_len"); assert_eq!(error.context()[2].value(), "255"); return; } #[test] fn instruction_accepts_data_limit_and_rejects_one_byte_above_it() { let program_id = crate::Pubkey::new_from_array([0x61_u8; 32]); let accepted = crate::ProgramInstruction::try_new(program_id, std::vec::Vec::new(), std::vec![0xA5_u8; crate::MAX_PROGRAM_INSTRUCTION_DATA_LEN]); assert!(accepted.is_ok()); let rejected = crate::ProgramInstruction::try_new(program_id, std::vec::Vec::new(), std::vec![0x5A_u8; crate::MAX_PROGRAM_INSTRUCTION_DATA_LEN + 1]); assert!(rejected.is_err()); let error = match rejected { std::result::Result::Err(value) => value, std::result::Result::Ok(_) => return, }; assert_eq!(error.code(), crate::ERROR_CODE_PROGRAM_INSTRUCTION_LIMIT_EXCEEDED); assert_eq!(error.message(), "Program instruction data length exceeds the Interface admission limit"); assert_eq!(error.context().len(), 3); assert_eq!(error.context()[0].value(), "data"); assert_eq!(error.context()[1].value(), "10241"); assert_eq!(error.context()[2].value(), "10240"); return; } #[test] fn instruction_debug_is_bounded_and_omits_accounts_and_payload_bytes() { let program_id = crate::Pubkey::new_from_array([0x71_u8; 32]); let payload = b"PAYLOAD_SENTINEL_NEVER_RENDER".to_vec(); let instruction = crate::ProgramInstruction::try_new(program_id, std::vec![meta(8, false), meta(9, true)], payload); assert!(instruction.is_ok()); let instruction = match instruction { std::result::Result::Ok(value) => value, std::result::Result::Err(_) => return, }; let rendered = std::format!("{instruction:?}"); assert!(rendered.contains("ProgramInstruction")); assert!(rendered.contains("account_count: 2")); assert!(rendered.contains("data_len: 29")); assert!(!rendered.contains("PAYLOAD_SENTINEL_NEVER_RENDER")); assert!(!rendered.contains("is_signer")); assert!(!rendered.contains("is_writable")); return; } #[test] fn instruction_preserves_owned_vector_allocations_without_internal_reallocation() { let program_id = crate::Pubkey::new_from_array([0x81_u8; 32]); let accounts = std::vec![meta(10, false), meta(11, true), meta(12, false)]; let accounts_ptr = accounts.as_ptr(); let data = std::vec![0x10_u8, 0x20, 0x30, 0x40]; let data_ptr = data.as_ptr(); let instruction = crate::ProgramInstruction::try_new(program_id, accounts, data); assert!(instruction.is_ok()); let instruction = match instruction { std::result::Result::Ok(value) => value, std::result::Result::Err(_) => return, }; assert_eq!(instruction.accounts().as_ptr(), accounts_ptr); assert_eq!(instruction.data().as_ptr(), data_ptr); return; } #[test] fn instruction_limit_errors_do_not_echo_hostile_payload_or_account_material() { let program_id = crate::Pubkey::new_from_array([0x91_u8; 32]); let hostile_marker = b"HOSTILE_INTERFACE_PAYLOAD_SENTINEL"; let mut hostile_data = hostile_marker.to_vec(); hostile_data.resize(crate::MAX_PROGRAM_INSTRUCTION_DATA_LEN + 1, 0xA5_u8); let data_error = crate::ProgramInstruction::try_new(program_id, std::vec::Vec::new(), hostile_data); assert!(data_error.is_err()); let data_error = match data_error { std::result::Result::Err(value) => value, std::result::Result::Ok(_) => return, }; let data_display = std::format!("{data_error}"); let data_debug = std::format!("{data_error:?}"); assert!(!data_display.contains("HOSTILE_INTERFACE_PAYLOAD_SENTINEL")); assert!(!data_debug.contains("HOSTILE_INTERFACE_PAYLOAD_SENTINEL")); for context in data_error.context() { assert!(!context.value().contains("HOSTILE_INTERFACE_PAYLOAD_SENTINEL")); } let hostile_account = meta(0xE1, true); let hostile_account_debug = std::format!("{hostile_account:?}"); let account_error = crate::ProgramInstruction::try_new(program_id, std::vec![hostile_account; crate::MAX_PROGRAM_INSTRUCTION_ACCOUNTS + 1], std::vec::Vec::new()); assert!(account_error.is_err()); let account_error = match account_error { std::result::Result::Err(value) => value, std::result::Result::Ok(_) => return, }; let account_debug = std::format!("{account_error:?}"); assert!(!account_debug.contains(hostile_account_debug.as_str())); assert_eq!(account_error.context().len(), 3); assert_eq!(account_error.context()[0].value(), "accounts"); assert_eq!(account_error.context()[1].value(), "256"); assert_eq!(account_error.context()[2].value(), "255"); return; }