Files
khadhroony-bot3/ks-pipeline/src/spl_ata_stateful.rs
2026-08-09 19:34:08 +02:00

1178 lines
46 KiB
Rust

// file: ks-pipeline/src/spl_ata_stateful.rs
// version: 5
//! 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: ks_lib::ExApiExecutionCluster,
/// Typed simulation-first ATA intent.
pub intent: ks_lib::ExSplAssociatedTokenAccountExecutionIntent,
/// Signer public keys currently available to the caller.
pub available_signers: std::vec::Vec<ks_lib::MdPubkey>,
}
/// Complete ATA stateful readiness report.
#[derive(Clone, Debug, Eq, PartialEq, serde::Deserialize, serde::Serialize)]
pub struct SplAssociatedTokenAccountStatefulReadinessReport {
/// Expected cluster.
pub cluster: ks_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<u64>,
/// Ordered checks.
pub checks: std::vec::Vec<SplAssociatedTokenAccountStatefulCheck>,
/// Ordered facts.
pub facts: std::vec::Vec<SplAssociatedTokenAccountStatefulFact>,
}
/// 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: ks_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<u64>,
/// Ordered state checks.
pub checks: std::vec::Vec<SplAssociatedTokenAccountStatefulCheck>,
/// Ordered derived-address facts.
pub facts: std::vec::Vec<SplAssociatedTokenAccountStatefulFact>,
}
type AccountCache = std::collections::BTreeMap<
std::string::String,
std::option::Option<ks_onchain_transport::AccountInfoValue>,
>;
/// Inspects Localnet or Devnet ATA state required before simulation.
pub async fn inspect_spl_associated_token_account_stateful_readiness(
pool: &ks_onchain_transport::HttpEndpointPool,
request: &SplAssociatedTokenAccountStatefulReadinessRequest,
) -> ks_core::Result<SplAssociatedTokenAccountStatefulReadinessReport> {
if request.query_role.trim().is_empty() {
return std::result::Result::Err(ks_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 ks_lib::ExApiTypedInstructionExecutor::build_prepared_plan(
&ks_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 ks_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<u64> = 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,
&ks_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 = ks_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: &ks_onchain_transport::HttpEndpointPool,
query_role: &str,
cluster: ks_lib::ExApiExecutionCluster,
operation: &ks_lib::ExSplAssociatedTokenAccountOperation,
) -> ks_core::Result<SplAssociatedTokenAccountPostExecutionReport> {
if query_role.trim().is_empty() {
return std::result::Result::Err(ks_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 ks_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<u64> = 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: ks_lib::ExApiExecutionCluster) -> ks_core::Result<()> {
return match cluster {
ks_lib::ExApiExecutionCluster::Localnet | ks_lib::ExApiExecutionCluster::Devnet => {
std::result::Result::Ok(())
},
ks_lib::ExApiExecutionCluster::Testnet | ks_lib::ExApiExecutionCluster::Mainnet => {
std::result::Result::Err(ks_core::Error::new(
"spl_ata_stateful_cluster_unsupported",
"ATA stateful readiness is restricted to Localnet and Devnet",
))
},
};
}
fn validate_genesis(
expected: ks_lib::ExApiExecutionCluster,
genesis: &ks_onchain_transport::GenesisHashResult,
) -> ks_core::Result<()> {
return match expected {
ks_lib::ExApiExecutionCluster::Devnet
if genesis.classified_cluster
== std::option::Option::Some(ks_lib::ExApiExecutionCluster::Devnet) =>
{
std::result::Result::Ok(())
},
ks_lib::ExApiExecutionCluster::Localnet if genesis.classified_cluster.is_none() => {
std::result::Result::Ok(())
},
ks_lib::ExApiExecutionCluster::Devnet => std::result::Result::Err(ks_core::Error::new(
"spl_ata_stateful_devnet_genesis_mismatch",
"ATA Devnet readiness requires the official Devnet genesis hash",
)),
ks_lib::ExApiExecutionCluster::Localnet => std::result::Result::Err(ks_core::Error::new(
"spl_ata_stateful_localnet_public_cluster",
"ATA Localnet readiness refuses endpoints classified as a public cluster",
)),
ks_lib::ExApiExecutionCluster::Testnet | ks_lib::ExApiExecutionCluster::Mainnet => {
validate_cluster_kind(expected)
},
};
}
fn operation_addresses(
operation: &ks_lib::ExSplAssociatedTokenAccountOperation,
) -> ks_core::Result<std::vec::Vec<ks_lib::MdPubkey>> {
let mut values = std::vec::Vec::new();
match operation {
ks_lib::ExSplAssociatedTokenAccountOperation::Create {
wallet_owner,
mint,
token_program,
}
| ks_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(),)));
},
ks_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: &ks_lib::MdPubkey,
mint: &ks_lib::MdPubkey,
token_program: &str,
) -> ks_core::Result<ks_lib::MdPubkey> {
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(ks_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(ks_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(ks_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(ks_lib::MdPubkey(derived.to_string()));
}
fn evaluate(
request: &SplAssociatedTokenAccountStatefulReadinessRequest,
plan: &ks_lib::ExApiPreparedExecutionPlan,
genesis_hash: &str,
context_slot: std::option::Option<u64>,
cache: &AccountCache,
base_rent_lamports: u64,
payer_balance_lamports: u64,
) -> ks_core::Result<SplAssociatedTokenAccountStatefulReadinessReport> {
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 == ks_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 == ks_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 {
ks_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",
);
},
ks_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",
);
}
},
ks_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: ks_lib::ExApiExecutionCluster,
operation: &ks_lib::ExSplAssociatedTokenAccountOperation,
context_slot: std::option::Option<u64>,
cache: &AccountCache,
) -> ks_core::Result<SplAssociatedTokenAccountPostExecutionReport> {
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 {
ks_lib::ExSplAssociatedTokenAccountOperation::Create {
wallet_owner,
mint,
token_program,
}
| ks_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",
);
},
ks_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: &ks_lib::MdPubkey,
token_program: &str,
expected_mint: &ks_lib::MdPubkey,
expected_owner: &ks_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<std::string::String>,
passed: bool,
message: impl std::convert::Into<std::string::String>,
) {
report.checks.push(SplAssociatedTokenAccountStatefulCheck {
code: code.into(),
passed,
message: message.into(),
});
}
fn push_post_fact(
report: &mut SplAssociatedTokenAccountPostExecutionReport,
key: impl std::convert::Into<std::string::String>,
value: impl std::convert::Into<std::string::String>,
) {
report
.facts
.push(SplAssociatedTokenAccountStatefulFact { key: key.into(), value: value.into() });
}
fn check_creation_plan_accounts(
report: &mut SplAssociatedTokenAccountStatefulReadinessReport,
plan: &ks_lib::ExApiPreparedExecutionPlan,
derived_ata: &ks_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 == ks_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: &ks_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: &ks_lib::MdPubkey,
token_program: &str,
expected_mint: &ks_lib::MdPubkey,
expected_owner: &ks_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: &ks_onchain_transport::AccountInfoValue,
token_program: &str,
expected_mint: &ks_lib::MdPubkey,
expected_owner: &ks_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<std::string::String>,
passed: bool,
message: impl std::convert::Into<std::string::String>,
) {
report.checks.push(SplAssociatedTokenAccountStatefulCheck {
code: code.into(),
passed,
message: message.into(),
});
}
fn push_fact(
report: &mut SplAssociatedTokenAccountStatefulReadinessReport,
key: impl std::convert::Into<std::string::String>,
value: impl std::convert::Into<std::string::String>,
) {
report
.facts
.push(SplAssociatedTokenAccountStatefulFact { key: key.into(), value: value.into() });
}
#[cfg(test)]
mod tests {
fn pubkey(value: &str) -> ks_lib::MdPubkey {
return ks_lib::MdPubkey(value.to_string());
}
fn policy(rent: u64, signers: std::vec::Vec<ks_lib::MdPubkey>) -> ks_lib::ExApiExecutionPolicy {
return ks_lib::ExApiExecutionPolicy {
cost_limit: ks_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: ks_lib::ExApiPostExecutionValidationPolicy {
canonical_insert_required: true,
core_extraction_required: true,
decode_replay_required: true,
materialization_required: true,
},
..ks_lib::ExApiExecutionPolicy::default()
};
}
fn request(
operation: ks_lib::ExSplAssociatedTokenAccountOperation,
rent: u64,
) -> crate::SplAssociatedTokenAccountStatefulReadinessRequest {
let fee_payer = pubkey(ks_program_ids::SYSTEM_PROGRAM_ID);
let wallet = match &operation {
ks_lib::ExSplAssociatedTokenAccountOperation::Create { wallet_owner, .. }
| ks_lib::ExSplAssociatedTokenAccountOperation::CreateIdempotent {
wallet_owner, ..
}
| ks_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: ks_lib::ExApiExecutionCluster::Devnet,
intent: ks_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) -> ks_onchain_transport::AccountInfoValue {
let mut data = std::vec![0; super::BASE_MINT_LEN];
data[45] = 1;
return ks_onchain_transport::AccountInfoValue {
lamports: 1,
owner: ks_lib::MdProgramId(token_program.to_string()),
executable: false,
rent_epoch: 0,
space: data.len() as u64,
data,
};
}
fn token_account(
token_program: &str,
mint: &ks_lib::MdPubkey,
owner: &ks_lib::MdPubkey,
) -> ks_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 ks_onchain_transport::AccountInfoValue {
lamports: 1,
owner: ks_lib::MdProgramId(token_program.to_string()),
executable: false,
rent_epoch: 0,
space: data.len() as u64,
data,
};
}
fn plan(
request: &crate::SplAssociatedTokenAccountStatefulReadinessRequest,
) -> ks_lib::ExApiPreparedExecutionPlan {
return ks_lib::ExApiTypedInstructionExecutor::build_prepared_plan(
&ks_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(ks_program_ids::STAKE_PROGRAM_ID);
let mint = pubkey(ks_program_ids::VOTE_PROGRAM_ID);
let request = request(
ks_lib::ExSplAssociatedTokenAccountOperation::Create {
wallet_owner: wallet.clone(),
mint: mint.clone(),
token_program: ks_lib::ExSplAssociatedTokenProgram::Classic,
},
2_100_000,
);
let ata = super::derive_ata(&wallet, &mint, ks_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(ks_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(ks_program_ids::STAKE_PROGRAM_ID);
let mint = pubkey(ks_program_ids::VOTE_PROGRAM_ID);
let request = request(
ks_lib::ExSplAssociatedTokenAccountOperation::CreateIdempotent {
wallet_owner: wallet.clone(),
mint: mint.clone(),
token_program: ks_lib::ExSplAssociatedTokenProgram::Token2022,
},
2_100_000,
);
let ata = super::derive_ata(&wallet, &mint, ks_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(ks_program_ids::SPL_TOKEN_2022_PROGRAM_ID)),
);
cache.insert(
ata.0,
std::option::Option::Some(token_account(
ks_program_ids::SPL_TOKEN_2022_PROGRAM_ID,
&mint,
&pubkey(ks_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(ks_program_ids::STAKE_PROGRAM_ID);
let owner_mint = pubkey(ks_program_ids::VOTE_PROGRAM_ID);
let nested_mint = pubkey(ks_program_ids::CONFIG_PROGRAM_ID);
let token_program = ks_program_ids::SPL_TOKEN_PROGRAM_ID;
let request = request(
ks_lib::ExSplAssociatedTokenAccountOperation::RecoverNested {
wallet_owner: wallet.clone(),
owner_mint: owner_mint.clone(),
nested_mint: nested_mint.clone(),
token_program: ks_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(ks_program_ids::STAKE_PROGRAM_ID);
let mint = pubkey(ks_program_ids::VOTE_PROGRAM_ID);
let operation = ks_lib::ExSplAssociatedTokenAccountOperation::CreateIdempotent {
wallet_owner: wallet.clone(),
mint: mint.clone(),
token_program: ks_lib::ExSplAssociatedTokenProgram::Token2022,
};
let ata = super::derive_ata(&wallet, &mint, ks_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(
ks_program_ids::SPL_TOKEN_2022_PROGRAM_ID,
&mint,
&wallet,
)),
);
let report = super::evaluate_post_execution(
ks_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(ks_program_ids::STAKE_PROGRAM_ID);
let owner_mint = pubkey(ks_program_ids::VOTE_PROGRAM_ID);
let nested_mint = pubkey(ks_program_ids::CONFIG_PROGRAM_ID);
let token_program = ks_program_ids::SPL_TOKEN_PROGRAM_ID;
let operation = ks_lib::ExSplAssociatedTokenAccountOperation::RecoverNested {
wallet_owner: wallet.clone(),
owner_mint: owner_mint.clone(),
nested_mint: nested_mint.clone(),
token_program: ks_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(
ks_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);
}
}