v0.2.13-pre.005
This commit is contained in:
@@ -1,5 +1,5 @@
|
||||
// file: crates/ksp-interface-lib/tests/dependency_boundary.rs
|
||||
// version: 3
|
||||
// version: 4
|
||||
|
||||
//! Dependency and passive-surface canaries for the Interface foundation.
|
||||
|
||||
@@ -45,7 +45,7 @@ fn pre_002_manifest_has_exact_core_only_runtime_dependency() {
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn pre_004_surface_remains_passive_without_codecs_or_runtime_logging() {
|
||||
fn pre_005_surface_remains_passive_without_codecs_or_runtime_logging() {
|
||||
let crate_root = include_str!("../src/lib.rs");
|
||||
assert!(crate_root.contains("ProgramAccountMeta"));
|
||||
assert!(crate_root.contains("MAX_PROGRAM_INSTRUCTION_ACCOUNTS"));
|
||||
@@ -83,3 +83,49 @@ fn manifest_dependency_names(section: &str) -> std::vec::Vec<&str> {
|
||||
names.sort_unstable();
|
||||
return names;
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn pre_005_all_production_sources_preserve_the_dependency_firewall() {
|
||||
let production_sources = [
|
||||
include_str!("../src/error.rs"),
|
||||
include_str!("../src/lib.rs"),
|
||||
include_str!("../src/program_account_meta.rs"),
|
||||
include_str!("../src/program_instruction.rs"),
|
||||
];
|
||||
for source in production_sources {
|
||||
for forbidden in [
|
||||
"borsh::",
|
||||
"bincode::",
|
||||
"ksp_config_lib::",
|
||||
"ksp_logging_lib::",
|
||||
"ksp_offchain_transport_lib::",
|
||||
"ksp_onchain_transport_lib::",
|
||||
"ksp_program_api::",
|
||||
"ksp_program_lib::",
|
||||
"ksp_store_api::",
|
||||
"ksp_store_lib::",
|
||||
"ksp_wallet_lib::",
|
||||
"reqwest::",
|
||||
"serde::",
|
||||
"serde_json::",
|
||||
"solana_instruction::",
|
||||
"tauri::",
|
||||
"tokio::",
|
||||
"tonic::",
|
||||
"tracing::",
|
||||
"wincode::",
|
||||
] {
|
||||
assert!(!source.contains(forbidden), "forbidden production dependency path detected: {forbidden}");
|
||||
}
|
||||
}
|
||||
return;
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn pre_005_instruction_source_has_no_narrowing_cast_or_hidden_codec_entry_point() {
|
||||
let instruction_source = include_str!("../src/program_instruction.rs");
|
||||
for forbidden in [" as u8", " as u16", " as u32", " as u64", "serialize", "deserialize", "encode", "decode"] {
|
||||
assert!(!instruction_source.contains(forbidden), "forbidden instruction implementation pattern detected: {forbidden}");
|
||||
}
|
||||
return;
|
||||
}
|
||||
|
||||
27
crates/ksp-interface-lib/tests/external_consumer.rs
Normal file
27
crates/ksp-interface-lib/tests/external_consumer.rs
Normal file
@@ -0,0 +1,27 @@
|
||||
// file: crates/ksp-interface-lib/tests/external_consumer.rs
|
||||
// version: 1
|
||||
|
||||
//! Downstream-style consumer canary for the public Interface facade.
|
||||
|
||||
fn consume_instruction(instruction: ksp_interface_lib::ProgramInstruction) -> (ksp_interface_lib::Pubkey, usize, usize) {
|
||||
return (*instruction.program_id(), instruction.accounts().len(), instruction.data().len());
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn pre_005_external_consumer_uses_only_the_crate_root_facade() {
|
||||
let program_id = ksp_interface_lib::Pubkey::new_from_array([0xA1_u8; 32]);
|
||||
let account_id = ksp_interface_lib::Pubkey::new_from_array([0xA2_u8; 32]);
|
||||
let readonly = ksp_interface_lib::ProgramAccountMeta::readonly(account_id, true);
|
||||
let writable = ksp_interface_lib::ProgramAccountMeta::writable(account_id, false);
|
||||
let instruction = ksp_interface_lib::ProgramInstruction::try_new(program_id, std::vec![readonly, writable, readonly], std::vec![7_u8, 8, 9]);
|
||||
assert!(instruction.is_ok());
|
||||
let instruction = match instruction {
|
||||
std::result::Result::Ok(value) => value,
|
||||
std::result::Result::Err(_) => return,
|
||||
};
|
||||
assert_eq!(consume_instruction(instruction), (program_id, 3, 3));
|
||||
assert_eq!(ksp_interface_lib::MAX_PROGRAM_INSTRUCTION_ACCOUNTS, 255);
|
||||
assert_eq!(ksp_interface_lib::MAX_PROGRAM_INSTRUCTION_DATA_LEN, 10_240);
|
||||
assert_eq!(ksp_interface_lib::ERROR_CODE_PROGRAM_INSTRUCTION_LIMIT_EXCEEDED.domain(), "interface");
|
||||
return;
|
||||
}
|
||||
@@ -1,5 +1,5 @@
|
||||
// file: crates/ksp-interface-lib/tests/public_api.rs
|
||||
// version: 3
|
||||
// version: 4
|
||||
|
||||
//! Integration canaries for the public `ksp-interface-lib` foundation.
|
||||
|
||||
@@ -53,3 +53,27 @@ fn public_pre_004_program_instruction_contract_is_available_from_crate_root() {
|
||||
assert_eq!(ksp_interface_lib::MAX_PROGRAM_INSTRUCTION_DATA_LEN, 10_240);
|
||||
return;
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn public_pre_005_limit_error_is_observable_without_private_module_access() {
|
||||
let program_id = ksp_interface_lib::Pubkey::new_from_array([0xC1_u8; 32]);
|
||||
let rejected = ksp_interface_lib::ProgramInstruction::try_new(
|
||||
program_id,
|
||||
std::vec::Vec::new(),
|
||||
std::vec![0xC2_u8; ksp_interface_lib::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(), ksp_interface_lib::ERROR_CODE_PROGRAM_INSTRUCTION_LIMIT_EXCEEDED);
|
||||
assert_eq!(error.context().len(), 3);
|
||||
assert_eq!(error.context()[0].key(), "field");
|
||||
assert_eq!(error.context()[0].value(), "data");
|
||||
assert_eq!(error.context()[1].key(), "actual_len");
|
||||
assert_eq!(error.context()[1].value(), "10241");
|
||||
assert_eq!(error.context()[2].key(), "maximum_len");
|
||||
assert_eq!(error.context()[2].value(), "10240");
|
||||
return;
|
||||
}
|
||||
|
||||
75
crates/ksp-interface-lib/tests/release_completeness.rs
Normal file
75
crates/ksp-interface-lib/tests/release_completeness.rs
Normal file
@@ -0,0 +1,75 @@
|
||||
// file: crates/ksp-interface-lib/tests/release_completeness.rs
|
||||
// version: 1
|
||||
|
||||
//! Release-level completeness canaries for the `0.2.13` Interface foundation.
|
||||
|
||||
#[test]
|
||||
fn pre_005_exact_crate_root_export_inventory_is_stable() {
|
||||
let crate_root = include_str!("../src/lib.rs");
|
||||
let mut actual = std::vec::Vec::new();
|
||||
for line in crate_root.lines() {
|
||||
let trimmed = line.trim();
|
||||
if trimmed.starts_with("pub use ") {
|
||||
actual.push(trimmed);
|
||||
}
|
||||
}
|
||||
actual.sort_unstable();
|
||||
let mut expected = std::vec![
|
||||
"pub use self::error::ERROR_CODE_PROGRAM_INSTRUCTION_LIMIT_EXCEEDED;",
|
||||
"pub use self::program_account_meta::MAX_PROGRAM_INSTRUCTION_ACCOUNTS;",
|
||||
"pub use self::program_account_meta::ProgramAccountMeta;",
|
||||
"pub use self::program_instruction::MAX_PROGRAM_INSTRUCTION_DATA_LEN;",
|
||||
"pub use self::program_instruction::ProgramInstruction;",
|
||||
"pub use ksp_core_lib::Pubkey;",
|
||||
];
|
||||
expected.sort_unstable();
|
||||
assert_eq!(actual, expected);
|
||||
assert!(!crate_root.contains("pub mod "));
|
||||
return;
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn pre_005_production_module_inventory_contains_no_second_wire_domain() -> std::io::Result<()> {
|
||||
let source_root = std::path::Path::new(env!("CARGO_MANIFEST_DIR")).join("src");
|
||||
let entries = match std::fs::read_dir(source_root) {
|
||||
std::result::Result::Ok(value) => value,
|
||||
std::result::Result::Err(error) => return std::result::Result::Err(error),
|
||||
};
|
||||
let mut names = std::vec::Vec::new();
|
||||
for entry in entries {
|
||||
let entry = match entry {
|
||||
std::result::Result::Ok(value) => value,
|
||||
std::result::Result::Err(error) => return std::result::Result::Err(error),
|
||||
};
|
||||
let file_type = match entry.file_type() {
|
||||
std::result::Result::Ok(value) => value,
|
||||
std::result::Result::Err(error) => return std::result::Result::Err(error),
|
||||
};
|
||||
if !file_type.is_file() {
|
||||
continue;
|
||||
}
|
||||
let name = match entry.file_name().into_string() {
|
||||
std::result::Result::Ok(value) => value,
|
||||
std::result::Result::Err(_) => continue,
|
||||
};
|
||||
if name.ends_with(".rs") {
|
||||
names.push(name);
|
||||
}
|
||||
}
|
||||
names.sort_unstable();
|
||||
assert_eq!(names, std::vec!["error.rs", "lib.rs", "program_account_meta.rs", "program_instruction.rs"]);
|
||||
return std::result::Result::Ok(());
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn pre_005_foundation_has_one_error_code_and_two_bounded_passive_types() {
|
||||
assert_eq!(ksp_interface_lib::ERROR_CODE_PROGRAM_INSTRUCTION_LIMIT_EXCEEDED.domain(), "interface");
|
||||
assert_eq!(ksp_interface_lib::ERROR_CODE_PROGRAM_INSTRUCTION_LIMIT_EXCEEDED.code(), "program_instruction_limit_exceeded");
|
||||
assert_eq!(ksp_interface_lib::MAX_PROGRAM_INSTRUCTION_ACCOUNTS, 255);
|
||||
assert_eq!(ksp_interface_lib::MAX_PROGRAM_INSTRUCTION_DATA_LEN, 10_240);
|
||||
let program_id = ksp_interface_lib::Pubkey::new_from_array([0xB1_u8; 32]);
|
||||
let account = ksp_interface_lib::ProgramAccountMeta::readonly(ksp_interface_lib::Pubkey::new_from_array([0xB2_u8; 32]), false);
|
||||
let instruction = ksp_interface_lib::ProgramInstruction::try_new(program_id, std::vec![account], std::vec![0xB3_u8]);
|
||||
assert!(instruction.is_ok());
|
||||
return;
|
||||
}
|
||||
@@ -1,5 +1,5 @@
|
||||
// file: crates/ksp-interface-lib/unit_tests/program_instruction.rs
|
||||
// version: 1
|
||||
// version: 2
|
||||
|
||||
fn meta(byte: u8, writable: bool) -> crate::ProgramAccountMeta {
|
||||
let pubkey = crate::Pubkey::new_from_array([byte; 32]);
|
||||
@@ -105,3 +105,61 @@ fn instruction_debug_is_bounded_and_omits_accounts_and_payload_bytes() {
|
||||
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;
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user