// file: kb_executor_spl_memo/src/builder.rs // version: 6 //! Official SPL Memo instruction builder and conservative plan validation. use std::str::FromStr; // rust-rules: trait-import pub(crate) fn build_prepared_plan( intent: &crate::SplMemoExecutionIntent, ) -> kb_core::Result { if intent.intent_id.trim().is_empty() { return std::result::Result::Err(kb_core::Error::new( "execution_spl_memo_intent_id_empty", "SPL Memo execution intent id must not be empty", )); } let fee_payer = match parse_pubkey(&intent.fee_payer, "fee_payer") { std::result::Result::Ok(pubkey) => pubkey, std::result::Result::Err(error) => return std::result::Result::Err(error), }; match validate_policy(intent) { std::result::Result::Ok(()) => {}, std::result::Result::Err(error) => return std::result::Result::Err(error), } let (generation, message, signers) = match &intent.operation { crate::SplMemoOperation::AddMemo { generation, message, signers } => { (*generation, message, signers) }, }; if message.len() > crate::MAX_MEMO_MESSAGE_BYTES { return std::result::Result::Err(kb_core::Error::new( "execution_spl_memo_payload_too_large", format!( "SPL Memo payload length {} exceeds the executor limit {}", message.len(), crate::MAX_MEMO_MESSAGE_BYTES ), )); } if signers.len() > crate::MAX_MEMO_SIGNERS { return std::result::Result::Err(kb_core::Error::new( "execution_spl_memo_signer_limit_exceeded", format!( "SPL Memo signer occurrence count {} exceeds the executor limit {}", signers.len(), crate::MAX_MEMO_SIGNERS ), )); } let program_id = match solana_pubkey::Pubkey::from_str(generation.program_id()) { std::result::Result::Ok(pubkey) => pubkey, std::result::Result::Err(error) => { return std::result::Result::Err(kb_core::Error::new( "execution_spl_memo_program_id_invalid", error.to_string(), )); }, }; let mut signer_pubkeys = std::vec::Vec::with_capacity(signers.len()); for signer in signers { let pubkey = match parse_pubkey(&signer.pubkey, "memo_signer") { std::result::Result::Ok(pubkey) => pubkey, std::result::Result::Err(error) => return std::result::Result::Err(error), }; signer_pubkeys.push(pubkey); } let signer_refs = signer_pubkeys.iter().collect::>(); let instruction = spl_memo_interface::instruction::build_memo( &program_id, message.as_bytes(), signer_refs.as_slice(), ); let planned_instruction = planned_instruction(crate::SPL_MEMO_ADD_MEMO_OPERATION, &instruction); let required_signers = required_signers(&intent.fee_payer, signers.as_slice()); tracing::debug!( target: crate::TRACING_TARGET, action = "build_prepared_plan", intent_id = %intent.intent_id, operation_code = crate::SPL_MEMO_ADD_MEMO_OPERATION, program_id = generation.program_id(), payload_length = message.len(), signer_occurrence_count = signers.len(), required_signer_count = required_signers.len(), dry_run = intent.policy.dry_run, "built SPL Memo execution plan" ); return std::result::Result::Ok(kb_execution_api::PreparedExecutionPlan { executor_name: std::string::String::from("kb_executor_spl_memo"), executor_version: std::string::String::from(env!("CARGO_PKG_VERSION")), intent_id: intent.intent_id.clone(), operation_code: std::string::String::from(crate::SPL_MEMO_ADD_MEMO_OPERATION), fee_payer: kb_model::Pubkey(fee_payer.to_string()), instructions: vec![planned_instruction], required_signers, policy: intent.policy.clone(), requested_spend_lamports: 0, requested_compute_unit_price_micro_lamports: std::option::Option::None, }); } fn validate_policy(intent: &crate::SplMemoExecutionIntent) -> kb_core::Result<()> { if intent.policy.simulation != kb_execution_api::ExecutionSimulationPolicy::Required { return std::result::Result::Err(kb_core::Error::new( "execution_spl_memo_simulation_required", "SPL Memo execution requires simulation before signing or sending", )); } match intent.policy.cost_limit.max_fee_lamports { std::option::Option::Some(limit) if limit > 0 => {}, std::option::Option::Some(_) | std::option::Option::None => { return std::result::Result::Err(kb_core::Error::new( "execution_spl_memo_fee_limit_missing", "SPL Memo execution requires a positive transaction fee ceiling", )); }, } let validation = &intent.policy.post_execution_validation; if !validation.canonical_insert_required || !validation.core_extraction_required || !validation.decode_replay_required || !validation.materialization_required { return std::result::Result::Err(kb_core::Error::new( "execution_spl_memo_post_validation_required", "SPL Memo execution requires canonical insertion, core extraction, decode replay and materialization validation", )); } return std::result::Result::Ok(()); } fn parse_pubkey(pubkey: &kb_model::Pubkey, field: &str) -> kb_core::Result { if pubkey.0.trim().is_empty() { return std::result::Result::Err(kb_core::Error::new( "execution_spl_memo_pubkey_empty", format!("SPL Memo {field} public key must not be empty"), )); } return match solana_pubkey::Pubkey::from_str(pubkey.0.as_str()) { std::result::Result::Ok(parsed) => std::result::Result::Ok(parsed), std::result::Result::Err(error) => std::result::Result::Err(kb_core::Error::new( "execution_spl_memo_pubkey_invalid", format!("invalid SPL Memo {field} public key: {error}"), )), }; } fn planned_instruction( operation_code: &str, instruction: &solana_instruction::Instruction, ) -> kb_execution_api::PlannedInstruction { return kb_execution_api::PlannedInstruction { program_id: kb_model::ProgramId(instruction.program_id.to_string()), operation_code: std::string::String::from(operation_code), accounts: instruction .accounts .iter() .map(|account| { return kb_execution_api::PlannedAccount { pubkey: kb_model::Pubkey(account.pubkey.to_string()), is_signer: account.is_signer, is_writable: account.is_writable, }; }) .collect(), data: instruction.data.clone(), }; } fn required_signers( fee_payer: &kb_model::Pubkey, signers: &[crate::SplMemoSigner], ) -> std::vec::Vec { let mut required = vec![kb_execution_api::RequiredSigner { pubkey: fee_payer.clone(), role: std::string::String::from("fee_payer"), }]; for signer in signers { if required.iter().any(|candidate| return candidate.pubkey == signer.pubkey) { continue; } required.push(kb_execution_api::RequiredSigner { pubkey: signer.pubkey.clone(), role: std::string::String::from("memo_signer"), }); } return required; } #[cfg(test)] pub(crate) mod tests { use std::str::FromStr; // rust-rules: trait-import fn pubkey(value: &str) -> kb_model::Pubkey { return kb_model::Pubkey(std::string::String::from(value)); } fn intent( generation: crate::SplMemoGeneration, message: &str, signers: &[&str], dry_run: bool, ) -> crate::SplMemoExecutionIntent { let fee_payer = kb_program_ids::SYSTEM_PROGRAM_ID; let mut authorized_signers = vec![pubkey(fee_payer)]; for signer in signers { let candidate = pubkey(signer); if !authorized_signers.contains(&candidate) { authorized_signers.push(candidate); } } return crate::SplMemoExecutionIntent { intent_id: std::string::String::from("memo-intent-1"), fee_payer: pubkey(fee_payer), policy: kb_execution_api::ExecutionPolicy { cost_limit: kb_execution_api::ExecutionCostLimit { max_spend_lamports: std::option::Option::Some(0), max_fee_lamports: std::option::Option::Some(10_000), max_compute_unit_price_micro_lamports: std::option::Option::None, }, authorized_signers, dry_run, post_execution_validation: kb_execution_api::PostExecutionValidationPolicy { canonical_insert_required: true, core_extraction_required: true, decode_replay_required: true, materialization_required: true, }, ..kb_execution_api::ExecutionPolicy::default() }, operation: crate::SplMemoOperation::AddMemo { generation, message: std::string::String::from(message), signers: signers .iter() .map(|value| { return crate::SplMemoSigner { pubkey: pubkey(value) }; }) .collect(), }, }; } fn assert_matches_official( plan: &kb_execution_api::PreparedExecutionPlan, generation: crate::SplMemoGeneration, message: &str, signers: &[&str], ) { let program_id = solana_pubkey::Pubkey::from_str(generation.program_id()) .unwrap_or_else(|error| panic!("invalid program fixture: {error}")); let signer_pubkeys = signers .iter() .map(|value| { return solana_pubkey::Pubkey::from_str(value) .unwrap_or_else(|error| panic!("invalid signer fixture: {error}")); }) .collect::>(); let signer_refs = signer_pubkeys.iter().collect::>(); let official = spl_memo_interface::instruction::build_memo( &program_id, message.as_bytes(), signer_refs.as_slice(), ); assert_eq!(plan.instructions.len(), 1); let actual = &plan.instructions[0]; assert_eq!(actual.program_id.0, official.program_id.to_string()); assert_eq!(actual.data, official.data); assert_eq!(actual.accounts.len(), official.accounts.len()); for (actual_account, official_account) in actual.accounts.iter().zip(official.accounts.iter()) { assert_eq!(actual_account.pubkey.0, official_account.pubkey.to_string()); assert_eq!(actual_account.is_signer, official_account.is_signer); assert_eq!(actual_account.is_writable, official_account.is_writable); } } #[test] fn current_generation_matches_the_official_explicit_program_builder() { let generation = crate::SplMemoGeneration::V4; let intent = intent(generation, "memo 🐆", &[kb_program_ids::VOTE_PROGRAM_ID], true); let plan = kb_execution_api::TypedInstructionExecutor::build_prepared_plan( &crate::SplMemoExecutor, &intent, ) .unwrap_or_else(|error| panic!("Memo plan failed: {error}")); assert_matches_official(&plan, generation, "memo 🐆", &[kb_program_ids::VOTE_PROGRAM_ID]); } #[test] fn empty_payload_and_zero_signers_are_constructible() { let intent = intent(crate::SplMemoGeneration::V4, "", &[], false); let plan = kb_execution_api::TypedInstructionExecutor::build_prepared_plan( &crate::SplMemoExecutor, &intent, ) .unwrap_or_else(|error| panic!("empty Memo plan failed: {error}")); assert!(plan.instructions[0].data.is_empty()); assert!(plan.instructions[0].accounts.is_empty()); assert_eq!(plan.required_signers.len(), 1); assert_eq!(plan.requested_spend_lamports, 0); } #[test] fn duplicate_signers_remain_ordered_readonly_accounts_but_required_signers_are_unique() { let first = kb_program_ids::VOTE_PROGRAM_ID; let second = kb_program_ids::STAKE_PROGRAM_ID; let intent = intent(crate::SplMemoGeneration::V4, "ordered", &[first, second, first], false); let plan = kb_execution_api::TypedInstructionExecutor::build_prepared_plan( &crate::SplMemoExecutor, &intent, ) .unwrap_or_else(|error| panic!("ordered Memo plan failed: {error}")); let accounts = &plan.instructions[0].accounts; assert_eq!(accounts.len(), 3); assert_eq!(accounts[0].pubkey.0, first); assert_eq!(accounts[1].pubkey.0, second); assert_eq!(accounts[2].pubkey.0, first); assert!(accounts.iter().all(|account| return account.is_signer)); assert!(accounts.iter().all(|account| return !account.is_writable)); assert_eq!(plan.required_signers.len(), 3); } #[test] fn prepared_plan_passes_common_pre_simulation_safety() { let intent = intent( crate::SplMemoGeneration::V4, "safe plan", &[kb_program_ids::VOTE_PROGRAM_ID], true, ); let plan = kb_execution_api::TypedInstructionExecutor::build_prepared_plan( &crate::SplMemoExecutor, &intent, ) .unwrap_or_else(|error| panic!("safe Memo plan failed: {error}")); let evaluation = kb_execution_safety::ExecutionSafetyChecker .evaluate_prepared_plan(&plan) .unwrap_or_else(|error| panic!("safety evaluation failed: {error}")); assert_eq!(evaluation.decision, kb_execution_safety::ExecutionSafetyDecision::Allow); assert!(evaluation.violations.is_empty()); } #[test] fn current_generation_accepts_non_dry_run_plans() { let generation = crate::SplMemoGeneration::V4; let intent = intent(generation, "dry", &[], false); let plan = kb_execution_api::TypedInstructionExecutor::build_prepared_plan( &crate::SplMemoExecutor, &intent, ) .unwrap_or_else(|error| panic!("non-dry-run Memo plan failed: {error}")); assert!(!plan.policy.dry_run); assert_eq!(plan.instructions[0].program_id.0, generation.program_id()); } #[test] fn rejects_oversize_payload_and_optional_simulation() { let mut oversize = intent( crate::SplMemoGeneration::V4, std::string::String::from_utf8(vec![b'a'; crate::MAX_MEMO_MESSAGE_BYTES + 1]) .unwrap_or_else(|error| panic!("fixture creation failed: {error}")) .as_str(), &[], true, ); let error = kb_execution_api::TypedInstructionExecutor::build_prepared_plan( &crate::SplMemoExecutor, &oversize, ) .expect_err("oversize payload must fail"); assert_eq!(error.code(), "execution_spl_memo_payload_too_large"); oversize.operation = crate::SplMemoOperation::AddMemo { generation: crate::SplMemoGeneration::V4, message: std::string::String::from("valid"), signers: std::vec::Vec::new(), }; oversize.policy.simulation = kb_execution_api::ExecutionSimulationPolicy::Optional; let error = kb_execution_api::TypedInstructionExecutor::build_prepared_plan( &crate::SplMemoExecutor, &oversize, ) .expect_err("optional simulation must fail"); assert_eq!(error.code(), "execution_spl_memo_simulation_required"); } #[test] fn every_cluster_is_constructible_and_mainnet_remains_governed_by_common_safety() { for cluster in [ kb_execution_api::ExecutionCluster::Localnet, kb_execution_api::ExecutionCluster::Devnet, kb_execution_api::ExecutionCluster::Testnet, kb_execution_api::ExecutionCluster::Mainnet, ] { let mut intent = intent(crate::SplMemoGeneration::V4, "universal", &[], false); intent.policy.cluster.expected_cluster = cluster; let plan = kb_execution_api::TypedInstructionExecutor::build_prepared_plan( &crate::SplMemoExecutor, &intent, ) .unwrap_or_else(|error| panic!("cluster plan failed: {error}")); assert_eq!(plan.policy.cluster.expected_cluster, cluster); if cluster == kb_execution_api::ExecutionCluster::Mainnet { let evaluation = kb_execution_safety::ExecutionSafetyChecker .evaluate_prepared_plan(&plan) .unwrap_or_else(|error| panic!("Mainnet safety evaluation failed: {error}")); assert_eq!(evaluation.decision, kb_execution_safety::ExecutionSafetyDecision::Deny); assert!(evaluation.violations.iter().any(|violation| { return violation.code == "execution_mainnet_disabled"; })); let mut explicitly_enabled = plan.clone(); explicitly_enabled.policy.cluster.allow_mainnet = true; explicitly_enabled.policy.cluster.mainnet_confirmation = true; let enabled_evaluation = kb_execution_safety::ExecutionSafetyChecker .evaluate_prepared_plan(&explicitly_enabled) .unwrap_or_else(|error| { panic!("enabled Mainnet safety evaluation failed: {error}") }); assert_eq!( enabled_evaluation.decision, kb_execution_safety::ExecutionSafetyDecision::Allow ); } } } #[test] fn rejects_missing_fee_cap_incomplete_post_validation_and_signer_overflow() { let mut intent = intent(crate::SplMemoGeneration::V4, "policy", &[], true); intent.policy.cost_limit.max_fee_lamports = std::option::Option::None; let error = kb_execution_api::TypedInstructionExecutor::build_prepared_plan( &crate::SplMemoExecutor, &intent, ) .expect_err("missing fee cap must fail"); assert_eq!(error.code(), "execution_spl_memo_fee_limit_missing"); intent.policy.cost_limit.max_fee_lamports = std::option::Option::Some(10_000); intent.policy.post_execution_validation.materialization_required = false; let error = kb_execution_api::TypedInstructionExecutor::build_prepared_plan( &crate::SplMemoExecutor, &intent, ) .expect_err("incomplete post validation must fail"); assert_eq!(error.code(), "execution_spl_memo_post_validation_required"); intent.policy.post_execution_validation.materialization_required = true; intent.operation = crate::SplMemoOperation::AddMemo { generation: crate::SplMemoGeneration::V4, message: std::string::String::from("bounded"), signers: (0..=crate::MAX_MEMO_SIGNERS) .map(|_| { return crate::SplMemoSigner { pubkey: pubkey(kb_program_ids::VOTE_PROGRAM_ID), }; }) .collect(), }; let error = kb_execution_api::TypedInstructionExecutor::build_prepared_plan( &crate::SplMemoExecutor, &intent, ) .expect_err("signer overflow must fail"); assert_eq!(error.code(), "execution_spl_memo_signer_limit_exceeded"); } #[test] fn machine_readable_matrix_matches_executor_policy() { let parsed = serde_json::from_str::(include_str!( "../../docs/SPL_MEMO_MATRIX.json" )) .unwrap_or_else(|error| panic!("Memo matrix parsing failed: {error}")); let contract = parsed .get("executorContract") .unwrap_or_else(|| panic!("Memo executor contract is missing")); assert_eq!( contract.get("operationCode").and_then(serde_json::Value::as_str), std::option::Option::Some(crate::SPL_MEMO_ADD_MEMO_OPERATION) ); assert_eq!( contract.get("maximumMessageBytes").and_then(serde_json::Value::as_u64), std::option::Option::Some(crate::MAX_MEMO_MESSAGE_BYTES as u64) ); assert_eq!( contract.get("maximumSignerOccurrences").and_then(serde_json::Value::as_u64), std::option::Option::Some(crate::MAX_MEMO_SIGNERS as u64) ); assert_eq!( contract.get("allClustersConstructible").and_then(serde_json::Value::as_bool), std::option::Option::Some(true) ); assert_eq!( contract .get("historicalGenerationSendEnabled") .and_then(serde_json::Value::as_bool), std::option::Option::Some(false) ); } }