1305 lines
53 KiB
Rust
1305 lines
53 KiB
Rust
// file: kb_pipeline/src/solana_ata_stateful.rs
|
|
// version: 3
|
|
|
|
//! 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_execution_api::ExecutionCluster,
|
|
/// Typed simulation-first ATA intent.
|
|
pub intent: kb_executor_spl_associated_token_account::SplAssociatedTokenAccountExecutionIntent,
|
|
/// Signer public keys currently available to the caller.
|
|
pub available_signers: std::vec::Vec<kb_model::Pubkey>,
|
|
}
|
|
|
|
/// Complete ATA stateful readiness report.
|
|
#[derive(Clone, Debug, Eq, PartialEq, serde::Deserialize, serde::Serialize)]
|
|
pub struct SplAssociatedTokenAccountStatefulReadinessReport {
|
|
/// Expected cluster.
|
|
pub cluster: kb_execution_api::ExecutionCluster,
|
|
/// Stable operation code.
|
|
pub operation_code: std::string::String,
|
|
/// Aggregate readiness status.
|
|
pub status: crate::solana_ata_stateful::SplAssociatedTokenAccountStatefulReadinessStatus,
|
|
/// Highest contextual slot observed.
|
|
pub context_slot: std::option::Option<u64>,
|
|
/// Ordered checks.
|
|
pub checks: std::vec::Vec<crate::solana_ata_stateful::SplAssociatedTokenAccountStatefulCheck>,
|
|
/// Ordered facts.
|
|
pub facts: std::vec::Vec<crate::solana_ata_stateful::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: kb_execution_api::ExecutionCluster,
|
|
/// Stable operation code.
|
|
pub operation_code: std::string::String,
|
|
/// Aggregate postcondition status.
|
|
pub status: crate::solana_ata_stateful::SplAssociatedTokenAccountStatefulReadinessStatus,
|
|
/// Highest contextual slot observed.
|
|
pub context_slot: std::option::Option<u64>,
|
|
/// Ordered state checks.
|
|
pub checks: std::vec::Vec<crate::solana_ata_stateful::SplAssociatedTokenAccountStatefulCheck>,
|
|
/// Ordered derived-address facts.
|
|
pub facts: std::vec::Vec<crate::solana_ata_stateful::SplAssociatedTokenAccountStatefulFact>,
|
|
}
|
|
|
|
type AccountCache =
|
|
std::collections::BTreeMap<std::string::String, std::option::Option<kb_rpc::AccountInfoValue>>;
|
|
|
|
/// Inspects Localnet or Devnet ATA state required before simulation.
|
|
pub async fn inspect_spl_associated_token_account_stateful_readiness(
|
|
pool: &kb_rpc::HttpEndpointPool,
|
|
request: &crate::solana_ata_stateful::SplAssociatedTokenAccountStatefulReadinessRequest,
|
|
) -> kb_core::Result<crate::solana_ata_stateful::SplAssociatedTokenAccountStatefulReadinessReport> {
|
|
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 crate::solana_ata_stateful::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 crate::solana_ata_stateful::validate_genesis(request.cluster, &genesis) {
|
|
std::result::Result::Ok(()) => {},
|
|
std::result::Result::Err(error) => return std::result::Result::Err(error),
|
|
}
|
|
let plan = match kb_execution_api::TypedInstructionExecutor::build_prepared_plan(
|
|
&kb_executor_spl_associated_token_account::SplAssociatedTokenAccountExecutor,
|
|
&request.intent,
|
|
) {
|
|
std::result::Result::Ok(value) => value,
|
|
std::result::Result::Err(error) => return std::result::Result::Err(error),
|
|
};
|
|
let addresses = match crate::solana_ata_stateful::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_rpc::GetAccountInfoConfig::confirmed_with_data(
|
|
crate::solana_ata_stateful::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 = crate::solana_ata_stateful::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(),
|
|
crate::solana_ata_stateful::BASE_TOKEN_ACCOUNT_LEN as u64,
|
|
&kb_rpc::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_rpc::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 crate::solana_ata_stateful::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_rpc::HttpEndpointPool,
|
|
query_role: &str,
|
|
cluster: kb_execution_api::ExecutionCluster,
|
|
operation: &kb_executor_spl_associated_token_account::SplAssociatedTokenAccountOperation,
|
|
) -> kb_core::Result<crate::solana_ata_stateful::SplAssociatedTokenAccountPostExecutionReport> {
|
|
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) =
|
|
crate::solana_ata_stateful::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) =
|
|
crate::solana_ata_stateful::validate_genesis(cluster, &genesis)
|
|
{
|
|
return std::result::Result::Err(error);
|
|
}
|
|
let addresses = match crate::solana_ata_stateful::operation_addresses(operation) {
|
|
std::result::Result::Ok(value) => value,
|
|
std::result::Result::Err(error) => return std::result::Result::Err(error),
|
|
};
|
|
let config = match kb_rpc::GetAccountInfoConfig::confirmed_with_data(
|
|
crate::solana_ata_stateful::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 = crate::solana_ata_stateful::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 crate::solana_ata_stateful::evaluate_post_execution(
|
|
cluster,
|
|
operation,
|
|
context_slot,
|
|
&cache,
|
|
);
|
|
}
|
|
|
|
fn validate_cluster_kind(cluster: kb_execution_api::ExecutionCluster) -> kb_core::Result<()> {
|
|
return match cluster {
|
|
kb_execution_api::ExecutionCluster::Localnet
|
|
| kb_execution_api::ExecutionCluster::Devnet => std::result::Result::Ok(()),
|
|
kb_execution_api::ExecutionCluster::Testnet
|
|
| kb_execution_api::ExecutionCluster::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_execution_api::ExecutionCluster,
|
|
genesis: &kb_rpc::GenesisHashResult,
|
|
) -> kb_core::Result<()> {
|
|
return match expected {
|
|
kb_execution_api::ExecutionCluster::Devnet
|
|
if genesis.classified_cluster
|
|
== std::option::Option::Some(kb_execution_api::ExecutionCluster::Devnet) =>
|
|
{
|
|
std::result::Result::Ok(())
|
|
},
|
|
kb_execution_api::ExecutionCluster::Localnet if genesis.classified_cluster.is_none() => {
|
|
std::result::Result::Ok(())
|
|
},
|
|
kb_execution_api::ExecutionCluster::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_execution_api::ExecutionCluster::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_execution_api::ExecutionCluster::Testnet
|
|
| kb_execution_api::ExecutionCluster::Mainnet => {
|
|
crate::solana_ata_stateful::validate_cluster_kind(expected)
|
|
},
|
|
};
|
|
}
|
|
|
|
fn operation_addresses(
|
|
operation: &kb_executor_spl_associated_token_account::SplAssociatedTokenAccountOperation,
|
|
) -> kb_core::Result<std::vec::Vec<kb_model::Pubkey>> {
|
|
let mut values = std::vec::Vec::new();
|
|
match operation {
|
|
kb_executor_spl_associated_token_account::SplAssociatedTokenAccountOperation::Create {
|
|
wallet_owner,
|
|
mint,
|
|
token_program,
|
|
}
|
|
| kb_executor_spl_associated_token_account::SplAssociatedTokenAccountOperation::CreateIdempotent {
|
|
wallet_owner,
|
|
mint,
|
|
token_program,
|
|
} => {
|
|
values.push(mint.clone());
|
|
values.push(result_value!(crate::solana_ata_stateful::derive_ata(
|
|
wallet_owner,
|
|
mint,
|
|
token_program.program_id(),
|
|
)));
|
|
},
|
|
kb_executor_spl_associated_token_account::SplAssociatedTokenAccountOperation::RecoverNested {
|
|
wallet_owner,
|
|
owner_mint,
|
|
nested_mint,
|
|
token_program,
|
|
} => {
|
|
let owner_ata = result_value!(crate::solana_ata_stateful::derive_ata(
|
|
wallet_owner,
|
|
owner_mint,
|
|
token_program.program_id(),
|
|
));
|
|
let nested_ata = result_value!(crate::solana_ata_stateful::derive_ata(
|
|
&owner_ata,
|
|
nested_mint,
|
|
token_program.program_id(),
|
|
));
|
|
let destination_ata = result_value!(crate::solana_ata_stateful::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_model::Pubkey,
|
|
mint: &kb_model::Pubkey,
|
|
token_program: &str,
|
|
) -> kb_core::Result<kb_model::Pubkey> {
|
|
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_model::Pubkey(derived.to_string()));
|
|
}
|
|
|
|
fn evaluate(
|
|
request: &crate::solana_ata_stateful::SplAssociatedTokenAccountStatefulReadinessRequest,
|
|
plan: &kb_execution_api::PreparedExecutionPlan,
|
|
genesis_hash: &str,
|
|
context_slot: std::option::Option<u64>,
|
|
cache: &crate::solana_ata_stateful::AccountCache,
|
|
base_rent_lamports: u64,
|
|
payer_balance_lamports: u64,
|
|
) -> kb_core::Result<crate::solana_ata_stateful::SplAssociatedTokenAccountStatefulReadinessReport> {
|
|
let mut report = crate::solana_ata_stateful::SplAssociatedTokenAccountStatefulReadinessReport {
|
|
cluster: request.cluster,
|
|
operation_code: request.intent.operation.operation_code().to_string(),
|
|
status: crate::solana_ata_stateful::SplAssociatedTokenAccountStatefulReadinessStatus::Ready,
|
|
context_slot,
|
|
checks: std::vec::Vec::new(),
|
|
facts: std::vec![
|
|
crate::solana_ata_stateful::SplAssociatedTokenAccountStatefulFact {
|
|
key: "genesis_hash".to_string(),
|
|
value: genesis_hash.to_string(),
|
|
},
|
|
crate::solana_ata_stateful::SplAssociatedTokenAccountStatefulFact {
|
|
key: "base_token_account_rent_lamports".to_string(),
|
|
value: base_rent_lamports.to_string(),
|
|
},
|
|
],
|
|
};
|
|
crate::solana_ata_stateful::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",
|
|
);
|
|
crate::solana_ata_stateful::push_check(
|
|
&mut report,
|
|
"simulation_required",
|
|
plan.policy.simulation == kb_execution_api::ExecutionSimulationPolicy::Required,
|
|
"ATA plan must require simulation",
|
|
);
|
|
for signer in &plan.required_signers {
|
|
crate::solana_ata_stateful::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,
|
|
};
|
|
crate::solana_ata_stateful::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_executor_spl_associated_token_account::SplAssociatedTokenAccountOperation::Create {
|
|
wallet_owner,
|
|
mint,
|
|
token_program,
|
|
} => {
|
|
crate::solana_ata_stateful::check_mint(&mut report, cache, mint, token_program.program_id());
|
|
let ata = result_value!(crate::solana_ata_stateful::derive_ata(
|
|
wallet_owner,
|
|
mint,
|
|
token_program.program_id(),
|
|
));
|
|
crate::solana_ata_stateful::push_fact(&mut report, "derived_ata", ata.0.clone());
|
|
crate::solana_ata_stateful::check_creation_plan_accounts(
|
|
&mut report,
|
|
plan,
|
|
&ata,
|
|
token_program.program_id(),
|
|
);
|
|
crate::solana_ata_stateful::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",
|
|
);
|
|
crate::solana_ata_stateful::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_executor_spl_associated_token_account::SplAssociatedTokenAccountOperation::CreateIdempotent {
|
|
wallet_owner,
|
|
mint,
|
|
token_program,
|
|
} => {
|
|
crate::solana_ata_stateful::check_mint(&mut report, cache, mint, token_program.program_id());
|
|
let ata = result_value!(crate::solana_ata_stateful::derive_ata(
|
|
wallet_owner,
|
|
mint,
|
|
token_program.program_id(),
|
|
));
|
|
crate::solana_ata_stateful::push_fact(&mut report, "derived_ata", ata.0.clone());
|
|
crate::solana_ata_stateful::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()) {
|
|
crate::solana_ata_stateful::check_token_account(
|
|
&mut report,
|
|
account,
|
|
token_program.program_id(),
|
|
mint,
|
|
wallet_owner,
|
|
"idempotent_existing_ata",
|
|
);
|
|
} else {
|
|
crate::solana_ata_stateful::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",
|
|
);
|
|
crate::solana_ata_stateful::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_executor_spl_associated_token_account::SplAssociatedTokenAccountOperation::RecoverNested {
|
|
wallet_owner,
|
|
owner_mint,
|
|
nested_mint,
|
|
token_program,
|
|
} => {
|
|
crate::solana_ata_stateful::check_mint(&mut report, cache, owner_mint, token_program.program_id());
|
|
crate::solana_ata_stateful::check_mint(&mut report, cache, nested_mint, token_program.program_id());
|
|
let owner_ata = result_value!(crate::solana_ata_stateful::derive_ata(
|
|
wallet_owner,
|
|
owner_mint,
|
|
token_program.program_id(),
|
|
));
|
|
let nested_ata = result_value!(crate::solana_ata_stateful::derive_ata(
|
|
&owner_ata,
|
|
nested_mint,
|
|
token_program.program_id(),
|
|
));
|
|
let destination_ata = result_value!(crate::solana_ata_stateful::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),
|
|
] {
|
|
crate::solana_ata_stateful::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,
|
|
};
|
|
crate::solana_ata_stateful::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",
|
|
);
|
|
crate::solana_ata_stateful::check_cached_token_account(&mut report, cache, &owner_ata, token_program.program_id(), owner_mint, wallet_owner, "owner_ata");
|
|
crate::solana_ata_stateful::check_cached_token_account(&mut report, cache, &nested_ata, token_program.program_id(), nested_mint, &owner_ata, "nested_ata");
|
|
crate::solana_ata_stateful::check_cached_token_account(&mut report, cache, &destination_ata, token_program.program_id(), nested_mint, wallet_owner, "destination_ata");
|
|
crate::solana_ata_stateful::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) {
|
|
crate::solana_ata_stateful::SplAssociatedTokenAccountStatefulReadinessStatus::Ready
|
|
} else {
|
|
crate::solana_ata_stateful::SplAssociatedTokenAccountStatefulReadinessStatus::Blocked
|
|
};
|
|
return std::result::Result::Ok(report);
|
|
}
|
|
|
|
fn evaluate_post_execution(
|
|
cluster: kb_execution_api::ExecutionCluster,
|
|
operation: &kb_executor_spl_associated_token_account::SplAssociatedTokenAccountOperation,
|
|
context_slot: std::option::Option<u64>,
|
|
cache: &crate::solana_ata_stateful::AccountCache,
|
|
) -> kb_core::Result<crate::solana_ata_stateful::SplAssociatedTokenAccountPostExecutionReport> {
|
|
let mut report = crate::solana_ata_stateful::SplAssociatedTokenAccountPostExecutionReport {
|
|
cluster,
|
|
operation_code: operation.operation_code().to_string(),
|
|
status: crate::solana_ata_stateful::SplAssociatedTokenAccountStatefulReadinessStatus::Ready,
|
|
context_slot,
|
|
checks: std::vec::Vec::new(),
|
|
facts: std::vec::Vec::new(),
|
|
};
|
|
match operation {
|
|
kb_executor_spl_associated_token_account::SplAssociatedTokenAccountOperation::Create {
|
|
wallet_owner,
|
|
mint,
|
|
token_program,
|
|
}
|
|
| kb_executor_spl_associated_token_account::SplAssociatedTokenAccountOperation::CreateIdempotent {
|
|
wallet_owner,
|
|
mint,
|
|
token_program,
|
|
} => {
|
|
let ata = match crate::solana_ata_stateful::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),
|
|
};
|
|
crate::solana_ata_stateful::push_post_fact(&mut report, "derived_ata", ata.0.clone());
|
|
crate::solana_ata_stateful::check_post_cached_token_account(
|
|
&mut report,
|
|
cache,
|
|
&ata,
|
|
token_program.program_id(),
|
|
mint,
|
|
wallet_owner,
|
|
"post_execution_ata",
|
|
);
|
|
},
|
|
kb_executor_spl_associated_token_account::SplAssociatedTokenAccountOperation::RecoverNested {
|
|
wallet_owner,
|
|
owner_mint,
|
|
nested_mint,
|
|
token_program,
|
|
} => {
|
|
let owner_ata = result_value!(crate::solana_ata_stateful::derive_ata(
|
|
wallet_owner,
|
|
owner_mint,
|
|
token_program.program_id(),
|
|
));
|
|
let nested_ata = result_value!(crate::solana_ata_stateful::derive_ata(
|
|
&owner_ata,
|
|
nested_mint,
|
|
token_program.program_id(),
|
|
));
|
|
let destination_ata = result_value!(crate::solana_ata_stateful::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),
|
|
] {
|
|
crate::solana_ata_stateful::push_post_fact(&mut report, key, value.0.clone());
|
|
}
|
|
crate::solana_ata_stateful::check_post_cached_token_account(
|
|
&mut report,
|
|
cache,
|
|
&owner_ata,
|
|
token_program.program_id(),
|
|
owner_mint,
|
|
wallet_owner,
|
|
"post_execution_owner_ata",
|
|
);
|
|
crate::solana_ata_stateful::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",
|
|
);
|
|
crate::solana_ata_stateful::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) {
|
|
crate::solana_ata_stateful::SplAssociatedTokenAccountStatefulReadinessStatus::Ready
|
|
} else {
|
|
crate::solana_ata_stateful::SplAssociatedTokenAccountStatefulReadinessStatus::Blocked
|
|
};
|
|
return std::result::Result::Ok(report);
|
|
}
|
|
|
|
fn check_post_cached_token_account(
|
|
report: &mut crate::solana_ata_stateful::SplAssociatedTokenAccountPostExecutionReport,
|
|
cache: &crate::solana_ata_stateful::AccountCache,
|
|
address: &kb_model::Pubkey,
|
|
token_program: &str,
|
|
expected_mint: &kb_model::Pubkey,
|
|
expected_owner: &kb_model::Pubkey,
|
|
role: &str,
|
|
) {
|
|
let account = cache.get(address.0.as_str()).and_then(|value| return value.as_ref());
|
|
crate::solana_ata_stateful::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 {
|
|
crate::solana_ata_stateful::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() >= crate::solana_ata_stateful::BASE_TOKEN_ACCOUNT_LEN;
|
|
crate::solana_ata_stateful::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();
|
|
crate::solana_ata_stateful::push_post_check(
|
|
report,
|
|
format!("{role}_mint"),
|
|
mint == expected_mint.0,
|
|
format!("{role} mint must match {}", expected_mint.0),
|
|
);
|
|
crate::solana_ata_stateful::push_post_check(
|
|
report,
|
|
format!("{role}_wallet_owner"),
|
|
owner == expected_owner.0,
|
|
format!("{role} owner must match {}", expected_owner.0),
|
|
);
|
|
crate::solana_ata_stateful::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 crate::solana_ata_stateful::SplAssociatedTokenAccountPostExecutionReport,
|
|
code: impl std::convert::Into<std::string::String>,
|
|
passed: bool,
|
|
message: impl std::convert::Into<std::string::String>,
|
|
) {
|
|
report
|
|
.checks
|
|
.push(crate::solana_ata_stateful::SplAssociatedTokenAccountStatefulCheck {
|
|
code: code.into(),
|
|
passed,
|
|
message: message.into(),
|
|
});
|
|
}
|
|
|
|
fn push_post_fact(
|
|
report: &mut crate::solana_ata_stateful::SplAssociatedTokenAccountPostExecutionReport,
|
|
key: impl std::convert::Into<std::string::String>,
|
|
value: impl std::convert::Into<std::string::String>,
|
|
) {
|
|
report
|
|
.facts
|
|
.push(crate::solana_ata_stateful::SplAssociatedTokenAccountStatefulFact {
|
|
key: key.into(),
|
|
value: value.into(),
|
|
});
|
|
}
|
|
|
|
fn check_creation_plan_accounts(
|
|
report: &mut crate::solana_ata_stateful::SplAssociatedTokenAccountStatefulReadinessReport,
|
|
plan: &kb_execution_api::PreparedExecutionPlan,
|
|
derived_ata: &kb_model::Pubkey,
|
|
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,
|
|
};
|
|
crate::solana_ata_stateful::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 crate::solana_ata_stateful::SplAssociatedTokenAccountStatefulReadinessReport,
|
|
cache: &crate::solana_ata_stateful::AccountCache,
|
|
mint: &kb_model::Pubkey,
|
|
token_program: &str,
|
|
) {
|
|
let account = cache.get(mint.0.as_str()).and_then(|value| return value.as_ref());
|
|
crate::solana_ata_stateful::push_check(
|
|
report,
|
|
"mint_exists",
|
|
account.is_some(),
|
|
format!("mint {} must exist", mint.0),
|
|
);
|
|
if let std::option::Option::Some(account) = account {
|
|
crate::solana_ata_stateful::push_check(
|
|
report,
|
|
"mint_token_program_owner",
|
|
account.owner.0 == token_program,
|
|
format!("mint {} must be owned by selected Token Program", mint.0),
|
|
);
|
|
crate::solana_ata_stateful::push_check(
|
|
report,
|
|
"mint_base_layout",
|
|
account.data.len() >= crate::solana_ata_stateful::BASE_MINT_LEN,
|
|
format!("mint {} must expose the complete base Mint layout", mint.0),
|
|
);
|
|
crate::solana_ata_stateful::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 crate::solana_ata_stateful::SplAssociatedTokenAccountStatefulReadinessReport,
|
|
cache: &crate::solana_ata_stateful::AccountCache,
|
|
address: &kb_model::Pubkey,
|
|
token_program: &str,
|
|
expected_mint: &kb_model::Pubkey,
|
|
expected_owner: &kb_model::Pubkey,
|
|
role: &str,
|
|
) {
|
|
let account = cache.get(address.0.as_str()).and_then(|value| return value.as_ref());
|
|
crate::solana_ata_stateful::push_check(
|
|
report,
|
|
format!("{role}_exists"),
|
|
account.is_some(),
|
|
format!("{role} {} must exist", address.0),
|
|
);
|
|
if let std::option::Option::Some(account) = account {
|
|
crate::solana_ata_stateful::check_token_account(
|
|
report,
|
|
account,
|
|
token_program,
|
|
expected_mint,
|
|
expected_owner,
|
|
role,
|
|
);
|
|
}
|
|
}
|
|
|
|
fn check_token_account(
|
|
report: &mut crate::solana_ata_stateful::SplAssociatedTokenAccountStatefulReadinessReport,
|
|
account: &kb_rpc::AccountInfoValue,
|
|
token_program: &str,
|
|
expected_mint: &kb_model::Pubkey,
|
|
expected_owner: &kb_model::Pubkey,
|
|
role: &str,
|
|
) {
|
|
crate::solana_ata_stateful::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() >= crate::solana_ata_stateful::BASE_TOKEN_ACCOUNT_LEN;
|
|
crate::solana_ata_stateful::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();
|
|
crate::solana_ata_stateful::push_check(
|
|
report,
|
|
format!("{role}_mint"),
|
|
mint == expected_mint.0,
|
|
format!("{role} mint must match {}", expected_mint.0),
|
|
);
|
|
crate::solana_ata_stateful::push_check(
|
|
report,
|
|
format!("{role}_wallet_owner"),
|
|
owner == expected_owner.0,
|
|
format!("{role} owner must match {}", expected_owner.0),
|
|
);
|
|
crate::solana_ata_stateful::push_check(
|
|
report,
|
|
format!("{role}_initialized"),
|
|
matches!(account.data[108], 1 | 2),
|
|
format!("{role} must be initialized or frozen"),
|
|
);
|
|
}
|
|
}
|
|
|
|
fn push_check(
|
|
report: &mut crate::solana_ata_stateful::SplAssociatedTokenAccountStatefulReadinessReport,
|
|
code: impl std::convert::Into<std::string::String>,
|
|
passed: bool,
|
|
message: impl std::convert::Into<std::string::String>,
|
|
) {
|
|
report
|
|
.checks
|
|
.push(crate::solana_ata_stateful::SplAssociatedTokenAccountStatefulCheck {
|
|
code: code.into(),
|
|
passed,
|
|
message: message.into(),
|
|
});
|
|
}
|
|
|
|
fn push_fact(
|
|
report: &mut crate::solana_ata_stateful::SplAssociatedTokenAccountStatefulReadinessReport,
|
|
key: impl std::convert::Into<std::string::String>,
|
|
value: impl std::convert::Into<std::string::String>,
|
|
) {
|
|
report
|
|
.facts
|
|
.push(crate::solana_ata_stateful::SplAssociatedTokenAccountStatefulFact {
|
|
key: key.into(),
|
|
value: value.into(),
|
|
});
|
|
}
|
|
|
|
#[cfg(test)]
|
|
mod tests {
|
|
fn pubkey(value: &str) -> kb_model::Pubkey {
|
|
return kb_model::Pubkey(value.to_string());
|
|
}
|
|
|
|
fn policy(
|
|
rent: u64,
|
|
signers: std::vec::Vec<kb_model::Pubkey>,
|
|
) -> kb_execution_api::ExecutionPolicy {
|
|
return kb_execution_api::ExecutionPolicy {
|
|
cost_limit: kb_execution_api::ExecutionCostLimit {
|
|
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_execution_api::PostExecutionValidationPolicy {
|
|
canonical_insert_required: true,
|
|
core_extraction_required: true,
|
|
decode_replay_required: true,
|
|
materialization_required: true,
|
|
},
|
|
..kb_execution_api::ExecutionPolicy::default()
|
|
};
|
|
}
|
|
|
|
fn request(
|
|
operation: kb_executor_spl_associated_token_account::SplAssociatedTokenAccountOperation,
|
|
rent: u64,
|
|
) -> crate::solana_ata_stateful::SplAssociatedTokenAccountStatefulReadinessRequest {
|
|
let fee_payer =
|
|
crate::solana_ata_stateful::tests::pubkey(kb_program_ids::SYSTEM_PROGRAM_ID);
|
|
let wallet = match &operation {
|
|
kb_executor_spl_associated_token_account::SplAssociatedTokenAccountOperation::Create {
|
|
wallet_owner,
|
|
..
|
|
}
|
|
| kb_executor_spl_associated_token_account::SplAssociatedTokenAccountOperation::CreateIdempotent {
|
|
wallet_owner,
|
|
..
|
|
}
|
|
| kb_executor_spl_associated_token_account::SplAssociatedTokenAccountOperation::RecoverNested {
|
|
wallet_owner,
|
|
..
|
|
} => wallet_owner.clone(),
|
|
};
|
|
let mut signers = std::vec![fee_payer.clone()];
|
|
if !signers.contains(&wallet) {
|
|
signers.push(wallet);
|
|
}
|
|
return crate::solana_ata_stateful::SplAssociatedTokenAccountStatefulReadinessRequest {
|
|
query_role: "query".to_string(),
|
|
cluster: kb_execution_api::ExecutionCluster::Devnet,
|
|
intent:
|
|
kb_executor_spl_associated_token_account::SplAssociatedTokenAccountExecutionIntent {
|
|
intent_id: "ata-stateful-1".to_string(),
|
|
fee_payer,
|
|
max_rent_lamports: rent,
|
|
policy: crate::solana_ata_stateful::tests::policy(rent, signers.clone()),
|
|
operation,
|
|
},
|
|
available_signers: signers,
|
|
};
|
|
}
|
|
|
|
fn mint_account(token_program: &str) -> kb_rpc::AccountInfoValue {
|
|
let mut data = std::vec![0; crate::solana_ata_stateful::BASE_MINT_LEN];
|
|
data[45] = 1;
|
|
return kb_rpc::AccountInfoValue {
|
|
lamports: 1,
|
|
owner: kb_model::ProgramId(token_program.to_string()),
|
|
executable: false,
|
|
rent_epoch: 0,
|
|
space: data.len() as u64,
|
|
data,
|
|
};
|
|
}
|
|
|
|
fn token_account(
|
|
token_program: &str,
|
|
mint: &kb_model::Pubkey,
|
|
owner: &kb_model::Pubkey,
|
|
) -> kb_rpc::AccountInfoValue {
|
|
let mut data = std::vec![0; crate::solana_ata_stateful::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_rpc::AccountInfoValue {
|
|
lamports: 1,
|
|
owner: kb_model::ProgramId(token_program.to_string()),
|
|
executable: false,
|
|
rent_epoch: 0,
|
|
space: data.len() as u64,
|
|
data,
|
|
};
|
|
}
|
|
|
|
fn plan(
|
|
request: &crate::solana_ata_stateful::SplAssociatedTokenAccountStatefulReadinessRequest,
|
|
) -> kb_execution_api::PreparedExecutionPlan {
|
|
return kb_execution_api::TypedInstructionExecutor::build_prepared_plan(
|
|
&kb_executor_spl_associated_token_account::SplAssociatedTokenAccountExecutor,
|
|
&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 = crate::solana_ata_stateful::tests::pubkey(kb_program_ids::STAKE_PROGRAM_ID);
|
|
let mint = crate::solana_ata_stateful::tests::pubkey(kb_program_ids::VOTE_PROGRAM_ID);
|
|
let request = crate::solana_ata_stateful::tests::request(
|
|
kb_executor_spl_associated_token_account::SplAssociatedTokenAccountOperation::Create {
|
|
wallet_owner: wallet.clone(),
|
|
mint: mint.clone(),
|
|
token_program:
|
|
kb_executor_spl_associated_token_account::SplAssociatedTokenProgram::Classic,
|
|
},
|
|
2_100_000,
|
|
);
|
|
let ata = crate::solana_ata_stateful::derive_ata(
|
|
&wallet,
|
|
&mint,
|
|
kb_program_ids::SPL_TOKEN_PROGRAM_ID,
|
|
)
|
|
.unwrap_or_else(|error| panic!("ATA derivation failed: {error}"));
|
|
let mut cache = crate::solana_ata_stateful::AccountCache::new();
|
|
cache.insert(
|
|
mint.0.clone(),
|
|
std::option::Option::Some(crate::solana_ata_stateful::tests::mint_account(
|
|
kb_program_ids::SPL_TOKEN_PROGRAM_ID,
|
|
)),
|
|
);
|
|
cache.insert(ata.0, std::option::Option::None);
|
|
let report = crate::solana_ata_stateful::evaluate(
|
|
&request,
|
|
&crate::solana_ata_stateful::tests::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 = crate::solana_ata_stateful::tests::pubkey(kb_program_ids::STAKE_PROGRAM_ID);
|
|
let mint = crate::solana_ata_stateful::tests::pubkey(kb_program_ids::VOTE_PROGRAM_ID);
|
|
let request = crate::solana_ata_stateful::tests::request(
|
|
kb_executor_spl_associated_token_account::SplAssociatedTokenAccountOperation::CreateIdempotent {
|
|
wallet_owner: wallet.clone(),
|
|
mint: mint.clone(),
|
|
token_program: kb_executor_spl_associated_token_account::SplAssociatedTokenProgram::Token2022,
|
|
},
|
|
2_100_000,
|
|
);
|
|
let ata = crate::solana_ata_stateful::derive_ata(
|
|
&wallet,
|
|
&mint,
|
|
kb_program_ids::SPL_TOKEN_2022_PROGRAM_ID,
|
|
)
|
|
.unwrap_or_else(|error| panic!("ATA derivation failed: {error}"));
|
|
let mut cache = crate::solana_ata_stateful::AccountCache::new();
|
|
cache.insert(
|
|
mint.0.clone(),
|
|
std::option::Option::Some(crate::solana_ata_stateful::tests::mint_account(
|
|
kb_program_ids::SPL_TOKEN_2022_PROGRAM_ID,
|
|
)),
|
|
);
|
|
cache.insert(
|
|
ata.0,
|
|
std::option::Option::Some(crate::solana_ata_stateful::tests::token_account(
|
|
kb_program_ids::SPL_TOKEN_2022_PROGRAM_ID,
|
|
&mint,
|
|
&crate::solana_ata_stateful::tests::pubkey(kb_program_ids::CONFIG_PROGRAM_ID),
|
|
)),
|
|
);
|
|
let report = crate::solana_ata_stateful::evaluate(
|
|
&request,
|
|
&crate::solana_ata_stateful::tests::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 = crate::solana_ata_stateful::tests::pubkey(kb_program_ids::STAKE_PROGRAM_ID);
|
|
let owner_mint = crate::solana_ata_stateful::tests::pubkey(kb_program_ids::VOTE_PROGRAM_ID);
|
|
let nested_mint =
|
|
crate::solana_ata_stateful::tests::pubkey(kb_program_ids::CONFIG_PROGRAM_ID);
|
|
let token_program = kb_program_ids::SPL_TOKEN_PROGRAM_ID;
|
|
let request = crate::solana_ata_stateful::tests::request(
|
|
kb_executor_spl_associated_token_account::SplAssociatedTokenAccountOperation::RecoverNested {
|
|
wallet_owner: wallet.clone(),
|
|
owner_mint: owner_mint.clone(),
|
|
nested_mint: nested_mint.clone(),
|
|
token_program: kb_executor_spl_associated_token_account::SplAssociatedTokenProgram::Classic,
|
|
},
|
|
0,
|
|
);
|
|
let owner_ata = crate::solana_ata_stateful::derive_ata(&wallet, &owner_mint, token_program)
|
|
.unwrap_or_else(|error| panic!("owner ATA failed: {error}"));
|
|
let nested_ata =
|
|
crate::solana_ata_stateful::derive_ata(&owner_ata, &nested_mint, token_program)
|
|
.unwrap_or_else(|error| panic!("nested ATA failed: {error}"));
|
|
let destination =
|
|
crate::solana_ata_stateful::derive_ata(&wallet, &nested_mint, token_program)
|
|
.unwrap_or_else(|error| panic!("destination ATA failed: {error}"));
|
|
let mut cache = crate::solana_ata_stateful::AccountCache::new();
|
|
cache.insert(
|
|
owner_mint.0.clone(),
|
|
std::option::Option::Some(crate::solana_ata_stateful::tests::mint_account(
|
|
token_program,
|
|
)),
|
|
);
|
|
cache.insert(
|
|
nested_mint.0.clone(),
|
|
std::option::Option::Some(crate::solana_ata_stateful::tests::mint_account(
|
|
token_program,
|
|
)),
|
|
);
|
|
cache.insert(
|
|
owner_ata.0.clone(),
|
|
std::option::Option::Some(crate::solana_ata_stateful::tests::token_account(
|
|
token_program,
|
|
&owner_mint,
|
|
&wallet,
|
|
)),
|
|
);
|
|
cache.insert(
|
|
nested_ata.0,
|
|
std::option::Option::Some(crate::solana_ata_stateful::tests::token_account(
|
|
token_program,
|
|
&nested_mint,
|
|
&owner_ata,
|
|
)),
|
|
);
|
|
cache.insert(
|
|
destination.0,
|
|
std::option::Option::Some(crate::solana_ata_stateful::tests::token_account(
|
|
token_program,
|
|
&nested_mint,
|
|
&wallet,
|
|
)),
|
|
);
|
|
let report = crate::solana_ata_stateful::evaluate(
|
|
&request,
|
|
&crate::solana_ata_stateful::tests::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 = crate::solana_ata_stateful::tests::pubkey(kb_program_ids::STAKE_PROGRAM_ID);
|
|
let mint = crate::solana_ata_stateful::tests::pubkey(kb_program_ids::VOTE_PROGRAM_ID);
|
|
let operation = kb_executor_spl_associated_token_account::SplAssociatedTokenAccountOperation::CreateIdempotent {
|
|
wallet_owner: wallet.clone(),
|
|
mint: mint.clone(),
|
|
token_program: kb_executor_spl_associated_token_account::SplAssociatedTokenProgram::Token2022,
|
|
};
|
|
let ata = crate::solana_ata_stateful::derive_ata(
|
|
&wallet,
|
|
&mint,
|
|
kb_program_ids::SPL_TOKEN_2022_PROGRAM_ID,
|
|
)
|
|
.unwrap_or_else(|error| panic!("ATA derivation failed: {error}"));
|
|
let mut cache = crate::solana_ata_stateful::AccountCache::new();
|
|
cache.insert(
|
|
ata.0,
|
|
std::option::Option::Some(crate::solana_ata_stateful::tests::token_account(
|
|
kb_program_ids::SPL_TOKEN_2022_PROGRAM_ID,
|
|
&mint,
|
|
&wallet,
|
|
)),
|
|
);
|
|
let report = crate::solana_ata_stateful::evaluate_post_execution(
|
|
kb_execution_api::ExecutionCluster::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 = crate::solana_ata_stateful::tests::pubkey(kb_program_ids::STAKE_PROGRAM_ID);
|
|
let owner_mint = crate::solana_ata_stateful::tests::pubkey(kb_program_ids::VOTE_PROGRAM_ID);
|
|
let nested_mint =
|
|
crate::solana_ata_stateful::tests::pubkey(kb_program_ids::CONFIG_PROGRAM_ID);
|
|
let token_program = kb_program_ids::SPL_TOKEN_PROGRAM_ID;
|
|
let operation = kb_executor_spl_associated_token_account::SplAssociatedTokenAccountOperation::RecoverNested {
|
|
wallet_owner: wallet.clone(),
|
|
owner_mint: owner_mint.clone(),
|
|
nested_mint: nested_mint.clone(),
|
|
token_program: kb_executor_spl_associated_token_account::SplAssociatedTokenProgram::Classic,
|
|
};
|
|
let owner_ata = crate::solana_ata_stateful::derive_ata(&wallet, &owner_mint, token_program)
|
|
.unwrap_or_else(|error| panic!("owner ATA failed: {error}"));
|
|
let nested_ata =
|
|
crate::solana_ata_stateful::derive_ata(&owner_ata, &nested_mint, token_program)
|
|
.unwrap_or_else(|error| panic!("nested ATA failed: {error}"));
|
|
let destination =
|
|
crate::solana_ata_stateful::derive_ata(&wallet, &nested_mint, token_program)
|
|
.unwrap_or_else(|error| panic!("destination ATA failed: {error}"));
|
|
let mut cache = crate::solana_ata_stateful::AccountCache::new();
|
|
cache.insert(
|
|
owner_ata.0,
|
|
std::option::Option::Some(crate::solana_ata_stateful::tests::token_account(
|
|
token_program,
|
|
&owner_mint,
|
|
&wallet,
|
|
)),
|
|
);
|
|
cache.insert(nested_ata.0, std::option::Option::None);
|
|
cache.insert(
|
|
destination.0,
|
|
std::option::Option::Some(crate::solana_ata_stateful::tests::token_account(
|
|
token_program,
|
|
&nested_mint,
|
|
&wallet,
|
|
)),
|
|
);
|
|
let report = crate::solana_ata_stateful::evaluate_post_execution(
|
|
kb_execution_api::ExecutionCluster::Devnet,
|
|
&operation,
|
|
std::option::Option::Some(13),
|
|
&cache,
|
|
)
|
|
.unwrap_or_else(|error| panic!("RecoverNested postcondition failed: {error}"));
|
|
assert_eq!(report.status, crate::SplAssociatedTokenAccountStatefulReadinessStatus::Ready);
|
|
}
|
|
}
|