Files
khadhroony-bot3/ks-lib/src/executor/spl/token/builder.rs
2026-08-09 19:34:08 +02:00

1215 lines
49 KiB
Rust

// file: ks-lib/src/executor/spl/token/builder.rs
// version: 10
//! Official classic SPL Token builders and conservative plan validation.
use std::str::FromStr; // rust-rules: trait-import
struct ParsedAuthority {
authority: solana_pubkey::Pubkey,
signers: std::vec::Vec<solana_pubkey::Pubkey>,
}
pub(crate) fn executor_spl_token_build_prepared_plan(
intent: &crate::ExSplClassicTokenExecutionIntent,
) -> ks_core::Result<crate::ExApiPreparedExecutionPlan> {
if intent.intent_id.trim().is_empty() {
return std::result::Result::Err(ks_core::Error::new(
"execution_spl_token_intent_id_empty",
"SPL Token execution intent id must not be empty",
));
}
let fee_payer = match parse_pubkey(&intent.fee_payer, "fee_payer") {
std::result::Result::Ok(value) => value,
std::result::Result::Err(error) => return std::result::Result::Err(error),
};
match validate_policy(intent) {
std::result::Result::Ok(()) => {},
std::result::Result::Err(error) => return std::result::Result::Err(error),
}
let program_id = match solana_pubkey::Pubkey::from_str(ks_program_ids::SPL_TOKEN_PROGRAM_ID) {
std::result::Result::Ok(value) => value,
std::result::Result::Err(error) => {
return std::result::Result::Err(ks_core::Error::new(
"execution_spl_token_program_id_invalid",
error.to_string(),
));
},
};
let instruction = match &intent.operation {
crate::ExSplClassicTokenOperation::Instruction { value } => {
match build_single(&program_id, value) {
std::result::Result::Ok(value) => value,
std::result::Result::Err(error) => return std::result::Result::Err(error),
}
},
crate::ExSplClassicTokenOperation::Batch { instructions } => {
match build_batch(&program_id, instructions.as_slice()) {
std::result::Result::Ok(value) => value,
std::result::Result::Err(error) => return std::result::Result::Err(error),
}
},
};
let requested_spend_lamports = match declared_spend_lamports(&intent.operation) {
std::result::Result::Ok(value) => value,
std::result::Result::Err(error) => return std::result::Result::Err(error),
};
let planned_instruction = planned_instruction(intent.operation.operation_code(), &instruction);
let required_signers = required_signers(&intent.fee_payer, &instruction.accounts);
tracing::debug!(
target: crate::TRACING_TARGET_EXECUTOR_SPL_TOKEN,
action = "executor_spl_token_build_prepared_plan",
intent_id = %intent.intent_id,
operation_code = intent.operation.operation_code(),
program_id = ks_program_ids::SPL_TOKEN_PROGRAM_ID,
account_occurrence_count = instruction.accounts.len(),
required_signer_count = required_signers.len(),
requested_spend_lamports,
dry_run = intent.policy.dry_run,
"built classic SPL Token execution plan"
);
return std::result::Result::Ok(crate::ExApiPreparedExecutionPlan {
executor_name: std::string::String::from("kb-lib.executor.spl.token"),
executor_version: std::string::String::from(env!("CARGO_PKG_VERSION")),
intent_id: intent.intent_id.clone(),
operation_code: std::string::String::from(intent.operation.operation_code()),
fee_payer: crate::MdPubkey(fee_payer.to_string()),
instructions: vec![planned_instruction],
required_signers,
policy: intent.policy.clone(),
requested_spend_lamports,
requested_compute_unit_price_micro_lamports: std::option::Option::None,
});
}
fn validate_policy(intent: &crate::ExSplClassicTokenExecutionIntent) -> ks_core::Result<()> {
if intent.policy.simulation != crate::ExApiExecutionSimulationPolicy::Required {
return std::result::Result::Err(ks_core::Error::new(
"execution_spl_token_simulation_required",
"SPL Token execution requires simulation before signing or sending",
));
}
match intent.policy.cost_limit.max_fee_lamports {
std::option::Option::Some(limit) if limit > 0 => {},
std::option::Option::Some(_) | std::option::Option::None => {
return std::result::Result::Err(ks_core::Error::new(
"execution_spl_token_fee_limit_missing",
"SPL Token execution requires a positive transaction fee ceiling",
));
},
}
let validation = &intent.policy.post_execution_validation;
if !validation.canonical_insert_required
|| !validation.core_extraction_required
|| !validation.decode_replay_required
{
return std::result::Result::Err(ks_core::Error::new(
"execution_spl_token_post_validation_required",
"SPL Token execution requires canonical insertion, core extraction and decode replay validation",
));
}
if intent.operation.requires_materialization() && !validation.materialization_required {
return std::result::Result::Err(ks_core::Error::new(
"execution_spl_token_materialization_validation_required",
"Mutating SPL Token execution requires materialization validation",
));
}
if has_unbounded_lamport_movement(&intent.operation) && !intent.policy.dry_run {
return std::result::Result::Err(ks_core::Error::new(
"execution_spl_token_stateful_lamport_preflight_required",
"WithdrawExcessLamports and full-balance UnwrapLamports remain simulation-only until a stateful preflight bounds the movable lamports",
));
}
return std::result::Result::Ok(());
}
macro_rules! parse_key {
($value:expr, $field:expr) => {
match parse_pubkey($value, $field) {
std::result::Result::Ok(value) => value,
std::result::Result::Err(error) => return std::result::Result::Err(error),
}
};
}
macro_rules! parse_raw_amount {
($value:expr, $field:expr) => {
match parse_amount($value, $field) {
std::result::Result::Ok(value) => value,
std::result::Result::Err(error) => return std::result::Result::Err(error),
}
};
}
macro_rules! parse_token_authority {
($value:expr) => {
match parse_authority($value) {
std::result::Result::Ok(value) => value,
std::result::Result::Err(error) => return std::result::Result::Err(error),
}
};
}
fn build_single(
program_id: &solana_pubkey::Pubkey,
operation: &crate::ExSplClassicTokenSingleOperation,
) -> ks_core::Result<solana_instruction::Instruction> {
return match operation {
crate::ExSplClassicTokenSingleOperation::InitializeMint {
mint,
mint_authority,
freeze_authority,
decimals,
} => {
let mint = parse_key!(mint, "mint");
let mint_authority = parse_key!(mint_authority, "mint_authority");
let freeze_authority = match freeze_authority {
std::option::Option::Some(value) => {
std::option::Option::Some(parse_key!(value, "freeze_authority"))
},
std::option::Option::None => std::option::Option::None,
};
official_instruction(spl_token_interface::instruction::initialize_mint2(
program_id,
&mint,
&mint_authority,
freeze_authority.as_ref(),
*decimals,
))
},
crate::ExSplClassicTokenSingleOperation::InitializeAccount { account, mint, owner } => {
let account = parse_key!(account, "account");
let mint = parse_key!(mint, "mint");
let owner = parse_key!(owner, "owner");
official_instruction(spl_token_interface::instruction::initialize_account3(
program_id, &account, &mint, &owner,
))
},
crate::ExSplClassicTokenSingleOperation::InitializeMultisig {
multisig,
members,
threshold,
} => {
if members.is_empty()
|| members.len() > crate::EX_SPL_TOKEN_MAX_MULTISIG_SIGNERS
|| *threshold == 0
|| usize::from(*threshold) > members.len()
{
return std::result::Result::Err(ks_core::Error::new(
"execution_spl_token_multisig_threshold_invalid",
format!(
"SPL Token multisig requires 1..={} members and threshold 1..=N; received M={} N={}",
crate::EX_SPL_TOKEN_MAX_MULTISIG_SIGNERS,
threshold,
members.len()
),
));
}
let multisig = parse_key!(multisig, "multisig");
let mut parsed_members = std::vec::Vec::with_capacity(members.len());
for member in members {
parsed_members.push(parse_key!(member, "multisig_member"));
}
let member_refs = parsed_members.iter().collect::<std::vec::Vec<_>>();
official_instruction(spl_token_interface::instruction::initialize_multisig2(
program_id,
&multisig,
member_refs.as_slice(),
*threshold,
))
},
crate::ExSplClassicTokenSingleOperation::Transfer {
source,
destination,
authority,
amount,
} => {
let source = parse_key!(source, "source");
let destination = parse_key!(destination, "destination");
let authority = parse_token_authority!(authority);
let signer_refs = authority.signers.iter().collect::<std::vec::Vec<_>>();
let amount = parse_raw_amount!(amount, "amount");
official_instruction(spl_token_interface::instruction::transfer(
program_id,
&source,
&destination,
&authority.authority,
signer_refs.as_slice(),
amount,
))
},
crate::ExSplClassicTokenSingleOperation::Approve {
source,
delegate,
authority,
amount,
} => {
let source = parse_key!(source, "source");
let delegate = parse_key!(delegate, "delegate");
let authority = parse_token_authority!(authority);
let signer_refs = authority.signers.iter().collect::<std::vec::Vec<_>>();
let amount = parse_raw_amount!(amount, "amount");
official_instruction(spl_token_interface::instruction::approve(
program_id,
&source,
&delegate,
&authority.authority,
signer_refs.as_slice(),
amount,
))
},
crate::ExSplClassicTokenSingleOperation::Revoke { source, authority } => {
let source = parse_key!(source, "source");
let authority = parse_token_authority!(authority);
let signer_refs = authority.signers.iter().collect::<std::vec::Vec<_>>();
official_instruction(spl_token_interface::instruction::revoke(
program_id,
&source,
&authority.authority,
signer_refs.as_slice(),
))
},
crate::ExSplClassicTokenSingleOperation::SetAuthority {
owned,
authority_type,
new_authority,
current_authority,
} => {
let owned = parse_key!(owned, "owned");
let new_authority = match new_authority {
std::option::Option::Some(value) => {
std::option::Option::Some(parse_key!(value, "new_authority"))
},
std::option::Option::None => std::option::Option::None,
};
let current_authority = parse_token_authority!(current_authority);
let signer_refs = current_authority.signers.iter().collect::<std::vec::Vec<_>>();
let authority_type = match authority_type {
crate::ExSplClassicTokenAuthorityType::MintTokens => {
spl_token_interface::instruction::AuthorityType::MintTokens
},
crate::ExSplClassicTokenAuthorityType::FreezeAccount => {
spl_token_interface::instruction::AuthorityType::FreezeAccount
},
crate::ExSplClassicTokenAuthorityType::AccountOwner => {
spl_token_interface::instruction::AuthorityType::AccountOwner
},
crate::ExSplClassicTokenAuthorityType::CloseAccount => {
spl_token_interface::instruction::AuthorityType::CloseAccount
},
};
official_instruction(spl_token_interface::instruction::set_authority(
program_id,
&owned,
new_authority.as_ref(),
authority_type,
&current_authority.authority,
signer_refs.as_slice(),
))
},
crate::ExSplClassicTokenSingleOperation::MintTo {
mint,
destination,
authority,
amount,
} => {
let mint = parse_key!(mint, "mint");
let destination = parse_key!(destination, "destination");
let authority = parse_token_authority!(authority);
let signer_refs = authority.signers.iter().collect::<std::vec::Vec<_>>();
let amount = parse_raw_amount!(amount, "amount");
official_instruction(spl_token_interface::instruction::mint_to(
program_id,
&mint,
&destination,
&authority.authority,
signer_refs.as_slice(),
amount,
))
},
crate::ExSplClassicTokenSingleOperation::Burn { source, mint, authority, amount } => {
let source = parse_key!(source, "source");
let mint = parse_key!(mint, "mint");
let authority = parse_token_authority!(authority);
let signer_refs = authority.signers.iter().collect::<std::vec::Vec<_>>();
let amount = parse_raw_amount!(amount, "amount");
official_instruction(spl_token_interface::instruction::burn(
program_id,
&source,
&mint,
&authority.authority,
signer_refs.as_slice(),
amount,
))
},
crate::ExSplClassicTokenSingleOperation::CloseAccount {
account,
destination,
authority,
} => {
let account = parse_key!(account, "account");
let destination = parse_key!(destination, "destination");
let authority = parse_token_authority!(authority);
let signer_refs = authority.signers.iter().collect::<std::vec::Vec<_>>();
official_instruction(spl_token_interface::instruction::close_account(
program_id,
&account,
&destination,
&authority.authority,
signer_refs.as_slice(),
))
},
crate::ExSplClassicTokenSingleOperation::FreezeAccount { account, mint, authority } => {
let account = parse_key!(account, "account");
let mint = parse_key!(mint, "mint");
let authority = parse_token_authority!(authority);
let signer_refs = authority.signers.iter().collect::<std::vec::Vec<_>>();
official_instruction(spl_token_interface::instruction::freeze_account(
program_id,
&account,
&mint,
&authority.authority,
signer_refs.as_slice(),
))
},
crate::ExSplClassicTokenSingleOperation::ThawAccount { account, mint, authority } => {
let account = parse_key!(account, "account");
let mint = parse_key!(mint, "mint");
let authority = parse_token_authority!(authority);
let signer_refs = authority.signers.iter().collect::<std::vec::Vec<_>>();
official_instruction(spl_token_interface::instruction::thaw_account(
program_id,
&account,
&mint,
&authority.authority,
signer_refs.as_slice(),
))
},
crate::ExSplClassicTokenSingleOperation::TransferChecked {
source,
mint,
destination,
authority,
amount,
decimals,
} => {
let source = parse_key!(source, "source");
let mint = parse_key!(mint, "mint");
let destination = parse_key!(destination, "destination");
let authority = parse_token_authority!(authority);
let signer_refs = authority.signers.iter().collect::<std::vec::Vec<_>>();
let amount = parse_raw_amount!(amount, "amount");
official_instruction(spl_token_interface::instruction::transfer_checked(
program_id,
&source,
&mint,
&destination,
&authority.authority,
signer_refs.as_slice(),
amount,
*decimals,
))
},
crate::ExSplClassicTokenSingleOperation::ApproveChecked {
source,
mint,
delegate,
authority,
amount,
decimals,
} => {
let source = parse_key!(source, "source");
let mint = parse_key!(mint, "mint");
let delegate = parse_key!(delegate, "delegate");
let authority = parse_token_authority!(authority);
let signer_refs = authority.signers.iter().collect::<std::vec::Vec<_>>();
let amount = parse_raw_amount!(amount, "amount");
official_instruction(spl_token_interface::instruction::approve_checked(
program_id,
&source,
&mint,
&delegate,
&authority.authority,
signer_refs.as_slice(),
amount,
*decimals,
))
},
crate::ExSplClassicTokenSingleOperation::MintToChecked {
mint,
destination,
authority,
amount,
decimals,
} => {
let mint = parse_key!(mint, "mint");
let destination = parse_key!(destination, "destination");
let authority = parse_token_authority!(authority);
let signer_refs = authority.signers.iter().collect::<std::vec::Vec<_>>();
let amount = parse_raw_amount!(amount, "amount");
official_instruction(spl_token_interface::instruction::mint_to_checked(
program_id,
&mint,
&destination,
&authority.authority,
signer_refs.as_slice(),
amount,
*decimals,
))
},
crate::ExSplClassicTokenSingleOperation::BurnChecked {
source,
mint,
authority,
amount,
decimals,
} => {
let source = parse_key!(source, "source");
let mint = parse_key!(mint, "mint");
let authority = parse_token_authority!(authority);
let signer_refs = authority.signers.iter().collect::<std::vec::Vec<_>>();
let amount = parse_raw_amount!(amount, "amount");
official_instruction(spl_token_interface::instruction::burn_checked(
program_id,
&source,
&mint,
&authority.authority,
signer_refs.as_slice(),
amount,
*decimals,
))
},
crate::ExSplClassicTokenSingleOperation::SyncNative { account, include_rent_sysvar } => {
let account = parse_key!(account, "account");
if *include_rent_sysvar {
official_instruction(
spl_token_interface::instruction::sync_native_with_rent_sysvar(
program_id, &account,
),
)
} else {
official_instruction(spl_token_interface::instruction::sync_native(
program_id, &account,
))
}
},
crate::ExSplClassicTokenSingleOperation::GetAccountDataSize { mint } => {
let mint = parse_key!(mint, "mint");
official_instruction(spl_token_interface::instruction::get_account_data_size(
program_id, &mint,
))
},
crate::ExSplClassicTokenSingleOperation::InitializeImmutableOwner { account } => {
let account = parse_key!(account, "account");
official_instruction(spl_token_interface::instruction::initialize_immutable_owner(
program_id, &account,
))
},
crate::ExSplClassicTokenSingleOperation::AmountToUiAmount { mint, amount } => {
let mint = parse_key!(mint, "mint");
let amount = parse_raw_amount!(amount, "amount");
official_instruction(spl_token_interface::instruction::amount_to_ui_amount(
program_id, &mint, amount,
))
},
crate::ExSplClassicTokenSingleOperation::UiAmountToAmount { mint, ui_amount } => {
if ui_amount.len() > crate::EX_SPL_TOKEN_MAX_UI_AMOUNT_BYTES {
return std::result::Result::Err(ks_core::Error::new(
"execution_spl_token_ui_amount_too_large",
format!(
"SPL Token UI amount length {} exceeds {} bytes",
ui_amount.len(),
crate::EX_SPL_TOKEN_MAX_UI_AMOUNT_BYTES
),
));
}
let mint = parse_key!(mint, "mint");
official_instruction(spl_token_interface::instruction::ui_amount_to_amount(
program_id,
&mint,
ui_amount.as_str(),
))
},
crate::ExSplClassicTokenSingleOperation::WithdrawExcessLamports {
account,
destination,
authority,
} => {
let account = parse_key!(account, "account");
let destination = parse_key!(destination, "destination");
let authority = parse_token_authority!(authority);
let signer_refs = authority.signers.iter().collect::<std::vec::Vec<_>>();
official_instruction(spl_token_interface::instruction::withdraw_excess_lamports(
program_id,
&account,
&destination,
&authority.authority,
signer_refs.as_slice(),
))
},
crate::ExSplClassicTokenSingleOperation::UnwrapLamports {
account,
destination,
authority,
amount_lamports,
} => {
let account = parse_key!(account, "account");
let destination = parse_key!(destination, "destination");
let authority = parse_token_authority!(authority);
let signer_refs = authority.signers.iter().collect::<std::vec::Vec<_>>();
let amount_lamports = match amount_lamports {
std::option::Option::Some(value) => {
std::option::Option::Some(parse_raw_amount!(value, "amount_lamports"))
},
std::option::Option::None => std::option::Option::None,
};
official_instruction(spl_token_interface::instruction::unwrap_lamports(
program_id,
&account,
&destination,
&authority.authority,
signer_refs.as_slice(),
amount_lamports,
))
},
};
}
fn build_batch(
program_id: &solana_pubkey::Pubkey,
operations: &[crate::ExSplClassicTokenSingleOperation],
) -> ks_core::Result<solana_instruction::Instruction> {
if operations.len() > crate::EX_SPL_TOKEN_MAX_BATCH_INSTRUCTIONS {
return std::result::Result::Err(ks_core::Error::new(
"execution_spl_token_batch_instruction_limit_exceeded",
format!(
"SPL Token Batch child count {} exceeds {}",
operations.len(),
crate::EX_SPL_TOKEN_MAX_BATCH_INSTRUCTIONS
),
));
}
let mut instructions = std::vec::Vec::with_capacity(operations.len());
let mut account_count = 0_usize;
for operation in operations {
let instruction = match build_single(program_id, operation) {
std::result::Result::Ok(value) => value,
std::result::Result::Err(error) => return std::result::Result::Err(error),
};
if instruction.data.len() > usize::from(u8::MAX) {
return std::result::Result::Err(ks_core::Error::new(
"execution_spl_token_batch_child_data_too_large",
format!(
"SPL Token Batch child {} has {} data bytes; maximum is {}",
instructions.len(),
instruction.data.len(),
u8::MAX
),
));
}
account_count = match account_count.checked_add(instruction.accounts.len()) {
std::option::Option::Some(value) => value,
std::option::Option::None => {
return std::result::Result::Err(ks_core::Error::new(
"execution_spl_token_batch_account_count_overflow",
"SPL Token Batch aggregate account count overflowed usize",
));
},
};
if account_count > crate::EX_SPL_TOKEN_MAX_BATCH_ACCOUNTS {
return std::result::Result::Err(ks_core::Error::new(
"execution_spl_token_batch_account_limit_exceeded",
format!(
"SPL Token Batch account occurrence count {account_count} exceeds {}",
crate::EX_SPL_TOKEN_MAX_BATCH_ACCOUNTS
),
));
}
instructions.push(instruction);
}
return official_instruction(spl_token_interface::instruction::batch(
program_id,
instructions.as_slice(),
));
}
fn official_instruction<BuilderError>(
result: std::result::Result<solana_instruction::Instruction, BuilderError>,
) -> ks_core::Result<solana_instruction::Instruction>
where
BuilderError: std::fmt::Display,
{
return match result {
std::result::Result::Ok(value) => std::result::Result::Ok(value),
std::result::Result::Err(error) => std::result::Result::Err(ks_core::Error::new(
"execution_spl_token_official_builder_failed",
error.to_string(),
)),
};
}
fn parse_pubkey(pubkey: &crate::MdPubkey, field: &str) -> ks_core::Result<solana_pubkey::Pubkey> {
if pubkey.0.trim().is_empty() {
return std::result::Result::Err(ks_core::Error::new(
"execution_spl_token_pubkey_empty",
format!("SPL Token {field} public key must not be empty"),
));
}
return match solana_pubkey::Pubkey::from_str(pubkey.0.as_str()) {
std::result::Result::Ok(value) => std::result::Result::Ok(value),
std::result::Result::Err(error) => std::result::Result::Err(ks_core::Error::new(
"execution_spl_token_pubkey_invalid",
format!("invalid SPL Token {field} public key: {error}"),
)),
};
}
fn parse_amount(amount: &crate::ExSplClassicTokenAmount, field: &str) -> ks_core::Result<u64> {
if amount.0.is_empty() || !amount.0.bytes().all(|value| return value.is_ascii_digit()) {
return std::result::Result::Err(ks_core::Error::new(
"execution_spl_token_amount_invalid",
format!("SPL Token {field} must be an unsigned decimal u64 string"),
));
}
return match amount.0.parse::<u64>() {
std::result::Result::Ok(value) => std::result::Result::Ok(value),
std::result::Result::Err(error) => std::result::Result::Err(ks_core::Error::new(
"execution_spl_token_amount_invalid",
format!("invalid SPL Token {field}: {error}"),
)),
};
}
fn parse_authority(
authority: &crate::ExSplClassicTokenAuthority,
) -> ks_core::Result<ParsedAuthority> {
if authority.multisig_signers.len() > crate::EX_SPL_TOKEN_MAX_MULTISIG_SIGNERS {
return std::result::Result::Err(ks_core::Error::new(
"execution_spl_token_multisig_signer_limit_exceeded",
format!(
"SPL Token multisig signer occurrence count {} exceeds {}",
authority.multisig_signers.len(),
crate::EX_SPL_TOKEN_MAX_MULTISIG_SIGNERS
),
));
}
let parsed_authority = match parse_pubkey(&authority.authority, "authority") {
std::result::Result::Ok(value) => value,
std::result::Result::Err(error) => return std::result::Result::Err(error),
};
let mut signers = std::vec::Vec::with_capacity(authority.multisig_signers.len());
for signer in &authority.multisig_signers {
let signer = match parse_pubkey(signer, "multisig_signer") {
std::result::Result::Ok(value) => value,
std::result::Result::Err(error) => return std::result::Result::Err(error),
};
signers.push(signer);
}
return std::result::Result::Ok(ParsedAuthority { authority: parsed_authority, signers });
}
fn declared_spend_lamports(operation: &crate::ExSplClassicTokenOperation) -> ks_core::Result<u64> {
return match operation {
crate::ExSplClassicTokenOperation::Instruction { value } => single_spend_lamports(value),
crate::ExSplClassicTokenOperation::Batch { instructions } => {
let mut total = 0_u64;
for instruction in instructions {
let amount = match single_spend_lamports(instruction) {
std::result::Result::Ok(value) => value,
std::result::Result::Err(error) => return std::result::Result::Err(error),
};
total = match total.checked_add(amount) {
std::option::Option::Some(value) => value,
std::option::Option::None => {
return std::result::Result::Err(ks_core::Error::new(
"execution_spl_token_lamport_spend_overflow",
"SPL Token Batch declared lamport spend exceeds u64",
));
},
};
}
std::result::Result::Ok(total)
},
};
}
fn single_spend_lamports(
operation: &crate::ExSplClassicTokenSingleOperation,
) -> ks_core::Result<u64> {
return match operation {
crate::ExSplClassicTokenSingleOperation::UnwrapLamports {
amount_lamports: std::option::Option::Some(value),
..
} => parse_amount(value, "amount_lamports"),
_ => std::result::Result::Ok(0),
};
}
fn has_unbounded_lamport_movement(operation: &crate::ExSplClassicTokenOperation) -> bool {
return match operation {
crate::ExSplClassicTokenOperation::Instruction { value } => {
single_has_unbounded_lamport_movement(value)
},
crate::ExSplClassicTokenOperation::Batch { instructions } => {
instructions.iter().any(single_has_unbounded_lamport_movement)
},
};
}
fn single_has_unbounded_lamport_movement(
operation: &crate::ExSplClassicTokenSingleOperation,
) -> bool {
return matches!(
operation,
crate::ExSplClassicTokenSingleOperation::WithdrawExcessLamports { .. }
| crate::ExSplClassicTokenSingleOperation::UnwrapLamports {
amount_lamports: std::option::Option::None,
..
}
);
}
fn planned_instruction(
operation_code: &str,
instruction: &solana_instruction::Instruction,
) -> crate::ExApiPlannedInstruction {
return crate::ExApiPlannedInstruction {
program_id: crate::MdProgramId(instruction.program_id.to_string()),
operation_code: std::string::String::from(operation_code),
accounts: instruction
.accounts
.iter()
.map(|account| {
return crate::ExApiPlannedAccount {
pubkey: crate::MdPubkey(account.pubkey.to_string()),
is_signer: account.is_signer,
is_writable: account.is_writable,
};
})
.collect(),
data: instruction.data.clone(),
};
}
fn required_signers(
fee_payer: &crate::MdPubkey,
accounts: &[solana_instruction::AccountMeta],
) -> std::vec::Vec<crate::ExApiRequiredSigner> {
let mut required = vec![crate::ExApiRequiredSigner {
pubkey: fee_payer.clone(),
role: std::string::String::from("fee_payer"),
}];
for account in accounts {
if !account.is_signer {
continue;
}
let pubkey = crate::MdPubkey(account.pubkey.to_string());
if required.iter().any(|value| return value.pubkey == pubkey) {
continue;
}
required.push(crate::ExApiRequiredSigner {
pubkey,
role: std::string::String::from("token_authority_or_multisig_signer"),
});
}
return required;
}
#[cfg(test)]
pub(crate) mod tests {
fn pubkey(value: &str) -> crate::MdPubkey {
return crate::MdPubkey(std::string::String::from(value));
}
fn policy(signers: &[&str], materialization_required: bool) -> crate::ExApiExecutionPolicy {
return crate::ExApiExecutionPolicy {
cost_limit: crate::ExApiExecutionCostLimit {
max_spend_lamports: std::option::Option::Some(u64::MAX),
max_fee_lamports: std::option::Option::Some(10_000),
max_compute_unit_price_micro_lamports: std::option::Option::None,
},
authorized_signers: signers.iter().map(|value| return pubkey(value)).collect(),
post_execution_validation: crate::ExApiPostExecutionValidationPolicy {
canonical_insert_required: true,
core_extraction_required: true,
decode_replay_required: true,
materialization_required,
},
..crate::ExApiExecutionPolicy::default()
};
}
pub(crate) fn intent(
operation: crate::ExSplClassicTokenOperation,
) -> crate::ExSplClassicTokenExecutionIntent {
let fee_payer = ks_program_ids::SYSTEM_PROGRAM_ID;
return crate::ExSplClassicTokenExecutionIntent {
intent_id: std::string::String::from("token-intent-1"),
fee_payer: pubkey(fee_payer),
policy: policy(
&[fee_payer, ks_program_ids::VOTE_PROGRAM_ID, ks_program_ids::STAKE_PROGRAM_ID],
operation.requires_materialization(),
),
operation,
};
}
fn authority(signers: &[&str]) -> crate::ExSplClassicTokenAuthority {
return crate::ExSplClassicTokenAuthority {
authority: pubkey(ks_program_ids::CONFIG_PROGRAM_ID),
multisig_signers: signers.iter().map(|value| return pubkey(value)).collect(),
};
}
fn transfer_checked() -> crate::ExSplClassicTokenSingleOperation {
return crate::ExSplClassicTokenSingleOperation::TransferChecked {
source: pubkey(ks_program_ids::SYSTEM_PROGRAM_ID),
mint: pubkey(ks_program_ids::CONFIG_PROGRAM_ID),
destination: pubkey(ks_program_ids::STAKE_PROGRAM_ID),
authority: authority(&[ks_program_ids::VOTE_PROGRAM_ID]),
amount: crate::ExSplClassicTokenAmount(std::string::String::from(
"18446744073709551615",
)),
decimals: 9,
};
}
fn all_single_operations() -> std::vec::Vec<(u8, crate::ExSplClassicTokenSingleOperation)> {
let first = pubkey(ks_program_ids::SYSTEM_PROGRAM_ID);
let second = pubkey(ks_program_ids::CONFIG_PROGRAM_ID);
let third = pubkey(ks_program_ids::STAKE_PROGRAM_ID);
let authority = authority(&[]);
let amount = crate::ExSplClassicTokenAmount(std::string::String::from("42"));
return vec![
(
20,
crate::ExSplClassicTokenSingleOperation::InitializeMint {
mint: first.clone(),
mint_authority: second.clone(),
freeze_authority: std::option::Option::Some(third.clone()),
decimals: 9,
},
),
(
18,
crate::ExSplClassicTokenSingleOperation::InitializeAccount {
account: first.clone(),
mint: second.clone(),
owner: third.clone(),
},
),
(
19,
crate::ExSplClassicTokenSingleOperation::InitializeMultisig {
multisig: first.clone(),
members: vec![second.clone(), third.clone()],
threshold: 1,
},
),
(
3,
crate::ExSplClassicTokenSingleOperation::Transfer {
source: first.clone(),
destination: second.clone(),
authority: authority.clone(),
amount: amount.clone(),
},
),
(
4,
crate::ExSplClassicTokenSingleOperation::Approve {
source: first.clone(),
delegate: second.clone(),
authority: authority.clone(),
amount: amount.clone(),
},
),
(
5,
crate::ExSplClassicTokenSingleOperation::Revoke {
source: first.clone(),
authority: authority.clone(),
},
),
(
6,
crate::ExSplClassicTokenSingleOperation::SetAuthority {
owned: first.clone(),
authority_type: crate::ExSplClassicTokenAuthorityType::CloseAccount,
new_authority: std::option::Option::Some(second.clone()),
current_authority: authority.clone(),
},
),
(
7,
crate::ExSplClassicTokenSingleOperation::MintTo {
mint: first.clone(),
destination: second.clone(),
authority: authority.clone(),
amount: amount.clone(),
},
),
(
8,
crate::ExSplClassicTokenSingleOperation::Burn {
source: first.clone(),
mint: second.clone(),
authority: authority.clone(),
amount: amount.clone(),
},
),
(
9,
crate::ExSplClassicTokenSingleOperation::CloseAccount {
account: first.clone(),
destination: second.clone(),
authority: authority.clone(),
},
),
(
10,
crate::ExSplClassicTokenSingleOperation::FreezeAccount {
account: first.clone(),
mint: second.clone(),
authority: authority.clone(),
},
),
(
11,
crate::ExSplClassicTokenSingleOperation::ThawAccount {
account: first.clone(),
mint: second.clone(),
authority: authority.clone(),
},
),
(12, transfer_checked()),
(
13,
crate::ExSplClassicTokenSingleOperation::ApproveChecked {
source: first.clone(),
mint: second.clone(),
delegate: third.clone(),
authority: authority.clone(),
amount: amount.clone(),
decimals: 9,
},
),
(
14,
crate::ExSplClassicTokenSingleOperation::MintToChecked {
mint: first.clone(),
destination: second.clone(),
authority: authority.clone(),
amount: amount.clone(),
decimals: 9,
},
),
(
15,
crate::ExSplClassicTokenSingleOperation::BurnChecked {
source: first.clone(),
mint: second.clone(),
authority: authority.clone(),
amount: amount.clone(),
decimals: 9,
},
),
(
17,
crate::ExSplClassicTokenSingleOperation::SyncNative {
account: first.clone(),
include_rent_sysvar: true,
},
),
(
21,
crate::ExSplClassicTokenSingleOperation::GetAccountDataSize { mint: first.clone() },
),
(
22,
crate::ExSplClassicTokenSingleOperation::InitializeImmutableOwner {
account: first.clone(),
},
),
(
23,
crate::ExSplClassicTokenSingleOperation::AmountToUiAmount {
mint: first.clone(),
amount: amount.clone(),
},
),
(
24,
crate::ExSplClassicTokenSingleOperation::UiAmountToAmount {
mint: first.clone(),
ui_amount: std::string::String::from("0.000000042"),
},
),
(
38,
crate::ExSplClassicTokenSingleOperation::WithdrawExcessLamports {
account: first.clone(),
destination: second.clone(),
authority: authority.clone(),
},
),
(
45,
crate::ExSplClassicTokenSingleOperation::UnwrapLamports {
account: first,
destination: second,
authority,
amount_lamports: std::option::Option::Some(amount),
},
),
];
}
#[test]
fn every_current_or_recent_single_operation_uses_its_published_tag() {
let operations = all_single_operations();
assert_eq!(operations.len() + 1, crate::EX_SPL_TOKEN_SUPPORTED_OPERATION_CODES.len());
for (tag, operation) in operations {
let intent =
intent(crate::ExSplClassicTokenOperation::Instruction { value: operation });
let plan = crate::ExApiTypedInstructionExecutor::build_prepared_plan(
&crate::ExSplTokenExecutor,
&intent,
)
.unwrap_or_else(|error| panic!("tag {tag} plan failed: {error}"));
assert_eq!(plan.instructions[0].data[0], tag);
}
}
#[test]
fn checked_transfer_matches_official_builder_and_preserves_u64_max() {
let operation = transfer_checked();
let intent = intent(crate::ExSplClassicTokenOperation::Instruction { value: operation });
let plan = crate::ExApiTypedInstructionExecutor::build_prepared_plan(
&crate::ExSplTokenExecutor,
&intent,
)
.unwrap_or_else(|error| panic!("checked transfer plan failed: {error}"));
assert_eq!(plan.instructions.len(), 1);
assert_eq!(plan.instructions[0].data[0], 12);
assert_eq!(&plan.instructions[0].data[1..9], &u64::MAX.to_le_bytes());
assert_eq!(plan.instructions[0].data[9], 9);
assert_eq!(plan.required_signers.len(), 2);
}
#[test]
fn multisig_meta_order_is_preserved_while_transaction_signers_are_unique() {
let first = ks_program_ids::VOTE_PROGRAM_ID;
let second = ks_program_ids::STAKE_PROGRAM_ID;
let operation = crate::ExSplClassicTokenSingleOperation::Transfer {
source: pubkey(ks_program_ids::SYSTEM_PROGRAM_ID),
destination: pubkey(ks_program_ids::CONFIG_PROGRAM_ID),
authority: authority(&[first, second, first]),
amount: crate::ExSplClassicTokenAmount(std::string::String::from("1")),
};
let intent = intent(crate::ExSplClassicTokenOperation::Instruction { value: operation });
let plan = crate::ExApiTypedInstructionExecutor::build_prepared_plan(
&crate::ExSplTokenExecutor,
&intent,
)
.unwrap_or_else(|error| panic!("multisig transfer plan failed: {error}"));
let accounts = &plan.instructions[0].accounts;
assert_eq!(accounts[3].pubkey.0, first);
assert_eq!(accounts[4].pubkey.0, second);
assert_eq!(accounts[5].pubkey.0, first);
assert_eq!(plan.required_signers.len(), 3);
}
#[test]
fn recent_unwrap_and_batch_match_published_wire() {
let unwrap = crate::ExSplClassicTokenSingleOperation::UnwrapLamports {
account: pubkey(ks_program_ids::SYSTEM_PROGRAM_ID),
destination: pubkey(ks_program_ids::STAKE_PROGRAM_ID),
authority: authority(&[]),
amount_lamports: std::option::Option::Some(crate::ExSplClassicTokenAmount(
std::string::String::from("42"),
)),
};
let intent = intent(crate::ExSplClassicTokenOperation::Batch {
instructions: vec![unwrap, transfer_checked()],
});
let plan = crate::ExApiTypedInstructionExecutor::build_prepared_plan(
&crate::ExSplTokenExecutor,
&intent,
)
.unwrap_or_else(|error| panic!("recent Batch plan failed: {error}"));
assert_eq!(plan.instructions[0].data[0], 255);
assert_eq!(plan.instructions[0].data[2], 10);
assert_eq!(plan.instructions[0].data[3], 45);
assert_eq!(plan.requested_spend_lamports, 42);
}
#[test]
fn invalid_amount_multisig_and_optional_simulation_fail_closed() {
let mut operation = transfer_checked();
if let crate::ExSplClassicTokenSingleOperation::TransferChecked { amount, .. } =
&mut operation
{
amount.0 = std::string::String::from("-1");
}
let pintent = intent(crate::ExSplClassicTokenOperation::Instruction { value: operation });
let error = crate::ExApiTypedInstructionExecutor::build_prepared_plan(
&crate::ExSplTokenExecutor,
&pintent,
)
.expect_err("negative amount must fail");
assert_eq!(error.code(), "execution_spl_token_amount_invalid");
let mut intent =
intent(crate::ExSplClassicTokenOperation::Instruction { value: transfer_checked() });
intent.policy.simulation = crate::ExApiExecutionSimulationPolicy::Optional;
let error = crate::ExApiTypedInstructionExecutor::build_prepared_plan(
&crate::ExSplTokenExecutor,
&intent,
)
.expect_err("optional simulation must fail");
assert_eq!(error.code(), "execution_spl_token_simulation_required");
}
#[test]
fn every_cluster_is_constructible_and_mainnet_remains_governed_by_common_safety() {
for cluster in [
crate::ExApiExecutionCluster::Localnet,
crate::ExApiExecutionCluster::Devnet,
crate::ExApiExecutionCluster::Testnet,
crate::ExApiExecutionCluster::Mainnet,
] {
let mut intent = intent(crate::ExSplClassicTokenOperation::Instruction {
value: transfer_checked(),
});
intent.policy.cluster.expected_cluster = cluster;
let plan = crate::ExApiTypedInstructionExecutor::build_prepared_plan(
&crate::ExSplTokenExecutor,
&intent,
)
.unwrap_or_else(|error| panic!("cluster plan failed: {error}"));
assert_eq!(plan.policy.cluster.expected_cluster, cluster);
let evaluation = crate::ExSafetyChecker
.evaluate_prepared_plan(&plan)
.unwrap_or_else(|error| panic!("safety evaluation failed: {error}"));
if cluster == crate::ExApiExecutionCluster::Mainnet {
assert_eq!(evaluation.decision, crate::ExSafetyDecision::Deny);
assert!(evaluation.violations.iter().any(|violation| {
return violation.code == "execution_mainnet_disabled";
}));
let mut explicitly_enabled = plan.clone();
explicitly_enabled.policy.cluster.allow_mainnet = true;
explicitly_enabled.policy.cluster.mainnet_confirmation = true;
let enabled_evaluation = crate::ExSafetyChecker
.evaluate_prepared_plan(&explicitly_enabled)
.unwrap_or_else(|error| {
panic!("enabled Mainnet safety evaluation failed: {error}")
});
assert_eq!(enabled_evaluation.decision, crate::ExSafetyDecision::Allow);
} else {
assert_eq!(evaluation.decision, crate::ExSafetyDecision::Allow);
}
}
}
}