// file: ks-lib/src/executor/spl/token/builder.rs // version: 11 //! 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, } pub(crate) fn executor_spl_token_build_prepared_plan( intent: &crate::ExSplClassicTokenExecutionIntent, ) -> ks_core::Result { 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("ks-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 { 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::>(); 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::>(); 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::>(); 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::>(); 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::>(); 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, ¤t_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::>(); 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::>(); 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::>(); 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::>(); 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::>(); 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::>(); 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::>(); 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::>(); 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::>(); 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::>(); 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::>(); 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 { 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( result: std::result::Result, ) -> ks_core::Result 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 { 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 { 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::() { 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 { 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 { 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 { 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 { 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); } } } }