// file: kb-pipeline/src/spl_ata_stateful.rs // version: 4 //! Stateful Localnet and Devnet readiness checks for ATA operations. const BASE_MINT_LEN: usize = 82; const BASE_TOKEN_ACCOUNT_LEN: usize = 165; const MAX_TOKEN_2022_ACCOUNT_BYTES: usize = 16_384; macro_rules! result_value { ($expression:expr) => { match $expression { std::result::Result::Ok(value) => value, std::result::Result::Err(error) => return std::result::Result::Err(error), } }; } /// ATA stateful readiness outcome. #[derive(Clone, Copy, Debug, Eq, PartialEq, serde::Deserialize, serde::Serialize)] #[serde(rename_all = "snake_case")] pub enum SplAssociatedTokenAccountStatefulReadinessStatus { /// Every stateful check passed. Ready, /// At least one stateful check failed. Blocked, } /// One machine-readable ATA stateful check. #[derive(Clone, Debug, Eq, PartialEq, serde::Deserialize, serde::Serialize)] pub struct SplAssociatedTokenAccountStatefulCheck { /// Stable diagnostic code. pub code: std::string::String, /// Whether the check passed. pub passed: bool, /// Operator-readable explanation. pub message: std::string::String, } /// One contextual ATA stateful fact. #[derive(Clone, Debug, Eq, PartialEq, serde::Deserialize, serde::Serialize)] pub struct SplAssociatedTokenAccountStatefulFact { /// Stable fact key. pub key: std::string::String, /// Exact string representation. pub value: std::string::String, } /// Complete request for one ATA stateful readiness inspection. #[derive(Clone, Debug, Eq, PartialEq, serde::Deserialize, serde::Serialize)] pub struct SplAssociatedTokenAccountStatefulReadinessRequest { /// HTTP endpoint role used for every state read. pub query_role: std::string::String, /// Expected Localnet or Devnet cluster. pub cluster: kb_lib::ExApiExecutionCluster, /// Typed simulation-first ATA intent. pub intent: kb_lib::ExSplAssociatedTokenAccountExecutionIntent, /// Signer public keys currently available to the caller. pub available_signers: std::vec::Vec, } /// Complete ATA stateful readiness report. #[derive(Clone, Debug, Eq, PartialEq, serde::Deserialize, serde::Serialize)] pub struct SplAssociatedTokenAccountStatefulReadinessReport { /// Expected cluster. pub cluster: kb_lib::ExApiExecutionCluster, /// Stable operation code. pub operation_code: std::string::String, /// Aggregate readiness status. pub status: SplAssociatedTokenAccountStatefulReadinessStatus, /// Highest contextual slot observed. pub context_slot: std::option::Option, /// Ordered checks. pub checks: std::vec::Vec, /// Ordered facts. pub facts: std::vec::Vec, } /// Stateful ATA invariants observed after a confirmed execution. #[derive(Clone, Debug, Eq, PartialEq, serde::Deserialize, serde::Serialize)] pub struct SplAssociatedTokenAccountPostExecutionReport { /// Expected Localnet or Devnet cluster. pub cluster: kb_lib::ExApiExecutionCluster, /// Stable operation code. pub operation_code: std::string::String, /// Aggregate postcondition status. pub status: SplAssociatedTokenAccountStatefulReadinessStatus, /// Highest contextual slot observed. pub context_slot: std::option::Option, /// Ordered state checks. pub checks: std::vec::Vec, /// Ordered derived-address facts. pub facts: std::vec::Vec, } type AccountCache = std::collections::BTreeMap< std::string::String, std::option::Option, >; /// Inspects Localnet or Devnet ATA state required before simulation. pub async fn inspect_spl_associated_token_account_stateful_readiness( pool: &kb_onchain_transport::HttpEndpointPool, request: &SplAssociatedTokenAccountStatefulReadinessRequest, ) -> kb_core::Result { if request.query_role.trim().is_empty() { return std::result::Result::Err(kb_core::Error::config( "ATA stateful readiness query_role must not be empty", )); } match validate_cluster_kind(request.cluster) { std::result::Result::Ok(()) => {}, std::result::Result::Err(error) => return std::result::Result::Err(error), } let genesis = match pool.get_genesis_hash_for_role(request.query_role.as_str()).await { std::result::Result::Ok(value) => value, std::result::Result::Err(error) => return std::result::Result::Err(error), }; match validate_genesis(request.cluster, &genesis) { std::result::Result::Ok(()) => {}, std::result::Result::Err(error) => return std::result::Result::Err(error), } let plan = match kb_lib::ExApiTypedInstructionExecutor::build_prepared_plan( &kb_lib::ExSplAssociatedTokenAccountExecutor, &request.intent, ) { std::result::Result::Ok(value) => value, std::result::Result::Err(error) => return std::result::Result::Err(error), }; let addresses = match operation_addresses(&request.intent.operation) { std::result::Result::Ok(value) => value, std::result::Result::Err(error) => return std::result::Result::Err(error), }; let config = match kb_onchain_transport::GetAccountInfoConfig::confirmed_with_data( MAX_TOKEN_2022_ACCOUNT_BYTES, ) { std::result::Result::Ok(value) => value, std::result::Result::Err(error) => return std::result::Result::Err(error), }; let mut cache = AccountCache::new(); let mut context_slot: std::option::Option = std::option::Option::None; for address in &addresses { let result = match pool .get_account_info_for_role(request.query_role.as_str(), address, &config) .await { std::result::Result::Ok(value) => value, std::result::Result::Err(error) => return std::result::Result::Err(error), }; context_slot = std::option::Option::Some(match context_slot { std::option::Option::Some(slot) => slot.max(result.context.slot), std::option::Option::None => result.context.slot, }); cache.insert(address.0.clone(), result.account); } let rent = match pool .get_minimum_balance_for_rent_exemption_for_role( request.query_role.as_str(), BASE_TOKEN_ACCOUNT_LEN as u64, &kb_onchain_transport::GetMinimumBalanceForRentExemptionConfig::confirmed(), ) .await { std::result::Result::Ok(value) => value.minimum_balance_lamports, std::result::Result::Err(error) => return std::result::Result::Err(error), }; let balance_config = kb_onchain_transport::GetBalanceConfig::confirmed(); let payer_balance = match pool .get_balance_for_role( request.query_role.as_str(), &request.intent.fee_payer, &balance_config, ) .await { std::result::Result::Ok(value) => value.lamports, std::result::Result::Err(error) => return std::result::Result::Err(error), }; return evaluate( request, &plan, &genesis.genesis_hash, context_slot, &cache, rent, payer_balance, ); } /// Verifies final ATA account relationships after a confirmed execution. pub async fn inspect_spl_associated_token_account_post_execution( pool: &kb_onchain_transport::HttpEndpointPool, query_role: &str, cluster: kb_lib::ExApiExecutionCluster, operation: &kb_lib::ExSplAssociatedTokenAccountOperation, ) -> kb_core::Result { if query_role.trim().is_empty() { return std::result::Result::Err(kb_core::Error::config( "ATA post-execution query role must not be empty", )); } if let std::result::Result::Err(error) = validate_cluster_kind(cluster) { return std::result::Result::Err(error); } let genesis = match pool.get_genesis_hash_for_role(query_role).await { std::result::Result::Ok(value) => value, std::result::Result::Err(error) => return std::result::Result::Err(error), }; if let std::result::Result::Err(error) = validate_genesis(cluster, &genesis) { return std::result::Result::Err(error); } let addresses = match operation_addresses(operation) { std::result::Result::Ok(value) => value, std::result::Result::Err(error) => return std::result::Result::Err(error), }; let config = match kb_onchain_transport::GetAccountInfoConfig::confirmed_with_data( MAX_TOKEN_2022_ACCOUNT_BYTES, ) { std::result::Result::Ok(value) => value, std::result::Result::Err(error) => return std::result::Result::Err(error), }; let mut cache = AccountCache::new(); let mut context_slot: std::option::Option = std::option::Option::None; for address in &addresses { let result = match pool.get_account_info_for_role(query_role, address, &config).await { std::result::Result::Ok(value) => value, std::result::Result::Err(error) => return std::result::Result::Err(error), }; context_slot = std::option::Option::Some(match context_slot { std::option::Option::Some(slot) => slot.max(result.context.slot), std::option::Option::None => result.context.slot, }); cache.insert(address.0.clone(), result.account); } return evaluate_post_execution(cluster, operation, context_slot, &cache); } fn validate_cluster_kind(cluster: kb_lib::ExApiExecutionCluster) -> kb_core::Result<()> { return match cluster { kb_lib::ExApiExecutionCluster::Localnet | kb_lib::ExApiExecutionCluster::Devnet => { std::result::Result::Ok(()) }, kb_lib::ExApiExecutionCluster::Testnet | kb_lib::ExApiExecutionCluster::Mainnet => { std::result::Result::Err(kb_core::Error::new( "spl_ata_stateful_cluster_unsupported", "ATA stateful readiness is restricted to Localnet and Devnet", )) }, }; } fn validate_genesis( expected: kb_lib::ExApiExecutionCluster, genesis: &kb_onchain_transport::GenesisHashResult, ) -> kb_core::Result<()> { return match expected { kb_lib::ExApiExecutionCluster::Devnet if genesis.classified_cluster == std::option::Option::Some(kb_lib::ExApiExecutionCluster::Devnet) => { std::result::Result::Ok(()) }, kb_lib::ExApiExecutionCluster::Localnet if genesis.classified_cluster.is_none() => { std::result::Result::Ok(()) }, kb_lib::ExApiExecutionCluster::Devnet => std::result::Result::Err(kb_core::Error::new( "spl_ata_stateful_devnet_genesis_mismatch", "ATA Devnet readiness requires the official Devnet genesis hash", )), kb_lib::ExApiExecutionCluster::Localnet => std::result::Result::Err(kb_core::Error::new( "spl_ata_stateful_localnet_public_cluster", "ATA Localnet readiness refuses endpoints classified as a public cluster", )), kb_lib::ExApiExecutionCluster::Testnet | kb_lib::ExApiExecutionCluster::Mainnet => { validate_cluster_kind(expected) }, }; } fn operation_addresses( operation: &kb_lib::ExSplAssociatedTokenAccountOperation, ) -> kb_core::Result> { let mut values = std::vec::Vec::new(); match operation { kb_lib::ExSplAssociatedTokenAccountOperation::Create { wallet_owner, mint, token_program, } | kb_lib::ExSplAssociatedTokenAccountOperation::CreateIdempotent { wallet_owner, mint, token_program, } => { values.push(mint.clone()); values.push(result_value!(derive_ata(wallet_owner, mint, token_program.program_id(),))); }, kb_lib::ExSplAssociatedTokenAccountOperation::RecoverNested { wallet_owner, owner_mint, nested_mint, token_program, } => { let owner_ata = result_value!(derive_ata(wallet_owner, owner_mint, token_program.program_id(),)); let nested_ata = result_value!(derive_ata(&owner_ata, nested_mint, token_program.program_id(),)); let destination_ata = result_value!(derive_ata(wallet_owner, nested_mint, token_program.program_id(),)); values.extend([ owner_mint.clone(), nested_mint.clone(), owner_ata, nested_ata, destination_ata, ]); }, } values.sort_by(|left, right| return left.0.cmp(&right.0)); values.dedup(); return std::result::Result::Ok(values); } fn derive_ata( wallet: &kb_lib::MdPubkey, mint: &kb_lib::MdPubkey, token_program: &str, ) -> kb_core::Result { let wallet: solana_pubkey::Pubkey = match wallet.0.parse() { std::result::Result::Ok(value) => value, std::result::Result::Err(error) => { return std::result::Result::Err(kb_core::Error::new( "spl_ata_stateful_wallet_invalid", error.to_string(), )); }, }; let mint: solana_pubkey::Pubkey = match mint.0.parse() { std::result::Result::Ok(value) => value, std::result::Result::Err(error) => { return std::result::Result::Err(kb_core::Error::new( "spl_ata_stateful_mint_invalid", error.to_string(), )); }, }; let token_program: solana_pubkey::Pubkey = match token_program.parse() { std::result::Result::Ok(value) => value, std::result::Result::Err(error) => { return std::result::Result::Err(kb_core::Error::new( "spl_ata_stateful_token_program_invalid", error.to_string(), )); }, }; let derived = spl_associated_token_account_interface::address::get_associated_token_address_with_program_id( &wallet, &mint, &token_program, ); return std::result::Result::Ok(kb_lib::MdPubkey(derived.to_string())); } fn evaluate( request: &SplAssociatedTokenAccountStatefulReadinessRequest, plan: &kb_lib::ExApiPreparedExecutionPlan, genesis_hash: &str, context_slot: std::option::Option, cache: &AccountCache, base_rent_lamports: u64, payer_balance_lamports: u64, ) -> kb_core::Result { let mut report = SplAssociatedTokenAccountStatefulReadinessReport { cluster: request.cluster, operation_code: request.intent.operation.operation_code().to_string(), status: SplAssociatedTokenAccountStatefulReadinessStatus::Ready, context_slot, checks: std::vec::Vec::new(), facts: std::vec![ SplAssociatedTokenAccountStatefulFact { key: "genesis_hash".to_string(), value: genesis_hash.to_string(), }, SplAssociatedTokenAccountStatefulFact { key: "base_token_account_rent_lamports".to_string(), value: base_rent_lamports.to_string(), }, ], }; push_check( &mut report, "ata_program_id", plan.instructions.len() == 1 && plan.instructions[0].program_id.0 == kb_program_ids::ASSOCIATED_TOKEN_PROGRAM_ID, "plan must contain exactly one instruction for the canonical ATA Program", ); push_check( &mut report, "simulation_required", plan.policy.simulation == kb_lib::ExApiExecutionSimulationPolicy::Required, "ATA plan must require simulation", ); for signer in &plan.required_signers { push_check( &mut report, "required_signer_available", request.available_signers.contains(&signer.pubkey), format!("required {} signer {} must be available", signer.role, signer.pubkey.0), ); } let max_fee = match plan.policy.cost_limit.max_fee_lamports { std::option::Option::Some(value) => value, std::option::Option::None => 0, }; let total_ceiling = match plan.requested_spend_lamports.checked_add(max_fee) { std::option::Option::Some(value) => value, std::option::Option::None => u64::MAX, }; push_check( &mut report, "payer_balance_covers_ceiling", payer_balance_lamports >= total_ceiling, format!("payer balance must cover rent-plus-fee ceiling {total_ceiling}"), ); let operation = &request.intent.operation; match operation { kb_lib::ExSplAssociatedTokenAccountOperation::Create { wallet_owner, mint, token_program, } => { check_mint(&mut report, cache, mint, token_program.program_id()); let ata = result_value!(derive_ata(wallet_owner, mint, token_program.program_id(),)); push_fact(&mut report, "derived_ata", ata.0.clone()); check_creation_plan_accounts(&mut report, plan, &ata, token_program.program_id()); push_check( &mut report, "create_ata_absent", cache.get(ata.0.as_str()).is_some_and(|value| return value.is_none()), "strict Create requires the canonical ATA to be absent", ); push_check( &mut report, "rent_ceiling_covers_base_account", plan.requested_spend_lamports >= base_rent_lamports, "creation rent ceiling must cover at least the base Token account rent", ); }, kb_lib::ExSplAssociatedTokenAccountOperation::CreateIdempotent { wallet_owner, mint, token_program, } => { check_mint(&mut report, cache, mint, token_program.program_id()); let ata = result_value!(derive_ata(wallet_owner, mint, token_program.program_id(),)); push_fact(&mut report, "derived_ata", ata.0.clone()); check_creation_plan_accounts(&mut report, plan, &ata, token_program.program_id()); if let std::option::Option::Some(std::option::Option::Some(account)) = cache.get(ata.0.as_str()) { check_token_account( &mut report, account, token_program.program_id(), mint, wallet_owner, "idempotent_existing_ata", ); } else { push_check( &mut report, "idempotent_absent_or_compatible", cache.contains_key(ata.0.as_str()), "idempotent ATA must be confirmed absent or validated as compatible", ); push_check( &mut report, "rent_ceiling_covers_base_account", plan.requested_spend_lamports >= base_rent_lamports, "absent idempotent ATA requires a ceiling covering base account rent", ); } }, kb_lib::ExSplAssociatedTokenAccountOperation::RecoverNested { wallet_owner, owner_mint, nested_mint, token_program, } => { check_mint(&mut report, cache, owner_mint, token_program.program_id()); check_mint(&mut report, cache, nested_mint, token_program.program_id()); let owner_ata = result_value!(derive_ata(wallet_owner, owner_mint, token_program.program_id(),)); let nested_ata = result_value!(derive_ata(&owner_ata, nested_mint, token_program.program_id(),)); let destination_ata = result_value!(derive_ata(wallet_owner, nested_mint, token_program.program_id(),)); for (key, value) in [ ("derived_owner_ata", &owner_ata), ("derived_nested_ata", &nested_ata), ("derived_destination_ata", &destination_ata), ] { push_fact(&mut report, key, value.0.clone()); } let accounts_exact = match plan.instructions.first() { std::option::Option::Some(instruction) => { instruction.accounts.len() == 7 && instruction.accounts[0].pubkey == nested_ata && instruction.accounts[2].pubkey == destination_ata && instruction.accounts[3].pubkey == owner_ata && instruction.accounts[6].pubkey.0 == token_program.program_id() }, std::option::Option::None => false, }; push_check( &mut report, "recover_plan_accounts_exact", accounts_exact, "RecoverNested plan must retain all three derived ATA addresses and selected Token Program in official positions", ); check_cached_token_account( &mut report, cache, &owner_ata, token_program.program_id(), owner_mint, wallet_owner, "owner_ata", ); check_cached_token_account( &mut report, cache, &nested_ata, token_program.program_id(), nested_mint, &owner_ata, "nested_ata", ); check_cached_token_account( &mut report, cache, &destination_ata, token_program.program_id(), nested_mint, wallet_owner, "destination_ata", ); push_check( &mut report, "recovery_zero_rent_spend", plan.requested_spend_lamports == 0, "RecoverNested must not reserve rent spending", ); }, } report.status = if report.checks.iter().all(|check| return check.passed) { SplAssociatedTokenAccountStatefulReadinessStatus::Ready } else { SplAssociatedTokenAccountStatefulReadinessStatus::Blocked }; return std::result::Result::Ok(report); } fn evaluate_post_execution( cluster: kb_lib::ExApiExecutionCluster, operation: &kb_lib::ExSplAssociatedTokenAccountOperation, context_slot: std::option::Option, cache: &AccountCache, ) -> kb_core::Result { let mut report = SplAssociatedTokenAccountPostExecutionReport { cluster, operation_code: operation.operation_code().to_string(), status: SplAssociatedTokenAccountStatefulReadinessStatus::Ready, context_slot, checks: std::vec::Vec::new(), facts: std::vec::Vec::new(), }; match operation { kb_lib::ExSplAssociatedTokenAccountOperation::Create { wallet_owner, mint, token_program, } | kb_lib::ExSplAssociatedTokenAccountOperation::CreateIdempotent { wallet_owner, mint, token_program, } => { let ata = match derive_ata(wallet_owner, mint, token_program.program_id()) { std::result::Result::Ok(value) => value, std::result::Result::Err(error) => return std::result::Result::Err(error), }; push_post_fact(&mut report, "derived_ata", ata.0.clone()); check_post_cached_token_account( &mut report, cache, &ata, token_program.program_id(), mint, wallet_owner, "post_execution_ata", ); }, kb_lib::ExSplAssociatedTokenAccountOperation::RecoverNested { wallet_owner, owner_mint, nested_mint, token_program, } => { let owner_ata = result_value!(derive_ata(wallet_owner, owner_mint, token_program.program_id(),)); let nested_ata = result_value!(derive_ata(&owner_ata, nested_mint, token_program.program_id(),)); let destination_ata = result_value!(derive_ata(wallet_owner, nested_mint, token_program.program_id(),)); for (key, value) in [ ("derived_owner_ata", &owner_ata), ("derived_nested_ata", &nested_ata), ("derived_destination_ata", &destination_ata), ] { push_post_fact(&mut report, key, value.0.clone()); } check_post_cached_token_account( &mut report, cache, &owner_ata, token_program.program_id(), owner_mint, wallet_owner, "post_execution_owner_ata", ); push_post_check( &mut report, "post_execution_nested_ata_closed", cache.get(nested_ata.0.as_str()).is_some_and(|account| return account.is_none()), "confirmed RecoverNested must leave the nested ATA closed", ); check_post_cached_token_account( &mut report, cache, &destination_ata, token_program.program_id(), nested_mint, wallet_owner, "post_execution_destination_ata", ); }, } report.status = if report.checks.iter().all(|check| return check.passed) { SplAssociatedTokenAccountStatefulReadinessStatus::Ready } else { SplAssociatedTokenAccountStatefulReadinessStatus::Blocked }; return std::result::Result::Ok(report); } fn check_post_cached_token_account( report: &mut SplAssociatedTokenAccountPostExecutionReport, cache: &AccountCache, address: &kb_lib::MdPubkey, token_program: &str, expected_mint: &kb_lib::MdPubkey, expected_owner: &kb_lib::MdPubkey, role: &str, ) { let account = cache.get(address.0.as_str()).and_then(|value| return value.as_ref()); push_post_check( report, format!("{role}_exists"), account.is_some(), format!("{role} {} must exist after confirmed execution", address.0), ); if let std::option::Option::Some(account) = account { push_post_check( report, format!("{role}_token_program_owner"), account.owner.0 == token_program, format!("{role} must be owned by the selected Token Program"), ); let layout_valid = account.data.len() >= BASE_TOKEN_ACCOUNT_LEN; push_post_check( report, format!("{role}_base_layout"), layout_valid, format!("{role} must expose the complete base Token Account layout"), ); if layout_valid { let mint = bs58::encode(&account.data[0..32]).into_string(); let owner = bs58::encode(&account.data[32..64]).into_string(); push_post_check( report, format!("{role}_mint"), mint == expected_mint.0, format!("{role} mint must match {}", expected_mint.0), ); push_post_check( report, format!("{role}_wallet_owner"), owner == expected_owner.0, format!("{role} owner must match {}", expected_owner.0), ); push_post_check( report, format!("{role}_initialized"), matches!(account.data[108], 1 | 2), format!("{role} must be initialized or frozen"), ); } } } fn push_post_check( report: &mut SplAssociatedTokenAccountPostExecutionReport, code: impl std::convert::Into, passed: bool, message: impl std::convert::Into, ) { report.checks.push(SplAssociatedTokenAccountStatefulCheck { code: code.into(), passed, message: message.into(), }); } fn push_post_fact( report: &mut SplAssociatedTokenAccountPostExecutionReport, key: impl std::convert::Into, value: impl std::convert::Into, ) { report .facts .push(SplAssociatedTokenAccountStatefulFact { key: key.into(), value: value.into() }); } fn check_creation_plan_accounts( report: &mut SplAssociatedTokenAccountStatefulReadinessReport, plan: &kb_lib::ExApiPreparedExecutionPlan, derived_ata: &kb_lib::MdPubkey, token_program: &str, ) { let accounts_exact = match plan.instructions.first() { std::option::Option::Some(instruction) => { instruction.accounts.len() == 6 && instruction.accounts[1].pubkey == derived_ata.clone() && instruction.accounts[4].pubkey.0 == kb_program_ids::SYSTEM_PROGRAM_ID && instruction.accounts[5].pubkey.0 == token_program }, std::option::Option::None => false, }; push_check( report, "creation_plan_accounts_exact", accounts_exact, "creation plan must retain derived ATA, System Program and selected Token Program in official positions", ); } fn check_mint( report: &mut SplAssociatedTokenAccountStatefulReadinessReport, cache: &AccountCache, mint: &kb_lib::MdPubkey, token_program: &str, ) { let account = cache.get(mint.0.as_str()).and_then(|value| return value.as_ref()); push_check(report, "mint_exists", account.is_some(), format!("mint {} must exist", mint.0)); if let std::option::Option::Some(account) = account { push_check( report, "mint_token_program_owner", account.owner.0 == token_program, format!("mint {} must be owned by selected Token Program", mint.0), ); push_check( report, "mint_base_layout", account.data.len() >= BASE_MINT_LEN, format!("mint {} must expose the complete base Mint layout", mint.0), ); push_check( report, "mint_initialized", account.data.get(45) == std::option::Option::Some(&1), format!("mint {} must be initialized", mint.0), ); } } fn check_cached_token_account( report: &mut SplAssociatedTokenAccountStatefulReadinessReport, cache: &AccountCache, address: &kb_lib::MdPubkey, token_program: &str, expected_mint: &kb_lib::MdPubkey, expected_owner: &kb_lib::MdPubkey, role: &str, ) { let account = cache.get(address.0.as_str()).and_then(|value| return value.as_ref()); push_check( report, format!("{role}_exists"), account.is_some(), format!("{role} {} must exist", address.0), ); if let std::option::Option::Some(account) = account { check_token_account(report, account, token_program, expected_mint, expected_owner, role); } } fn check_token_account( report: &mut SplAssociatedTokenAccountStatefulReadinessReport, account: &kb_onchain_transport::AccountInfoValue, token_program: &str, expected_mint: &kb_lib::MdPubkey, expected_owner: &kb_lib::MdPubkey, role: &str, ) { push_check( report, format!("{role}_token_program_owner"), account.owner.0 == token_program, format!("{role} must be owned by selected Token Program"), ); let layout_valid = account.data.len() >= BASE_TOKEN_ACCOUNT_LEN; push_check( report, format!("{role}_base_layout"), layout_valid, format!("{role} must expose the complete base Token Account layout"), ); if layout_valid { let mint = bs58::encode(&account.data[0..32]).into_string(); let owner = bs58::encode(&account.data[32..64]).into_string(); push_check( report, format!("{role}_mint"), mint == expected_mint.0, format!("{role} mint must match {}", expected_mint.0), ); push_check( report, format!("{role}_wallet_owner"), owner == expected_owner.0, format!("{role} owner must match {}", expected_owner.0), ); push_check( report, format!("{role}_initialized"), matches!(account.data[108], 1 | 2), format!("{role} must be initialized or frozen"), ); } } fn push_check( report: &mut SplAssociatedTokenAccountStatefulReadinessReport, code: impl std::convert::Into, passed: bool, message: impl std::convert::Into, ) { report.checks.push(SplAssociatedTokenAccountStatefulCheck { code: code.into(), passed, message: message.into(), }); } fn push_fact( report: &mut SplAssociatedTokenAccountStatefulReadinessReport, key: impl std::convert::Into, value: impl std::convert::Into, ) { report .facts .push(SplAssociatedTokenAccountStatefulFact { key: key.into(), value: value.into() }); } #[cfg(test)] mod tests { fn pubkey(value: &str) -> kb_lib::MdPubkey { return kb_lib::MdPubkey(value.to_string()); } fn policy(rent: u64, signers: std::vec::Vec) -> kb_lib::ExApiExecutionPolicy { return kb_lib::ExApiExecutionPolicy { cost_limit: kb_lib::ExApiExecutionCostLimit { max_spend_lamports: std::option::Option::Some(rent), max_fee_lamports: std::option::Option::Some(10_000), max_compute_unit_price_micro_lamports: std::option::Option::None, }, authorized_signers: signers, post_execution_validation: kb_lib::ExApiPostExecutionValidationPolicy { canonical_insert_required: true, core_extraction_required: true, decode_replay_required: true, materialization_required: true, }, ..kb_lib::ExApiExecutionPolicy::default() }; } fn request( operation: kb_lib::ExSplAssociatedTokenAccountOperation, rent: u64, ) -> crate::SplAssociatedTokenAccountStatefulReadinessRequest { let fee_payer = pubkey(kb_program_ids::SYSTEM_PROGRAM_ID); let wallet = match &operation { kb_lib::ExSplAssociatedTokenAccountOperation::Create { wallet_owner, .. } | kb_lib::ExSplAssociatedTokenAccountOperation::CreateIdempotent { wallet_owner, .. } | kb_lib::ExSplAssociatedTokenAccountOperation::RecoverNested { wallet_owner, .. } => wallet_owner.clone(), }; let mut signers = std::vec![fee_payer.clone()]; if !signers.contains(&wallet) { signers.push(wallet); } return crate::SplAssociatedTokenAccountStatefulReadinessRequest { query_role: "query".to_string(), cluster: kb_lib::ExApiExecutionCluster::Devnet, intent: kb_lib::ExSplAssociatedTokenAccountExecutionIntent { intent_id: "ata-stateful-1".to_string(), fee_payer, max_rent_lamports: rent, policy: policy(rent, signers.clone()), operation, }, available_signers: signers, }; } fn mint_account(token_program: &str) -> kb_onchain_transport::AccountInfoValue { let mut data = std::vec![0; super::BASE_MINT_LEN]; data[45] = 1; return kb_onchain_transport::AccountInfoValue { lamports: 1, owner: kb_lib::MdProgramId(token_program.to_string()), executable: false, rent_epoch: 0, space: data.len() as u64, data, }; } fn token_account( token_program: &str, mint: &kb_lib::MdPubkey, owner: &kb_lib::MdPubkey, ) -> kb_onchain_transport::AccountInfoValue { let mut data = std::vec![0; super::BASE_TOKEN_ACCOUNT_LEN]; let mint_bytes = bs58::decode(&mint.0) .into_vec() .unwrap_or_else(|error| panic!("mint fixture decode failed: {error}")); let owner_bytes = bs58::decode(&owner.0) .into_vec() .unwrap_or_else(|error| panic!("owner fixture decode failed: {error}")); data[0..32].copy_from_slice(&mint_bytes); data[32..64].copy_from_slice(&owner_bytes); data[108] = 1; return kb_onchain_transport::AccountInfoValue { lamports: 1, owner: kb_lib::MdProgramId(token_program.to_string()), executable: false, rent_epoch: 0, space: data.len() as u64, data, }; } fn plan( request: &crate::SplAssociatedTokenAccountStatefulReadinessRequest, ) -> kb_lib::ExApiPreparedExecutionPlan { return kb_lib::ExApiTypedInstructionExecutor::build_prepared_plan( &kb_lib::ExSplAssociatedTokenAccountExecutor, &request.intent, ) .unwrap_or_else(|error| panic!("ATA test plan failed: {error}")); } #[test] fn absent_classic_create_is_ready_with_exact_derived_address_and_rent_ceiling() { let wallet = pubkey(kb_program_ids::STAKE_PROGRAM_ID); let mint = pubkey(kb_program_ids::VOTE_PROGRAM_ID); let request = request( kb_lib::ExSplAssociatedTokenAccountOperation::Create { wallet_owner: wallet.clone(), mint: mint.clone(), token_program: kb_lib::ExSplAssociatedTokenProgram::Classic, }, 2_100_000, ); let ata = super::derive_ata(&wallet, &mint, kb_program_ids::SPL_TOKEN_PROGRAM_ID) .unwrap_or_else(|error| panic!("ATA derivation failed: {error}")); let mut cache = super::AccountCache::new(); cache.insert( mint.0.clone(), std::option::Option::Some(mint_account(kb_program_ids::SPL_TOKEN_PROGRAM_ID)), ); cache.insert(ata.0, std::option::Option::None); let report = super::evaluate( &request, &plan(&request), "devnet", std::option::Option::Some(1), &cache, 2_000_000, 3_000_000, ) .unwrap_or_else(|error| panic!("ATA readiness failed: {error}")); assert_eq!(report.status, crate::SplAssociatedTokenAccountStatefulReadinessStatus::Ready); } #[test] fn idempotent_existing_account_conflict_is_blocked() { let wallet = pubkey(kb_program_ids::STAKE_PROGRAM_ID); let mint = pubkey(kb_program_ids::VOTE_PROGRAM_ID); let request = request( kb_lib::ExSplAssociatedTokenAccountOperation::CreateIdempotent { wallet_owner: wallet.clone(), mint: mint.clone(), token_program: kb_lib::ExSplAssociatedTokenProgram::Token2022, }, 2_100_000, ); let ata = super::derive_ata(&wallet, &mint, kb_program_ids::SPL_TOKEN_2022_PROGRAM_ID) .unwrap_or_else(|error| panic!("ATA derivation failed: {error}")); let mut cache = super::AccountCache::new(); cache.insert( mint.0.clone(), std::option::Option::Some(mint_account(kb_program_ids::SPL_TOKEN_2022_PROGRAM_ID)), ); cache.insert( ata.0, std::option::Option::Some(token_account( kb_program_ids::SPL_TOKEN_2022_PROGRAM_ID, &mint, &pubkey(kb_program_ids::CONFIG_PROGRAM_ID), )), ); let report = super::evaluate( &request, &plan(&request), "devnet", std::option::Option::Some(1), &cache, 2_000_000, 3_000_000, ) .unwrap_or_else(|error| panic!("ATA readiness failed: {error}")); assert_eq!(report.status, crate::SplAssociatedTokenAccountStatefulReadinessStatus::Blocked); assert!( report .checks .iter() .any(|check| return check.code == "idempotent_existing_ata_wallet_owner" && !check.passed) ); } #[test] fn recover_nested_validates_all_three_canonical_accounts_and_signers() { let wallet = pubkey(kb_program_ids::STAKE_PROGRAM_ID); let owner_mint = pubkey(kb_program_ids::VOTE_PROGRAM_ID); let nested_mint = pubkey(kb_program_ids::CONFIG_PROGRAM_ID); let token_program = kb_program_ids::SPL_TOKEN_PROGRAM_ID; let request = request( kb_lib::ExSplAssociatedTokenAccountOperation::RecoverNested { wallet_owner: wallet.clone(), owner_mint: owner_mint.clone(), nested_mint: nested_mint.clone(), token_program: kb_lib::ExSplAssociatedTokenProgram::Classic, }, 0, ); let owner_ata = super::derive_ata(&wallet, &owner_mint, token_program) .unwrap_or_else(|error| panic!("owner ATA failed: {error}")); let nested_ata = super::derive_ata(&owner_ata, &nested_mint, token_program) .unwrap_or_else(|error| panic!("nested ATA failed: {error}")); let destination = super::derive_ata(&wallet, &nested_mint, token_program) .unwrap_or_else(|error| panic!("destination ATA failed: {error}")); let mut cache = super::AccountCache::new(); cache.insert(owner_mint.0.clone(), std::option::Option::Some(mint_account(token_program))); cache.insert(nested_mint.0.clone(), std::option::Option::Some(mint_account(token_program))); cache.insert( owner_ata.0.clone(), std::option::Option::Some(token_account(token_program, &owner_mint, &wallet)), ); cache.insert( nested_ata.0, std::option::Option::Some(token_account(token_program, &nested_mint, &owner_ata)), ); cache.insert( destination.0, std::option::Option::Some(token_account(token_program, &nested_mint, &wallet)), ); let report = super::evaluate( &request, &plan(&request), "devnet", std::option::Option::Some(1), &cache, 2_000_000, 20_000, ) .unwrap_or_else(|error| panic!("ATA readiness failed: {error}")); assert_eq!(report.status, crate::SplAssociatedTokenAccountStatefulReadinessStatus::Ready); } #[test] fn creation_postcondition_requires_one_compatible_existing_ata() { let wallet = pubkey(kb_program_ids::STAKE_PROGRAM_ID); let mint = pubkey(kb_program_ids::VOTE_PROGRAM_ID); let operation = kb_lib::ExSplAssociatedTokenAccountOperation::CreateIdempotent { wallet_owner: wallet.clone(), mint: mint.clone(), token_program: kb_lib::ExSplAssociatedTokenProgram::Token2022, }; let ata = super::derive_ata(&wallet, &mint, kb_program_ids::SPL_TOKEN_2022_PROGRAM_ID) .unwrap_or_else(|error| panic!("ATA derivation failed: {error}")); let mut cache = super::AccountCache::new(); cache.insert( ata.0, std::option::Option::Some(token_account( kb_program_ids::SPL_TOKEN_2022_PROGRAM_ID, &mint, &wallet, )), ); let report = super::evaluate_post_execution( kb_lib::ExApiExecutionCluster::Devnet, &operation, std::option::Option::Some(12), &cache, ) .unwrap_or_else(|error| panic!("ATA postcondition failed: {error}")); assert_eq!(report.status, crate::SplAssociatedTokenAccountStatefulReadinessStatus::Ready); } #[test] fn recover_postcondition_requires_nested_closed_and_destination_compatible() { let wallet = pubkey(kb_program_ids::STAKE_PROGRAM_ID); let owner_mint = pubkey(kb_program_ids::VOTE_PROGRAM_ID); let nested_mint = pubkey(kb_program_ids::CONFIG_PROGRAM_ID); let token_program = kb_program_ids::SPL_TOKEN_PROGRAM_ID; let operation = kb_lib::ExSplAssociatedTokenAccountOperation::RecoverNested { wallet_owner: wallet.clone(), owner_mint: owner_mint.clone(), nested_mint: nested_mint.clone(), token_program: kb_lib::ExSplAssociatedTokenProgram::Classic, }; let owner_ata = super::derive_ata(&wallet, &owner_mint, token_program) .unwrap_or_else(|error| panic!("owner ATA failed: {error}")); let nested_ata = super::derive_ata(&owner_ata, &nested_mint, token_program) .unwrap_or_else(|error| panic!("nested ATA failed: {error}")); let destination = super::derive_ata(&wallet, &nested_mint, token_program) .unwrap_or_else(|error| panic!("destination ATA failed: {error}")); let mut cache = super::AccountCache::new(); cache.insert( owner_ata.0, std::option::Option::Some(token_account(token_program, &owner_mint, &wallet)), ); cache.insert(nested_ata.0, std::option::Option::None); cache.insert( destination.0, std::option::Option::Some(token_account(token_program, &nested_mint, &wallet)), ); let report = super::evaluate_post_execution( kb_lib::ExApiExecutionCluster::Devnet, &operation, std::option::Option::Some(13), &cache, ) .unwrap_or_else(|error| panic!("RecoverNested postcondition failed: {error}")); assert_eq!(report.status, crate::SplAssociatedTokenAccountStatefulReadinessStatus::Ready); } }