// file: kb-lib/src/materializer/token/accounts.rs // version: 3 //! Stable committed SPL Token mutation and ATA lifecycle projections. const ACCEPTED_FAMILIES: &[crate::MdEventFamily] = &[ crate::MdEventFamily::TokenAccount, crate::MdEventFamily::TokenMint, crate::MdEventFamily::TokenBurn, crate::MdEventFamily::Lifecycle, ]; const MUTATION_ENTRIES: &[&str] = &[ "initialize_mint", "initialize_mint2", "initialize_account", "initialize_account2", "initialize_account3", "transfer", "transfer_checked", "approve", "approve_checked", "revoke", "mint_to", "mint_to_checked", "burn", "burn_checked", "close_account", "freeze_account", "thaw_account", "sync_native", "initialize_immutable_owner", "withdraw_excess_lamports", "unwrap_lamports", "initialize_non_transferable_mint", "reallocate", ]; const ATA_LIFECYCLE_ENTRIES: &[(&str, &str)] = &[ ("create", "ata_created"), ("create_idempotent", "ata_created_or_reused_idempotently"), ("recover_nested", "nested_ata_recovered"), ]; /// Stable committed classic SPL Token account and mint mutation materializer. #[derive(Clone, Debug, Default)] pub struct MtTokenAccountsMaterializer; impl crate::MtMaterializer for crate::MtTokenAccountsMaterializer { fn materializer_name(&self) -> &'static str { return "kb_materializer_token_accounts"; } fn materializer_version(&self) -> &'static str { return env!("CARGO_PKG_VERSION"); } fn accepts_event(&self, event: &crate::MdDecodedProtocolEvent) -> bool { return accepts_event(event); } fn materialize_event( &self, _event: &crate::MdDecodedProtocolEvent, ) -> kb_core::Result> { return std::result::Result::Ok(std::vec::Vec::new()); } } impl crate::MtApiEventMaterializer for crate::MtTokenAccountsMaterializer { fn identity(&self) -> crate::MtApiMaterializerIdentity { return crate::MtApiMaterializerIdentity { name: crate::MT_TOKEN_ACCOUNTS_PROCESSOR_NAME.to_string(), version: env!("CARGO_PKG_VERSION").to_string(), }; } fn accepted_families(&self) -> &'static [crate::MdEventFamily] { return ACCEPTED_FAMILIES; } fn accepts_observation(&self, observation: &crate::DcApiDecodedObservation) -> bool { return accepts_event(&observation.event); } fn transaction_policy( &self, _family: crate::MdEventFamily, ) -> crate::MtApiMaterializationTransactionPolicy { return crate::MtApiMaterializationTransactionPolicy::SuccessfulCommittedOnly; } fn materialize( &self, observation: &crate::DcApiDecodedObservation, ) -> crate::MtApiMaterializerExecutionResult { if !accepts_event(&observation.event) { return crate::MtApiMaterializerExecutionResult::ignored(); } if observation.transaction_failed || !observation.observation_committed { return crate::MtApiMaterializerExecutionResult::refused( "uncommitted_token_account_projection_refused", "failed or uncommitted SPL Token or ATA observations cannot create mutable outputs", ); } if is_ata_event(&observation.event) { return materialize_ata(observation); } let parameters = observation .payload_json .get("parameters") .cloned() .unwrap_or(serde_json::Value::Null); let accounts = observation .payload_json .get("accounts") .cloned() .unwrap_or(serde_json::Value::Null); let authority = observation .payload_json .get("authority") .cloned() .unwrap_or(serde_json::Value::Null); let operation = observation.event.event_name.0.clone(); let output = crate::MtApiMaterializedOutput { output_key: format!("token_account_mutation:{operation}:0"), family: crate::MdMaterializedEventFamily::TokenAccount, payload_json: serde_json::json!({ "projectionVersion": crate::MT_TOKEN_ACCOUNTS_PROJECTION_VERSION, "domain": "spl_token_account_mutation", "projectionSemantics": "committed_instruction_mutation_not_final_account_snapshot", "idempotenceKey": format!( "spl_token:{}:{}:{}", observation.event.signature.0, observation.event.instruction_path.0, operation ), "operation": operation, "signature": observation.event.signature.0.clone(), "slot": observation.event.slot.0, "instructionPath": observation.event.instruction_path.0.clone(), "programId": observation.event.program_id.0.clone(), "mint": account_key_for_role(&accounts, "mint"), "amountRaw": parameters.get("amountRaw").cloned().unwrap_or(serde_json::Value::Null), "decimals": parameters.get("decimals").cloned().unwrap_or(serde_json::Value::Null), "parameters": parameters, "accounts": accounts, "authority": authority, "committed": true, "transactionFinalAccountStateCaptured": false, "coreBalanceChangesDuplicated": false, "provenance": { "processorName": crate::MT_TOKEN_ACCOUNTS_PROCESSOR_NAME, "processorVersion": env!("CARGO_PKG_VERSION"), "sourceSurface": observation.event.surface_code.0.clone(), "sourceEventCode": observation.event.event_code.0.clone(), "sourceEventKey": observation.event_key.clone() } }), }; tracing::debug!( target: crate::MT_TOKEN_ACCOUNTS_TRACING_TARGET, action = "materialize_token_account_mutation", signature = %observation.event.signature.0, instruction_path = %observation.event.instruction_path.0, operation = %observation.event.event_name.0, "materialize committed classic SPL Token mutation" ); return crate::MtApiMaterializerExecutionResult { status: crate::MtApiMaterializerOutcomeStatus::Inserted, outputs: std::vec![output], diagnostics: std::vec::Vec::new(), }; } } fn accepts_event(event: &crate::MdDecodedProtocolEvent) -> bool { let token_program = (event.program_id.0 == kb_program_ids::SPL_TOKEN_PROGRAM_ID && event.surface_code.0 == "spl_token") || (event.program_id.0 == kb_program_ids::SPL_TOKEN2022_PROGRAM_ID && event.surface_code.0 == "spl_token2022"); let token_mutation = token_program && MUTATION_ENTRIES.contains(&event.event_name.0.as_str()); let ata = is_ata_event(event); return ACCEPTED_FAMILIES.contains(&event.event_family) && (token_mutation || ata); } fn is_ata_event(event: &crate::MdDecodedProtocolEvent) -> bool { return event.program_id.0 == kb_program_ids::ASSOCIATED_TOKEN_PROGRAM_ID && event.surface_code.0 == "spl_associated_token_account" && event.event_family == crate::MdEventFamily::Lifecycle && ATA_LIFECYCLE_ENTRIES .iter() .any(|(entry, _)| return *entry == event.event_name.0); } fn materialize_ata( observation: &crate::DcApiDecodedObservation, ) -> crate::MtApiMaterializerExecutionResult { let operation = observation.event.event_name.0.as_str(); let addresses = match observation.payload_json.get("addresses") { std::option::Option::Some(value) if value.is_object() => value, _ => { return crate::MtApiMaterializerExecutionResult::refused( "ata_addresses_missing", "committed ATA lifecycle projection requires the decoded address contract", ); }, }; let validation = validate_ata_addresses(operation, addresses); if let std::result::Result::Err((code, message)) = validation { return crate::MtApiMaterializerExecutionResult::refused(code, message); } let lifecycle_kind = match ATA_LIFECYCLE_ENTRIES.iter().find(|(entry, _)| return *entry == operation) { std::option::Option::Some((_, value)) => *value, std::option::Option::None => { return crate::MtApiMaterializerExecutionResult::ignored(); }, }; let associated_token_account = if operation == "recover_nested" { observed_address(addresses, "nestedAssociatedTokenAccount") } else { observed_address(addresses, "associatedTokenAccount") }; let token_program = addresses .get("tokenProgram") .and_then(|value| return value.get("programId")) .cloned() .unwrap_or(serde_json::Value::Null); let wallet = addresses.get("walletOwner").cloned().unwrap_or(serde_json::Value::Null); let mint = if operation == "recover_nested" { addresses.get("nestedMint").cloned().unwrap_or(serde_json::Value::Null) } else { addresses.get("mint").cloned().unwrap_or(serde_json::Value::Null) }; let output = crate::MtApiMaterializedOutput { output_key: format!("ata_lifecycle:{lifecycle_kind}:0"), family: crate::MdMaterializedEventFamily::TokenAccount, payload_json: serde_json::json!({ "projectionVersion": crate::MT_TOKEN_ACCOUNTS_PROJECTION_VERSION, "domain": "spl_associated_token_account_lifecycle", "projectionSemantics": "committed_associated_address_lifecycle_not_token_state_snapshot", "idempotenceKey": format!( "ata:{}:{}:{}:{}:{}", observation.event.signature.0, observation.event.instruction_path.0, observation.event.program_id.0, lifecycle_kind, associated_token_account.as_str().unwrap_or("") ), "operation": operation, "lifecycleKind": lifecycle_kind, "associatedTokenAccount": associated_token_account, "walletOwner": wallet, "mint": mint, "tokenProgramId": token_program, "fundingAccount": addresses.get("fundingAccount").cloned().unwrap_or(serde_json::Value::Null), "ownerMint": addresses.get("ownerMint").cloned().unwrap_or(serde_json::Value::Null), "nestedMint": addresses.get("nestedMint").cloned().unwrap_or(serde_json::Value::Null), "ownerAssociatedTokenAccount": observed_address(addresses, "ownerAssociatedTokenAccount"), "nestedAssociatedTokenAccount": observed_address(addresses, "nestedAssociatedTokenAccount"), "walletNestedMintAssociatedTokenAccount": observed_address(addresses, "walletNestedMintAssociatedTokenAccount"), "idempotentOutcome": if operation == "create_idempotent" { "created_or_reused_not_distinguishable_from_parent_instruction_alone" } else { "not_applicable" }, "signature": observation.event.signature.0.clone(), "slot": observation.event.slot.0, "instructionPath": observation.event.instruction_path.0.clone(), "programId": observation.event.program_id.0.clone(), "committed": true, "transactionFinalAccountStateCaptured": false, "tokenBalanceReconstructed": false, "splTokenCpiMutationsDuplicated": false, "ownership": { "ataParent": "associated_address_lifecycle_only", "splTokenCpi": "initialize_transfer_and_close_mutations" }, "provenance": { "processorName": crate::MT_TOKEN_ACCOUNTS_PROCESSOR_NAME, "processorVersion": env!("CARGO_PKG_VERSION"), "sourceSurface": observation.event.surface_code.0.clone(), "sourceEventCode": observation.event.event_code.0.clone(), "sourceEventKey": observation.event_key.clone() } }), }; tracing::debug!( target: crate::MT_TOKEN_ACCOUNTS_TRACING_TARGET, action = "materialize_ata_lifecycle", signature = %observation.event.signature.0, instruction_path = %observation.event.instruction_path.0, operation, lifecycle_kind, "materialize committed ATA lifecycle without duplicating SPL Token CPI mutations" ); return crate::MtApiMaterializerExecutionResult { status: crate::MtApiMaterializerOutcomeStatus::Inserted, outputs: std::vec![output], diagnostics: std::vec::Vec::new(), }; } fn validate_ata_addresses( operation: &str, addresses: &serde_json::Value, ) -> std::result::Result<(), (&'static str, &'static str)> { let token_supported = addresses .get("tokenProgram") .and_then(|value| return value.get("supported")) .and_then(serde_json::Value::as_bool) == std::option::Option::Some(true); let token_program_id = addresses .get("tokenProgram") .and_then(|value| return value.get("programId")) .and_then(serde_json::Value::as_str); let token_program_exact = matches!( token_program_id, std::option::Option::Some(kb_program_ids::SPL_TOKEN_PROGRAM_ID) | std::option::Option::Some(kb_program_ids::SPL_TOKEN2022_PROGRAM_ID) ); if !token_supported || !token_program_exact { return std::result::Result::Err(( "ata_token_program_not_supported", "ATA lifecycle projection requires classic SPL Token or Token-2022", )); } let fields: &[&str] = if operation == "recover_nested" { &[ "ownerAssociatedTokenAccount", "nestedAssociatedTokenAccount", "walletNestedMintAssociatedTokenAccount", ] } else { &["associatedTokenAccount"] }; for field in fields { let valid = addresses .get(*field) .and_then(|value| return value.get("valid")) .and_then(serde_json::Value::as_bool) == std::option::Option::Some(true); let observed = observed_address(addresses, field); if !valid || observed.as_str().is_none_or(|value| return value.is_empty()) { return std::result::Result::Err(( "ata_derived_address_invalid", "ATA lifecycle projection requires every canonical address validation to pass", )); } } let identity_fields: &[&str] = if operation == "recover_nested" { &["walletOwner", "ownerMint", "nestedMint"] } else { &["walletOwner", "mint"] }; if identity_fields.iter().any(|field| { return addresses .get(*field) .and_then(serde_json::Value::as_str) .is_none_or(|value| return value.is_empty()); }) { return std::result::Result::Err(( "ata_identity_field_missing", "ATA lifecycle projection requires wallet and mint identities", )); } return std::result::Result::Ok(()); } fn observed_address(addresses: &serde_json::Value, field: &str) -> serde_json::Value { return addresses .get(field) .and_then(|value| return value.get("observed")) .cloned() .unwrap_or(serde_json::Value::Null); } fn account_key_for_role(accounts: &serde_json::Value, role: &str) -> serde_json::Value { let value = accounts.as_array().and_then(|rows| { return rows.iter().find_map(|row| { if row.get("role").and_then(serde_json::Value::as_str) != std::option::Option::Some(role) { return std::option::Option::None; } return row.get("accountKey").and_then(serde_json::Value::as_str); }); }); return match value { std::option::Option::Some(account_key) => { serde_json::Value::String(account_key.to_string()) }, std::option::Option::None => serde_json::Value::Null, }; } /// Materialize one already parsed Token-2022 account snapshot without inferring hidden state. pub fn materializer_token_materialize_token2022_state_snapshot( account_key: &str, slot: u64, state: &crate::DcToken2022State, ) -> std::result::Result { if account_key.is_empty() { return std::result::Result::Err( "Token-2022 state snapshot requires a non-empty account key".to_string(), ); } let kind = match state.kind { crate::DcToken2022StateKind::Mint => "mint", crate::DcToken2022StateKind::Account => "account", crate::DcToken2022StateKind::Multisig => "multisig", }; let extensions = state .extensions .iter() .map(|entry| { return serde_json::json!({ "extensionType": entry.extension_type, "extensionName": entry.extension_name, "valueHex": entry.value_hex, "valueFields": entry.value_fields, }); }) .collect::>(); let extension_names = state .extensions .iter() .map(|entry| return entry.extension_name) .collect::>(); let contains_confidential_state = state.extensions.iter().any(|entry| { return entry.extension_name.starts_with("confidential_") || entry.extension_name == "confidential_mint_burn"; }); return std::result::Result::Ok(crate::MtApiMaterializedOutput { output_key: format!("token2022_state_snapshot:{account_key}:{slot}"), family: crate::MdMaterializedEventFamily::TokenAccount, payload_json: serde_json::json!({ "projectionVersion": crate::MT_TOKEN_ACCOUNTS_PROJECTION_VERSION, "domain": "spl_token2022_account_state", "projectionSemantics": "authoritative_bounded_account_snapshot", "idempotenceKey": format!("token2022_state:{account_key}:{slot}"), "accountKey": account_key, "slot": slot, "programId": kb_program_ids::SPL_TOKEN2022_PROGRAM_ID, "stateKind": kind, "baseFields": state.base_fields, "baseHex": state.base_hex, "accountType": state.account_type, "extensionCount": state.extensions.len(), "extensionNames": extension_names, "extensions": extensions, "containsConfidentialState": contains_confidential_state, "confidentialValuesDecrypted": false, "finalAccountStateCaptured": true, "provenance": { "processorName": crate::MT_TOKEN_ACCOUNTS_PROCESSOR_NAME, "processorVersion": env!("CARGO_PKG_VERSION"), "source": "bounded_token2022_state_parser" } }), }); } #[cfg(test)] mod tests { fn observation( entry: &str, family: crate::MdEventFamily, failed: bool, ) -> crate::DcApiDecodedObservation { return crate::DcApiDecodedObservation { event_key: format!("token:{entry}:0"), event: crate::MdDecodedProtocolEvent { signature: crate::MdSignature("signature".to_string()), slot: crate::MdSlot(42), instruction_path: crate::MdInstructionPath("1/0".to_string()), program_id: crate::MdProgramId(kb_program_ids::SPL_TOKEN_PROGRAM_ID.to_string()), protocol_code: crate::MdProtocolCode("spl_token".to_string()), surface_code: crate::MdSurfaceCode("spl_token".to_string()), event_code: crate::MdEventCode(format!("spl_token.{entry}")), event_name: crate::MdEventName(entry.to_string()), event_family: family, source_kind: crate::MdEventSourceKind::InnerInstruction, confidence: crate::MdDecoderConfidence::ManualExact, }, payload_json: serde_json::json!({ "parameters": {"amountRaw":"18446744073709551615","decimals":9}, "accounts": [{"role":"mint","accountKey":"mint111"}], "authority": {"form":"single"} }), transaction_failed: failed, transaction_error: if failed { std::option::Option::Some(serde_json::json!({"error":"failed"})) } else { std::option::Option::None }, observation_committed: !failed, proof: crate::DcApiDecoderProof { kind: crate::DcApiDecoderProofKind::Manual, confidence: crate::MdDecoderConfidence::ManualExact, evidence: std::vec!["fixture".to_string()], }, }; } fn token2022_observation( entry: &str, family: crate::MdEventFamily, failed: bool, ) -> crate::DcApiDecodedObservation { let mut observation = observation(entry, family, failed); observation.event.program_id = crate::MdProgramId(kb_program_ids::SPL_TOKEN2022_PROGRAM_ID.to_string()); observation.event.protocol_code = crate::MdProtocolCode("spl_token2022".to_string()); observation.event.surface_code = crate::MdSurfaceCode("spl_token2022".to_string()); observation.event.event_code = crate::MdEventCode(format!("spl_token2022.{entry}")); observation.event_key = format!("token2022:{entry}:0"); return observation; } fn ata_observation(entry: &str, failed: bool, valid: bool) -> crate::DcApiDecodedObservation { let addresses = if entry == "recover_nested" { serde_json::json!({ "tokenProgram": { "programId": kb_program_ids::SPL_TOKEN_PROGRAM_ID, "kind": "spl_token_classic", "supported": true }, "ownerAssociatedTokenAccount": {"expected":"ownerAta","observed":"ownerAta","valid":valid}, "nestedAssociatedTokenAccount": {"expected":"nestedAta","observed":"nestedAta","valid":valid}, "walletNestedMintAssociatedTokenAccount": {"expected":"destinationAta","observed":"destinationAta","valid":valid}, "walletOwner": "wallet111", "ownerMint": "ownerMint111", "nestedMint": "nestedMint111" }) } else { serde_json::json!({ "tokenProgram": { "programId": kb_program_ids::SPL_TOKEN2022_PROGRAM_ID, "kind": "token2022", "supported": true }, "associatedTokenAccount": {"expected":"ata111","observed":"ata111","valid":valid}, "fundingAccount": "payer111", "walletOwner": "wallet111", "mint": "mint111" }) }; return crate::DcApiDecodedObservation { event_key: format!("ata:{entry}:0"), event: crate::MdDecodedProtocolEvent { signature: crate::MdSignature("signature".to_string()), slot: crate::MdSlot(42), instruction_path: crate::MdInstructionPath("0".to_string()), program_id: crate::MdProgramId( kb_program_ids::ASSOCIATED_TOKEN_PROGRAM_ID.to_string(), ), protocol_code: crate::MdProtocolCode("spl_associated_token_account".to_string()), surface_code: crate::MdSurfaceCode("spl_associated_token_account".to_string()), event_code: crate::MdEventCode(format!("spl_associated_token_account.{entry}")), event_name: crate::MdEventName(entry.to_string()), event_family: crate::MdEventFamily::Lifecycle, source_kind: crate::MdEventSourceKind::Instruction, confidence: crate::MdDecoderConfidence::ManualExact, }, payload_json: serde_json::json!({"addresses":addresses}), transaction_failed: failed, transaction_error: std::option::Option::None, observation_committed: !failed, proof: crate::DcApiDecoderProof { kind: crate::DcApiDecoderProofKind::Manual, confidence: crate::MdDecoderConfidence::ManualExact, evidence: std::vec!["fixture".to_string()], }, }; } #[test] fn committed_checked_transfer_preserves_exact_amount_mint_and_path() { let observation = observation("transfer_checked", crate::MdEventFamily::TokenAccount, false); let result = crate::MtApiEventMaterializer::materialize( &crate::MtTokenAccountsMaterializer, &observation, ); assert_eq!(result.status, crate::MtApiMaterializerOutcomeStatus::Inserted); assert_eq!(result.outputs[0].payload_json["amountRaw"], "18446744073709551615"); assert_eq!(result.outputs[0].payload_json["mint"], "mint111"); assert_eq!(result.outputs[0].payload_json["instructionPath"], "1/0"); assert_eq!(result.outputs[0].payload_json["transactionFinalAccountStateCaptured"], false); } #[test] fn committed_token2022_transfer_has_the_same_single_projection_owner() { let observation = token2022_observation("transfer_checked", crate::MdEventFamily::TokenAccount, false); let result = crate::MtApiEventMaterializer::materialize( &crate::MtTokenAccountsMaterializer, &observation, ); assert_eq!(result.status, crate::MtApiMaterializerOutcomeStatus::Inserted); assert_eq!(result.outputs.len(), 1); assert_eq!(result.outputs[0].payload_json["operation"], "transfer_checked"); assert_eq!( result.outputs[0].payload_json["programId"], kb_program_ids::SPL_TOKEN2022_PROGRAM_ID ); } #[test] fn failed_mutation_is_refused() { let observation = observation("burn", crate::MdEventFamily::TokenBurn, true); let result = crate::MtApiEventMaterializer::materialize( &crate::MtTokenAccountsMaterializer, &observation, ); assert_eq!(result.status, crate::MtApiMaterializerOutcomeStatus::Refused); assert!(result.outputs.is_empty()); } #[test] fn batch_parent_admin_and_conversion_events_are_not_duplicated() { for (entry, family) in [ ("batch", crate::MdEventFamily::Audit), ("set_authority", crate::MdEventFamily::Admin), ("amount_to_ui_amount", crate::MdEventFamily::Audit), ] { let observation = observation(entry, family, false); let result = crate::MtApiEventMaterializer::materialize( &crate::MtTokenAccountsMaterializer, &observation, ); assert_eq!(result.status, crate::MtApiMaterializerOutcomeStatus::Ignored); } } #[test] fn every_ata_variant_has_one_exact_lifecycle_projection() { for (entry, expected_kind, expected_ata, expected_token_program) in [ ("create", "ata_created", "ata111", kb_program_ids::SPL_TOKEN2022_PROGRAM_ID), ( "create_idempotent", "ata_created_or_reused_idempotently", "ata111", kb_program_ids::SPL_TOKEN2022_PROGRAM_ID, ), ( "recover_nested", "nested_ata_recovered", "nestedAta", kb_program_ids::SPL_TOKEN_PROGRAM_ID, ), ] { let result = crate::MtApiEventMaterializer::materialize( &crate::MtTokenAccountsMaterializer, &ata_observation(entry, false, true), ); assert_eq!(result.status, crate::MtApiMaterializerOutcomeStatus::Inserted); assert_eq!(result.outputs.len(), 1); assert_eq!(result.outputs[0].payload_json["lifecycleKind"], expected_kind); assert_eq!(result.outputs[0].payload_json["associatedTokenAccount"], expected_ata); assert_eq!(result.outputs[0].payload_json["tokenProgramId"], expected_token_program); assert_eq!(result.outputs[0].payload_json["tokenBalanceReconstructed"], false); assert_eq!(result.outputs[0].payload_json["splTokenCpiMutationsDuplicated"], false); } } #[test] fn idempotent_parent_does_not_invent_created_or_reused_branch() { let result = crate::MtApiEventMaterializer::materialize( &crate::MtTokenAccountsMaterializer, &ata_observation("create_idempotent", false, true), ); assert_eq!( result.outputs[0].payload_json["idempotentOutcome"], "created_or_reused_not_distinguishable_from_parent_instruction_alone" ); assert!(result.outputs[0].payload_json.get("reused").is_none()); assert!(result.outputs[0].payload_json.get("created").is_none()); } #[test] fn uncommitted_or_invalid_ata_lifecycle_is_refused() { for observation in [ ata_observation("create", true, true), ata_observation("recover_nested", false, false), ] { let result = crate::MtApiEventMaterializer::materialize( &crate::MtTokenAccountsMaterializer, &observation, ); assert_eq!(result.status, crate::MtApiMaterializerOutcomeStatus::Refused); assert!(result.outputs.is_empty()); } } #[test] fn ata_parent_and_classic_token_cpi_children_own_distinct_facts() { let parent = crate::MtApiEventMaterializer::materialize( &crate::MtTokenAccountsMaterializer, &ata_observation("recover_nested", false, true), ); let transfer = crate::MtApiEventMaterializer::materialize( &crate::MtTokenAccountsMaterializer, &observation("transfer", crate::MdEventFamily::TokenAccount, false), ); let close = crate::MtApiEventMaterializer::materialize( &crate::MtTokenAccountsMaterializer, &observation("close_account", crate::MdEventFamily::TokenAccount, false), ); assert_eq!(parent.outputs[0].payload_json["lifecycleKind"], "nested_ata_recovered"); assert_eq!( parent.outputs[0].payload_json["ownership"]["ataParent"], "associated_address_lifecycle_only" ); assert!(parent.outputs[0].payload_json.get("amountRaw").is_none()); assert_eq!(transfer.outputs[0].payload_json["operation"], "transfer"); assert_eq!(close.outputs[0].payload_json["operation"], "close_account"); assert_eq!(transfer.outputs[0].payload_json["domain"], "spl_token_account_mutation"); assert_eq!(close.outputs[0].payload_json["domain"], "spl_token_account_mutation"); } #[test] fn ata_matrix_and_compiled_token_account_projection_ownership_are_equal() { let matrix: serde_json::Value = match serde_json::from_str(include_str!( "../../../../docs/SPL_ASSOCIATED_TOKEN_ACCOUNT_MATRIX.json" )) { std::result::Result::Ok(value) => value, std::result::Result::Err(error) => panic!("ATA matrix is invalid: {error}"), }; let instructions = match matrix.get("instructions").and_then(serde_json::Value::as_array) { std::option::Option::Some(value) => value, std::option::Option::None => panic!("ATA matrix instructions are absent"), }; for (entry, fact) in super::ATA_LIFECYCLE_ENTRIES { let row = match instructions.iter().find(|row| { return row.get("name").and_then(serde_json::Value::as_str) == std::option::Option::Some(*entry); }) { std::option::Option::Some(value) => value, std::option::Option::None => panic!("ATA matrix entry {entry} is absent"), }; let expected = format!("token_accounts:{fact}"); assert!(row["authorizedProjections"].as_array().is_some_and(|values| { return values.iter().any(|value| return value == expected.as_str()); })); } let owner = &matrix["materializationContract"]["projectionOwners"][0]; let matrix_facts = match owner["facts"].as_array() { std::option::Option::Some(values) => values .iter() .filter_map(|value| return value.as_str()) .collect::>(), std::option::Option::None => panic!("ATA token-account owner facts are absent"), }; let compiled_facts = super::ATA_LIFECYCLE_ENTRIES .iter() .map(|(_, fact)| return *fact) .collect::>(); assert_eq!(matrix_facts, compiled_facts); } #[test] fn second_ata_materialization_is_byte_for_byte_idempotent() { let observation = ata_observation("create_idempotent", false, true); let first = crate::MtApiEventMaterializer::materialize( &crate::MtTokenAccountsMaterializer, &observation, ); let second = crate::MtApiEventMaterializer::materialize( &crate::MtTokenAccountsMaterializer, &observation, ); assert_eq!(first, second); assert_eq!(first.outputs[0].output_key, second.outputs[0].output_key); assert_eq!( first.outputs[0].payload_json["idempotenceKey"], second.outputs[0].payload_json["idempotenceKey"] ); } #[test] fn token2022_state_snapshot_preserves_base_and_ordered_extensions() { let state = crate::DcToken2022State { kind: crate::DcToken2022StateKind::Mint, base_fields: serde_json::json!({"supply":"42","decimals":9,"initialized":true}), base_hex: "00".repeat(82), account_type: std::option::Option::Some(1), extensions: std::vec![ crate::DcToken2022TlvEntry { extension_type: 1, extension_name: "transfer_fee_config", value_hex: "11".repeat(108), value_fields: serde_json::json!({"withheldAmount":"7"}), }, crate::DcToken2022TlvEntry { extension_type: 4, extension_name: "confidential_transfer_mint", value_hex: "22".repeat(65), value_fields: serde_json::json!({"autoApproveNewAccounts":true}), }, ], }; let output = match crate::materializer_token_materialize_token2022_state_snapshot( "mint111", 99, &state, ) { std::result::Result::Ok(value) => value, std::result::Result::Err(error) => panic!("snapshot failed: {error}"), }; assert_eq!(output.payload_json["stateKind"], "mint"); assert_eq!(output.payload_json["extensionCount"], 2); assert_eq!(output.payload_json["extensionNames"][0], "transfer_fee_config"); assert_eq!(output.payload_json["extensionNames"][1], "confidential_transfer_mint"); assert_eq!(output.payload_json["containsConfidentialState"], true); assert_eq!(output.payload_json["confidentialValuesDecrypted"], false); assert_eq!(output.payload_json["baseFields"]["supply"], "42"); } #[test] fn token2022_state_snapshot_is_idempotent_and_rejects_missing_identity() { let state = crate::DcToken2022State { kind: crate::DcToken2022StateKind::Account, base_fields: serde_json::json!({"mint":"mint111","owner":"wallet111","amount":"5"}), base_hex: "00".repeat(165), account_type: std::option::Option::None, extensions: std::vec::Vec::new(), }; let first = crate::materializer_token_materialize_token2022_state_snapshot("account111", 7, &state); let second = crate::materializer_token_materialize_token2022_state_snapshot("account111", 7, &state); assert_eq!(first, second); assert!( crate::materializer_token_materialize_token2022_state_snapshot("", 7, &state).is_err() ); } }