v0.2.14-pre.005

This commit is contained in:
2026-08-28 15:12:59 +02:00
parent bcf2f16c05
commit 0319794e59
6 changed files with 589 additions and 10 deletions

View File

@@ -0,0 +1,138 @@
// file: crates/ksp-program-api/tests/release_completeness.rs
// version: 1
//! Release-level completeness canaries for the `0.2.14` Program API 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::program_instruction_decode::ProgramInstructionDecodeOutcome;",
"pub use self::program_instruction_decode::ProgramInstructionRecognition;",
"pub use self::program_instruction_decoder::ProgramInstructionDecoder;",
"pub use ksp_core_lib::Error;",
"pub use ksp_core_lib::ErrorCode;",
"pub use ksp_core_lib::ErrorContext;",
"pub use ksp_core_lib::Pubkey;",
"pub use ksp_core_lib::Result;",
"pub use ksp_interface_lib::ProgramAccountMeta;",
"pub use ksp_interface_lib::ProgramInstruction;",
];
expected.sort_unstable();
assert_eq!(actual, expected);
assert!(!crate_root.contains("pub mod "));
return;
}
#[test]
fn pre_005_production_module_inventory_is_instruction_only() -> 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!["lib.rs", "program_instruction_decode.rs", "program_instruction_decoder.rs"]);
return std::result::Result::Ok(());
}
#[test]
fn pre_005_public_enum_and_trait_inventory_remains_open_world() {
let sources = [
include_str!("../src/lib.rs"),
include_str!("../src/program_instruction_decode.rs"),
include_str!("../src/program_instruction_decoder.rs"),
];
let mut public_enums = std::vec::Vec::new();
let mut public_traits = std::vec::Vec::new();
for source in sources {
for line in source.lines() {
let trimmed = line.trim();
if trimmed.starts_with("pub enum ") {
public_enums.push(trimmed);
}
if trimmed.starts_with("pub trait ") {
public_traits.push(trimmed);
}
}
}
public_enums.sort_unstable();
public_traits.sort_unstable();
assert_eq!(
public_enums,
std::vec!["pub enum ProgramInstructionDecodeOutcome<Decoded> {", "pub enum ProgramInstructionRecognition {"]
);
assert_eq!(public_traits, std::vec!["pub trait ProgramInstructionDecoder: Send + Sync {"]);
let decode_source = include_str!("../src/program_instruction_decode.rs");
assert!(decode_source.contains("#[non_exhaustive]\npub enum ProgramInstructionRecognition"));
assert!(decode_source.contains("#[non_exhaustive]\npub enum ProgramInstructionDecodeOutcome<Decoded>"));
return;
}
#[test]
fn pre_005_production_sources_have_no_registry_preparer_codec_or_runtime_creep() {
let sources = [
include_str!("../src/lib.rs"),
include_str!("../src/program_instruction_decode.rs"),
include_str!("../src/program_instruction_decoder.rs"),
];
for source in sources {
for forbidden in [
"pub enum ProgramKind",
"pub struct ProgramRegistry",
"pub trait ProgramAccountDecoder",
"pub trait ProgramEventDecoder",
"pub trait ProgramReturnDataDecoder",
"pub trait ProgramExecutionPreparer",
"std::any::Any",
"serde::",
"serde_json::",
"borsh::",
"bincode::",
"wincode::",
"ksp_logging_lib::",
"tracing::",
"reqwest::",
"tokio::",
"tonic::",
"tauri::",
"std::env::",
"std::fs::",
"std::net::",
"dyn ProgramInstructionDecoder",
"dyn crate::ProgramInstructionDecoder",
] {
assert!(!source.contains(forbidden), "forbidden Program API production surface detected: {forbidden}");
}
}
return;
}

View File

@@ -0,0 +1,156 @@
// file: crates/ksp-program-api/tests/security_hardening.rs
// version: 1
//! Adversarial and bound-safety canaries for the Program API foundation.
const HOSTILE_MARKER: &str = "PROGRAM-SECRET-CANARY";
const MALFORMED_OPCODE: u8 = 0xFF_u8;
const PROGRAM_ID_BYTES: [u8; 32] = [0xD1_u8; 32];
struct BoundsObserved {
account_count: usize,
data_len: usize,
}
struct BoundedDecoder {
program_ids: [ksp_program_api::Pubkey; 1],
}
impl BoundedDecoder {
fn new() -> Self {
return Self { program_ids: [ksp_program_api::Pubkey::new_from_array(PROGRAM_ID_BYTES)] };
}
}
impl ksp_program_api::ProgramInstructionDecoder for BoundedDecoder {
type Decoded = BoundsObserved;
fn program_ids(&self) -> &[ksp_program_api::Pubkey] {
return &self.program_ids;
}
fn recognize(&self, instruction: &ksp_program_api::ProgramInstruction) -> ksp_program_api::ProgramInstructionRecognition {
if instruction.program_id() == &self.program_ids[0] {
return ksp_program_api::ProgramInstructionRecognition::ExactMatch;
}
return ksp_program_api::ProgramInstructionRecognition::NoMatch;
}
fn decode(
&self,
instruction: &ksp_program_api::ProgramInstruction,
) -> ksp_program_api::Result<ksp_program_api::ProgramInstructionDecodeOutcome<Self::Decoded>> {
if instruction.data().first() == std::option::Option::Some(&MALFORMED_OPCODE) {
return std::result::Result::Err(ksp_program_api::Error::new(
ksp_program_api::ErrorCode::new("program_test", "malformed_instruction"),
"malformed external Program instruction",
));
}
return std::result::Result::Ok(ksp_program_api::ProgramInstructionDecodeOutcome::Decoded(BoundsObserved {
account_count: instruction.accounts().len(),
data_len: instruction.data().len(),
}));
}
}
struct BoundlessDecoded {
marker: std::rc::Rc<std::cell::Cell<u8>>,
}
struct BoundlessOutputDecoder;
impl ksp_program_api::ProgramInstructionDecoder for BoundlessOutputDecoder {
type Decoded = BoundlessDecoded;
fn program_ids(&self) -> &[ksp_program_api::Pubkey] {
return &[];
}
fn recognize(&self, _instruction: &ksp_program_api::ProgramInstruction) -> ksp_program_api::ProgramInstructionRecognition {
return ksp_program_api::ProgramInstructionRecognition::NoMatch;
}
fn decode(
&self,
_instruction: &ksp_program_api::ProgramInstruction,
) -> ksp_program_api::Result<ksp_program_api::ProgramInstructionDecodeOutcome<Self::Decoded>> {
return std::result::Result::Ok(ksp_program_api::ProgramInstructionDecodeOutcome::Decoded(BoundlessDecoded {
marker: std::rc::Rc::new(std::cell::Cell::new(0x5A_u8)),
}));
}
}
#[test]
fn pre_005_max_interface_instruction_crosses_decoder_boundary_without_new_contract() {
let decoder = BoundedDecoder::new();
let account = ksp_program_api::ProgramAccountMeta::readonly(ksp_program_api::Pubkey::new_from_array([0xD2_u8; 32]), false);
let accounts = std::vec![account; ksp_interface_lib::MAX_PROGRAM_INSTRUCTION_ACCOUNTS];
let data = std::vec![0x5A_u8; ksp_interface_lib::MAX_PROGRAM_INSTRUCTION_DATA_LEN];
let instruction = ksp_program_api::ProgramInstruction::try_new(decoder.program_ids[0], accounts, data);
assert!(instruction.is_ok());
let instruction = match instruction {
std::result::Result::Ok(value) => value,
std::result::Result::Err(_) => return,
};
let outcome = ksp_program_api::ProgramInstructionDecoder::decode(&decoder, &instruction);
assert!(outcome.is_ok());
let outcome = match outcome {
std::result::Result::Ok(value) => value,
std::result::Result::Err(_) => return,
};
let observed = match outcome {
ksp_program_api::ProgramInstructionDecodeOutcome::Decoded(value) => value,
_ => return,
};
assert_eq!(observed.account_count, ksp_interface_lib::MAX_PROGRAM_INSTRUCTION_ACCOUNTS);
assert_eq!(observed.data_len, ksp_interface_lib::MAX_PROGRAM_INSTRUCTION_DATA_LEN);
return;
}
#[test]
fn pre_005_malformed_payload_error_path_does_not_gain_automatic_payload_echo() {
let decoder = BoundedDecoder::new();
let mut payload = std::vec![MALFORMED_OPCODE];
payload.extend_from_slice(HOSTILE_MARKER.as_bytes());
let instruction = ksp_program_api::ProgramInstruction::try_new(decoder.program_ids[0], std::vec![], payload);
assert!(instruction.is_ok());
let instruction = match instruction {
std::result::Result::Ok(value) => value,
std::result::Result::Err(_) => return,
};
let outcome = ksp_program_api::ProgramInstructionDecoder::decode(&decoder, &instruction);
assert!(outcome.is_err());
let error = match outcome {
std::result::Result::Err(value) => value,
std::result::Result::Ok(_) => return,
};
assert_eq!(error.code().domain(), "program_test");
assert_eq!(error.code().code(), "malformed_instruction");
assert!(!std::format!("{error}").contains(HOSTILE_MARKER));
assert!(!std::format!("{error:?}").contains(HOSTILE_MARKER));
return;
}
#[test]
fn pre_005_associated_decoded_type_keeps_no_implicit_debug_clone_send_or_sync_bound() {
let decoder = BoundlessOutputDecoder;
let instruction = ksp_program_api::ProgramInstruction::try_new(ksp_program_api::Pubkey::new_from_array([0xD3_u8; 32]), std::vec![], std::vec![]);
assert!(instruction.is_ok());
let instruction = match instruction {
std::result::Result::Ok(value) => value,
std::result::Result::Err(_) => return,
};
let outcome = ksp_program_api::ProgramInstructionDecoder::decode(&decoder, &instruction);
assert!(outcome.is_ok());
let outcome = match outcome {
std::result::Result::Ok(value) => value,
std::result::Result::Err(_) => return,
};
assert_eq!(std::format!("{outcome:?}"), "Decoded");
let decoded = match outcome {
ksp_program_api::ProgramInstructionDecodeOutcome::Decoded(value) => value,
_ => return,
};
assert_eq!(decoded.marker.get(), 0x5A_u8);
return;
}