v0.1.0-pre.011

This commit is contained in:
2026-07-24 14:23:58 +02:00
parent 62bc0ffb44
commit 42e8a203ec
460 changed files with 12005 additions and 7020 deletions

View File

@@ -1,17 +1,19 @@
// file: kb-lib/src/materializer/admin.rs
// version: 2
// version: 3
//! `admin` materializer family.
mod constants;
pub mod core;
mod core;
pub(crate) use crate::materializer::admin::constants::PROCESSOR_NAME;
pub(crate) use crate::materializer::admin::constants::TRACING_TARGET as MATERIALIZER_ADMIN_TRACING_TARGET;
/// Canonical processor name for the native and SPL administration materializer.
pub(crate) use self::constants::MT_ADMIN_PROCESSOR_NAME;
/// Canonical tracing target for the native and SPL administration materializer.
pub(crate) use self::constants::MT_ADMIN_TRACING_TARGET;
/// Stable native and SPL administration materializer.
pub use crate::materializer::admin::core::AdminMaterializer;
/// Materializes one exact SPL ElGamal registry account snapshot.
pub use crate::materializer::admin::core::materialize_elgamal_registry_state_snapshot;
pub use self::core::MtAdminMaterializer;
/// Materializes exact Token-2022 administration account snapshots.
pub use crate::materializer::admin::core::materialize_token_2022_admin_state_snapshots;
pub use self::core::materialize_token2022_admin_state_snapshots;
/// Materializes one exact SPL ElGamal registry account snapshot.
pub use self::core::materializer_admin_materialize_elgamal_registry_state_snapshot;

View File

@@ -4,7 +4,7 @@
//! Local constants for the native and SPL administration materializer.
/// Canonical processor name for this component.
pub(crate) const PROCESSOR_NAME: &str = "solana_native_admin";
pub(crate) const MT_ADMIN_PROCESSOR_NAME: &str = "solana_native_admin";
/// Canonical tracing target for this component.
pub(crate) const TRACING_TARGET: &str = "kb-lib.materializer.admin";
pub(crate) const MT_ADMIN_TRACING_TARGET: &str = "kb-lib.materializer.admin";

View File

@@ -3,8 +3,8 @@
//! Stable administrative projections derived from exact committed observations.
const ACCEPTED_FAMILIES: &[crate::EventFamily] =
&[crate::EventFamily::Admin, crate::EventFamily::TokenAccount];
const ACCEPTED_FAMILIES: &[crate::MdEventFamily] =
&[crate::MdEventFamily::Admin, crate::MdEventFamily::TokenAccount];
const SYSTEM_SURFACE: &str = "solana_native_system";
const SYSTEM_ENTRIES: &[&str] = &["assign", "assign_with_seed"];
const CONFIG_SURFACE: &str = "solana_native_config";
@@ -14,7 +14,7 @@ const BPF_LOADER_UPGRADEABLE_ENTRIES: &[&str] = &["set_authority", "set_authorit
const LOADER_V4_SURFACE: &str = "solana_native_loader_v4";
const LOADER_V4_ENTRIES: &[&str] = &["transfer_authority"];
const SPL_TOKEN_SURFACE: &str = "spl_token";
const SPL_TOKEN_2022_SURFACE: &str = "spl_token_2022";
const SPL_TOKEN2022_SURFACE: &str = "spl_token2022";
const SPL_ELGAMAL_REGISTRY_SURFACE: &str = "spl_elgamal_registry";
const SPL_ELGAMAL_REGISTRY_ENTRIES: &[&str] = &["create_registry", "update_registry"];
const SPL_TOKEN_AUTHORITY_ENTRIES: &[&str] = &[
@@ -24,7 +24,7 @@ const SPL_TOKEN_AUTHORITY_ENTRIES: &[&str] = &[
"update_interest_bearing_rate",
];
const SPL_TOKEN_MULTISIG_ENTRIES: &[&str] = &["initialize_multisig", "initialize_multisig2"];
const SPL_TOKEN_2022_EXTENSION_ADMIN_ENTRIES: &[&str] = &[
const SPL_TOKEN2022_EXTENSION_ADMIN_ENTRIES: &[&str] = &[
"initialize_transfer_hook",
"update_transfer_hook_program_id",
"initialize_metadata_pointer",
@@ -57,7 +57,7 @@ impl ProjectionKind {
Self::SplTokenAuthority => "spl_token_authority",
Self::SplTokenMultisig => "spl_token_multisig",
Self::SplElgamalRegistry => "spl_elgamal_registry",
Self::SplToken2022ExtensionAdmin => "spl_token_2022_extension_admin",
Self::SplToken2022ExtensionAdmin => "spl_token2022_extension_admin",
};
}
@@ -79,7 +79,7 @@ impl ProjectionKind {
Self::SplTokenMultisig => format!("spl_token_multisig:{operation}:0"),
Self::SplElgamalRegistry => format!("spl_elgamal_registry:{operation}:0"),
Self::SplToken2022ExtensionAdmin => {
format!("spl_token_2022_extension_admin:{operation}:0")
format!("spl_token2022_extension_admin:{operation}:0")
},
};
}
@@ -87,9 +87,9 @@ impl ProjectionKind {
/// Stable administrative materializer for supported native mutations.
#[derive(Clone, Debug, Default)]
pub struct AdminMaterializer;
pub struct MtAdminMaterializer;
impl crate::Materializer for crate::AdminMaterializer {
impl crate::MtMaterializer for crate::MtAdminMaterializer {
fn materializer_name(&self) -> &'static str {
return "kb_materializer_admin";
}
@@ -98,7 +98,7 @@ impl crate::Materializer for crate::AdminMaterializer {
return env!("CARGO_PKG_VERSION");
}
fn accepts_event(&self, event: &crate::DecodedProtocolEvent) -> bool {
fn accepts_event(&self, event: &crate::MdDecodedProtocolEvent) -> bool {
return ACCEPTED_FAMILIES.contains(&event.event_family)
&& program_matches_surface(event)
&& projection(event.surface_code.0.as_str(), event.event_name.0.as_str()).is_some();
@@ -106,28 +106,28 @@ impl crate::Materializer for crate::AdminMaterializer {
fn materialize_event(
&self,
event: &crate::DecodedProtocolEvent,
) -> kb_core::Result<std::vec::Vec<crate::MaterializedEvent>> {
if !crate::Materializer::accepts_event(self, event) {
event: &crate::MdDecodedProtocolEvent,
) -> kb_core::Result<std::vec::Vec<crate::MdMaterializedEvent>> {
if !crate::MtMaterializer::accepts_event(self, event) {
return std::result::Result::Ok(std::vec::Vec::new());
}
return std::result::Result::Ok(std::vec::Vec::new());
}
}
impl crate::EventMaterializer for crate::AdminMaterializer {
fn identity(&self) -> crate::MaterializerIdentity {
return crate::MaterializerIdentity {
impl crate::MtApiEventMaterializer for crate::MtAdminMaterializer {
fn identity(&self) -> crate::MtApiMaterializerIdentity {
return crate::MtApiMaterializerIdentity {
name: "solana_native_admin".to_string(),
version: env!("CARGO_PKG_VERSION").to_string(),
};
}
fn accepted_families(&self) -> &'static [crate::EventFamily] {
fn accepted_families(&self) -> &'static [crate::MdEventFamily] {
return ACCEPTED_FAMILIES;
}
fn accepts_observation(&self, observation: &crate::DecodedObservation) -> bool {
fn accepts_observation(&self, observation: &crate::DcApiDecodedObservation) -> bool {
return ACCEPTED_FAMILIES.contains(&observation.event.event_family)
&& program_matches_surface(&observation.event)
&& projection(
@@ -139,15 +139,15 @@ impl crate::EventMaterializer for crate::AdminMaterializer {
fn transaction_policy(
&self,
_family: crate::EventFamily,
) -> crate::MaterializationTransactionPolicy {
return crate::MaterializationTransactionPolicy::SuccessfulCommittedOnly;
_family: crate::MdEventFamily,
) -> crate::MtApiMaterializationTransactionPolicy {
return crate::MtApiMaterializationTransactionPolicy::SuccessfulCommittedOnly;
}
fn materialize(
&self,
observation: &crate::DecodedObservation,
) -> crate::MaterializerExecutionResult {
observation: &crate::DcApiDecodedObservation,
) -> crate::MtApiMaterializerExecutionResult {
let projection = match projection(
observation.event.surface_code.0.as_str(),
observation.event.event_name.0.as_str(),
@@ -155,25 +155,25 @@ impl crate::EventMaterializer for crate::AdminMaterializer {
std::option::Option::Some(value) => value,
std::option::Option::None => {
tracing::debug!(
target: crate::MATERIALIZER_ADMIN_TRACING_TARGET,
target: crate::MT_ADMIN_TRACING_TARGET,
action = "materialize_admin",
surface_code = %observation.event.surface_code.0.as_str(),
entry_code = %observation.event.event_name.0.as_str(),
accepted = false,
"ignore observation outside supported native administrative projections"
);
return crate::MaterializerExecutionResult::ignored();
return crate::MtApiMaterializerExecutionResult::ignored();
},
};
if !ACCEPTED_FAMILIES.contains(&observation.event.event_family) {
return crate::MaterializerExecutionResult::ignored();
return crate::MtApiMaterializerExecutionResult::ignored();
}
if !program_matches_surface(&observation.event) {
return crate::MaterializerExecutionResult::ignored();
return crate::MtApiMaterializerExecutionResult::ignored();
}
if observation.transaction_failed || !observation.observation_committed {
tracing::debug!(
target: crate::MATERIALIZER_ADMIN_TRACING_TARGET,
target: crate::MT_ADMIN_TRACING_TARGET,
action = "materialize_admin",
surface_code = %observation.event.surface_code.0.as_str(),
entry_code = %observation.event.event_name.0.as_str(),
@@ -181,7 +181,7 @@ impl crate::EventMaterializer for crate::AdminMaterializer {
transaction_failed = observation.transaction_failed,
"refuse uncommitted native administrative observation"
);
return crate::MaterializerExecutionResult::refused(
return crate::MtApiMaterializerExecutionResult::refused(
"failed_transaction_admin_refused",
"failed or uncommitted native observations cannot create administrative mutation outputs",
);
@@ -197,10 +197,10 @@ impl crate::EventMaterializer for crate::AdminMaterializer {
.cloned()
.unwrap_or_else(|| return observation.payload_json.clone());
let operation = observation.event.event_name.0.clone();
let output = crate::MaterializedOutput {
let output = crate::MtApiMaterializedOutput {
output_key: projection
.output_key(observation.event.surface_code.0.as_str(), operation.as_str()),
family: crate::MaterializedEventFamily::Admin,
family: crate::MdMaterializedEventFamily::Admin,
payload_json: serde_json::json!({
"projectionVersion": 1,
"projectionSemantics": "committed_instruction_admin_event",
@@ -223,7 +223,7 @@ impl crate::EventMaterializer for crate::AdminMaterializer {
}),
};
tracing::debug!(
target: crate::MATERIALIZER_ADMIN_TRACING_TARGET,
target: crate::MT_ADMIN_TRACING_TARGET,
action = "materialize_admin",
surface_code = %observation.event.surface_code.0.as_str(),
entry_code = %observation.event.event_name.0.as_str(),
@@ -231,8 +231,8 @@ impl crate::EventMaterializer for crate::AdminMaterializer {
output_count = 1_usize,
"materialize committed native administrative observation"
);
return crate::MaterializerExecutionResult {
status: crate::MaterializerOutcomeStatus::Inserted,
return crate::MtApiMaterializerExecutionResult {
status: crate::MtApiMaterializerOutcomeStatus::Inserted,
outputs: std::vec![output],
diagnostics: std::vec::Vec::new(),
};
@@ -258,18 +258,18 @@ fn projection(surface_code: &str, entry_code: &str) -> std::option::Option<Proje
LOADER_V4_SURFACE,
));
}
if matches!(surface_code, SPL_TOKEN_SURFACE | SPL_TOKEN_2022_SURFACE)
if matches!(surface_code, SPL_TOKEN_SURFACE | SPL_TOKEN2022_SURFACE)
&& contains(SPL_TOKEN_AUTHORITY_ENTRIES, entry_code)
{
return std::option::Option::Some(ProjectionKind::SplTokenAuthority);
}
if matches!(surface_code, SPL_TOKEN_SURFACE | SPL_TOKEN_2022_SURFACE)
if matches!(surface_code, SPL_TOKEN_SURFACE | SPL_TOKEN2022_SURFACE)
&& contains(SPL_TOKEN_MULTISIG_ENTRIES, entry_code)
{
return std::option::Option::Some(ProjectionKind::SplTokenMultisig);
}
if surface_code == SPL_TOKEN_2022_SURFACE
&& contains(SPL_TOKEN_2022_EXTENSION_ADMIN_ENTRIES, entry_code)
if surface_code == SPL_TOKEN2022_SURFACE
&& contains(SPL_TOKEN2022_EXTENSION_ADMIN_ENTRIES, entry_code)
{
return std::option::Option::Some(ProjectionKind::SplToken2022ExtensionAdmin);
}
@@ -281,15 +281,15 @@ fn projection(surface_code: &str, entry_code: &str) -> std::option::Option<Proje
return std::option::Option::None;
}
fn program_matches_surface(event: &crate::DecodedProtocolEvent) -> bool {
fn program_matches_surface(event: &crate::MdDecodedProtocolEvent) -> bool {
if event.surface_code.0 == SPL_TOKEN_SURFACE {
return event.program_id.0 == kb_program_ids::SPL_TOKEN_PROGRAM_ID;
}
if event.surface_code.0 == SPL_TOKEN_2022_SURFACE {
return event.program_id.0 == kb_program_ids::SPL_TOKEN_2022_PROGRAM_ID;
if event.surface_code.0 == SPL_TOKEN2022_SURFACE {
return event.program_id.0 == kb_program_ids::SPL_TOKEN2022_PROGRAM_ID;
}
if event.surface_code.0 == SPL_ELGAMAL_REGISTRY_SURFACE {
return event.program_id.0 == kb_program_ids::SPL_TOKEN_2022_ELGAMAL_REGISTRY_PROGRAM_ID;
return event.program_id.0 == kb_program_ids::SPL_TOKEN2022_ELGAMAL_REGISTRY_PROGRAM_ID;
}
return true;
}
@@ -497,21 +497,21 @@ mod tests {
transaction_failed: bool,
accounts: serde_json::Value,
parameters: serde_json::Value,
) -> crate::DecodedObservation {
return crate::DecodedObservation {
) -> crate::DcApiDecodedObservation {
return crate::DcApiDecodedObservation {
event_key: format!("{entry_code}:0"),
event: crate::DecodedProtocolEvent {
signature: crate::Signature("signature".to_string()),
slot: crate::Slot(42),
instruction_path: crate::InstructionPath("0".to_string()),
program_id: crate::ProgramId("program-id".to_string()),
protocol_code: crate::ProtocolCode("solana_native".to_string()),
surface_code: crate::SurfaceCode(surface_code.to_string()),
event_code: crate::EventCode(format!("{surface_code}.{entry_code}")),
event_name: crate::EventName(entry_code.to_string()),
event_family: crate::EventFamily::Admin,
source_kind: crate::EventSourceKind::Instruction,
confidence: crate::DecoderConfidence::ManualExact,
event: crate::MdDecodedProtocolEvent {
signature: crate::MdSignature("signature".to_string()),
slot: crate::MdSlot(42),
instruction_path: crate::MdInstructionPath("0".to_string()),
program_id: crate::MdProgramId("program-id".to_string()),
protocol_code: crate::MdProtocolCode("solana_native".to_string()),
surface_code: crate::MdSurfaceCode(surface_code.to_string()),
event_code: crate::MdEventCode(format!("{surface_code}.{entry_code}")),
event_name: crate::MdEventName(entry_code.to_string()),
event_family: crate::MdEventFamily::Admin,
source_kind: crate::MdEventSourceKind::Instruction,
confidence: crate::MdDecoderConfidence::ManualExact,
},
payload_json: serde_json::json!({"accounts": accounts, "parameters": parameters}),
transaction_failed,
@@ -521,9 +521,9 @@ mod tests {
std::option::Option::None
},
observation_committed: !transaction_failed,
proof: crate::DecoderProof {
kind: crate::DecoderProofKind::Manual,
confidence: crate::DecoderConfidence::ManualExact,
proof: crate::DcApiDecoderProof {
kind: crate::DcApiDecoderProofKind::Manual,
confidence: crate::MdDecoderConfidence::ManualExact,
evidence: std::vec!["fixture".to_string()],
},
};
@@ -535,7 +535,7 @@ mod tests {
#[test]
fn system_assign_creates_owner_projection() {
let materializer = crate::AdminMaterializer;
let materializer = crate::MtAdminMaterializer;
let observation = observation(
super::SYSTEM_SURFACE,
"assign",
@@ -543,16 +543,16 @@ mod tests {
serde_json::json!([account("assigned_account", "account111",)]),
serde_json::json!({"owner": "owner111"}),
);
let result = crate::EventMaterializer::materialize(&materializer, &observation);
assert_eq!(result.status, crate::MaterializerOutcomeStatus::Inserted);
assert_eq!(result.outputs[0].family, crate::MaterializedEventFamily::Admin);
let result = crate::MtApiEventMaterializer::materialize(&materializer, &observation);
assert_eq!(result.status, crate::MtApiMaterializerOutcomeStatus::Inserted);
assert_eq!(result.outputs[0].family, crate::MdMaterializedEventFamily::Admin);
assert_eq!(result.outputs[0].output_key, "system_account_ownership:assign:0");
assert_eq!(result.outputs[0].payload_json["projection"]["newOwner"], "owner111");
}
#[test]
fn seeded_assign_preserves_base_seed_and_owner() {
let materializer = crate::AdminMaterializer;
let materializer = crate::MtAdminMaterializer;
let observation = observation(
super::SYSTEM_SURFACE,
"assign_with_seed",
@@ -563,7 +563,7 @@ mod tests {
]),
serde_json::json!({"owner": "owner111", "seed": "seed"}),
);
let result = crate::EventMaterializer::materialize(&materializer, &observation);
let result = crate::MtApiEventMaterializer::materialize(&materializer, &observation);
assert_eq!(result.outputs[0].payload_json["projection"]["targetAccount"], "derived111");
assert_eq!(result.outputs[0].payload_json["projection"]["baseAccount"], "base111");
assert_eq!(result.outputs[0].payload_json["projection"]["seed"], "seed");
@@ -571,7 +571,7 @@ mod tests {
#[test]
fn config_store_preserves_bounded_opaque_audit_fields() {
let materializer = crate::AdminMaterializer;
let materializer = crate::MtAdminMaterializer;
let observation = observation(
super::CONFIG_SURFACE,
"store",
@@ -589,7 +589,7 @@ mod tests {
"configDataPrefixHex": "0102",
}),
);
let result = crate::EventMaterializer::materialize(&materializer, &observation);
let result = crate::MtApiEventMaterializer::materialize(&materializer, &observation);
assert_eq!(result.outputs[0].output_key, "config_account_update:store:0");
assert_eq!(result.outputs[0].payload_json["projection"]["targetAccount"], "config111");
assert_eq!(result.outputs[0].payload_json["projection"]["configDataSha256"], "abc");
@@ -598,7 +598,7 @@ mod tests {
#[test]
fn loader_authority_variants_create_exact_admin_outputs() {
let materializer = crate::AdminMaterializer;
let materializer = crate::MtAdminMaterializer;
for (surface, entry, target_role) in [
(super::BPF_LOADER_UPGRADEABLE_SURFACE, "set_authority", "authority_target"),
(
@@ -619,8 +619,8 @@ mod tests {
]),
serde_json::json!({}),
);
let result = crate::EventMaterializer::materialize(&materializer, &observation);
assert_eq!(result.status, crate::MaterializerOutcomeStatus::Inserted);
let result = crate::MtApiEventMaterializer::materialize(&materializer, &observation);
assert_eq!(result.status, crate::MtApiMaterializerOutcomeStatus::Inserted);
assert_eq!(result.outputs[0].payload_json["projection"]["targetAccount"], "target111");
assert_eq!(result.outputs[0].payload_json["projection"]["newAuthority"], "new111");
}
@@ -639,9 +639,9 @@ mod tests {
serde_json::json!({"authorityType":"mint_tokens","newAuthority":null}),
);
authority.event.program_id =
crate::ProgramId(kb_program_ids::SPL_TOKEN_PROGRAM_ID.to_string());
crate::MdProgramId(kb_program_ids::SPL_TOKEN_PROGRAM_ID.to_string());
let authority_result =
crate::EventMaterializer::materialize(&crate::AdminMaterializer, &authority);
crate::MtApiEventMaterializer::materialize(&crate::MtAdminMaterializer, &authority);
assert_eq!(authority_result.outputs[0].output_key, "spl_token_authority:set_authority:0");
assert_eq!(
authority_result.outputs[0].payload_json["projection"]["stateTransition"],
@@ -660,9 +660,9 @@ mod tests {
serde_json::json!({"m":2}),
);
multisig.event.program_id =
crate::ProgramId(kb_program_ids::SPL_TOKEN_PROGRAM_ID.to_string());
crate::MdProgramId(kb_program_ids::SPL_TOKEN_PROGRAM_ID.to_string());
let multisig_result =
crate::EventMaterializer::materialize(&crate::AdminMaterializer, &multisig);
crate::MtApiEventMaterializer::materialize(&crate::MtAdminMaterializer, &multisig);
assert_eq!(
multisig_result.outputs[0].output_key,
"spl_token_multisig:initialize_multisig2:0"
@@ -676,9 +676,9 @@ mod tests {
}
#[test]
fn token_2022_authority_change_uses_the_admin_owner_once() {
fn token2022_authority_change_uses_the_admin_owner_once() {
let mut observation = observation(
super::SPL_TOKEN_2022_SURFACE,
super::SPL_TOKEN2022_SURFACE,
"set_authority",
false,
serde_json::json!([
@@ -688,19 +688,20 @@ mod tests {
serde_json::json!({"authorityType":"mint_tokens","newAuthority":null}),
);
observation.event.program_id =
crate::ProgramId(kb_program_ids::SPL_TOKEN_2022_PROGRAM_ID.to_string());
let result = crate::EventMaterializer::materialize(&crate::AdminMaterializer, &observation);
assert_eq!(result.status, crate::MaterializerOutcomeStatus::Inserted);
crate::MdProgramId(kb_program_ids::SPL_TOKEN2022_PROGRAM_ID.to_string());
let result =
crate::MtApiEventMaterializer::materialize(&crate::MtAdminMaterializer, &observation);
assert_eq!(result.status, crate::MtApiMaterializerOutcomeStatus::Inserted);
assert_eq!(result.outputs.len(), 1);
assert_eq!(
result.outputs[0].payload_json["programId"],
kb_program_ids::SPL_TOKEN_2022_PROGRAM_ID
kb_program_ids::SPL_TOKEN2022_PROGRAM_ID
);
}
#[test]
fn failed_admin_mutation_is_refused() {
let materializer = crate::AdminMaterializer;
let materializer = crate::MtAdminMaterializer;
let observation = observation(
super::SYSTEM_SURFACE,
"assign",
@@ -708,8 +709,8 @@ mod tests {
serde_json::json!([account("assigned_account", "account111",)]),
serde_json::json!({"owner": "owner111"}),
);
let result = crate::EventMaterializer::materialize(&materializer, &observation);
assert_eq!(result.status, crate::MaterializerOutcomeStatus::Refused);
let result = crate::MtApiEventMaterializer::materialize(&materializer, &observation);
assert_eq!(result.status, crate::MtApiMaterializerOutcomeStatus::Refused);
assert!(result.outputs.is_empty());
}
@@ -723,20 +724,21 @@ mod tests {
serde_json::json!({}),
);
observation.event.program_id =
crate::ProgramId(kb_program_ids::ASSOCIATED_TOKEN_PROGRAM_ID.to_string());
observation.event.event_family = crate::EventFamily::Lifecycle;
assert!(!crate::EventMaterializer::accepts_observation(
&crate::AdminMaterializer,
crate::MdProgramId(kb_program_ids::ASSOCIATED_TOKEN_PROGRAM_ID.to_string());
observation.event.event_family = crate::MdEventFamily::Lifecycle;
assert!(!crate::MtApiEventMaterializer::accepts_observation(
&crate::MtAdminMaterializer,
&observation,
));
let result = crate::EventMaterializer::materialize(&crate::AdminMaterializer, &observation);
assert_eq!(result.status, crate::MaterializerOutcomeStatus::Ignored);
let result =
crate::MtApiEventMaterializer::materialize(&crate::MtAdminMaterializer, &observation);
assert_eq!(result.status, crate::MtApiMaterializerOutcomeStatus::Ignored);
assert!(result.outputs.is_empty());
}
#[test]
fn unrelated_admin_observation_is_ignored() {
let materializer = crate::AdminMaterializer;
let materializer = crate::MtAdminMaterializer;
let observation = observation(
"other_surface",
"set_authority",
@@ -744,13 +746,13 @@ mod tests {
serde_json::json!([]),
serde_json::json!({}),
);
let result = crate::EventMaterializer::materialize(&materializer, &observation);
assert_eq!(result.status, crate::MaterializerOutcomeStatus::Ignored);
let result = crate::MtApiEventMaterializer::materialize(&materializer, &observation);
assert_eq!(result.status, crate::MtApiMaterializerOutcomeStatus::Ignored);
}
#[test]
fn output_serialization_is_deterministic() {
let materializer = crate::AdminMaterializer;
let materializer = crate::MtAdminMaterializer;
let observation = observation(
super::CONFIG_SURFACE,
"store",
@@ -765,8 +767,8 @@ mod tests {
"configDataPrefixHex": "01",
}),
);
let first = crate::EventMaterializer::materialize(&materializer, &observation);
let second = crate::EventMaterializer::materialize(&materializer, &observation);
let first = crate::MtApiEventMaterializer::materialize(&materializer, &observation);
let second = crate::MtApiEventMaterializer::materialize(&materializer, &observation);
let first_json = match serde_json::to_string(&first) {
std::result::Result::Ok(value) => value,
std::result::Result::Err(error) => panic!("first admin serialization failed: {error}"),
@@ -780,7 +782,7 @@ mod tests {
#[test]
fn elgamal_registry_create_and_update_have_one_admin_owner() {
let materializer = crate::AdminMaterializer;
let materializer = crate::MtAdminMaterializer;
for (operation, offset, owner_position, transition) in [
("create_registry", 1_i64, 1_u64, "elgamal_registry_created"),
("update_registry", 0_i64, 2_u64, "elgamal_registry_key_updated"),
@@ -797,8 +799,8 @@ mod tests {
]),
serde_json::json!({}),
);
observation.event.program_id = crate::ProgramId(
kb_program_ids::SPL_TOKEN_2022_ELGAMAL_REGISTRY_PROGRAM_ID.to_string(),
observation.event.program_id = crate::MdProgramId(
kb_program_ids::SPL_TOKEN2022_ELGAMAL_REGISTRY_PROGRAM_ID.to_string(),
);
observation.payload_json = serde_json::json!({
"accounts": observation.payload_json["accounts"].clone(),
@@ -809,8 +811,8 @@ mod tests {
"relative_instruction_offset"
},
});
let result = crate::EventMaterializer::materialize(&materializer, &observation);
assert_eq!(result.status, crate::MaterializerOutcomeStatus::Inserted);
let result = crate::MtApiEventMaterializer::materialize(&materializer, &observation);
assert_eq!(result.status, crate::MtApiMaterializerOutcomeStatus::Inserted);
assert_eq!(result.outputs.len(), 1);
assert_eq!(
result.outputs[0].payload_json["projection"]["registryAccount"],
@@ -826,7 +828,7 @@ mod tests {
#[test]
fn elgamal_registry_foreign_program_and_failed_transaction_are_refused() {
let materializer = crate::AdminMaterializer;
let materializer = crate::MtAdminMaterializer;
let mut foreign = observation(
super::SPL_ELGAMAL_REGISTRY_SURFACE,
"create_registry",
@@ -835,20 +837,20 @@ mod tests {
serde_json::json!({}),
);
foreign.event.program_id =
crate::ProgramId(kb_program_ids::SPL_TOKEN_2022_PROGRAM_ID.to_string());
assert!(!crate::EventMaterializer::accepts_observation(&materializer, &foreign,));
crate::MdProgramId(kb_program_ids::SPL_TOKEN2022_PROGRAM_ID.to_string());
assert!(!crate::MtApiEventMaterializer::accepts_observation(&materializer, &foreign,));
let mut failed = foreign.clone();
failed.event.program_id = crate::ProgramId(
kb_program_ids::SPL_TOKEN_2022_ELGAMAL_REGISTRY_PROGRAM_ID.to_string(),
failed.event.program_id = crate::MdProgramId(
kb_program_ids::SPL_TOKEN2022_ELGAMAL_REGISTRY_PROGRAM_ID.to_string(),
);
failed.transaction_failed = true;
failed.observation_committed = false;
let result = crate::EventMaterializer::materialize(&materializer, &failed);
assert_eq!(result.status, crate::MaterializerOutcomeStatus::Refused);
let result = crate::MtApiEventMaterializer::materialize(&materializer, &failed);
assert_eq!(result.status, crate::MtApiMaterializerOutcomeStatus::Refused);
}
#[test]
fn token_2022_pointer_and_hook_admin_events_have_one_owner() {
fn token2022_pointer_and_hook_admin_events_have_one_owner() {
for (operation, field, value) in [
("initialize_transfer_hook", "programId", "hook111"),
("update_metadata_pointer", "metadataAddress", "metadata111"),
@@ -856,18 +858,20 @@ mod tests {
("update_group_member_pointer", "memberAddress", "member111"),
] {
let mut observation = observation(
super::SPL_TOKEN_2022_SURFACE,
super::SPL_TOKEN2022_SURFACE,
operation,
false,
serde_json::json!([{"position":0,"accountKey":"mint111"}]),
serde_json::json!({(field):value,"authority":"authority111"}),
);
observation.event.event_family = crate::EventFamily::TokenAccount;
observation.event.event_family = crate::MdEventFamily::TokenAccount;
observation.event.program_id =
crate::ProgramId(kb_program_ids::SPL_TOKEN_2022_PROGRAM_ID.to_string());
let result =
crate::EventMaterializer::materialize(&crate::AdminMaterializer, &observation);
assert_eq!(result.status, crate::MaterializerOutcomeStatus::Inserted);
crate::MdProgramId(kb_program_ids::SPL_TOKEN2022_PROGRAM_ID.to_string());
let result = crate::MtApiEventMaterializer::materialize(
&crate::MtAdminMaterializer,
&observation,
);
assert_eq!(result.status, crate::MtApiMaterializerOutcomeStatus::Inserted);
assert_eq!(result.outputs.len(), 1);
assert_eq!(result.outputs[0].payload_json["projection"][field], value);
assert_eq!(result.outputs[0].payload_json["projection"]["stateReadRequired"], true);
@@ -875,44 +879,45 @@ mod tests {
}
#[test]
fn token_2022_extension_admin_rejects_foreign_program_and_failed_transaction() {
fn token2022_extension_admin_rejects_foreign_program_and_failed_transaction() {
let mut observation = observation(
super::SPL_TOKEN_2022_SURFACE,
super::SPL_TOKEN2022_SURFACE,
"initialize_metadata_pointer",
false,
serde_json::json!([]),
serde_json::json!({"metadataAddress":"metadata111"}),
);
observation.event.event_family = crate::EventFamily::TokenAccount;
observation.event.event_family = crate::MdEventFamily::TokenAccount;
observation.event.program_id =
crate::ProgramId(kb_program_ids::SPL_TOKEN_PROGRAM_ID.to_string());
assert!(!crate::EventMaterializer::accepts_observation(
&crate::AdminMaterializer,
crate::MdProgramId(kb_program_ids::SPL_TOKEN_PROGRAM_ID.to_string());
assert!(!crate::MtApiEventMaterializer::accepts_observation(
&crate::MtAdminMaterializer,
&observation,
));
observation.event.program_id =
crate::ProgramId(kb_program_ids::SPL_TOKEN_2022_PROGRAM_ID.to_string());
crate::MdProgramId(kb_program_ids::SPL_TOKEN2022_PROGRAM_ID.to_string());
observation.transaction_failed = true;
observation.observation_committed = false;
let result = crate::EventMaterializer::materialize(&crate::AdminMaterializer, &observation);
assert_eq!(result.status, crate::MaterializerOutcomeStatus::Refused);
let result =
crate::MtApiEventMaterializer::materialize(&crate::MtAdminMaterializer, &observation);
assert_eq!(result.status, crate::MtApiMaterializerOutcomeStatus::Refused);
}
}
/// Materialize authoritative Token-2022 administrative extension snapshots.
pub fn materialize_token_2022_admin_state_snapshots(
pub fn materialize_token2022_admin_state_snapshots(
account_key: &str,
slot: u64,
state: &crate::Token2022State,
) -> std::result::Result<std::vec::Vec<crate::MaterializedOutput>, String> {
state: &crate::DcToken2022State,
) -> std::result::Result<std::vec::Vec<crate::MtApiMaterializedOutput>, String> {
if account_key.is_empty() {
return std::result::Result::Err(
"Token-2022 admin state snapshot requires a non-empty account key".to_string(),
);
}
if state.kind != crate::Token2022StateKind::Mint {
if state.kind != crate::DcToken2022StateKind::Mint {
for entry in &state.extensions {
if is_token_2022_admin_mint_extension(entry.extension_name) {
if is_token2022_admin_mint_extension(entry.extension_name) {
return std::result::Result::Err(format!(
"Token-2022 {} extension requires mint state",
entry.extension_name
@@ -923,22 +928,22 @@ pub fn materialize_token_2022_admin_state_snapshots(
}
let mut outputs = std::vec::Vec::new();
for entry in &state.extensions {
if !is_token_2022_admin_mint_extension(entry.extension_name) {
if !is_token2022_admin_mint_extension(entry.extension_name) {
continue;
}
let output_key =
format!("token_2022_admin_state:{}:{account_key}:{slot}", entry.extension_name);
outputs.push(crate::MaterializedOutput {
format!("token2022_admin_state:{}:{account_key}:{slot}", entry.extension_name);
outputs.push(crate::MtApiMaterializedOutput {
output_key: output_key.clone(),
family: crate::MaterializedEventFamily::Admin,
family: crate::MdMaterializedEventFamily::Admin,
payload_json: serde_json::json!({
"projectionVersion": 1,
"projectionSemantics": "authoritative_bounded_admin_state_snapshot",
"domain": "token_2022_extension_admin_state",
"domain": "token2022_extension_admin_state",
"idempotenceKey": output_key,
"accountKey": account_key,
"slot": slot,
"programId": kb_program_ids::SPL_TOKEN_2022_PROGRAM_ID,
"programId": kb_program_ids::SPL_TOKEN2022_PROGRAM_ID,
"stateKind": "mint",
"extensionType": entry.extension_type,
"extensionName": entry.extension_name,
@@ -946,9 +951,9 @@ pub fn materialize_token_2022_admin_state_snapshots(
"valueFields": entry.value_fields,
"finalAccountStateCaptured": true,
"provenance": {
"processorName": crate::MATERIALIZER_ADMIN_PROCESSOR_NAME,
"processorName": crate::MT_ADMIN_PROCESSOR_NAME,
"processorVersion": env!("CARGO_PKG_VERSION"),
"source": "exact_token_2022_tlv_state_parser"
"source": "exact_token2022_tlv_state_parser"
}
}),
});
@@ -956,7 +961,7 @@ pub fn materialize_token_2022_admin_state_snapshots(
return std::result::Result::Ok(outputs);
}
fn is_token_2022_admin_mint_extension(extension_name: &str) -> bool {
fn is_token2022_admin_mint_extension(extension_name: &str) -> bool {
return matches!(
extension_name,
"mint_close_authority"
@@ -974,27 +979,27 @@ fn is_token_2022_admin_mint_extension(extension_name: &str) -> bool {
}
#[cfg(test)]
mod token_2022_admin_state_tests {
fn mint_state() -> crate::Token2022State {
return crate::Token2022State {
kind: crate::Token2022StateKind::Mint,
mod token2022_admin_state_tests {
fn mint_state() -> crate::DcToken2022State {
return crate::DcToken2022State {
kind: crate::DcToken2022StateKind::Mint,
base_fields: serde_json::json!({}),
base_hex: String::new(),
account_type: std::option::Option::Some(1),
extensions: vec![
crate::Token2022TlvEntry {
crate::DcToken2022TlvEntry {
extension_type: 6,
extension_name: "default_account_state",
value_hex: "02".to_string(),
value_fields: serde_json::json!({"state": 2}),
},
crate::Token2022TlvEntry {
crate::DcToken2022TlvEntry {
extension_type: 26,
extension_name: "pausable",
value_hex: "00".repeat(33),
value_fields: serde_json::json!({"paused": false}),
},
crate::Token2022TlvEntry {
crate::DcToken2022TlvEntry {
extension_type: 1,
extension_name: "transfer_fee_config",
value_hex: "00".to_string(),
@@ -1007,8 +1012,8 @@ mod token_2022_admin_state_tests {
#[test]
fn admin_snapshots_are_owned_once_filtered_and_idempotent() {
let state = mint_state();
let first = crate::materialize_token_2022_admin_state_snapshots("mint111", 44, &state);
let second = crate::materialize_token_2022_admin_state_snapshots("mint111", 44, &state);
let first = crate::materialize_token2022_admin_state_snapshots("mint111", 44, &state);
let second = crate::materialize_token2022_admin_state_snapshots("mint111", 44, &state);
assert_eq!(first, second);
assert_eq!(first.as_ref().map(std::vec::Vec::len), std::result::Result::Ok(2));
assert_eq!(
@@ -1028,35 +1033,35 @@ mod token_2022_admin_state_tests {
#[test]
fn admin_snapshots_reject_empty_identity_and_wrong_state_kind() {
let state = mint_state();
assert!(crate::materialize_token_2022_admin_state_snapshots("", 44, &state).is_err());
let account_state = crate::Token2022State {
kind: crate::Token2022StateKind::Account,
assert!(crate::materialize_token2022_admin_state_snapshots("", 44, &state).is_err());
let account_state = crate::DcToken2022State {
kind: crate::DcToken2022StateKind::Account,
base_fields: serde_json::json!({}),
base_hex: String::new(),
account_type: std::option::Option::Some(2),
extensions: state.extensions[..1].to_vec(),
};
assert!(
crate::materialize_token_2022_admin_state_snapshots("account111", 44, &account_state,)
crate::materialize_token2022_admin_state_snapshots("account111", 44, &account_state,)
.is_err()
);
}
}
/// Materialize one authoritative SPL ElGamal registry account snapshot.
pub fn materialize_elgamal_registry_state_snapshot(
pub fn materializer_admin_materialize_elgamal_registry_state_snapshot(
registry_account_key: &str,
slot: u64,
state: &crate::ElGamalRegistryState,
) -> std::result::Result<crate::MaterializedOutput, String> {
state: &crate::DcSplElGamalRegistryState,
) -> std::result::Result<crate::MtApiMaterializedOutput, String> {
if registry_account_key.is_empty() {
return std::result::Result::Err(
"SPL ElGamal registry snapshot requires a non-empty account key".to_string(),
);
}
return std::result::Result::Ok(crate::MaterializedOutput {
return std::result::Result::Ok(crate::MtApiMaterializedOutput {
output_key: format!("spl_elgamal_registry_state:{registry_account_key}:{slot}"),
family: crate::MaterializedEventFamily::Admin,
family: crate::MdMaterializedEventFamily::Admin,
payload_json: serde_json::json!({
"projectionVersion": 1,
"domain": "spl_elgamal_registry_account_state",
@@ -1064,7 +1069,7 @@ pub fn materialize_elgamal_registry_state_snapshot(
"idempotenceKey": format!("spl_elgamal_registry:{registry_account_key}:{slot}"),
"registryAccountKey": registry_account_key,
"slot": slot,
"programId": kb_program_ids::SPL_TOKEN_2022_ELGAMAL_REGISTRY_PROGRAM_ID,
"programId": kb_program_ids::SPL_TOKEN2022_ELGAMAL_REGISTRY_PROGRAM_ID,
"owner": state.owner,
"elgamalPubkeyBase64": state.elgamal_pubkey_base64,
"wireHex": state.wire_hex,
@@ -1072,7 +1077,7 @@ pub fn materialize_elgamal_registry_state_snapshot(
"confidentialValuesDecrypted": false,
"finalAccountStateCaptured": true,
"provenance": {
"processorName": crate::MATERIALIZER_ADMIN_PROCESSOR_NAME,
"processorName": crate::MT_ADMIN_PROCESSOR_NAME,
"processorVersion": env!("CARGO_PKG_VERSION"),
"source": "exact_spl_elgamal_registry_state_parser"
}
@@ -1084,12 +1089,16 @@ pub fn materialize_elgamal_registry_state_snapshot(
mod elgamal_registry_state_tests {
#[test]
fn exact_registry_snapshot_preserves_public_state_without_claiming_proof_validation() {
let state = crate::ElGamalRegistryState {
let state = crate::DcSplElGamalRegistryState {
owner: "owner111".to_string(),
elgamal_pubkey_base64: "AQID".to_string(),
wire_hex: "00".repeat(64),
};
let output = crate::materialize_elgamal_registry_state_snapshot("registry111", 11, &state);
let output = crate::materializer_admin_materialize_elgamal_registry_state_snapshot(
"registry111",
11,
&state,
);
assert_eq!(
output.as_ref().map(|value| return value.payload_json["owner"].clone()),
std::result::Result::Ok(serde_json::json!("owner111"))
@@ -1111,14 +1120,25 @@ mod elgamal_registry_state_tests {
#[test]
fn registry_snapshot_is_idempotent_and_requires_account_identity() {
let state = crate::ElGamalRegistryState {
let state = crate::DcSplElGamalRegistryState {
owner: "owner111".to_string(),
elgamal_pubkey_base64: "AQID".to_string(),
wire_hex: "00".repeat(64),
};
let first = crate::materialize_elgamal_registry_state_snapshot("registry111", 11, &state);
let second = crate::materialize_elgamal_registry_state_snapshot("registry111", 11, &state);
let first = crate::materializer_admin_materialize_elgamal_registry_state_snapshot(
"registry111",
11,
&state,
);
let second = crate::materializer_admin_materialize_elgamal_registry_state_snapshot(
"registry111",
11,
&state,
);
assert_eq!(first, second);
assert!(crate::materialize_elgamal_registry_state_snapshot("", 11, &state).is_err());
assert!(
crate::materializer_admin_materialize_elgamal_registry_state_snapshot("", 11, &state)
.is_err()
);
}
}

View File

@@ -1,31 +1,32 @@
// file: kb-lib/src/materializer/api.rs
// version: 2
// version: 3
//! Materializer contracts consolidated from `kb_materializer_api`.
pub mod contracts;
pub mod materializer;
mod contracts;
mod materializer;
/// Stable decoded observation materializer contract used by the common pipeline.
pub use crate::materializer::api::contracts::EventMaterializer;
pub use self::contracts::MtApiEventMaterializer;
/// Explicit policy applied to successful and failed source transactions.
pub use crate::materializer::api::contracts::MaterializationTransactionPolicy;
pub use self::contracts::MtApiMaterializationTransactionPolicy;
/// Stable processor-owned business output derived from one decoded observation.
pub use crate::materializer::api::contracts::MaterializedOutput;
pub use self::contracts::MtApiMaterializedOutput;
/// Structured materializer diagnostic without generic error wrappers.
pub use crate::materializer::api::contracts::MaterializerDiagnostic;
pub use self::contracts::MtApiMaterializerDiagnostic;
/// Complete explicit result produced by one materializer invocation.
pub use crate::materializer::api::contracts::MaterializerExecutionResult;
pub use self::contracts::MtApiMaterializerExecutionResult;
/// Stable identity of one decoded event materializer.
pub use crate::materializer::api::contracts::MaterializerIdentity;
pub use self::contracts::MtApiMaterializerIdentity;
/// Terminal materializer result status.
pub use crate::materializer::api::contracts::MaterializerOutcomeStatus;
pub use self::contracts::MtApiMaterializerOutcomeStatus;
/// Returns true when a materializer explicitly accepts one decoded family.
pub use crate::materializer::api::contracts::materializer_accepts_family;
pub use self::contracts::materializer_api_accepts_family;
/// Returns true when a materializer accepts one exact decoded observation.
pub use crate::materializer::api::contracts::materializer_accepts_observation;
pub use self::contracts::materializer_api_accepts_observation;
/// Applies the mandatory source transaction policy before materialization.
pub use crate::materializer::api::contracts::validate_materialization_policy;
pub use self::contracts::materializer_api_validate_materialization_policy;
/// Rejects mutable business outputs derived from failed or uncommitted observations.
pub use crate::materializer::api::contracts::validate_materialized_output_policy;
pub use self::contracts::materializer_api_validate_materialized_output_policy;
/// Exposes the legacy common business materializer trait.
pub use crate::materializer::api::materializer::Materializer;
pub use self::materializer::MtMaterializer;

View File

@@ -5,14 +5,14 @@
/// Stable identity of one decoded event materializer.
#[derive(Clone, Debug, Eq, PartialEq, serde::Deserialize, serde::Serialize)]
pub struct MaterializerIdentity {
pub struct MtApiMaterializerIdentity {
/// Stable lower snake case processor name.
pub name: std::string::String,
/// Semantic or deterministic implementation version.
pub version: std::string::String,
}
impl MaterializerIdentity {
impl MtApiMaterializerIdentity {
/// Builds a validated materializer identity.
pub fn new(
name: impl std::convert::Into<std::string::String>,
@@ -33,7 +33,7 @@ impl MaterializerIdentity {
/// Explicit policy applied to successful and failed source transactions.
#[derive(Clone, Copy, Debug, Eq, PartialEq, serde::Deserialize, serde::Serialize)]
pub enum MaterializationTransactionPolicy {
pub enum MtApiMaterializationTransactionPolicy {
/// Accept committed observations from successful transactions only.
SuccessfulCommittedOnly,
/// Accept successful observations and failed audit-only observations.
@@ -44,7 +44,7 @@ pub enum MaterializationTransactionPolicy {
/// Terminal materializer result status.
#[derive(Clone, Copy, Debug, Eq, PartialEq, serde::Deserialize, serde::Serialize)]
pub enum MaterializerOutcomeStatus {
pub enum MtApiMaterializerOutcomeStatus {
/// One or more new stable outputs were inserted.
Inserted,
/// One or more processor-owned outputs were deterministically replaced.
@@ -59,7 +59,7 @@ pub enum MaterializerOutcomeStatus {
/// Structured materializer diagnostic without generic error wrappers.
#[derive(Clone, Debug, Eq, PartialEq, serde::Deserialize, serde::Serialize)]
pub struct MaterializerDiagnostic {
pub struct MtApiMaterializerDiagnostic {
/// Stable machine-readable diagnostic code.
pub code: std::string::String,
/// Human-readable diagnostic message.
@@ -68,7 +68,7 @@ pub struct MaterializerDiagnostic {
pub retriable: bool,
}
impl MaterializerDiagnostic {
impl MtApiMaterializerDiagnostic {
/// Builds a validated materializer diagnostic.
pub fn new(
code: impl std::convert::Into<std::string::String>,
@@ -91,16 +91,16 @@ impl MaterializerDiagnostic {
/// Stable processor-owned business output derived from one decoded observation.
#[derive(Clone, Debug, PartialEq, serde::Deserialize, serde::Serialize)]
pub struct MaterializedOutput {
pub struct MtApiMaterializedOutput {
/// Stable processor-owned output key within one decoded observation.
pub output_key: std::string::String,
/// Business output family.
pub family: crate::MaterializedEventFamily,
pub family: crate::MdMaterializedEventFamily,
/// Typed business payload serialized as deterministic JSON.
pub payload_json: serde_json::Value,
}
impl MaterializedOutput {
impl MtApiMaterializedOutput {
/// Validates stable output identity.
pub fn validate(&self) -> kb_core::Result<()> {
if self.output_key.trim().is_empty() {
@@ -114,22 +114,22 @@ impl MaterializedOutput {
/// Complete explicit result produced by one materializer invocation.
#[derive(Clone, Debug, PartialEq, serde::Deserialize, serde::Serialize)]
pub struct MaterializerExecutionResult {
pub struct MtApiMaterializerExecutionResult {
/// Terminal materializer status.
pub status: MaterializerOutcomeStatus,
pub status: MtApiMaterializerOutcomeStatus,
/// Stable business outputs.
pub outputs: std::vec::Vec<MaterializedOutput>,
pub outputs: std::vec::Vec<MtApiMaterializedOutput>,
/// Structured diagnostics.
pub diagnostics: std::vec::Vec<MaterializerDiagnostic>,
pub diagnostics: std::vec::Vec<MtApiMaterializerDiagnostic>,
}
impl MaterializerExecutionResult {
impl MtApiMaterializerExecutionResult {
/// Builds a policy refusal result.
pub fn refused(code: &str, message: &str) -> Self {
return Self {
status: MaterializerOutcomeStatus::Refused,
status: MtApiMaterializerOutcomeStatus::Refused,
outputs: std::vec::Vec::new(),
diagnostics: std::vec![MaterializerDiagnostic {
diagnostics: std::vec![MtApiMaterializerDiagnostic {
code: code.to_string(),
message: message.to_string(),
retriable: false,
@@ -140,7 +140,7 @@ impl MaterializerExecutionResult {
/// Builds an ignored result.
pub fn ignored() -> Self {
return Self {
status: MaterializerOutcomeStatus::Ignored,
status: MtApiMaterializerOutcomeStatus::Ignored,
outputs: std::vec::Vec::new(),
diagnostics: std::vec::Vec::new(),
};
@@ -148,8 +148,8 @@ impl MaterializerExecutionResult {
/// Validates status and output consistency.
pub fn validate(&self) -> kb_core::Result<()> {
let output_status = self.status == MaterializerOutcomeStatus::Inserted
|| self.status == MaterializerOutcomeStatus::Replaced;
let output_status = self.status == MtApiMaterializerOutcomeStatus::Inserted
|| self.status == MtApiMaterializerOutcomeStatus::Replaced;
if output_status && self.outputs.is_empty() {
return std::result::Result::Err(kb_core::Error::invalid_state(
"inserted or replaced materializer status requires outputs",
@@ -160,7 +160,7 @@ impl MaterializerExecutionResult {
"ignored, refused or failed materializer status must not contain outputs",
));
}
if self.status == MaterializerOutcomeStatus::Failed && self.diagnostics.is_empty() {
if self.status == MtApiMaterializerOutcomeStatus::Failed && self.diagnostics.is_empty() {
return std::result::Result::Err(kb_core::Error::invalid_state(
"failed materializer status requires at least one diagnostic",
));
@@ -176,26 +176,32 @@ impl MaterializerExecutionResult {
}
/// Stable decoded observation materializer contract used by the common pipeline.
pub trait EventMaterializer: std::marker::Send + std::marker::Sync {
pub trait MtApiEventMaterializer: std::marker::Send + std::marker::Sync {
/// Returns the stable materializer identity.
fn identity(&self) -> MaterializerIdentity;
fn identity(&self) -> MtApiMaterializerIdentity;
/// Returns every decoded event family explicitly accepted by this materializer.
fn accepted_families(&self) -> &'static [crate::EventFamily];
fn accepted_families(&self) -> &'static [crate::MdEventFamily];
/// Returns whether this materializer accepts one exact decoded observation.
fn accepts_observation(&self, observation: &crate::DecodedObservation) -> bool {
fn accepts_observation(&self, observation: &crate::DcApiDecodedObservation) -> bool {
let family = observation.event.event_family;
return self.accepted_families().iter().any(|accepted| return *accepted == family);
}
/// Returns the failed/successful transaction policy for one accepted family.
fn transaction_policy(&self, family: crate::EventFamily) -> MaterializationTransactionPolicy;
fn transaction_policy(
&self,
family: crate::MdEventFamily,
) -> MtApiMaterializationTransactionPolicy;
/// Materializes one validated decoded observation.
fn materialize(&self, observation: &crate::DecodedObservation) -> MaterializerExecutionResult;
fn materialize(
&self,
observation: &crate::DcApiDecodedObservation,
) -> MtApiMaterializerExecutionResult;
}
/// Returns true when a materializer explicitly accepts one decoded family.
pub fn materializer_accepts_family(
materializer: &dyn EventMaterializer,
family: crate::EventFamily,
pub fn materializer_api_accepts_family(
materializer: &dyn MtApiEventMaterializer,
family: crate::MdEventFamily,
) -> bool {
return materializer
.accepted_families()
@@ -204,48 +210,48 @@ pub fn materializer_accepts_family(
}
/// Returns true when a materializer accepts one exact decoded observation.
pub fn materializer_accepts_observation(
materializer: &dyn EventMaterializer,
observation: &crate::DecodedObservation,
pub fn materializer_api_accepts_observation(
materializer: &dyn MtApiEventMaterializer,
observation: &crate::DcApiDecodedObservation,
) -> bool {
return materializer.accepts_observation(observation);
}
/// Applies the mandatory source transaction policy before materialization.
pub fn validate_materialization_policy(
materializer: &dyn EventMaterializer,
observation: &crate::DecodedObservation,
) -> std::result::Result<(), MaterializerExecutionResult> {
pub fn materializer_api_validate_materialization_policy(
materializer: &dyn MtApiEventMaterializer,
observation: &crate::DcApiDecodedObservation,
) -> std::result::Result<(), MtApiMaterializerExecutionResult> {
let family = observation.event.event_family;
if !crate::materializer_accepts_observation(materializer, observation) {
return std::result::Result::Err(MaterializerExecutionResult::refused(
if !crate::materializer_api_accepts_observation(materializer, observation) {
return std::result::Result::Err(MtApiMaterializerExecutionResult::refused(
"unsupported_decoded_observation",
"materializer does not accept the decoded observation surface and entry",
));
}
let policy = materializer.transaction_policy(family);
if observation.transaction_failed || !observation.observation_committed {
if family == crate::EventFamily::Trade
|| family == crate::EventFamily::Liquidity
|| family == crate::EventFamily::Lifecycle
if family == crate::MdEventFamily::Trade
|| family == crate::MdEventFamily::Liquidity
|| family == crate::MdEventFamily::Lifecycle
{
return std::result::Result::Err(MaterializerExecutionResult::refused(
return std::result::Result::Err(MtApiMaterializerExecutionResult::refused(
"failed_transaction_mutation_refused",
"failed or uncommitted observations cannot create successful mutable business outputs",
));
}
if policy == MaterializationTransactionPolicy::SuccessfulCommittedOnly {
return std::result::Result::Err(MaterializerExecutionResult::refused(
if policy == MtApiMaterializationTransactionPolicy::SuccessfulCommittedOnly {
return std::result::Result::Err(MtApiMaterializerExecutionResult::refused(
"failed_transaction_policy_refused",
"materializer accepts committed observations from successful transactions only",
));
}
if policy == MaterializationTransactionPolicy::SuccessfulOrFailedAudit
&& family != crate::EventFamily::Audit
&& family != crate::EventFamily::ComplianceAudit
&& family != crate::EventFamily::Risk
if policy == MtApiMaterializationTransactionPolicy::SuccessfulOrFailedAudit
&& family != crate::MdEventFamily::Audit
&& family != crate::MdEventFamily::ComplianceAudit
&& family != crate::MdEventFamily::Risk
{
return std::result::Result::Err(MaterializerExecutionResult::refused(
return std::result::Result::Err(MtApiMaterializerExecutionResult::refused(
"failed_transaction_non_audit_refused",
"failed transaction materialization is limited to declared audit or risk families",
));
@@ -255,20 +261,20 @@ pub fn validate_materialization_policy(
}
/// Rejects successful mutable business outputs derived from failed or uncommitted observations.
pub fn validate_materialized_output_policy(
observation: &crate::DecodedObservation,
result: &MaterializerExecutionResult,
) -> std::result::Result<(), MaterializerExecutionResult> {
pub fn materializer_api_validate_materialized_output_policy(
observation: &crate::DcApiDecodedObservation,
result: &MtApiMaterializerExecutionResult,
) -> std::result::Result<(), MtApiMaterializerExecutionResult> {
if !observation.transaction_failed && observation.observation_committed {
return std::result::Result::Ok(());
}
let forbidden_output = result.outputs.iter().any(|output| {
return output.family != crate::MaterializedEventFamily::ComplianceAudit
&& output.family != crate::MaterializedEventFamily::TokenMetadataRisk
&& output.family != crate::MaterializedEventFamily::Risk;
return output.family != crate::MdMaterializedEventFamily::ComplianceAudit
&& output.family != crate::MdMaterializedEventFamily::TokenMetadataRisk
&& output.family != crate::MdMaterializedEventFamily::Risk;
});
if forbidden_output {
return std::result::Result::Err(MaterializerExecutionResult::refused(
return std::result::Result::Err(MtApiMaterializerExecutionResult::refused(
"failed_transaction_output_refused",
"failed or uncommitted observations may only produce audit or risk outputs",
));
@@ -280,48 +286,48 @@ pub fn validate_materialized_output_policy(
mod tests {
struct TradeMaterializer;
impl crate::EventMaterializer for TradeMaterializer {
fn identity(&self) -> crate::MaterializerIdentity {
return crate::MaterializerIdentity {
impl crate::MtApiEventMaterializer for TradeMaterializer {
fn identity(&self) -> crate::MtApiMaterializerIdentity {
return crate::MtApiMaterializerIdentity {
name: "trade_materializer".to_string(),
version: "1".to_string(),
};
}
fn accepted_families(&self) -> &'static [crate::EventFamily] {
return &[crate::EventFamily::Trade];
fn accepted_families(&self) -> &'static [crate::MdEventFamily] {
return &[crate::MdEventFamily::Trade];
}
fn transaction_policy(
&self,
_family: crate::EventFamily,
) -> crate::MaterializationTransactionPolicy {
return crate::MaterializationTransactionPolicy::SuccessfulCommittedOnly;
_family: crate::MdEventFamily,
) -> crate::MtApiMaterializationTransactionPolicy {
return crate::MtApiMaterializationTransactionPolicy::SuccessfulCommittedOnly;
}
fn materialize(
&self,
_observation: &crate::DecodedObservation,
) -> crate::MaterializerExecutionResult {
return crate::MaterializerExecutionResult::ignored();
_observation: &crate::DcApiDecodedObservation,
) -> crate::MtApiMaterializerExecutionResult {
return crate::MtApiMaterializerExecutionResult::ignored();
}
}
fn failed_trade_observation() -> crate::DecodedObservation {
return crate::DecodedObservation {
fn failed_trade_observation() -> crate::DcApiDecodedObservation {
return crate::DcApiDecodedObservation {
event_key: "trade".to_string(),
event: crate::DecodedProtocolEvent {
signature: crate::Signature("signature".to_string()),
slot: crate::Slot(42),
instruction_path: crate::InstructionPath("0".to_string()),
program_id: crate::ProgramId("program".to_string()),
protocol_code: crate::ProtocolCode("protocol".to_string()),
surface_code: crate::SurfaceCode("surface".to_string()),
event_code: crate::EventCode("trade".to_string()),
event_name: crate::EventName("trade".to_string()),
event_family: crate::EventFamily::Trade,
source_kind: crate::EventSourceKind::Instruction,
confidence: crate::DecoderConfidence::Exact,
event: crate::MdDecodedProtocolEvent {
signature: crate::MdSignature("signature".to_string()),
slot: crate::MdSlot(42),
instruction_path: crate::MdInstructionPath("0".to_string()),
program_id: crate::MdProgramId("program".to_string()),
protocol_code: crate::MdProtocolCode("protocol".to_string()),
surface_code: crate::MdSurfaceCode("surface".to_string()),
event_code: crate::MdEventCode("trade".to_string()),
event_name: crate::MdEventName("trade".to_string()),
event_family: crate::MdEventFamily::Trade,
source_kind: crate::MdEventSourceKind::Instruction,
confidence: crate::MdDecoderConfidence::Exact,
},
payload_json: serde_json::json!({}),
transaction_failed: true,
@@ -329,9 +335,9 @@ mod tests {
serde_json::json!({"InstructionError": [0, "Custom"]}),
),
observation_committed: false,
proof: crate::DecoderProof {
kind: crate::DecoderProofKind::ExactLayout,
confidence: crate::DecoderConfidence::Exact,
proof: crate::DcApiDecoderProof {
kind: crate::DcApiDecoderProofKind::ExactLayout,
confidence: crate::MdDecoderConfidence::Exact,
evidence: std::vec!["layout".to_string()],
},
};
@@ -339,47 +345,49 @@ mod tests {
#[test]
fn failed_trade_is_refused_before_materialization() {
let result =
crate::validate_materialization_policy(&TradeMaterializer, &failed_trade_observation());
let result = crate::materializer_api_validate_materialization_policy(
&TradeMaterializer,
&failed_trade_observation(),
);
assert!(result.is_err());
let refusal = match result {
std::result::Result::Ok(()) => panic!("failed trade unexpectedly accepted"),
std::result::Result::Err(value) => value,
};
assert_eq!(refusal.status, crate::MaterializerOutcomeStatus::Refused);
assert_eq!(refusal.status, crate::MtApiMaterializerOutcomeStatus::Refused);
assert_eq!(refusal.diagnostics[0].code, "failed_transaction_mutation_refused");
}
struct FailedAuditToTradeMaterializer;
impl crate::EventMaterializer for FailedAuditToTradeMaterializer {
fn identity(&self) -> crate::MaterializerIdentity {
return crate::MaterializerIdentity {
impl crate::MtApiEventMaterializer for FailedAuditToTradeMaterializer {
fn identity(&self) -> crate::MtApiMaterializerIdentity {
return crate::MtApiMaterializerIdentity {
name: "failed_audit_to_trade".to_string(),
version: "1".to_string(),
};
}
fn accepted_families(&self) -> &'static [crate::EventFamily] {
return &[crate::EventFamily::Audit];
fn accepted_families(&self) -> &'static [crate::MdEventFamily] {
return &[crate::MdEventFamily::Audit];
}
fn transaction_policy(
&self,
_family: crate::EventFamily,
) -> crate::MaterializationTransactionPolicy {
return crate::MaterializationTransactionPolicy::SuccessfulOrFailedAudit;
_family: crate::MdEventFamily,
) -> crate::MtApiMaterializationTransactionPolicy {
return crate::MtApiMaterializationTransactionPolicy::SuccessfulOrFailedAudit;
}
fn materialize(
&self,
_observation: &crate::DecodedObservation,
) -> crate::MaterializerExecutionResult {
return crate::MaterializerExecutionResult {
status: crate::MaterializerOutcomeStatus::Inserted,
outputs: std::vec![crate::MaterializedOutput {
_observation: &crate::DcApiDecodedObservation,
) -> crate::MtApiMaterializerExecutionResult {
return crate::MtApiMaterializerExecutionResult {
status: crate::MtApiMaterializerOutcomeStatus::Inserted,
outputs: std::vec![crate::MtApiMaterializedOutput {
output_key: "trade".to_string(),
family: crate::MaterializedEventFamily::Trade,
family: crate::MdMaterializedEventFamily::Trade,
payload_json: serde_json::json!({}),
}],
diagnostics: std::vec::Vec::new(),
@@ -387,9 +395,9 @@ mod tests {
}
}
fn failed_audit_observation() -> crate::DecodedObservation {
fn failed_audit_observation() -> crate::DcApiDecodedObservation {
let mut observation = failed_trade_observation();
observation.event.event_family = crate::EventFamily::Audit;
observation.event.event_family = crate::MdEventFamily::Audit;
return observation;
}
@@ -397,16 +405,20 @@ mod tests {
fn failed_audit_cannot_emit_successful_trade_output() {
let materializer = FailedAuditToTradeMaterializer;
let observation = failed_audit_observation();
let policy_result = crate::validate_materialization_policy(&materializer, &observation);
let policy_result =
crate::materializer_api_validate_materialization_policy(&materializer, &observation);
assert!(policy_result.is_ok());
let materialized = crate::EventMaterializer::materialize(&materializer, &observation);
let output_result = crate::validate_materialized_output_policy(&observation, &materialized);
let materialized = crate::MtApiEventMaterializer::materialize(&materializer, &observation);
let output_result = crate::materializer_api_validate_materialized_output_policy(
&observation,
&materialized,
);
assert!(output_result.is_err());
let refusal = match output_result {
std::result::Result::Ok(()) => panic!("failed audit unexpectedly emitted a trade"),
std::result::Result::Err(value) => value,
};
assert_eq!(refusal.status, crate::MaterializerOutcomeStatus::Refused);
assert_eq!(refusal.status, crate::MtApiMaterializerOutcomeStatus::Refused);
assert_eq!(refusal.diagnostics[0].code, "failed_transaction_output_refused");
}
}

View File

@@ -4,7 +4,7 @@
//! Materializer API contract used by business materializer crates.
/// Common contract implemented by all business materializer crates.
pub trait Materializer {
pub trait MtMaterializer {
/// Returns the stable materializer name.
fn materializer_name(&self) -> &'static str;
@@ -12,11 +12,11 @@ pub trait Materializer {
fn materializer_version(&self) -> &'static str;
/// Tests whether this materializer accepts a decoded event.
fn accepts_event(&self, event: &crate::DecodedProtocolEvent) -> bool;
fn accepts_event(&self, event: &crate::MdDecodedProtocolEvent) -> bool;
/// Materializes a decoded event into zero or more business events.
fn materialize_event(
&self,
event: &crate::DecodedProtocolEvent,
) -> kb_core::Result<std::vec::Vec<crate::MaterializedEvent>>;
event: &crate::MdDecodedProtocolEvent,
) -> kb_core::Result<std::vec::Vec<crate::MdMaterializedEvent>>;
}

View File

@@ -1,6 +1,6 @@
// file: kb-lib/src/materializer/bridge.rs
// version: 1
// version: 2
//! `bridge` materializer family.
pub mod core;
mod core;

View File

@@ -1,10 +1,10 @@
// file: kb-lib/src/materializer/bridge/core.rs
// version: 1
// version: 2
//! Migration boundary for legacy crate `kb_materializer_bridge`.
/// Legacy crate name retained for migration and compatibility tracking.
pub const LEGACY_CRATE: &str = "kb_materializer_bridge";
const LEGACY_CRATE: &str = "kb_materializer_bridge";
/// Current porting status.
pub const MIGRATION_STATUS: &str = "source-preserved-pending-port";
const MIGRATION_STATUS: &str = "source-preserved-pending-port";

View File

@@ -1,12 +1,13 @@
// file: kb-lib/src/materializer/compliance.rs
// version: 2
// version: 3
//! `compliance` materializer family.
pub mod audit;
mod audit;
mod constants;
pub(crate) use crate::materializer::compliance::constants::TRACING_TARGET as MATERIALIZER_COMPLIANCE_AUDIT_TRACING_TARGET;
/// Canonical tracing target for the native compliance audit materializer.
pub(crate) use self::constants::MT_COMPLIANCE_AUDIT_TRACING_TARGET;
/// Stable native compliance audit materializer.
pub use crate::materializer::compliance::audit::ComplianceAuditMaterializer;
pub use self::audit::MtComplianceAuditMaterializer;

View File

@@ -3,7 +3,7 @@
//! Stable compliance-audit projections for committed native bytecode mutations and execution profiles.
const ACCEPTED_FAMILIES: &[crate::EventFamily] = &[crate::EventFamily::Audit];
const ACCEPTED_FAMILIES: &[crate::MdEventFamily] = &[crate::MdEventFamily::Audit];
const COMPUTE_BUDGET_SURFACE: &str = "solana_native_compute_budget";
const COMPUTE_BUDGET_ENTRIES: &[&str] = &[
"request_units_deprecated",
@@ -47,9 +47,9 @@ impl ProjectionKind {
/// Stable compliance-audit materializer for native bytecode mutations and execution profiles.
#[derive(Clone, Debug, Default)]
pub struct ComplianceAuditMaterializer;
pub struct MtComplianceAuditMaterializer;
impl crate::Materializer for crate::ComplianceAuditMaterializer {
impl crate::MtMaterializer for crate::MtComplianceAuditMaterializer {
fn materializer_name(&self) -> &'static str {
return "kb_materializer_compliance_audit";
}
@@ -58,36 +58,36 @@ impl crate::Materializer for crate::ComplianceAuditMaterializer {
return env!("CARGO_PKG_VERSION");
}
fn accepts_event(&self, event: &crate::DecodedProtocolEvent) -> bool {
return event.event_family == crate::EventFamily::Audit
fn accepts_event(&self, event: &crate::MdDecodedProtocolEvent) -> bool {
return event.event_family == crate::MdEventFamily::Audit
&& projection(event.surface_code.0.as_str(), event.event_name.0.as_str()).is_some();
}
fn materialize_event(
&self,
event: &crate::DecodedProtocolEvent,
) -> kb_core::Result<std::vec::Vec<crate::MaterializedEvent>> {
if !crate::Materializer::accepts_event(self, event) {
event: &crate::MdDecodedProtocolEvent,
) -> kb_core::Result<std::vec::Vec<crate::MdMaterializedEvent>> {
if !crate::MtMaterializer::accepts_event(self, event) {
return std::result::Result::Ok(std::vec::Vec::new());
}
return std::result::Result::Ok(std::vec::Vec::new());
}
}
impl crate::EventMaterializer for crate::ComplianceAuditMaterializer {
fn identity(&self) -> crate::MaterializerIdentity {
return crate::MaterializerIdentity {
impl crate::MtApiEventMaterializer for crate::MtComplianceAuditMaterializer {
fn identity(&self) -> crate::MtApiMaterializerIdentity {
return crate::MtApiMaterializerIdentity {
name: "solana_native_compliance_audit".to_string(),
version: env!("CARGO_PKG_VERSION").to_string(),
};
}
fn accepted_families(&self) -> &'static [crate::EventFamily] {
fn accepted_families(&self) -> &'static [crate::MdEventFamily] {
return ACCEPTED_FAMILIES;
}
fn accepts_observation(&self, observation: &crate::DecodedObservation) -> bool {
return observation.event.event_family == crate::EventFamily::Audit
fn accepts_observation(&self, observation: &crate::DcApiDecodedObservation) -> bool {
return observation.event.event_family == crate::MdEventFamily::Audit
&& projection(
observation.event.surface_code.0.as_str(),
observation.event.event_name.0.as_str(),
@@ -97,15 +97,15 @@ impl crate::EventMaterializer for crate::ComplianceAuditMaterializer {
fn transaction_policy(
&self,
_family: crate::EventFamily,
) -> crate::MaterializationTransactionPolicy {
return crate::MaterializationTransactionPolicy::SuccessfulOrFailedAudit;
_family: crate::MdEventFamily,
) -> crate::MtApiMaterializationTransactionPolicy {
return crate::MtApiMaterializationTransactionPolicy::SuccessfulOrFailedAudit;
}
fn materialize(
&self,
observation: &crate::DecodedObservation,
) -> crate::MaterializerExecutionResult {
observation: &crate::DcApiDecodedObservation,
) -> crate::MtApiMaterializerExecutionResult {
let projection = match projection(
observation.event.surface_code.0.as_str(),
observation.event.event_name.0.as_str(),
@@ -113,24 +113,24 @@ impl crate::EventMaterializer for crate::ComplianceAuditMaterializer {
std::option::Option::Some(value) => value,
std::option::Option::None => {
tracing::debug!(
target: crate::MATERIALIZER_COMPLIANCE_AUDIT_TRACING_TARGET,
target: crate::MT_COMPLIANCE_AUDIT_TRACING_TARGET,
action = "materialize_compliance_audit",
surface_code = %observation.event.surface_code.0.as_str(),
entry_code = %observation.event.event_name.0.as_str(),
accepted = false,
"ignore observation outside supported native compliance-audit projections"
);
return crate::MaterializerExecutionResult::ignored();
return crate::MtApiMaterializerExecutionResult::ignored();
},
};
if observation.event.event_family != crate::EventFamily::Audit {
return crate::MaterializerExecutionResult::ignored();
if observation.event.event_family != crate::MdEventFamily::Audit {
return crate::MtApiMaterializerExecutionResult::ignored();
}
if projection != ProjectionKind::ComputeBudgetProfile
&& (observation.transaction_failed || !observation.observation_committed)
{
tracing::debug!(
target: crate::MATERIALIZER_COMPLIANCE_AUDIT_TRACING_TARGET,
target: crate::MT_COMPLIANCE_AUDIT_TRACING_TARGET,
action = "materialize_compliance_audit",
surface_code = %observation.event.surface_code.0.as_str(),
entry_code = %observation.event.event_name.0.as_str(),
@@ -138,7 +138,7 @@ impl crate::EventMaterializer for crate::ComplianceAuditMaterializer {
transaction_failed = observation.transaction_failed,
"refuse uncommitted native bytecode mutation observation"
);
return crate::MaterializerExecutionResult::refused(
return crate::MtApiMaterializerExecutionResult::refused(
"failed_transaction_compliance_audit_refused",
"failed or uncommitted native observations cannot create committed bytecode mutation outputs",
);
@@ -157,10 +157,10 @@ impl crate::EventMaterializer for crate::ComplianceAuditMaterializer {
if projection == ProjectionKind::ComputeBudgetProfile {
return materialize_compute_budget_profile(observation, parameters, operation);
}
let output = crate::MaterializedOutput {
let output = crate::MtApiMaterializedOutput {
output_key: projection
.output_key(observation.event.surface_code.0.as_str(), operation.as_str()),
family: crate::MaterializedEventFamily::ComplianceAudit,
family: crate::MdMaterializedEventFamily::ComplianceAudit,
payload_json: serde_json::json!({
"projectionVersion": 1,
"projectionSemantics": "committed_instruction_compliance_audit_event",
@@ -178,7 +178,7 @@ impl crate::EventMaterializer for crate::ComplianceAuditMaterializer {
}),
};
tracing::debug!(
target: crate::MATERIALIZER_COMPLIANCE_AUDIT_TRACING_TARGET,
target: crate::MT_COMPLIANCE_AUDIT_TRACING_TARGET,
action = "materialize_compliance_audit",
surface_code = %observation.event.surface_code.0.as_str(),
entry_code = %observation.event.event_name.0.as_str(),
@@ -186,8 +186,8 @@ impl crate::EventMaterializer for crate::ComplianceAuditMaterializer {
output_count = 1_usize,
"materialize committed native bytecode mutation observation"
);
return crate::MaterializerExecutionResult {
status: crate::MaterializerOutcomeStatus::Inserted,
return crate::MtApiMaterializerExecutionResult {
status: crate::MtApiMaterializerOutcomeStatus::Inserted,
outputs: std::vec![output],
diagnostics: std::vec::Vec::new(),
};
@@ -195,17 +195,17 @@ impl crate::EventMaterializer for crate::ComplianceAuditMaterializer {
}
fn materialize_compute_budget_profile(
observation: &crate::DecodedObservation,
observation: &crate::DcApiDecodedObservation,
parameters: serde_json::Value,
operation: std::string::String,
) -> crate::MaterializerExecutionResult {
) -> crate::MtApiMaterializerExecutionResult {
let profile = parameters.get("transactionProfile").cloned().unwrap_or(serde_json::Value::Null);
if !compute_budget_profile_emits(&profile) {
return crate::MaterializerExecutionResult::ignored();
return crate::MtApiMaterializerExecutionResult::ignored();
}
let output = crate::MaterializedOutput {
let output = crate::MtApiMaterializedOutput {
output_key: "transaction_compute_budget_profile:0".to_string(),
family: crate::MaterializedEventFamily::ComplianceAudit,
family: crate::MdMaterializedEventFamily::ComplianceAudit,
payload_json: serde_json::json!({
"projectionVersion": 1,
"projectionSemantics": "transaction_compute_budget_profile",
@@ -225,7 +225,7 @@ fn materialize_compute_budget_profile(
}),
};
tracing::debug!(
target: crate::MATERIALIZER_COMPLIANCE_AUDIT_TRACING_TARGET,
target: crate::MT_COMPLIANCE_AUDIT_TRACING_TARGET,
action = "materialize_compute_budget_profile",
surface_code = %observation.event.surface_code.0.as_str(),
entry_code = %observation.event.event_name.0.as_str(),
@@ -233,8 +233,8 @@ fn materialize_compute_budget_profile(
transaction_failed = observation.transaction_failed,
"materialize native Compute Budget transaction profile"
);
return crate::MaterializerExecutionResult {
status: crate::MaterializerOutcomeStatus::Inserted,
return crate::MtApiMaterializerExecutionResult {
status: crate::MtApiMaterializerOutcomeStatus::Inserted,
outputs: std::vec![output],
diagnostics: std::vec::Vec::new(),
};
@@ -349,21 +349,21 @@ mod tests {
transaction_failed: bool,
accounts: serde_json::Value,
parameters: serde_json::Value,
) -> crate::DecodedObservation {
return crate::DecodedObservation {
) -> crate::DcApiDecodedObservation {
return crate::DcApiDecodedObservation {
event_key: format!("{entry_code}:0"),
event: crate::DecodedProtocolEvent {
signature: crate::Signature("signature".to_string()),
slot: crate::Slot(42),
instruction_path: crate::InstructionPath("0".to_string()),
program_id: crate::ProgramId("program-id".to_string()),
protocol_code: crate::ProtocolCode("solana_native".to_string()),
surface_code: crate::SurfaceCode(surface_code.to_string()),
event_code: crate::EventCode(format!("{surface_code}.{entry_code}")),
event_name: crate::EventName(entry_code.to_string()),
event_family: crate::EventFamily::Audit,
source_kind: crate::EventSourceKind::Instruction,
confidence: crate::DecoderConfidence::ManualExact,
event: crate::MdDecodedProtocolEvent {
signature: crate::MdSignature("signature".to_string()),
slot: crate::MdSlot(42),
instruction_path: crate::MdInstructionPath("0".to_string()),
program_id: crate::MdProgramId("program-id".to_string()),
protocol_code: crate::MdProtocolCode("solana_native".to_string()),
surface_code: crate::MdSurfaceCode(surface_code.to_string()),
event_code: crate::MdEventCode(format!("{surface_code}.{entry_code}")),
event_name: crate::MdEventName(entry_code.to_string()),
event_family: crate::MdEventFamily::Audit,
source_kind: crate::MdEventSourceKind::Instruction,
confidence: crate::MdDecoderConfidence::ManualExact,
},
payload_json: serde_json::json!({"accounts": accounts, "parameters": parameters}),
transaction_failed,
@@ -373,9 +373,9 @@ mod tests {
std::option::Option::None
},
observation_committed: !transaction_failed,
proof: crate::DecoderProof {
kind: crate::DecoderProofKind::Manual,
confidence: crate::DecoderConfidence::ManualExact,
proof: crate::DcApiDecoderProof {
kind: crate::DcApiDecoderProofKind::Manual,
confidence: crate::MdDecoderConfidence::ManualExact,
evidence: std::vec!["fixture".to_string()],
},
};
@@ -387,7 +387,7 @@ mod tests {
#[test]
fn loader_write_variants_create_bounded_code_audits() {
let materializer = crate::ComplianceAuditMaterializer;
let materializer = crate::MtComplianceAuditMaterializer;
for (surface, target_role, authority_role) in [
(super::BPF_LOADER_DEPRECATED_SURFACE, "program_account", "missing"),
(super::BPF_LOADER_SURFACE, "program_account", "missing"),
@@ -409,9 +409,9 @@ mod tests {
"bytesPrefixHex": "0102",
}),
);
let result = crate::EventMaterializer::materialize(&materializer, &observation);
assert_eq!(result.status, crate::MaterializerOutcomeStatus::Inserted);
assert_eq!(result.outputs[0].family, crate::MaterializedEventFamily::ComplianceAudit);
let result = crate::MtApiEventMaterializer::materialize(&materializer, &observation);
assert_eq!(result.status, crate::MtApiMaterializerOutcomeStatus::Inserted);
assert_eq!(result.outputs[0].family, crate::MdMaterializedEventFamily::ComplianceAudit);
assert_eq!(result.outputs[0].payload_json["projection"]["targetAccount"], "target111");
assert_eq!(result.outputs[0].payload_json["projection"]["bytesSha256"], "abc");
assert_eq!(
@@ -423,7 +423,7 @@ mod tests {
#[test]
fn loader_v4_copy_preserves_source_and_bounded_ranges() {
let materializer = crate::ComplianceAuditMaterializer;
let materializer = crate::MtComplianceAuditMaterializer;
let observation = observation(
super::LOADER_V4_SURFACE,
"copy",
@@ -435,7 +435,7 @@ mod tests {
]),
serde_json::json!({"destinationOffset": 8, "sourceOffset": 4, "length": 16}),
);
let result = crate::EventMaterializer::materialize(&materializer, &observation);
let result = crate::MtApiEventMaterializer::materialize(&materializer, &observation);
assert_eq!(
result.outputs[0].output_key,
"program_loader_code:solana_native_loader_v4:copy:0"
@@ -449,7 +449,7 @@ mod tests {
#[test]
fn compute_budget_profile_is_emitted_only_by_profile_emitter() {
let materializer = crate::ComplianceAuditMaterializer;
let materializer = crate::MtComplianceAuditMaterializer;
let emitter = observation(
super::COMPUTE_BUDGET_SURFACE,
"set_compute_unit_limit",
@@ -469,10 +469,10 @@ mod tests {
}
}),
);
let emitted = crate::EventMaterializer::materialize(&materializer, &emitter);
assert_eq!(emitted.status, crate::MaterializerOutcomeStatus::Inserted);
let emitted = crate::MtApiEventMaterializer::materialize(&materializer, &emitter);
assert_eq!(emitted.status, crate::MtApiMaterializerOutcomeStatus::Inserted);
assert_eq!(emitted.outputs[0].output_key, "transaction_compute_budget_profile:0");
assert_eq!(emitted.outputs[0].family, crate::MaterializedEventFamily::ComplianceAudit);
assert_eq!(emitted.outputs[0].family, crate::MdMaterializedEventFamily::ComplianceAudit);
assert_eq!(emitted.outputs[0].payload_json["profile"]["computeBudgetInstructionCount"], 2);
let non_emitter = observation(
super::COMPUTE_BUDGET_SURFACE,
@@ -486,13 +486,13 @@ mod tests {
}
}),
);
let ignored = crate::EventMaterializer::materialize(&materializer, &non_emitter);
assert_eq!(ignored.status, crate::MaterializerOutcomeStatus::Ignored);
let ignored = crate::MtApiEventMaterializer::materialize(&materializer, &non_emitter);
assert_eq!(ignored.status, crate::MtApiMaterializerOutcomeStatus::Ignored);
}
#[test]
fn failed_compute_budget_profile_remains_audit_materializable() {
let materializer = crate::ComplianceAuditMaterializer;
let materializer = crate::MtComplianceAuditMaterializer;
let observation = observation(
super::COMPUTE_BUDGET_SURFACE,
"set_compute_unit_price",
@@ -506,14 +506,14 @@ mod tests {
}
}),
);
let result = crate::EventMaterializer::materialize(&materializer, &observation);
assert_eq!(result.status, crate::MaterializerOutcomeStatus::Inserted);
let result = crate::MtApiEventMaterializer::materialize(&materializer, &observation);
assert_eq!(result.status, crate::MtApiMaterializerOutcomeStatus::Inserted);
assert_eq!(result.outputs[0].payload_json["transactionSucceeded"], false);
}
#[test]
fn failed_code_mutation_is_refused() {
let materializer = crate::ComplianceAuditMaterializer;
let materializer = crate::MtComplianceAuditMaterializer;
let observation = observation(
super::LOADER_V4_SURFACE,
"write",
@@ -521,14 +521,14 @@ mod tests {
serde_json::json!([account("program_account", "target111")]),
serde_json::json!({"offset": 0, "byteLength": 1, "bytesSha256": "abc"}),
);
let result = crate::EventMaterializer::materialize(&materializer, &observation);
assert_eq!(result.status, crate::MaterializerOutcomeStatus::Refused);
let result = crate::MtApiEventMaterializer::materialize(&materializer, &observation);
assert_eq!(result.status, crate::MtApiMaterializerOutcomeStatus::Refused);
assert!(result.outputs.is_empty());
}
#[test]
fn unrelated_audit_observation_is_ignored() {
let materializer = crate::ComplianceAuditMaterializer;
let materializer = crate::MtComplianceAuditMaterializer;
let observation = observation(
"other_surface",
"write",
@@ -536,13 +536,13 @@ mod tests {
serde_json::json!([]),
serde_json::json!({}),
);
let result = crate::EventMaterializer::materialize(&materializer, &observation);
assert_eq!(result.status, crate::MaterializerOutcomeStatus::Ignored);
let result = crate::MtApiEventMaterializer::materialize(&materializer, &observation);
assert_eq!(result.status, crate::MtApiMaterializerOutcomeStatus::Ignored);
}
#[test]
fn output_serialization_is_deterministic() {
let materializer = crate::ComplianceAuditMaterializer;
let materializer = crate::MtComplianceAuditMaterializer;
let observation = observation(
super::BPF_LOADER_UPGRADEABLE_SURFACE,
"write",
@@ -558,8 +558,8 @@ mod tests {
"bytesPrefixHex": "0102",
}),
);
let first = crate::EventMaterializer::materialize(&materializer, &observation);
let second = crate::EventMaterializer::materialize(&materializer, &observation);
let first = crate::MtApiEventMaterializer::materialize(&materializer, &observation);
let second = crate::MtApiEventMaterializer::materialize(&materializer, &observation);
let first_json = match serde_json::to_string(&first) {
std::result::Result::Ok(value) => value,
std::result::Result::Err(error) => {

View File

@@ -4,4 +4,4 @@
//! Local constants for the native compliance audit materializer.
/// Canonical tracing target for this component.
pub(crate) const TRACING_TARGET: &str = "kb-lib.materializer.compliance";
pub(crate) const MT_COMPLIANCE_AUDIT_TRACING_TARGET: &str = "kb-lib.materializer.compliance";

View File

@@ -1,6 +1,11 @@
// file: kb-lib/src/materializer/fees.rs
// version: 1
// version: 2
//! `fees` materializer family.
pub mod core;
mod core;
/// Fee materializer for committed Token-2022 fee observations.
pub use self::core::MtFeesMaterializer;
/// Materializes authoritative Token-2022 fee extension snapshots.
pub use self::core::materialize_token2022_fee_state_snapshots;

View File

@@ -1,10 +1,432 @@
// file: kb-lib/src/materializer/fees/core.rs
// version: 1
// version: 2
//! Migration boundary for legacy crate `kb_materializer_fees`.
//! Stable fee projections derived from exact committed Token-2022 observations.
/// Legacy crate name retained for migration and compatibility tracking.
pub const LEGACY_CRATE: &str = "kb_materializer_fees";
const ACCEPTED_FAMILIES: &[crate::MdEventFamily] = &[crate::MdEventFamily::Fee];
const SPL_TOKEN2022_SURFACE: &str = "spl_token2022";
const TOKEN2022_FEE_ENTRIES: &[&str] = &[
"initialize_transfer_fee_config",
"set_transfer_fee",
"withdraw_withheld_tokens_from_mint",
"withdraw_withheld_tokens_from_accounts",
"harvest_withheld_tokens_to_mint",
"transfer_checked_with_fee",
"initialize_confidential_transfer_fee_config",
"withdraw_confidential_withheld_tokens_from_mint",
"withdraw_confidential_withheld_tokens_from_accounts",
"harvest_confidential_withheld_tokens_to_mint",
"enable_confidential_harvest_to_mint",
"disable_confidential_harvest_to_mint",
"transfer_confidential_tokens_with_fee",
];
/// Current porting status.
pub const MIGRATION_STATUS: &str = "source-preserved-pending-port";
/// Fee materializer for committed Token-2022 fee instructions.
#[derive(Clone, Debug, Default)]
pub struct MtFeesMaterializer;
impl crate::MtMaterializer for crate::MtFeesMaterializer {
fn materializer_name(&self) -> &'static str {
return "kb_materializer_fees";
}
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<std::vec::Vec<crate::MdMaterializedEvent>> {
return std::result::Result::Ok(std::vec::Vec::new());
}
}
impl crate::MtApiEventMaterializer for crate::MtFeesMaterializer {
fn identity(&self) -> crate::MtApiMaterializerIdentity {
return crate::MtApiMaterializerIdentity {
name: "fees".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(
"failed_transaction_fee_refused",
"failed or uncommitted Token-2022 observations cannot create fee outputs",
);
}
let operation = observation.event.event_name.0.clone();
let accounts = observation
.payload_json
.get("accounts")
.cloned()
.unwrap_or(serde_json::Value::Null);
let parameters = observation
.payload_json
.get("parameters")
.cloned()
.unwrap_or_else(|| return observation.payload_json.clone());
let output = crate::MtApiMaterializedOutput {
output_key: format!("token2022_fee:{operation}:0"),
family: crate::MdMaterializedEventFamily::Fee,
payload_json: serde_json::json!({
"projectionVersion": 1,
"projectionSemantics": "committed_instruction_fee_event",
"domain": fee_domain(observation.event.event_name.0.as_str()),
"operation": operation,
"programId": observation.event.program_id.0.clone(),
"signature": observation.event.signature.0.clone(),
"slot": observation.event.slot.0,
"instructionPath": observation.event.instruction_path.0.clone(),
"transactionSucceeded": true,
"accounts": accounts,
"parameters": parameters,
"stateReadRequired": state_read_required(
observation.event.event_name.0.as_str(),
),
"confidentialValuesDecrypted": false,
}),
};
return crate::MtApiMaterializerExecutionResult {
status: crate::MtApiMaterializerOutcomeStatus::Inserted,
outputs: std::vec![output],
diagnostics: std::vec::Vec::new(),
};
}
}
fn accepts_event(event: &crate::MdDecodedProtocolEvent) -> bool {
return event.event_family == crate::MdEventFamily::Fee
&& event.surface_code.0 == SPL_TOKEN2022_SURFACE
&& event.program_id.0 == kb_program_ids::SPL_TOKEN2022_PROGRAM_ID
&& contains(TOKEN2022_FEE_ENTRIES, event.event_name.0.as_str());
}
fn fee_domain(operation: &str) -> &'static str {
if operation.contains("confidential") {
return "token2022_confidential_transfer_fee";
}
return "token2022_transfer_fee";
}
fn state_read_required(operation: &str) -> bool {
return matches!(
operation,
"initialize_transfer_fee_config"
| "set_transfer_fee"
| "withdraw_withheld_tokens_from_mint"
| "withdraw_withheld_tokens_from_accounts"
| "harvest_withheld_tokens_to_mint"
| "initialize_confidential_transfer_fee_config"
| "withdraw_confidential_withheld_tokens_from_mint"
| "withdraw_confidential_withheld_tokens_from_accounts"
| "harvest_confidential_withheld_tokens_to_mint"
| "enable_confidential_harvest_to_mint"
| "disable_confidential_harvest_to_mint"
);
}
fn contains(entries: &[&str], entry_code: &str) -> bool {
return entries.iter().any(|entry| return *entry == entry_code);
}
/// Materialize authoritative Token-2022 fee extension snapshots without decrypting confidential values.
pub fn materialize_token2022_fee_state_snapshots(
account_key: &str,
slot: u64,
state: &crate::DcToken2022State,
) -> std::result::Result<std::vec::Vec<crate::MtApiMaterializedOutput>, String> {
if account_key.is_empty() {
return std::result::Result::Err(
"Token-2022 fee state snapshot requires a non-empty account key".to_string(),
);
}
let state_kind = match state.kind {
crate::DcToken2022StateKind::Mint => "mint",
crate::DcToken2022StateKind::Account => "account",
crate::DcToken2022StateKind::Multisig => {
return std::result::Result::Err(
"Token-2022 multisig state cannot contain fee extensions".to_string(),
);
},
};
let mut outputs = std::vec::Vec::new();
for entry in &state.extensions {
let confidential = match entry.extension_name {
"transfer_fee_config" | "transfer_fee_amount" => false,
"confidential_transfer_fee_config" | "confidential_transfer_fee_amount" => true,
_ => continue,
};
let expected_kind = match entry.extension_name {
"transfer_fee_config" | "confidential_transfer_fee_config" => "mint",
"transfer_fee_amount" | "confidential_transfer_fee_amount" => "account",
_ => continue,
};
if state_kind != expected_kind {
return std::result::Result::Err(format!(
"Token-2022 {} extension requires {expected_kind} state, got {state_kind}",
entry.extension_name
));
}
outputs.push(crate::MtApiMaterializedOutput {
output_key: format!(
"token2022_fee_state:{}:{account_key}:{slot}",
entry.extension_name
),
family: crate::MdMaterializedEventFamily::Fee,
payload_json: serde_json::json!({
"projectionVersion": 1,
"projectionSemantics": "authoritative_bounded_fee_state_snapshot",
"domain": if confidential {
"token2022_confidential_transfer_fee_state"
} else {
"token2022_transfer_fee_state"
},
"idempotenceKey": format!(
"token2022_fee_state:{}:{account_key}:{slot}",
entry.extension_name
),
"accountKey": account_key,
"slot": slot,
"programId": kb_program_ids::SPL_TOKEN2022_PROGRAM_ID,
"stateKind": state_kind,
"extensionType": entry.extension_type,
"extensionName": entry.extension_name,
"valueHex": entry.value_hex,
"valueFields": entry.value_fields,
"containsConfidentialState": confidential,
"confidentialValuesDecrypted": false,
"finalAccountStateCaptured": true,
"provenance": {
"processorName": "fees",
"processorVersion": env!("CARGO_PKG_VERSION"),
"source": "bounded_token2022_state_parser"
}
}),
});
}
return std::result::Result::Ok(outputs);
}
#[cfg(test)]
mod tests {
fn observation(
surface_code: &str,
program_id: &str,
entry_code: &str,
transaction_failed: bool,
) -> crate::DcApiDecodedObservation {
return crate::DcApiDecodedObservation {
event_key: format!("{entry_code}: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(program_id.to_string()),
protocol_code: crate::MdProtocolCode("spl_token2022".to_string()),
surface_code: crate::MdSurfaceCode(surface_code.to_string()),
event_code: crate::MdEventCode(format!("{surface_code}.{entry_code}")),
event_name: crate::MdEventName(entry_code.to_string()),
event_family: crate::MdEventFamily::Fee,
source_kind: crate::MdEventSourceKind::Instruction,
confidence: crate::MdDecoderConfidence::ManualExact,
},
payload_json: serde_json::json!({
"accounts": [{"position": 0, "role": "mint", "accountKey": "mint111"}],
"parameters": {"extension": "transfer_fee", "basisPoints": 25},
}),
transaction_failed,
transaction_error: if transaction_failed {
std::option::Option::Some(serde_json::json!({"InstructionError": [0, "Custom"]}))
} else {
std::option::Option::None
},
observation_committed: !transaction_failed,
proof: crate::DcApiDecoderProof {
kind: crate::DcApiDecoderProofKind::Manual,
confidence: crate::MdDecoderConfidence::ManualExact,
evidence: std::vec!["fixture".to_string()],
},
};
}
#[test]
fn committed_token2022_fee_instruction_creates_one_stable_output() {
let observation = observation(
super::SPL_TOKEN2022_SURFACE,
kb_program_ids::SPL_TOKEN2022_PROGRAM_ID,
"set_transfer_fee",
false,
);
let result =
crate::MtApiEventMaterializer::materialize(&crate::MtFeesMaterializer, &observation);
assert_eq!(result.status, crate::MtApiMaterializerOutcomeStatus::Inserted);
assert_eq!(result.outputs.len(), 1);
assert_eq!(result.outputs[0].family, crate::MdMaterializedEventFamily::Fee);
assert_eq!(result.outputs[0].output_key, "token2022_fee:set_transfer_fee:0");
assert_eq!(result.outputs[0].payload_json["stateReadRequired"], true);
}
#[test]
fn failed_token2022_fee_instruction_is_refused() {
let observation = observation(
super::SPL_TOKEN2022_SURFACE,
kb_program_ids::SPL_TOKEN2022_PROGRAM_ID,
"harvest_withheld_tokens_to_mint",
true,
);
let result =
crate::MtApiEventMaterializer::materialize(&crate::MtFeesMaterializer, &observation);
assert_eq!(result.status, crate::MtApiMaterializerOutcomeStatus::Refused);
assert!(result.outputs.is_empty());
}
#[test]
fn classic_token_and_program_surface_mismatches_are_ignored() {
let classic = observation(
"spl_token",
kb_program_ids::SPL_TOKEN_PROGRAM_ID,
"set_transfer_fee",
false,
);
let wrong_program = observation(
super::SPL_TOKEN2022_SURFACE,
kb_program_ids::SPL_TOKEN_PROGRAM_ID,
"set_transfer_fee",
false,
);
assert!(!crate::MtApiEventMaterializer::accepts_observation(
&crate::MtFeesMaterializer,
&classic,
));
assert!(!crate::MtApiEventMaterializer::accepts_observation(
&crate::MtFeesMaterializer,
&wrong_program,
));
}
#[test]
fn transfer_checked_with_fee_is_self_contained_but_state_mutations_are_not() {
assert!(!super::state_read_required("transfer_checked_with_fee"));
assert!(super::state_read_required("withdraw_withheld_tokens_from_accounts"));
}
#[test]
fn committed_confidential_fee_instruction_is_projected_without_decryption() {
let observation = observation(
super::SPL_TOKEN2022_SURFACE,
kb_program_ids::SPL_TOKEN2022_PROGRAM_ID,
"initialize_confidential_transfer_fee_config",
false,
);
let result =
crate::MtApiEventMaterializer::materialize(&crate::MtFeesMaterializer, &observation);
assert_eq!(result.status, crate::MtApiMaterializerOutcomeStatus::Inserted);
assert_eq!(result.outputs.len(), 1);
assert_eq!(result.outputs[0].payload_json["domain"], "token2022_confidential_transfer_fee");
assert_eq!(result.outputs[0].payload_json["stateReadRequired"], true);
assert_eq!(result.outputs[0].payload_json["confidentialValuesDecrypted"], false);
}
#[test]
fn confidential_transfer_with_fee_is_instructionally_self_contained() {
assert!(!super::state_read_required("transfer_confidential_tokens_with_fee",));
assert_eq!(
super::fee_domain("transfer_confidential_tokens_with_fee"),
"token2022_confidential_transfer_fee"
);
}
#[test]
fn public_fee_state_snapshots_are_owned_once_and_idempotent() {
let mint = crate::DcToken2022State {
kind: crate::DcToken2022StateKind::Mint,
base_fields: serde_json::json!({}),
base_hex: "00".to_string(),
account_type: std::option::Option::Some(1),
extensions: std::vec![crate::DcToken2022TlvEntry {
extension_type: 1,
extension_name: "transfer_fee_config",
value_hex: "01".to_string(),
value_fields: serde_json::json!({"withheldAmount":"7"}),
}],
};
let first = crate::materialize_token2022_fee_state_snapshots("mint111", 42, &mint);
let second = crate::materialize_token2022_fee_state_snapshots("mint111", 42, &mint);
assert!(first.is_ok());
assert_eq!(first, second);
let outputs = match first {
std::result::Result::Ok(outputs) => outputs,
std::result::Result::Err(error) => panic!("unexpected error: {error}"),
};
assert_eq!(outputs.len(), 1);
assert_eq!(outputs[0].payload_json["stateKind"], "mint");
assert_eq!(outputs[0].payload_json["valueFields"]["withheldAmount"], "7");
assert_eq!(outputs[0].payload_json["confidentialValuesDecrypted"], false);
}
#[test]
fn confidential_fee_amount_preserves_ciphertext_without_decryption_claim() {
let account = crate::DcToken2022State {
kind: crate::DcToken2022StateKind::Account,
base_fields: serde_json::json!({}),
base_hex: "00".to_string(),
account_type: std::option::Option::Some(2),
extensions: std::vec![crate::DcToken2022TlvEntry {
extension_type: 17,
extension_name: "confidential_transfer_fee_amount",
value_hex: "02".to_string(),
value_fields: serde_json::json!({"withheldAmount":"ciphertext"}),
}],
};
let result = crate::materialize_token2022_fee_state_snapshots("account111", 43, &account);
assert!(result.is_ok());
let outputs = match result {
std::result::Result::Ok(outputs) => outputs,
std::result::Result::Err(error) => panic!("unexpected error: {error}"),
};
assert_eq!(outputs[0].payload_json["containsConfidentialState"], true);
assert_eq!(outputs[0].payload_json["confidentialValuesDecrypted"], false);
assert_eq!(outputs[0].payload_json["valueFields"]["withheldAmount"], "ciphertext");
}
#[test]
fn fee_state_snapshot_rejects_wrong_state_kind_and_empty_identity() {
let wrong = crate::DcToken2022State {
kind: crate::DcToken2022StateKind::Account,
base_fields: serde_json::json!({}),
base_hex: "00".to_string(),
account_type: std::option::Option::Some(2),
extensions: std::vec![crate::DcToken2022TlvEntry {
extension_type: 1,
extension_name: "transfer_fee_config",
value_hex: "01".to_string(),
value_fields: serde_json::json!({}),
}],
};
assert!(
crate::materialize_token2022_fee_state_snapshots("account111", 44, &wrong,).is_err()
);
assert!(crate::materialize_token2022_fee_state_snapshots("", 44, &wrong,).is_err());
}
}

View File

@@ -1,6 +1,6 @@
// file: kb-lib/src/materializer/governance.rs
// version: 1
// version: 2
//! `governance` materializer family.
pub mod core;
mod core;

View File

@@ -1,10 +1,10 @@
// file: kb-lib/src/materializer/governance/core.rs
// version: 1
// version: 2
//! Migration boundary for legacy crate `kb_materializer_governance`.
/// Legacy crate name retained for migration and compatibility tracking.
pub const LEGACY_CRATE: &str = "kb_materializer_governance";
const LEGACY_CRATE: &str = "kb_materializer_governance";
/// Current porting status.
pub const MIGRATION_STATUS: &str = "source-preserved-pending-port";
const MIGRATION_STATUS: &str = "source-preserved-pending-port";

View File

@@ -1,6 +1,6 @@
// file: kb-lib/src/materializer/lending.rs
// version: 1
// version: 2
//! `lending` materializer family.
pub mod core;
mod core;

View File

@@ -1,10 +1,10 @@
// file: kb-lib/src/materializer/lending/core.rs
// version: 1
// version: 2
//! Migration boundary for legacy crate `kb_materializer_lending`.
/// Legacy crate name retained for migration and compatibility tracking.
pub const LEGACY_CRATE: &str = "kb_materializer_lending";
const LEGACY_CRATE: &str = "kb_materializer_lending";
/// Current porting status.
pub const MIGRATION_STATUS: &str = "source-preserved-pending-port";
const MIGRATION_STATUS: &str = "source-preserved-pending-port";

View File

@@ -1,12 +1,13 @@
// file: kb-lib/src/materializer/lifecycle.rs
// version: 2
// version: 3
//! `lifecycle` materializer family.
mod constants;
pub mod core;
mod core;
pub(crate) use crate::materializer::lifecycle::constants::TRACING_TARGET as MATERIALIZER_LIFECYCLE_TRACING_TARGET;
/// Canonical tracing target for the native lifecycle materializer.
pub(crate) use self::constants::MT_LIFECYCLE_TRACING_TARGET;
/// Stable native lifecycle materializer.
pub use crate::materializer::lifecycle::core::LifecycleMaterializer;
pub use self::core::MtLifecycleMaterializer;

View File

@@ -4,4 +4,4 @@
//! Local constants for the native lifecycle materializer.
/// Canonical tracing target for this component.
pub(crate) const TRACING_TARGET: &str = "kb-lib.materializer.lifecycle";
pub(crate) const MT_LIFECYCLE_TRACING_TARGET: &str = "kb-lib.materializer.lifecycle";

View File

@@ -3,10 +3,10 @@
//! Stable lifecycle projections derived from exact decoded observations.
const ACCEPTED_FAMILIES: &[crate::EventFamily] = &[
crate::EventFamily::Lifecycle,
crate::EventFamily::Admin,
crate::EventFamily::Audit,
const ACCEPTED_FAMILIES: &[crate::MdEventFamily] = &[
crate::MdEventFamily::Lifecycle,
crate::MdEventFamily::Admin,
crate::MdEventFamily::Audit,
];
const ADDRESS_LOOKUP_TABLE_SURFACE: &str = "solana_native_address_lookup_table";
const ADDRESS_LOOKUP_TABLE_ENTRIES: &[&str] = &[
@@ -121,9 +121,9 @@ impl ProjectionKind {
/// Stable lifecycle materializer for supported native lifecycle projections.
#[derive(Clone, Debug, Default)]
pub struct LifecycleMaterializer;
pub struct MtLifecycleMaterializer;
impl crate::Materializer for crate::LifecycleMaterializer {
impl crate::MtMaterializer for crate::MtLifecycleMaterializer {
fn materializer_name(&self) -> &'static str {
return "kb_materializer_lifecycle";
}
@@ -132,7 +132,7 @@ impl crate::Materializer for crate::LifecycleMaterializer {
return env!("CARGO_PKG_VERSION");
}
fn accepts_event(&self, event: &crate::DecodedProtocolEvent) -> bool {
fn accepts_event(&self, event: &crate::MdDecodedProtocolEvent) -> bool {
return family_is_accepted(event.event_family)
&& static_projection(event.surface_code.0.as_str(), event.event_name.0.as_str())
.is_some();
@@ -140,60 +140,60 @@ impl crate::Materializer for crate::LifecycleMaterializer {
fn materialize_event(
&self,
event: &crate::DecodedProtocolEvent,
) -> kb_core::Result<std::vec::Vec<crate::MaterializedEvent>> {
if !crate::Materializer::accepts_event(self, event) {
event: &crate::MdDecodedProtocolEvent,
) -> kb_core::Result<std::vec::Vec<crate::MdMaterializedEvent>> {
if !crate::MtMaterializer::accepts_event(self, event) {
return std::result::Result::Ok(std::vec::Vec::new());
}
return std::result::Result::Ok(std::vec::Vec::new());
}
}
impl crate::EventMaterializer for crate::LifecycleMaterializer {
fn identity(&self) -> crate::MaterializerIdentity {
return crate::MaterializerIdentity {
impl crate::MtApiEventMaterializer for crate::MtLifecycleMaterializer {
fn identity(&self) -> crate::MtApiMaterializerIdentity {
return crate::MtApiMaterializerIdentity {
name: "solana_native_lifecycle".to_string(),
version: env!("CARGO_PKG_VERSION").to_string(),
};
}
fn accepted_families(&self) -> &'static [crate::EventFamily] {
fn accepted_families(&self) -> &'static [crate::MdEventFamily] {
return ACCEPTED_FAMILIES;
}
fn accepts_observation(&self, observation: &crate::DecodedObservation) -> bool {
fn accepts_observation(&self, observation: &crate::DcApiDecodedObservation) -> bool {
return family_is_accepted(observation.event.event_family)
&& observation_projection(observation).is_some();
}
fn transaction_policy(
&self,
_family: crate::EventFamily,
) -> crate::MaterializationTransactionPolicy {
return crate::MaterializationTransactionPolicy::SuccessfulCommittedOnly;
_family: crate::MdEventFamily,
) -> crate::MtApiMaterializationTransactionPolicy {
return crate::MtApiMaterializationTransactionPolicy::SuccessfulCommittedOnly;
}
fn materialize(
&self,
observation: &crate::DecodedObservation,
) -> crate::MaterializerExecutionResult {
observation: &crate::DcApiDecodedObservation,
) -> crate::MtApiMaterializerExecutionResult {
let projection = match observation_projection(observation) {
std::option::Option::Some(value) => value,
std::option::Option::None => {
tracing::debug!(
target: crate::MATERIALIZER_LIFECYCLE_TRACING_TARGET,
target: crate::MT_LIFECYCLE_TRACING_TARGET,
action = "materialize_lifecycle",
surface_code = %observation.event.surface_code.0.as_str(),
entry_code = %observation.event.event_name.0.as_str(),
accepted = false,
"ignore observation outside supported native lifecycle projections"
);
return crate::MaterializerExecutionResult::ignored();
return crate::MtApiMaterializerExecutionResult::ignored();
},
};
if observation.transaction_failed || !observation.observation_committed {
tracing::debug!(
target: crate::MATERIALIZER_LIFECYCLE_TRACING_TARGET,
target: crate::MT_LIFECYCLE_TRACING_TARGET,
action = "materialize_lifecycle",
surface_code = %observation.event.surface_code.0.as_str(),
entry_code = %observation.event.event_name.0.as_str(),
@@ -202,7 +202,7 @@ impl crate::EventMaterializer for crate::LifecycleMaterializer {
transaction_failed = observation.transaction_failed,
"refuse uncommitted native lifecycle observation"
);
return crate::MaterializerExecutionResult::refused(
return crate::MtApiMaterializerExecutionResult::refused(
"failed_transaction_lifecycle_refused",
"failed or uncommitted native lifecycle observations cannot create lifecycle outputs",
);
@@ -225,7 +225,7 @@ impl crate::EventMaterializer for crate::LifecycleMaterializer {
let projection_details =
projection_details(projection, &accounts, &parameters, operation.as_str());
tracing::debug!(
target: crate::MATERIALIZER_LIFECYCLE_TRACING_TARGET,
target: crate::MT_LIFECYCLE_TRACING_TARGET,
action = "materialize_lifecycle",
surface_code = %observation.event.surface_code.0.as_str(),
entry_code = %observation.event.event_name.0.as_str(),
@@ -235,9 +235,9 @@ impl crate::EventMaterializer for crate::LifecycleMaterializer {
output_count = 1_usize,
"materialize committed native lifecycle observation"
);
let output = crate::MaterializedOutput {
let output = crate::MtApiMaterializedOutput {
output_key,
family: crate::MaterializedEventFamily::Lifecycle,
family: crate::MdMaterializedEventFamily::Lifecycle,
payload_json: serde_json::json!({
"projectionVersion": 1,
"projectionSemantics": "committed_instruction_lifecycle_event",
@@ -254,20 +254,20 @@ impl crate::EventMaterializer for crate::LifecycleMaterializer {
"projection": projection_details,
}),
};
return crate::MaterializerExecutionResult {
status: crate::MaterializerOutcomeStatus::Inserted,
return crate::MtApiMaterializerExecutionResult {
status: crate::MtApiMaterializerOutcomeStatus::Inserted,
outputs: std::vec![output],
diagnostics: std::vec::Vec::new(),
};
}
}
fn family_is_accepted(family: crate::EventFamily) -> bool {
fn family_is_accepted(family: crate::MdEventFamily) -> bool {
return ACCEPTED_FAMILIES.iter().any(|accepted| return *accepted == family);
}
fn observation_projection(
observation: &crate::DecodedObservation,
observation: &crate::DcApiDecodedObservation,
) -> std::option::Option<ProjectionKind> {
let surface_code = observation.event.surface_code.0.as_str();
let entry_code = observation.event.event_name.0.as_str();
@@ -479,25 +479,25 @@ mod tests {
fn observation_with_payload(
surface_code: &str,
entry_code: &str,
event_family: crate::EventFamily,
event_family: crate::MdEventFamily,
transaction_failed: bool,
accounts: serde_json::Value,
parameters: serde_json::Value,
) -> crate::DecodedObservation {
return crate::DecodedObservation {
) -> crate::DcApiDecodedObservation {
return crate::DcApiDecodedObservation {
event_key: format!("{entry_code}:0"),
event: crate::DecodedProtocolEvent {
signature: crate::Signature("signature".to_string()),
slot: crate::Slot(42),
instruction_path: crate::InstructionPath("0".to_string()),
program_id: crate::ProgramId("program-id".to_string()),
protocol_code: crate::ProtocolCode("solana_native".to_string()),
surface_code: crate::SurfaceCode(surface_code.to_string()),
event_code: crate::EventCode(format!("{surface_code}.{entry_code}")),
event_name: crate::EventName(entry_code.to_string()),
event: crate::MdDecodedProtocolEvent {
signature: crate::MdSignature("signature".to_string()),
slot: crate::MdSlot(42),
instruction_path: crate::MdInstructionPath("0".to_string()),
program_id: crate::MdProgramId("program-id".to_string()),
protocol_code: crate::MdProtocolCode("solana_native".to_string()),
surface_code: crate::MdSurfaceCode(surface_code.to_string()),
event_code: crate::MdEventCode(format!("{surface_code}.{entry_code}")),
event_name: crate::MdEventName(entry_code.to_string()),
event_family,
source_kind: crate::EventSourceKind::Instruction,
confidence: crate::DecoderConfidence::ManualExact,
source_kind: crate::MdEventSourceKind::Instruction,
confidence: crate::MdDecoderConfidence::ManualExact,
},
payload_json: serde_json::json!({
"accounts": accounts,
@@ -510,9 +510,9 @@ mod tests {
std::option::Option::None
},
observation_committed: !transaction_failed,
proof: crate::DecoderProof {
kind: crate::DecoderProofKind::Manual,
confidence: crate::DecoderConfidence::ManualExact,
proof: crate::DcApiDecoderProof {
kind: crate::DcApiDecoderProofKind::Manual,
confidence: crate::MdDecoderConfidence::ManualExact,
evidence: std::vec!["fixture".to_string()],
},
};
@@ -522,11 +522,11 @@ mod tests {
surface_code: &str,
entry_code: &str,
transaction_failed: bool,
) -> crate::DecodedObservation {
) -> crate::DcApiDecodedObservation {
return observation_with_payload(
surface_code,
entry_code,
crate::EventFamily::Lifecycle,
crate::MdEventFamily::Lifecycle,
transaction_failed,
serde_json::json!([{"role": "target", "accountKey": "account"}]),
serde_json::json!({"value": 41}),
@@ -535,9 +535,9 @@ mod tests {
fn nonce_observation(
entry_code: &str,
family: crate::EventFamily,
family: crate::MdEventFamily,
transaction_failed: bool,
) -> crate::DecodedObservation {
) -> crate::DcApiDecodedObservation {
return observation_with_payload(
super::SYSTEM_SURFACE,
entry_code,
@@ -558,10 +558,10 @@ mod tests {
fn slashing_observation(
entry_code: &str,
transaction_failed: bool,
) -> crate::DecodedObservation {
) -> crate::DcApiDecodedObservation {
let (family, accounts) = if entry_code == "duplicate_block_proof" {
(
crate::EventFamily::Audit,
crate::MdEventFamily::Audit,
serde_json::json!([
{"role": "proof_account", "accountKey": "proof111"},
{"role": "violation_report", "accountKey": "report111"},
@@ -571,7 +571,7 @@ mod tests {
)
} else {
(
crate::EventFamily::Lifecycle,
crate::MdEventFamily::Lifecycle,
serde_json::json!([
{"role": "violation_report", "accountKey": "report111"},
{"role": "destination", "accountKey": "destination111"}
@@ -599,7 +599,7 @@ mod tests {
entry_code: &str,
context_state_requested: bool,
transaction_failed: bool,
) -> crate::DecodedObservation {
) -> crate::DcApiDecodedObservation {
let accounts = if entry_code == "close_context_state" {
serde_json::json!([
{"role": "proof_context_state", "accountKey": "context111"},
@@ -615,9 +615,9 @@ mod tests {
serde_json::json!([])
};
let family = if entry_code == "close_context_state" {
crate::EventFamily::Lifecycle
crate::MdEventFamily::Lifecycle
} else {
crate::EventFamily::Audit
crate::MdEventFamily::Audit
};
return observation_with_payload(
super::ZK_ELGAMAL_PROOF_SURFACE,
@@ -634,12 +634,12 @@ mod tests {
#[test]
fn successful_alt_lifecycle_creates_one_stable_output() {
let materializer = crate::LifecycleMaterializer;
let result = crate::EventMaterializer::materialize(
let materializer = crate::MtLifecycleMaterializer;
let result = crate::MtApiEventMaterializer::materialize(
&materializer,
&observation(super::ADDRESS_LOOKUP_TABLE_SURFACE, "create_lookup_table", false),
);
assert_eq!(result.status, crate::MaterializerOutcomeStatus::Inserted);
assert_eq!(result.status, crate::MtApiMaterializerOutcomeStatus::Inserted);
assert_eq!(result.outputs.len(), 1);
assert_eq!(result.outputs[0].output_key, "address_lookup_table:create_lookup_table:0");
assert_eq!(result.outputs[0].payload_json["domain"], "address_lookup_table");
@@ -648,12 +648,12 @@ mod tests {
#[test]
fn successful_loader_lifecycle_creates_program_projection() {
let materializer = crate::LifecycleMaterializer;
let result = crate::EventMaterializer::materialize(
let materializer = crate::MtLifecycleMaterializer;
let result = crate::MtApiEventMaterializer::materialize(
&materializer,
&observation(super::BPF_LOADER_UPGRADEABLE_SURFACE, "deploy_with_max_data_len", false),
);
assert_eq!(result.status, crate::MaterializerOutcomeStatus::Inserted);
assert_eq!(result.status, crate::MtApiMaterializerOutcomeStatus::Inserted);
assert_eq!(
result.outputs[0].output_key,
"program_loader:solana_native_bpf_loader_upgradeable:deploy_with_max_data_len:0"
@@ -667,19 +667,19 @@ mod tests {
#[test]
fn successful_feature_revoke_creates_feature_gate_projection() {
let materializer = crate::LifecycleMaterializer;
let result = crate::EventMaterializer::materialize(
let materializer = crate::MtLifecycleMaterializer;
let result = crate::MtApiEventMaterializer::materialize(
&materializer,
&observation(super::FEATURE_SURFACE, "revoke_pending_activation", false),
);
assert_eq!(result.status, crate::MaterializerOutcomeStatus::Inserted);
assert_eq!(result.status, crate::MtApiMaterializerOutcomeStatus::Inserted);
assert_eq!(result.outputs[0].output_key, "feature_gate:revoke_pending_activation:0");
assert_eq!(result.outputs[0].payload_json["domain"], "feature_gate");
}
#[test]
fn every_committed_system_account_operation_creates_a_lifecycle_projection() {
let materializer = crate::LifecycleMaterializer;
let materializer = crate::MtLifecycleMaterializer;
for (operation, role, transition) in [
("create_account", "new_account", "created_allocated_funded_and_assigned"),
(
@@ -698,7 +698,7 @@ mod tests {
let observation = observation_with_payload(
super::SYSTEM_SURFACE,
operation,
crate::EventFamily::Lifecycle,
crate::MdEventFamily::Lifecycle,
false,
serde_json::json!([
{"role": role, "accountKey": "target111"},
@@ -712,8 +712,8 @@ mod tests {
"seed": "seed",
}),
);
let result = crate::EventMaterializer::materialize(&materializer, &observation);
assert_eq!(result.status, crate::MaterializerOutcomeStatus::Inserted);
let result = crate::MtApiEventMaterializer::materialize(&materializer, &observation);
assert_eq!(result.status, crate::MtApiMaterializerOutcomeStatus::Inserted);
assert_eq!(result.outputs[0].output_key, format!("system_account:{operation}:0"));
assert_eq!(result.outputs[0].payload_json["projection"]["targetAccount"], "target111");
assert_eq!(result.outputs[0].payload_json["projection"]["stateTransition"], transition);
@@ -722,11 +722,11 @@ mod tests {
#[test]
fn system_account_creation_does_not_duplicate_balance_changes() {
let materializer = crate::LifecycleMaterializer;
let materializer = crate::MtLifecycleMaterializer;
let observation = observation_with_payload(
super::SYSTEM_SURFACE,
"create_account",
crate::EventFamily::Lifecycle,
crate::MdEventFamily::Lifecycle,
false,
serde_json::json!([
{"role": "funding_account", "accountKey": "funding111"},
@@ -734,7 +734,7 @@ mod tests {
]),
serde_json::json!({"lamports": 55, "space": 64, "owner": "owner111"}),
);
let result = crate::EventMaterializer::materialize(&materializer, &observation);
let result = crate::MtApiEventMaterializer::materialize(&materializer, &observation);
assert_eq!(result.outputs[0].payload_json["projection"]["requestedLamports"], 55);
assert_eq!(
result.outputs[0].payload_json["projection"]["lamportTransferRepresentedByCoreBalanceChanges"],
@@ -745,26 +745,29 @@ mod tests {
#[test]
fn every_committed_system_nonce_operation_creates_a_lifecycle_projection() {
let materializer = crate::LifecycleMaterializer;
let materializer = crate::MtLifecycleMaterializer;
for (entry_code, family, transition) in [
("initialize_nonce_account", crate::EventFamily::Lifecycle, "initialized"),
("advance_nonce_account", crate::EventFamily::Lifecycle, "nonce_advanced"),
("authorize_nonce_account", crate::EventFamily::Admin, "authority_changed"),
("initialize_nonce_account", crate::MdEventFamily::Lifecycle, "initialized"),
("advance_nonce_account", crate::MdEventFamily::Lifecycle, "nonce_advanced"),
("authorize_nonce_account", crate::MdEventFamily::Admin, "authority_changed"),
(
"upgrade_nonce_account",
crate::EventFamily::Lifecycle,
crate::MdEventFamily::Lifecycle,
"legacy_version_upgraded",
),
(
"withdraw_nonce_account",
crate::EventFamily::Lifecycle,
crate::MdEventFamily::Lifecycle,
"lamports_withdrawn_possible_closure",
),
] {
let observation = nonce_observation(entry_code, family, false);
assert!(crate::EventMaterializer::accepts_observation(&materializer, &observation,));
let result = crate::EventMaterializer::materialize(&materializer, &observation);
assert_eq!(result.status, crate::MaterializerOutcomeStatus::Inserted);
assert!(crate::MtApiEventMaterializer::accepts_observation(
&materializer,
&observation,
));
let result = crate::MtApiEventMaterializer::materialize(&materializer, &observation);
assert_eq!(result.status, crate::MtApiMaterializerOutcomeStatus::Inserted);
assert_eq!(result.outputs[0].payload_json["domain"], "durable_nonce_account");
assert_eq!(result.outputs[0].payload_json["projection"]["targetAccount"], "nonce111");
assert_eq!(
@@ -777,10 +780,10 @@ mod tests {
#[test]
fn nonce_withdrawal_preserves_conditional_closure_semantics() {
let materializer = crate::LifecycleMaterializer;
let materializer = crate::MtLifecycleMaterializer;
let observation =
nonce_observation("withdraw_nonce_account", crate::EventFamily::Lifecycle, false);
let result = crate::EventMaterializer::materialize(&materializer, &observation);
nonce_observation("withdraw_nonce_account", crate::MdEventFamily::Lifecycle, false);
let result = crate::MtApiEventMaterializer::materialize(&materializer, &observation);
assert_eq!(result.outputs[0].payload_json["projection"]["withdrawLamports"], 55);
assert_eq!(
result.outputs[0].payload_json["projection"]["lamportDestination"],
@@ -799,13 +802,16 @@ mod tests {
#[test]
fn zk_context_is_materialized_only_when_requested() {
let materializer = crate::LifecycleMaterializer;
let materializer = crate::MtLifecycleMaterializer;
let without_context = zk_observation("verify_zero_ciphertext", false, false);
assert!(!crate::EventMaterializer::accepts_observation(&materializer, &without_context,));
assert!(!crate::MtApiEventMaterializer::accepts_observation(
&materializer,
&without_context,
));
let with_context = zk_observation("verify_zero_ciphertext", true, false);
assert!(crate::EventMaterializer::accepts_observation(&materializer, &with_context,));
let result = crate::EventMaterializer::materialize(&materializer, &with_context);
assert_eq!(result.status, crate::MaterializerOutcomeStatus::Inserted);
assert!(crate::MtApiEventMaterializer::accepts_observation(&materializer, &with_context,));
let result = crate::MtApiEventMaterializer::materialize(&materializer, &with_context);
assert_eq!(result.status, crate::MtApiMaterializerOutcomeStatus::Inserted);
assert_eq!(result.outputs[0].payload_json["domain"], "zk_proof_context");
assert_eq!(result.outputs[0].payload_json["projection"]["targetAccount"], "context111");
assert_eq!(
@@ -820,10 +826,10 @@ mod tests {
#[test]
fn zk_context_close_creates_a_stable_lifecycle_projection() {
let materializer = crate::LifecycleMaterializer;
let materializer = crate::MtLifecycleMaterializer;
let observation = zk_observation("close_context_state", false, false);
let result = crate::EventMaterializer::materialize(&materializer, &observation);
assert_eq!(result.status, crate::MaterializerOutcomeStatus::Inserted);
let result = crate::MtApiEventMaterializer::materialize(&materializer, &observation);
assert_eq!(result.status, crate::MtApiMaterializerOutcomeStatus::Inserted);
assert_eq!(result.outputs[0].output_key, "zk_proof_context:close_context_state:0");
assert_eq!(
result.outputs[0].payload_json["projection"]["lamportDestination"],
@@ -837,11 +843,11 @@ mod tests {
#[test]
fn successful_slashing_proof_initializes_violation_report_projection() {
let materializer = crate::LifecycleMaterializer;
let materializer = crate::MtLifecycleMaterializer;
let observation = slashing_observation("duplicate_block_proof", false);
assert!(crate::EventMaterializer::accepts_observation(&materializer, &observation,));
let result = crate::EventMaterializer::materialize(&materializer, &observation);
assert_eq!(result.status, crate::MaterializerOutcomeStatus::Inserted);
assert!(crate::MtApiEventMaterializer::accepts_observation(&materializer, &observation,));
let result = crate::MtApiEventMaterializer::materialize(&materializer, &observation);
assert_eq!(result.status, crate::MtApiMaterializerOutcomeStatus::Inserted);
assert_eq!(
result.outputs[0].output_key,
"slashing_violation_report:duplicate_block_proof:0"
@@ -854,10 +860,10 @@ mod tests {
#[test]
fn successful_slashing_close_creates_stable_closure_projection() {
let materializer = crate::LifecycleMaterializer;
let materializer = crate::MtLifecycleMaterializer;
let observation = slashing_observation("close_violation_report", false);
let result = crate::EventMaterializer::materialize(&materializer, &observation);
assert_eq!(result.status, crate::MaterializerOutcomeStatus::Inserted);
let result = crate::MtApiEventMaterializer::materialize(&materializer, &observation);
assert_eq!(result.status, crate::MtApiMaterializerOutcomeStatus::Inserted);
assert_eq!(
result.outputs[0].payload_json["projection"]["stateTransition"],
"closed_after_minimum_retention_lamports_reclaimed_owner_reset_to_system_program"
@@ -871,78 +877,86 @@ mod tests {
#[test]
fn historical_zk_token_proof_is_not_materialized() {
let materializer = crate::LifecycleMaterializer;
let materializer = crate::MtLifecycleMaterializer;
let observation = observation_with_payload(
"solana_native_zk_token_proof",
"close_context_state",
crate::EventFamily::Audit,
crate::MdEventFamily::Audit,
false,
serde_json::json!([]),
serde_json::json!({}),
);
assert!(!crate::EventMaterializer::accepts_observation(&materializer, &observation,));
let result = crate::EventMaterializer::materialize(&materializer, &observation);
assert_eq!(result.status, crate::MaterializerOutcomeStatus::Ignored);
assert!(!crate::MtApiEventMaterializer::accepts_observation(&materializer, &observation,));
let result = crate::MtApiEventMaterializer::materialize(&materializer, &observation);
assert_eq!(result.status, crate::MtApiMaterializerOutcomeStatus::Ignored);
}
#[test]
fn loader_write_and_admin_events_are_not_lifecycle_projections() {
let materializer = crate::LifecycleMaterializer;
let materializer = crate::MtLifecycleMaterializer;
for entry_code in ["write", "set_authority", "transfer_authority"] {
let observation = observation(super::BPF_LOADER_UPGRADEABLE_SURFACE, entry_code, false);
assert!(!crate::EventMaterializer::accepts_observation(&materializer, &observation,));
assert!(!crate::MtApiEventMaterializer::accepts_observation(
&materializer,
&observation,
));
}
}
#[test]
fn unrelated_lifecycle_is_ignored() {
let materializer = crate::LifecycleMaterializer;
let result = crate::EventMaterializer::materialize(
let materializer = crate::MtLifecycleMaterializer;
let result = crate::MtApiEventMaterializer::materialize(
&materializer,
&observation(super::SYSTEM_SURFACE, "transfer", false),
);
assert_eq!(result.status, crate::MaterializerOutcomeStatus::Ignored);
assert_eq!(result.status, crate::MtApiMaterializerOutcomeStatus::Ignored);
}
#[test]
fn ata_lifecycle_is_owned_by_token_accounts_materializer_only() {
let mut observation = observation("spl_associated_token_account", "create", false);
observation.event.program_id =
crate::ProgramId(kb_program_ids::ASSOCIATED_TOKEN_PROGRAM_ID.to_string());
assert!(!crate::EventMaterializer::accepts_observation(
&crate::LifecycleMaterializer,
crate::MdProgramId(kb_program_ids::ASSOCIATED_TOKEN_PROGRAM_ID.to_string());
assert!(!crate::MtApiEventMaterializer::accepts_observation(
&crate::MtLifecycleMaterializer,
&observation,
));
let result =
crate::EventMaterializer::materialize(&crate::LifecycleMaterializer, &observation);
assert_eq!(result.status, crate::MaterializerOutcomeStatus::Ignored);
let result = crate::MtApiEventMaterializer::materialize(
&crate::MtLifecycleMaterializer,
&observation,
);
assert_eq!(result.status, crate::MtApiMaterializerOutcomeStatus::Ignored);
assert!(result.outputs.is_empty());
}
#[test]
fn failed_native_lifecycle_is_refused() {
let materializer = crate::LifecycleMaterializer;
let materializer = crate::MtLifecycleMaterializer;
let observations = [
observation(super::ADDRESS_LOOKUP_TABLE_SURFACE, "close_lookup_table", true),
observation(super::LOADER_V4_SURFACE, "deploy", true),
nonce_observation("authorize_nonce_account", crate::EventFamily::Admin, true),
nonce_observation("authorize_nonce_account", crate::MdEventFamily::Admin, true),
zk_observation("verify_zero_ciphertext", true, true),
slashing_observation("duplicate_block_proof", true),
];
for observation in observations {
let policy = crate::validate_materialization_policy(&materializer, &observation);
let policy = crate::materializer_api_validate_materialization_policy(
&materializer,
&observation,
);
assert!(policy.is_err());
let direct = crate::EventMaterializer::materialize(&materializer, &observation);
assert_eq!(direct.status, crate::MaterializerOutcomeStatus::Refused);
let direct = crate::MtApiEventMaterializer::materialize(&materializer, &observation);
assert_eq!(direct.status, crate::MtApiMaterializerOutcomeStatus::Refused);
}
}
#[test]
fn output_serialization_is_deterministic() {
let materializer = crate::LifecycleMaterializer;
let materializer = crate::MtLifecycleMaterializer;
let observation = zk_observation("verify_batched_range_proof_u64", true, false);
let first = crate::EventMaterializer::materialize(&materializer, &observation);
let second = crate::EventMaterializer::materialize(&materializer, &observation);
let first = crate::MtApiEventMaterializer::materialize(&materializer, &observation);
let second = crate::MtApiEventMaterializer::materialize(&materializer, &observation);
let first_json = match serde_json::to_string(&first) {
std::result::Result::Ok(value) => value,
std::result::Result::Err(error) => {

View File

@@ -1,6 +1,6 @@
// file: kb-lib/src/materializer/liquidity.rs
// version: 1
// version: 2
//! `liquidity` materializer family.
pub mod core;
mod core;

View File

@@ -1,10 +1,10 @@
// file: kb-lib/src/materializer/liquidity/core.rs
// version: 1
// version: 2
//! Migration boundary for legacy crate `kb_materializer_liquidity`.
/// Legacy crate name retained for migration and compatibility tracking.
pub const LEGACY_CRATE: &str = "kb_materializer_liquidity";
const LEGACY_CRATE: &str = "kb_materializer_liquidity";
/// Current porting status.
pub const MIGRATION_STATUS: &str = "source-preserved-pending-port";
const MIGRATION_STATUS: &str = "source-preserved-pending-port";

View File

@@ -1,6 +1,15 @@
// file: kb-lib/src/materializer/metadata.rs
// version: 1
// version: 2
//! `metadata` materializer family.
pub mod core;
mod core;
/// Token-2022 and Metaplex metadata snapshot materializer.
pub use self::core::MtMetadataMaterializer;
/// Materializes one canonical non-Metadata Metaplex account snapshot.
pub use self::core::materialize_metaplex_account_snapshot;
/// Materializes one canonical Metaplex Metadata account snapshot.
pub use self::core::materialize_metaplex_metadata_snapshot;
/// Materializes Token-2022 embedded metadata and group snapshots.
pub use self::core::materialize_token2022_metadata_snapshot;

View File

@@ -1,10 +1,565 @@
// file: kb-lib/src/materializer/metadata/core.rs
// version: 1
// version: 2
//! Migration boundary for legacy crate `kb_materializer_metadata`.
//! Stable Token-2022 and Metaplex metadata snapshot materializer.
/// Legacy crate name retained for migration and compatibility tracking.
pub const LEGACY_CRATE: &str = "kb_materializer_metadata";
/// Initial materializer implementation placeholder.
#[derive(Clone, Debug, Default)]
pub struct MtMetadataMaterializer;
/// Current porting status.
pub const MIGRATION_STATUS: &str = "source-preserved-pending-port";
impl crate::MtMaterializer for crate::MtMetadataMaterializer {
fn materializer_name(&self) -> &'static str {
return "kb_materializer_metadata";
}
fn materializer_version(&self) -> &'static str {
return env!("CARGO_PKG_VERSION");
}
fn accepts_event(&self, _event: &crate::MdDecodedProtocolEvent) -> bool {
return false;
}
fn materialize_event(
&self,
event: &crate::MdDecodedProtocolEvent,
) -> kb_core::Result<std::vec::Vec<crate::MdMaterializedEvent>> {
let materialized = crate::MdMaterializedEvent {
signature: event.signature.clone(),
slot: event.slot,
decoded_family: event.event_family,
materialized_family: crate::MdMaterializedEventFamily::Metadata,
};
return std::result::Result::Ok(std::vec![materialized]);
}
}
/// Materialize embedded Token-2022 metadata, group, and group-member extensions.
pub fn materialize_token2022_metadata_snapshot(
account_key: &str,
slot: u64,
state: &crate::DcToken2022State,
) -> std::result::Result<std::vec::Vec<crate::MtApiMaterializedOutput>, String> {
if account_key.is_empty() {
return std::result::Result::Err(
"Token-2022 metadata snapshot requires a non-empty account key".to_string(),
);
}
if state.kind != crate::DcToken2022StateKind::Mint {
return std::result::Result::Err(
"Token-2022 metadata snapshot requires a parsed mint state".to_string(),
);
}
let mut outputs = std::vec::Vec::new();
for entry in &state.extensions {
let projection_kind = match entry.extension_name {
"token_metadata" => std::option::Option::Some("token_metadata"),
"token_group" => std::option::Option::Some("token_group"),
"token_group_member" => std::option::Option::Some("token_group_member"),
_ => std::option::Option::None,
};
let kind = match projection_kind {
std::option::Option::Some(value) => value,
std::option::Option::None => continue,
};
outputs.push(crate::MtApiMaterializedOutput {
output_key: format!("token2022_metadata_snapshot:{account_key}:{slot}:{kind}"),
family: crate::MdMaterializedEventFamily::Metadata,
payload_json: serde_json::json!({
"projectionVersion": 1,
"domain": "spl_token2022_embedded_metadata",
"projectionSemantics": "authoritative_bounded_mint_extension_snapshot",
"idempotenceKey": format!("token2022_metadata:{account_key}:{slot}:{kind}"),
"accountKey": account_key,
"slot": slot,
"programId": kb_program_ids::SPL_TOKEN2022_PROGRAM_ID,
"metadataSource": "token2022_embedded_tlv",
"projectionKind": kind,
"extensionType": entry.extension_type,
"extensionName": entry.extension_name,
"valueHex": entry.value_hex,
"valueFields": entry.value_fields,
"externalUriFetched": false,
"identityAuthority": "mint_address",
"finalAccountStateCaptured": true,
"provenance": {
"processorName": "kb_materializer_metadata",
"processorVersion": env!("CARGO_PKG_VERSION"),
"source": "bounded_token2022_state_parser"
}
}),
});
}
return std::result::Result::Ok(outputs);
}
/// Materialize one canonical Metaplex `MetadataV1` account snapshot.
///
/// Account snapshots are authoritative for current state. Instruction observations may describe
/// mutations, but must not emit a second competing metadata-state projection.
pub fn materialize_metaplex_metadata_snapshot(
snapshot: &crate::DcMetadataMtmCanonicalMetadataAccountSnapshot,
) -> std::result::Result<crate::MtApiMaterializedOutput, String> {
if !snapshot.provenance.committed {
return std::result::Result::Err(
"Metaplex metadata state materialization requires committed provenance".to_string(),
);
}
if snapshot.identity.materialization
!= crate::DcMetadataMtmCanonicalMaterializationPolicy::ActiveState
{
return std::result::Result::Err(
"Metaplex MetadataV1 materialization requires active-state policy".to_string(),
);
}
let metadata = match &snapshot.state {
crate::DcMetadataMtmCanonicalMetadataAccountState::Metadata(value) => value,
_ => {
return std::result::Result::Err(
"Metaplex metadata materializer received another account layout".to_string(),
);
},
};
let slot = snapshot.provenance.slot.map(|value| return value.to_string());
let write_version = snapshot.provenance.write_version.map(|value| return value.to_string());
let payload = serde_json::json!({
"projectionVersion": 1,
"domain": "metaplex_token_metadata",
"projectionSemantics": "authoritative_committed_account_snapshot",
"stableId": snapshot.identity.stable_id,
"account": metadata.account,
"mint": metadata.mint,
"updateAuthority": metadata.update_authority,
"name": metadata.name,
"symbol": metadata.symbol,
"uri": metadata.uri,
"sellerFeeBasisPoints": metadata.seller_fee_basis_points,
"primarySaleHappened": metadata.primary_sale_happened,
"isMutable": metadata.is_mutable,
"layout": snapshot.identity.layout,
"layoutDiscriminant": snapshot.identity.discriminant,
"canonicalVersion": snapshot.identity.canonical_version,
"lifecycle": "active",
"materializationPolicy": "active_state",
"metadataSource": "metaplex_owned_account",
"accountState": metadata.payload_json,
"externalUriFetched": false,
"stateAuthority": "owned_account_snapshot",
"instructionProjectionRole": "mutation_observation_only_no_duplicate_state",
"provenance": {
"source": format!("{:?}", snapshot.provenance.source),
"slot": slot,
"writeVersion": write_version,
"signature": snapshot.provenance.signature,
"instructionIndex": snapshot.provenance.instruction_index,
"innerInstructionIndex": snapshot.provenance.inner_instruction_index,
"committed": snapshot.provenance.committed,
"processorName": "kb_materializer_metadata",
"processorVersion": env!("CARGO_PKG_VERSION")
}
});
return std::result::Result::Ok(crate::MtApiMaterializedOutput {
output_key: format!("metaplex_metadata_state:{}", metadata.account),
family: crate::MdMaterializedEventFamily::Metadata,
payload_json: payload,
});
}
#[cfg(test)]
mod tests_metaplex_metadata_state {
pub(super) fn snapshot(
committed: bool,
) -> crate::DcMetadataMtmCanonicalMetadataAccountSnapshot {
let metadata = crate::DcMetadataMtmMetadataAccountSnapshot {
account: "metadata111".to_string(),
bump: 1,
mint: "mint111".to_string(),
update_authority: "authority111".to_string(),
name: "Asset".to_string(),
symbol: "AST".to_string(),
uri: "https://example.invalid/asset.json".to_string(),
seller_fee_basis_points: 500,
primary_sale_happened: true,
is_mutable: false,
payload_json: serde_json::json!({
"creators": [{"address":"creator111","verified":true,"share":100}],
"collection": {"verified":true,"key":"collection111"},
"uses": {"useMethod":"Single","remaining":"0","total":"1"},
"tokenStandard": "ProgrammableNonFungible",
"programmableConfig": {"ruleSet":"rules111"}
}),
};
return crate::DcMetadataMtmCanonicalMetadataAccountSnapshot::from_metadata(
metadata,
crate::DcMetadataMtmCanonicalAccountProvenance {
source: crate::DcMetadataMtmCanonicalObservationSource::Backfill,
slot: std::option::Option::Some(42),
write_version: std::option::Option::Some(7),
signature: std::option::Option::Some("signature111".to_string()),
instruction_index: std::option::Option::Some(3),
inner_instruction_index: std::option::Option::None,
committed,
},
);
}
#[test]
fn account_snapshot_is_one_stable_authoritative_metadata_projection() {
let first = crate::materialize_metaplex_metadata_snapshot(&snapshot(true));
let second = crate::materialize_metaplex_metadata_snapshot(&snapshot(true));
assert_eq!(first, second);
assert_eq!(
first.as_ref().map(|value| return value.output_key.as_str()),
std::result::Result::Ok("metaplex_metadata_state:metadata111")
);
assert_eq!(
first.as_ref().map(|value| return value.payload_json["metadataSource"].clone()),
std::result::Result::Ok(serde_json::json!("metaplex_owned_account"))
);
assert_eq!(
first
.as_ref()
.map(|value| return value.payload_json["instructionProjectionRole"].clone()),
std::result::Result::Ok(serde_json::json!(
"mutation_observation_only_no_duplicate_state"
))
);
assert_eq!(
first
.as_ref()
.map(|value| return value.payload_json["accountState"]["creators"][0]["share"]
.clone()),
std::result::Result::Ok(serde_json::json!(100))
);
}
#[test]
fn uncommitted_or_non_metadata_state_is_rejected() {
assert!(crate::materialize_metaplex_metadata_snapshot(&snapshot(false)).is_err());
let mut wrong = snapshot(true);
wrong.state = crate::DcMetadataMtmCanonicalMetadataAccountState::EditionMarker(
crate::DcMetadataMtmEditionMarkerAccountSnapshot {
account: "marker111".to_string(),
bump: 1,
mint: "mint111".to_string(),
kind: crate::DcMetadataMtmEditionMarkerAccountKind::EditionMarkerV1,
marker_group: std::option::Option::Some(0),
edition: 1,
byte_index: 0,
bit_mask: 64,
edition_taken: true,
ledger: std::vec![64],
payload_json: serde_json::json!({}),
},
);
assert!(crate::materialize_metaplex_metadata_snapshot(&wrong).is_err());
}
}
/// Materialize any canonical Metaplex account state other than `MetadataV1`.
///
/// Every account address maps to one stable state key. Historical layouts remain materializable
/// with an explicit historical lifecycle, while execution policy remains independently disabled.
pub fn materialize_metaplex_account_snapshot(
snapshot: &crate::DcMetadataMtmCanonicalMetadataAccountSnapshot,
) -> std::result::Result<crate::MtApiMaterializedOutput, String> {
if !snapshot.provenance.committed {
return std::result::Result::Err(
"Metaplex account-state materialization requires committed provenance".to_string(),
);
}
let (projection_kind, fact_owner, state_json) = match &snapshot.state {
crate::DcMetadataMtmCanonicalMetadataAccountState::Metadata(_) => {
return std::result::Result::Err(
"MetadataV1 must use the dedicated metadata-state materializer".to_string(),
);
},
crate::DcMetadataMtmCanonicalMetadataAccountState::Edition(value) => {
("edition_state", "kb_materializer_metadata", value.payload_json.clone())
},
crate::DcMetadataMtmCanonicalMetadataAccountState::EditionMarker(value) => {
("edition_marker_state", "kb_materializer_metadata", value.payload_json.clone())
},
crate::DcMetadataMtmCanonicalMetadataAccountState::TokenRecord(value) => (
"programmable_token_state",
"kb_materializer_lifecycle",
value.payload_json.clone(),
),
crate::DcMetadataMtmCanonicalMetadataAccountState::DelegateRecord(value) => {
("delegate_admin_state", "kb_materializer_admin", value.payload_json.clone())
},
crate::DcMetadataMtmCanonicalMetadataAccountState::AuthorityRecord(value) => {
("authority_admin_state", "kb_materializer_admin", value.payload_json.clone())
},
crate::DcMetadataMtmCanonicalMetadataAccountState::TokenOwnedEscrow(value) => (
"token_owned_escrow_state",
"kb_materializer_lifecycle",
value.payload_json.clone(),
),
crate::DcMetadataMtmCanonicalMetadataAccountState::ReservationList(value) => (
"historical_reservation_state",
"kb_materializer_metadata",
value.payload_json.clone(),
),
};
let lifecycle = match snapshot.identity.lifecycle {
crate::DcMetadataMtmCanonicalAccountLifecycle::Active => "active",
crate::DcMetadataMtmCanonicalAccountLifecycle::ActiveExperimental => "active_experimental",
crate::DcMetadataMtmCanonicalAccountLifecycle::Historical => "historical",
};
let materialization_policy = match snapshot.identity.materialization {
crate::DcMetadataMtmCanonicalMaterializationPolicy::ActiveState => "active_state",
crate::DcMetadataMtmCanonicalMaterializationPolicy::HistoricalState => "historical_state",
};
let execution_policy = match snapshot.identity.execution {
crate::DcMetadataMtmCanonicalExecutionPolicy::CurrentCandidate => "current_candidate",
crate::DcMetadataMtmCanonicalExecutionPolicy::ExperimentalCandidate => {
"experimental_candidate"
},
crate::DcMetadataMtmCanonicalExecutionPolicy::NeverHistorical => "never_historical",
};
return std::result::Result::Ok(crate::MtApiMaterializedOutput {
output_key: format!("metaplex_account_state:{}", snapshot.identity.account),
family: crate::MdMaterializedEventFamily::Metadata,
payload_json: serde_json::json!({
"projectionVersion": 1,
"domain": "metaplex_token_metadata",
"projectionKind": projection_kind,
"projectionSemantics": "authoritative_committed_owned_account_snapshot",
"stableId": snapshot.identity.stable_id,
"account": snapshot.identity.account,
"programId": snapshot.identity.program_id,
"layout": snapshot.identity.layout,
"layoutDiscriminant": snapshot.identity.discriminant,
"canonicalVersion": snapshot.identity.canonical_version,
"lifecycle": lifecycle,
"materializationPolicy": materialization_policy,
"executionPolicy": execution_policy,
"factOwner": fact_owner,
"metadataSource": "metaplex_owned_account",
"accountState": state_json,
"instructionProjectionRole": "mutation_observation_only_no_duplicate_state",
"provenance": {
"source": format!("{:?}", snapshot.provenance.source),
"slot": snapshot.provenance.slot.map(|value: u64| return value.to_string()),
"writeVersion": snapshot.provenance.write_version.map(|value: u64| return value.to_string()),
"signature": snapshot.provenance.signature,
"instructionIndex": snapshot.provenance.instruction_index,
"innerInstructionIndex": snapshot.provenance.inner_instruction_index,
"committed": snapshot.provenance.committed,
"processorName": "kb_materializer_metadata",
"processorVersion": env!("CARGO_PKG_VERSION")
}
}),
});
}
#[cfg(test)]
mod tests_metaplex_account_state {
fn provenance() -> crate::DcMetadataMtmCanonicalAccountProvenance {
return crate::DcMetadataMtmCanonicalAccountProvenance {
source: crate::DcMetadataMtmCanonicalObservationSource::Backfill,
slot: std::option::Option::Some(90),
write_version: std::option::Option::Some(2),
signature: std::option::Option::None,
instruction_index: std::option::Option::None,
inner_instruction_index: std::option::Option::None,
committed: true,
};
}
#[test]
fn historical_edition_and_reservation_are_stable_materializable_state() {
let edition = crate::DcMetadataMtmCanonicalMetadataAccountSnapshot::from_edition(
crate::DcMetadataMtmEditionAccountSnapshot {
account: "master-v1".to_string(),
bump: 1,
mint: "mint-v1".to_string(),
kind: crate::DcMetadataMtmEditionAccountKind::MasterEditionV1,
supply: std::option::Option::Some(3),
max_supply: std::option::Option::Some(10),
parent: std::option::Option::None,
edition: std::option::Option::None,
printing_mint: std::option::Option::Some("printing".to_string()),
one_time_printing_authorization_mint: std::option::Option::Some(
"authorization".to_string(),
),
payload_json: serde_json::json!({"supply":"3","maxSupply":"10"}),
},
provenance(),
);
let reservation =
crate::DcMetadataMtmCanonicalMetadataAccountSnapshot::from_reservation_list(
crate::DcMetadataMtmReservationListAccountSnapshot {
account: "reservation-v2".to_string(),
bump: 2,
kind: crate::DcMetadataMtmReservationListAccountKind::V2,
master_edition: "master-v1".to_string(),
resource: "resource".to_string(),
supply_snapshot: std::option::Option::Some(3),
reservations: std::vec::Vec::new(),
total_reservation_spots: 0,
current_reservation_spots: 0,
payload_json: serde_json::json!({"reservations":[]}),
},
provenance(),
);
let first = crate::materialize_metaplex_account_snapshot(&edition);
let second = crate::materialize_metaplex_account_snapshot(&reservation);
assert_eq!(
first.as_ref().map(|value| return value.payload_json["lifecycle"].clone()),
std::result::Result::Ok(serde_json::json!("historical"))
);
assert_eq!(
first.as_ref().map(|value| return value.payload_json["executionPolicy"].clone()),
std::result::Result::Ok(serde_json::json!("never_historical"))
);
assert_eq!(
second.as_ref().map(|value| return value.output_key.as_str()),
std::result::Result::Ok("metaplex_account_state:reservation-v2")
);
}
#[test]
fn programmable_delegate_authority_and_escrow_keep_unique_fact_owners() {
let token = crate::DcMetadataMtmCanonicalMetadataAccountSnapshot::from_token_record(
crate::DcMetadataMtmTokenRecordAccountSnapshot {
account: "token-record".to_string(),
bump: 1,
mint: "mint".to_string(),
token: "token".to_string(),
stored_bump: 1,
state: "Locked".to_string(),
rule_set_revision: std::option::Option::Some(4),
delegate: std::option::Option::Some("delegate".to_string()),
delegate_role: std::option::Option::Some("Transfer".to_string()),
locked_transfer: std::option::Option::Some("destination".to_string()),
locked: true,
payload_json: serde_json::json!({"state":"Locked"}),
},
provenance(),
);
let output = crate::materialize_metaplex_account_snapshot(&token);
assert_eq!(
output.as_ref().map(|value| return value.payload_json["factOwner"].clone()),
std::result::Result::Ok(serde_json::json!("kb_materializer_lifecycle"))
);
assert_eq!(
output.as_ref().map(|value| return value.payload_json["projectionKind"].clone()),
std::result::Result::Ok(serde_json::json!("programmable_token_state"))
);
}
#[test]
fn uncommitted_and_metadata_snapshots_use_explicit_boundaries() {
let mut marker = crate::DcMetadataMtmCanonicalMetadataAccountSnapshot::from_edition_marker(
crate::DcMetadataMtmEditionMarkerAccountSnapshot {
account: "marker".to_string(),
bump: 1,
mint: "mint".to_string(),
kind: crate::DcMetadataMtmEditionMarkerAccountKind::EditionMarkerV2,
marker_group: std::option::Option::None,
edition: 8,
byte_index: 1,
bit_mask: 128,
edition_taken: true,
ledger: std::vec![0, 128],
payload_json: serde_json::json!({"editionTaken":true}),
},
provenance(),
);
marker.provenance.committed = false;
assert!(crate::materialize_metaplex_account_snapshot(&marker).is_err());
assert!(
crate::materialize_metaplex_account_snapshot(
&super::tests_metaplex_metadata_state::snapshot(true)
)
.is_err()
);
}
}
#[cfg(test)]
mod tests_token2022_state {
#[test]
fn embedded_metadata_group_and_member_have_distinct_stable_outputs() {
let state = crate::DcToken2022State {
kind: crate::DcToken2022StateKind::Mint,
base_fields: serde_json::json!({"supply":"1","decimals":0}),
base_hex: "00".repeat(82),
account_type: std::option::Option::Some(1),
extensions: std::vec![
crate::DcToken2022TlvEntry {
extension_type: 19,
extension_name: "token_metadata",
value_hex: "01".to_string(),
value_fields: serde_json::json!({"name":"Token","symbol":"TOK","uri":"https://example.invalid/token.json"}),
},
crate::DcToken2022TlvEntry {
extension_type: 20,
extension_name: "token_group",
value_hex: "02".to_string(),
value_fields: serde_json::json!({"mint":"mint111","size":"1","maxSize":"10"}),
},
crate::DcToken2022TlvEntry {
extension_type: 21,
extension_name: "token_group_member",
value_hex: "03".to_string(),
value_fields: serde_json::json!({"mint":"mint111","group":"group111","memberNumber":"1"}),
},
],
};
let outputs = crate::materialize_token2022_metadata_snapshot("mint111", 9, &state);
assert_eq!(outputs.as_ref().map(|values| return values.len()), std::result::Result::Ok(3));
assert_eq!(
outputs
.as_ref()
.map(|values| return values[0].payload_json["metadataSource"].clone()),
std::result::Result::Ok(serde_json::json!("token2022_embedded_tlv"))
);
assert_eq!(
outputs
.as_ref()
.map(|values| return values[0].payload_json["externalUriFetched"].clone()),
std::result::Result::Ok(serde_json::json!(false))
);
assert_eq!(
outputs
.as_ref()
.map(|values| return values[1].payload_json["projectionKind"].clone()),
std::result::Result::Ok(serde_json::json!("token_group"))
);
}
#[test]
fn snapshot_is_idempotent_filters_unrelated_extensions_and_requires_mint_identity() {
let state = crate::DcToken2022State {
kind: crate::DcToken2022StateKind::Mint,
base_fields: serde_json::json!({}),
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: "00".to_string(),
value_fields: serde_json::json!({}),
}],
};
let first = crate::materialize_token2022_metadata_snapshot("mint111", 9, &state);
let second = crate::materialize_token2022_metadata_snapshot("mint111", 9, &state);
assert_eq!(first, second);
assert_eq!(first.as_ref().map(|values| return values.len()), std::result::Result::Ok(0));
assert!(crate::materialize_token2022_metadata_snapshot("", 9, &state).is_err());
let account_state = crate::DcToken2022State {
kind: crate::DcToken2022StateKind::Account,
base_fields: serde_json::json!({}),
base_hex: "00".repeat(165),
account_type: std::option::Option::None,
extensions: std::vec::Vec::new(),
};
assert!(
crate::materialize_token2022_metadata_snapshot("account111", 9, &account_state)
.is_err()
);
}
}

View File

@@ -1,6 +1,6 @@
// file: kb-lib/src/materializer/nft.rs
// version: 1
// version: 2
//! `nft` materializer family.
pub mod core;
mod core;

View File

@@ -1,10 +1,10 @@
// file: kb-lib/src/materializer/nft/core.rs
// version: 1
// version: 2
//! Migration boundary for legacy crate `kb_materializer_nft`.
/// Legacy crate name retained for migration and compatibility tracking.
pub const LEGACY_CRATE: &str = "kb_materializer_nft";
const LEGACY_CRATE: &str = "kb_materializer_nft";
/// Current porting status.
pub const MIGRATION_STATUS: &str = "source-preserved-pending-port";
const MIGRATION_STATUS: &str = "source-preserved-pending-port";

View File

@@ -1,6 +1,6 @@
// file: kb-lib/src/materializer/oracle.rs
// version: 1
// version: 2
//! `oracle` materializer family.
pub mod core;
mod core;

View File

@@ -1,10 +1,10 @@
// file: kb-lib/src/materializer/oracle/core.rs
// version: 1
// version: 2
//! Migration boundary for legacy crate `kb_materializer_oracle`.
/// Legacy crate name retained for migration and compatibility tracking.
pub const LEGACY_CRATE: &str = "kb_materializer_oracle";
const LEGACY_CRATE: &str = "kb_materializer_oracle";
/// Current porting status.
pub const MIGRATION_STATUS: &str = "source-preserved-pending-port";
const MIGRATION_STATUS: &str = "source-preserved-pending-port";

View File

@@ -1,6 +1,6 @@
// file: kb-lib/src/materializer/orderbook.rs
// version: 1
// version: 2
//! `orderbook` materializer family.
pub mod core;
mod core;

View File

@@ -1,10 +1,10 @@
// file: kb-lib/src/materializer/orderbook/core.rs
// version: 1
// version: 2
//! Migration boundary for legacy crate `kb_materializer_orderbook`.
/// Legacy crate name retained for migration and compatibility tracking.
pub const LEGACY_CRATE: &str = "kb_materializer_orderbook";
const LEGACY_CRATE: &str = "kb_materializer_orderbook";
/// Current porting status.
pub const MIGRATION_STATUS: &str = "source-preserved-pending-port";
const MIGRATION_STATUS: &str = "source-preserved-pending-port";

View File

@@ -1,6 +1,6 @@
// file: kb-lib/src/materializer/perpetuals.rs
// version: 1
// version: 2
//! `perpetuals` materializer family.
pub mod core;
mod core;

View File

@@ -1,10 +1,10 @@
// file: kb-lib/src/materializer/perpetuals/core.rs
// version: 1
// version: 2
//! Migration boundary for legacy crate `kb_materializer_perpetuals`.
/// Legacy crate name retained for migration and compatibility tracking.
pub const LEGACY_CRATE: &str = "kb_materializer_perpetuals";
const LEGACY_CRATE: &str = "kb_materializer_perpetuals";
/// Current porting status.
pub const MIGRATION_STATUS: &str = "source-preserved-pending-port";
const MIGRATION_STATUS: &str = "source-preserved-pending-port";

View File

@@ -1,6 +1,6 @@
// file: kb-lib/src/materializer/pool.rs
// version: 1
// version: 2
//! `pool` materializer family.
pub mod state;
mod state;

View File

@@ -1,10 +1,10 @@
// file: kb-lib/src/materializer/pool/state.rs
// version: 1
// version: 2
//! Migration boundary for legacy crate `kb_materializer_pool_state`.
/// Legacy crate name retained for migration and compatibility tracking.
pub const LEGACY_CRATE: &str = "kb_materializer_pool_state";
const LEGACY_CRATE: &str = "kb_materializer_pool_state";
/// Current porting status.
pub const MIGRATION_STATUS: &str = "source-preserved-pending-port";
const MIGRATION_STATUS: &str = "source-preserved-pending-port";

View File

@@ -1,6 +1,6 @@
// file: kb-lib/src/materializer/rewards.rs
// version: 1
// version: 2
//! `rewards` materializer family.
pub mod core;
mod core;

View File

@@ -1,10 +1,10 @@
// file: kb-lib/src/materializer/rewards/core.rs
// version: 1
// version: 2
//! Migration boundary for legacy crate `kb_materializer_rewards`.
/// Legacy crate name retained for migration and compatibility tracking.
pub const LEGACY_CRATE: &str = "kb_materializer_rewards";
const LEGACY_CRATE: &str = "kb_materializer_rewards";
/// Current porting status.
pub const MIGRATION_STATUS: &str = "source-preserved-pending-port";
const MIGRATION_STATUS: &str = "source-preserved-pending-port";

View File

@@ -1,6 +1,17 @@
// file: kb-lib/src/materializer/risk.rs
// version: 1
// version: 2
//! `risk` materializer family.
pub mod core;
mod constants;
mod core;
/// Stable processor name for SPL Token and ATA risk facts.
pub(crate) use self::constants::MT_RISK_PROCESSOR_NAME;
/// Stable projection contract version for SPL Token and ATA risk facts.
pub(crate) use self::constants::MT_RISK_PROJECTION_VERSION;
/// Canonical tracing target for SPL Token and ATA risk facts.
pub(crate) use self::constants::MT_RISK_TRACING_TARGET;
/// Stable SPL Token and ATA risk materializer.
pub use self::core::MtRiskMaterializer;

View File

@@ -0,0 +1,11 @@
// file: kb-lib/src/materializer/risk/constants.rs
// version: 1
//! Local constants for fact-only risk projections.
/// Stable processor name persisted by the replay ledger.
pub(crate) const MT_RISK_PROCESSOR_NAME: &str = "spl_token_risk";
/// Stable projection payload contract version.
pub(crate) const MT_RISK_PROJECTION_VERSION: u32 = 2;
/// Canonical tracing target for this component.
pub(crate) const MT_RISK_TRACING_TARGET: &str = "kb-lib.materializer.risk";

View File

@@ -1,10 +1,641 @@
// file: kb-lib/src/materializer/risk/core.rs
// version: 1
// version: 2
//! Migration boundary for legacy crate `kb_materializer_risk`.
//! Stable fact-only risk projections for committed SPL Token and ATA observations.
/// Legacy crate name retained for migration and compatibility tracking.
pub const LEGACY_CRATE: &str = "kb_materializer_risk";
const ACCEPTED_FAMILIES: &[crate::MdEventFamily] = &[
crate::MdEventFamily::TokenAccount,
crate::MdEventFamily::TokenMint,
crate::MdEventFamily::Admin,
crate::MdEventFamily::Lifecycle,
crate::MdEventFamily::Audit,
];
const RISK_ENTRIES: &[&str] = &[
"approve",
"approve_checked",
"revoke",
"freeze_account",
"thaw_account",
"set_authority",
"initialize_multisig",
"initialize_multisig2",
"initialize_default_account_state",
"update_default_account_state",
"enable_required_transfer_memos",
"disable_required_transfer_memos",
"enable_cpi_guard",
"disable_cpi_guard",
"initialize_non_transferable_mint",
"initialize_pausable_config",
"pause",
"resume",
"initialize_permissioned_burn",
"enable_confidential_credits",
"disable_confidential_credits",
"enable_non_confidential_credits",
"disable_non_confidential_credits",
];
/// Current porting status.
pub const MIGRATION_STATUS: &str = "source-preserved-pending-port";
/// Stable fact-only classic SPL Token risk materializer.
#[derive(Clone, Debug, Default)]
pub struct MtRiskMaterializer;
impl crate::MtMaterializer for crate::MtRiskMaterializer {
fn materializer_name(&self) -> &'static str {
return "kb_materializer_risk";
}
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<std::vec::Vec<crate::MdMaterializedEvent>> {
return std::result::Result::Ok(std::vec::Vec::new());
}
}
impl crate::MtApiEventMaterializer for crate::MtRiskMaterializer {
fn identity(&self) -> crate::MtApiMaterializerIdentity {
return crate::MtApiMaterializerIdentity {
name: crate::MT_RISK_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_risk_fact_refused",
"failed or uncommitted SPL Token or ATA observations cannot create risk facts",
);
}
let ata = is_ata_recovery(&observation.event);
if ata && !ata_addresses_valid(&observation.payload_json) {
return crate::MtApiMaterializerExecutionResult::refused(
"invalid_nested_ata_risk_fact_refused",
"nested ATA risk fact requires all three canonical address validations",
);
}
let operation = observation.event.event_name.0.as_str();
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 addresses = observation
.payload_json
.get("addresses")
.cloned()
.unwrap_or(serde_json::Value::Null);
let risk_kind = match risk_kind(operation, &parameters, &accounts) {
std::option::Option::Some(value) => value,
std::option::Option::None => {
return crate::MtApiMaterializerExecutionResult::ignored();
},
};
let output = crate::MtApiMaterializedOutput {
output_key: if ata {
format!("ata_risk_fact:{risk_kind}:0")
} else {
format!("spl_token_risk_fact:{risk_kind}:0")
},
family: crate::MdMaterializedEventFamily::Risk,
payload_json: serde_json::json!({
"projectionVersion": crate::MT_RISK_PROJECTION_VERSION,
"domain": if ata { "spl_associated_token_account_risk_fact" } else { "spl_token_risk_fact" },
"riskKind": risk_kind,
"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(),
"parameters": parameters,
"accounts": accounts,
"authority": authority,
"addresses": addresses,
"interpretation": {
"score": serde_json::Value::Null,
"severity": serde_json::Value::Null,
"factOnly": true
},
"committed": true,
"provenance": {
"processorName": crate::MT_RISK_PROCESSOR_NAME,
"processorVersion": env!("CARGO_PKG_VERSION"),
"sourceEventKey": observation.event_key.clone()
}
}),
};
tracing::debug!(target: crate::MT_RISK_TRACING_TARGET, action = "materialize_token_risk_fact", risk_kind, operation, ata, "materialize committed SPL Token or ATA risk fact");
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_risk = token_program && RISK_ENTRIES.contains(&event.event_name.0.as_str());
return ACCEPTED_FAMILIES.contains(&event.event_family)
&& (token_risk || is_ata_recovery(event));
}
fn is_ata_recovery(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
&& event.event_name.0 == "recover_nested";
}
fn ata_addresses_valid(payload: &serde_json::Value) -> bool {
let addresses = match payload.get("addresses") {
std::option::Option::Some(value) => value,
std::option::Option::None => return false,
};
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)
);
return token_supported
&& token_program_exact
&& [
"ownerAssociatedTokenAccount",
"nestedAssociatedTokenAccount",
"walletNestedMintAssociatedTokenAccount",
]
.iter()
.all(|field| {
let address = match addresses.get(*field) {
std::option::Option::Some(value) => value,
std::option::Option::None => return false,
};
return address
.get("observed")
.and_then(serde_json::Value::as_str)
.is_some_and(|value| return !value.is_empty())
&& address.get("valid").and_then(serde_json::Value::as_bool)
== std::option::Option::Some(true);
});
}
fn risk_kind(
operation: &str,
parameters: &serde_json::Value,
accounts: &serde_json::Value,
) -> std::option::Option<&'static str> {
return match operation {
"recover_nested" => std::option::Option::Some("nested_ata_anti_pattern_recovered"),
"approve" | "approve_checked" => std::option::Option::Some("delegation_allowance_granted"),
"revoke" => std::option::Option::Some("delegation_revoked"),
"freeze_account" => std::option::Option::Some("account_frozen"),
"thaw_account" => std::option::Option::Some("account_thawed"),
"set_authority"
if parameters.get("newAuthority").is_some_and(serde_json::Value::is_null) =>
{
std::option::Option::Some("authority_revoked")
},
"set_authority" => std::option::Option::Some("authority_changed"),
"initialize_default_account_state" | "update_default_account_state" => {
std::option::Option::Some("default_account_state_configured")
},
"enable_required_transfer_memos" => {
std::option::Option::Some("required_transfer_memos_enabled")
},
"disable_required_transfer_memos" => {
std::option::Option::Some("required_transfer_memos_disabled")
},
"enable_cpi_guard" => std::option::Option::Some("cpi_guard_enabled"),
"disable_cpi_guard" => std::option::Option::Some("cpi_guard_disabled"),
"initialize_non_transferable_mint" => {
std::option::Option::Some("non_transferable_mint_configured")
},
"initialize_pausable_config" => std::option::Option::Some("pausable_mint_configured"),
"pause" => std::option::Option::Some("mint_activity_paused"),
"resume" => std::option::Option::Some("mint_activity_resumed"),
"initialize_permissioned_burn" => std::option::Option::Some("permissioned_burn_configured"),
"enable_confidential_credits" => std::option::Option::Some("confidential_credits_enabled"),
"disable_confidential_credits" => {
std::option::Option::Some("confidential_credits_disabled")
},
"enable_non_confidential_credits" => {
std::option::Option::Some("non_confidential_credits_enabled")
},
"disable_non_confidential_credits" => {
std::option::Option::Some("non_confidential_credits_disabled")
},
"initialize_multisig" | "initialize_multisig2" => {
let m = parameters.get("m").and_then(serde_json::Value::as_u64).unwrap_or(0);
let n = role_count(accounts, "multisig_member");
if m == 1 && n > 1 {
std::option::Option::Some("weak_multisig_threshold")
} else {
std::option::Option::None
}
},
_ => std::option::Option::None,
};
}
fn role_count(accounts: &serde_json::Value, role: &str) -> usize {
return accounts
.as_array()
.map(|rows| {
return rows
.iter()
.filter(|row| {
return row.get("role").and_then(serde_json::Value::as_str)
== std::option::Option::Some(role);
})
.count();
})
.unwrap_or(0);
}
#[cfg(test)]
mod tests {
fn observation(
entry: &str,
family: crate::MdEventFamily,
parameters: serde_json::Value,
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(1),
instruction_path: crate::MdInstructionPath("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::Instruction,
confidence: crate::MdDecoderConfidence::ManualExact,
},
payload_json: serde_json::json!({"parameters":parameters,"accounts":[{"role":"multisig_member"},{"role":"multisig_member"}],"authority":{}}),
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()],
},
};
}
fn token2022_observation(
entry: &str,
family: crate::MdEventFamily,
parameters: serde_json::Value,
failed: bool,
) -> crate::DcApiDecodedObservation {
let mut observation = observation(entry, family, parameters, 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 nested_ata_observation(failed: bool, valid: bool) -> crate::DcApiDecodedObservation {
return crate::DcApiDecodedObservation {
event_key: "ata:recover_nested:0".to_string(),
event: crate::MdDecodedProtocolEvent {
signature: crate::MdSignature("signature".to_string()),
slot: crate::MdSlot(2),
instruction_path: crate::MdInstructionPath("1/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(
"spl_associated_token_account.recover_nested".to_string(),
),
event_name: crate::MdEventName("recover_nested".to_string()),
event_family: crate::MdEventFamily::Lifecycle,
source_kind: crate::MdEventSourceKind::InnerInstruction,
confidence: crate::MdDecoderConfidence::ManualExact,
},
payload_json: serde_json::json!({
"addresses": {
"tokenProgram": {
"programId": kb_program_ids::SPL_TOKEN2022_PROGRAM_ID,
"kind": "token2022",
"supported": true
},
"ownerAssociatedTokenAccount": {"observed":"ownerAta","valid":valid},
"nestedAssociatedTokenAccount": {"observed":"nestedAta","valid":valid},
"walletNestedMintAssociatedTokenAccount": {"observed":"destinationAta","valid":valid}
}
}),
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 approval_and_authority_revocation_are_fact_only_without_score() {
for (entry, family, parameters, expected) in [
(
"approve_checked",
crate::MdEventFamily::TokenAccount,
serde_json::json!({"amountRaw":"10"}),
"delegation_allowance_granted",
),
(
"set_authority",
crate::MdEventFamily::Admin,
serde_json::json!({"newAuthority":null}),
"authority_revoked",
),
] {
let result = crate::MtApiEventMaterializer::materialize(
&crate::MtRiskMaterializer,
&observation(entry, family, parameters, false),
);
assert_eq!(result.outputs[0].payload_json["riskKind"], expected);
assert!(result.outputs[0].payload_json["interpretation"]["score"].is_null());
}
}
#[test]
fn token2022_revocation_is_owned_once_without_an_invented_score() {
let observation = token2022_observation(
"set_authority",
crate::MdEventFamily::Admin,
serde_json::json!({"newAuthority":null}),
false,
);
let result =
crate::MtApiEventMaterializer::materialize(&crate::MtRiskMaterializer, &observation);
assert_eq!(result.status, crate::MtApiMaterializerOutcomeStatus::Inserted);
assert_eq!(result.outputs.len(), 1);
assert_eq!(result.outputs[0].payload_json["riskKind"], "authority_revoked");
assert!(result.outputs[0].payload_json["interpretation"]["score"].is_null());
}
#[test]
fn only_structurally_weak_multisig_creates_risk_fact() {
let weak = observation(
"initialize_multisig2",
crate::MdEventFamily::Admin,
serde_json::json!({"m":1}),
false,
);
let strong = observation(
"initialize_multisig2",
crate::MdEventFamily::Admin,
serde_json::json!({"m":2}),
false,
);
assert_eq!(
crate::MtApiEventMaterializer::materialize(&crate::MtRiskMaterializer, &weak).status,
crate::MtApiMaterializerOutcomeStatus::Inserted
);
assert_eq!(
crate::MtApiEventMaterializer::materialize(&crate::MtRiskMaterializer, &strong).status,
crate::MtApiMaterializerOutcomeStatus::Ignored
);
}
#[test]
fn failed_risk_fact_is_refused() {
let failed = observation(
"freeze_account",
crate::MdEventFamily::TokenAccount,
serde_json::json!({}),
true,
);
assert_eq!(
crate::MtApiEventMaterializer::materialize(&crate::MtRiskMaterializer, &failed).status,
crate::MtApiMaterializerOutcomeStatus::Refused
);
}
#[test]
fn committed_nested_ata_recovery_creates_one_fact_without_score() {
let result = crate::MtApiEventMaterializer::materialize(
&crate::MtRiskMaterializer,
&nested_ata_observation(false, true),
);
assert_eq!(result.status, crate::MtApiMaterializerOutcomeStatus::Inserted);
assert_eq!(result.outputs.len(), 1);
assert_eq!(
result.outputs[0].output_key,
"ata_risk_fact:nested_ata_anti_pattern_recovered:0"
);
assert_eq!(result.outputs[0].payload_json["riskKind"], "nested_ata_anti_pattern_recovered");
assert_eq!(
result.outputs[0].payload_json["domain"],
"spl_associated_token_account_risk_fact"
);
assert!(result.outputs[0].payload_json["interpretation"]["score"].is_null());
assert!(result.outputs[0].payload_json["interpretation"]["severity"].is_null());
}
#[test]
fn failed_or_invalid_nested_ata_recovery_creates_no_risk_fact() {
for observation in
[nested_ata_observation(true, true), nested_ata_observation(false, false)]
{
let result = crate::MtApiEventMaterializer::materialize(
&crate::MtRiskMaterializer,
&observation,
);
assert_eq!(result.status, crate::MtApiMaterializerOutcomeStatus::Refused);
assert!(result.outputs.is_empty());
}
}
#[test]
fn ata_matrix_declares_the_single_compiled_risk_fact() {
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["instructions"].as_array() {
std::option::Option::Some(value) => value,
std::option::Option::None => panic!("ATA matrix instructions are absent"),
};
let recover = match instructions.iter().find(|row| {
return row.get("name").and_then(serde_json::Value::as_str)
== std::option::Option::Some("recover_nested");
}) {
std::option::Option::Some(value) => value,
std::option::Option::None => panic!("RecoverNested matrix entry is absent"),
};
assert!(recover["authorizedProjections"].as_array().is_some_and(|values| {
return values.iter().any(|value| {
return value == "risk:nested_ata_anti_pattern_recovered";
});
}));
assert_eq!(
matrix["materializationContract"]["projectionOwners"][1]["facts"],
serde_json::json!(["nested_ata_anti_pattern_recovered"])
);
}
#[test]
fn token2022_compliance_toggles_are_projected_as_fact_only_risk_rows() {
for (entry, expected) in [
("initialize_default_account_state", "default_account_state_configured"),
("enable_required_transfer_memos", "required_transfer_memos_enabled"),
("disable_required_transfer_memos", "required_transfer_memos_disabled"),
("enable_cpi_guard", "cpi_guard_enabled"),
("disable_cpi_guard", "cpi_guard_disabled"),
] {
let result = crate::MtApiEventMaterializer::materialize(
&crate::MtRiskMaterializer,
&token2022_observation(
entry,
crate::MdEventFamily::Audit,
serde_json::json!({}),
false,
),
);
assert_eq!(result.status, crate::MtApiMaterializerOutcomeStatus::Inserted);
assert_eq!(result.outputs[0].payload_json["riskKind"], expected);
assert_eq!(result.outputs[0].payload_json["interpretation"]["factOnly"], true);
assert!(result.outputs[0].payload_json["interpretation"]["score"].is_null());
}
}
#[test]
fn token2022_restriction_and_pause_facts_have_unique_stable_kinds() {
for (entry, family, expected) in [
(
"initialize_non_transferable_mint",
crate::MdEventFamily::TokenMint,
"non_transferable_mint_configured",
),
(
"initialize_pausable_config",
crate::MdEventFamily::TokenAccount,
"pausable_mint_configured",
),
("pause", crate::MdEventFamily::TokenAccount, "mint_activity_paused"),
("resume", crate::MdEventFamily::TokenAccount, "mint_activity_resumed"),
(
"initialize_permissioned_burn",
crate::MdEventFamily::TokenAccount,
"permissioned_burn_configured",
),
] {
let result = crate::MtApiEventMaterializer::materialize(
&crate::MtRiskMaterializer,
&token2022_observation(entry, family, serde_json::json!({}), false),
);
assert_eq!(result.outputs[0].payload_json["riskKind"], expected);
}
}
#[test]
fn confidential_credit_toggles_are_fact_only_and_keep_wire_parameters() {
for (entry, credit_kind, enabled, expected) in [
(
"enable_confidential_credits",
"confidential",
true,
"confidential_credits_enabled",
),
(
"disable_confidential_credits",
"confidential",
false,
"confidential_credits_disabled",
),
(
"enable_non_confidential_credits",
"non_confidential",
true,
"non_confidential_credits_enabled",
),
(
"disable_non_confidential_credits",
"non_confidential",
false,
"non_confidential_credits_disabled",
),
] {
let result = crate::MtApiEventMaterializer::materialize(
&crate::MtRiskMaterializer,
&token2022_observation(
entry,
crate::MdEventFamily::TokenAccount,
serde_json::json!({
"extension":"confidential_transfer",
"creditKind":credit_kind,
"enabled":enabled,
}),
false,
),
);
assert_eq!(result.status, crate::MtApiMaterializerOutcomeStatus::Inserted);
assert_eq!(result.outputs.len(), 1);
assert_eq!(result.outputs[0].payload_json["riskKind"], expected);
assert_eq!(result.outputs[0].payload_json["parameters"]["creditKind"], credit_kind);
assert_eq!(result.outputs[0].payload_json["parameters"]["enabled"], enabled);
assert_eq!(result.outputs[0].payload_json["interpretation"]["factOnly"], true);
assert!(result.outputs[0].payload_json["interpretation"]["score"].is_null());
}
}
}

View File

@@ -1,6 +1,6 @@
// file: kb-lib/src/materializer/routing.rs
// version: 1
// version: 2
//! `routing` materializer family.
pub mod core;
mod core;

View File

@@ -1,10 +1,10 @@
// file: kb-lib/src/materializer/routing/core.rs
// version: 1
// version: 2
//! Migration boundary for legacy crate `kb_materializer_routing`.
/// Legacy crate name retained for migration and compatibility tracking.
pub const LEGACY_CRATE: &str = "kb_materializer_routing";
const LEGACY_CRATE: &str = "kb_materializer_routing";
/// Current porting status.
pub const MIGRATION_STATUS: &str = "source-preserved-pending-port";
const MIGRATION_STATUS: &str = "source-preserved-pending-port";

View File

@@ -1,12 +1,13 @@
// file: kb-lib/src/materializer/staking.rs
// version: 2
// version: 3
//! `staking` materializer family.
mod constants;
pub mod core;
mod core;
pub(crate) use crate::materializer::staking::constants::TRACING_TARGET as MATERIALIZER_STAKING_TRACING_TARGET;
/// Canonical tracing target for the native stake and vote materializer.
pub(crate) use self::constants::MT_STAKING_TRACING_TARGET;
/// Stable native stake and vote materializer.
pub use crate::materializer::staking::core::StakingMaterializer;
pub use self::core::MtStakingMaterializer;

View File

@@ -4,4 +4,4 @@
//! Local constants for the native stake and vote materializer.
/// Canonical tracing target for this component.
pub(crate) const TRACING_TARGET: &str = "kb-lib.materializer.staking";
pub(crate) const MT_STAKING_TRACING_TARGET: &str = "kb-lib.materializer.staking";

View File

@@ -3,11 +3,11 @@
//! Instruction-level staking projections for native Stake and Vote observations.
const ACCEPTED_FAMILIES: &[crate::EventFamily] = &[
crate::EventFamily::Lifecycle,
crate::EventFamily::Admin,
crate::EventFamily::Staking,
crate::EventFamily::Reward,
const ACCEPTED_FAMILIES: &[crate::MdEventFamily] = &[
crate::MdEventFamily::Lifecycle,
crate::MdEventFamily::Admin,
crate::MdEventFamily::Staking,
crate::MdEventFamily::Reward,
];
const STAKE_SURFACE: &str = "solana_native_stake";
const VOTE_SURFACE: &str = "solana_native_vote";
@@ -82,10 +82,10 @@ impl ProjectionKind {
};
}
fn family(self) -> crate::MaterializedEventFamily {
fn family(self) -> crate::MdMaterializedEventFamily {
return match self {
Self::VoteReward => crate::MaterializedEventFamily::Reward,
_ => crate::MaterializedEventFamily::Staking,
Self::VoteReward => crate::MdMaterializedEventFamily::Reward,
_ => crate::MdMaterializedEventFamily::Staking,
};
}
@@ -96,9 +96,9 @@ impl ProjectionKind {
/// Stable staking materializer for committed native Stake and Vote observations.
#[derive(Clone, Debug, Default)]
pub struct StakingMaterializer;
pub struct MtStakingMaterializer;
impl crate::Materializer for crate::StakingMaterializer {
impl crate::MtMaterializer for crate::MtStakingMaterializer {
fn materializer_name(&self) -> &'static str {
return "kb_materializer_staking";
}
@@ -107,35 +107,35 @@ impl crate::Materializer for crate::StakingMaterializer {
return env!("CARGO_PKG_VERSION");
}
fn accepts_event(&self, event: &crate::DecodedProtocolEvent) -> bool {
fn accepts_event(&self, event: &crate::MdDecodedProtocolEvent) -> bool {
return family_is_accepted(event.event_family)
&& projection(event.surface_code.0.as_str(), event.event_name.0.as_str()).is_some();
}
fn materialize_event(
&self,
event: &crate::DecodedProtocolEvent,
) -> kb_core::Result<std::vec::Vec<crate::MaterializedEvent>> {
if !crate::Materializer::accepts_event(self, event) {
event: &crate::MdDecodedProtocolEvent,
) -> kb_core::Result<std::vec::Vec<crate::MdMaterializedEvent>> {
if !crate::MtMaterializer::accepts_event(self, event) {
return std::result::Result::Ok(std::vec::Vec::new());
}
return std::result::Result::Ok(std::vec::Vec::new());
}
}
impl crate::EventMaterializer for crate::StakingMaterializer {
fn identity(&self) -> crate::MaterializerIdentity {
return crate::MaterializerIdentity {
impl crate::MtApiEventMaterializer for crate::MtStakingMaterializer {
fn identity(&self) -> crate::MtApiMaterializerIdentity {
return crate::MtApiMaterializerIdentity {
name: "solana_native_staking".to_string(),
version: env!("CARGO_PKG_VERSION").to_string(),
};
}
fn accepted_families(&self) -> &'static [crate::EventFamily] {
fn accepted_families(&self) -> &'static [crate::MdEventFamily] {
return ACCEPTED_FAMILIES;
}
fn accepts_observation(&self, observation: &crate::DecodedObservation) -> bool {
fn accepts_observation(&self, observation: &crate::DcApiDecodedObservation) -> bool {
return family_is_accepted(observation.event.event_family)
&& projection(
observation.event.surface_code.0.as_str(),
@@ -146,15 +146,15 @@ impl crate::EventMaterializer for crate::StakingMaterializer {
fn transaction_policy(
&self,
_family: crate::EventFamily,
) -> crate::MaterializationTransactionPolicy {
return crate::MaterializationTransactionPolicy::SuccessfulCommittedOnly;
_family: crate::MdEventFamily,
) -> crate::MtApiMaterializationTransactionPolicy {
return crate::MtApiMaterializationTransactionPolicy::SuccessfulCommittedOnly;
}
fn materialize(
&self,
observation: &crate::DecodedObservation,
) -> crate::MaterializerExecutionResult {
observation: &crate::DcApiDecodedObservation,
) -> crate::MtApiMaterializerExecutionResult {
let projection = match projection(
observation.event.surface_code.0.as_str(),
observation.event.event_name.0.as_str(),
@@ -162,22 +162,22 @@ impl crate::EventMaterializer for crate::StakingMaterializer {
std::option::Option::Some(value) => value,
std::option::Option::None => {
tracing::debug!(
target: crate::MATERIALIZER_STAKING_TRACING_TARGET,
target: crate::MT_STAKING_TRACING_TARGET,
action = "materialize_staking",
surface_code = %observation.event.surface_code.0.as_str(),
entry_code = %observation.event.event_name.0.as_str(),
accepted = false,
"ignore observation outside supported native staking projections"
);
return crate::MaterializerExecutionResult::ignored();
return crate::MtApiMaterializerExecutionResult::ignored();
},
};
if !family_is_accepted(observation.event.event_family) {
return crate::MaterializerExecutionResult::ignored();
return crate::MtApiMaterializerExecutionResult::ignored();
}
if observation.transaction_failed || !observation.observation_committed {
tracing::debug!(
target: crate::MATERIALIZER_STAKING_TRACING_TARGET,
target: crate::MT_STAKING_TRACING_TARGET,
action = "materialize_staking",
surface_code = %observation.event.surface_code.0.as_str(),
entry_code = %observation.event.event_name.0.as_str(),
@@ -185,7 +185,7 @@ impl crate::EventMaterializer for crate::StakingMaterializer {
transaction_failed = observation.transaction_failed,
"refuse uncommitted native staking observation"
);
return crate::MaterializerExecutionResult::refused(
return crate::MtApiMaterializerExecutionResult::refused(
"failed_transaction_staking_refused",
"failed or uncommitted native Stake/Vote observations cannot create staking outputs",
);
@@ -201,7 +201,7 @@ impl crate::EventMaterializer for crate::StakingMaterializer {
.cloned()
.unwrap_or(serde_json::Value::Null);
let operation = observation.event.event_name.0.clone();
let output = crate::MaterializedOutput {
let output = crate::MtApiMaterializedOutput {
output_key: projection.output_key(operation.as_str()),
family: projection.family(),
payload_json: serde_json::json!({
@@ -225,7 +225,7 @@ impl crate::EventMaterializer for crate::StakingMaterializer {
}),
};
tracing::debug!(
target: crate::MATERIALIZER_STAKING_TRACING_TARGET,
target: crate::MT_STAKING_TRACING_TARGET,
action = "materialize_staking",
surface_code = %observation.event.surface_code.0.as_str(),
entry_code = %observation.event.event_name.0.as_str(),
@@ -233,15 +233,15 @@ impl crate::EventMaterializer for crate::StakingMaterializer {
output_count = 1_usize,
"materialize committed native staking observation"
);
return crate::MaterializerExecutionResult {
status: crate::MaterializerOutcomeStatus::Inserted,
return crate::MtApiMaterializerExecutionResult {
status: crate::MtApiMaterializerOutcomeStatus::Inserted,
outputs: std::vec![output],
diagnostics: std::vec::Vec::new(),
};
}
}
fn family_is_accepted(family: crate::EventFamily) -> bool {
fn family_is_accepted(family: crate::MdEventFamily) -> bool {
return ACCEPTED_FAMILIES.iter().any(|accepted| return *accepted == family);
}
@@ -593,25 +593,25 @@ mod tests {
fn observation(
surface_code: &str,
entry_code: &str,
family: crate::EventFamily,
family: crate::MdEventFamily,
transaction_failed: bool,
accounts: serde_json::Value,
parameters: serde_json::Value,
) -> crate::DecodedObservation {
return crate::DecodedObservation {
) -> crate::DcApiDecodedObservation {
return crate::DcApiDecodedObservation {
event_key: format!("{entry_code}:0"),
event: crate::DecodedProtocolEvent {
signature: crate::Signature("signature".to_string()),
slot: crate::Slot(42),
instruction_path: crate::InstructionPath("0".to_string()),
program_id: crate::ProgramId("program-id".to_string()),
protocol_code: crate::ProtocolCode("solana_native".to_string()),
surface_code: crate::SurfaceCode(surface_code.to_string()),
event_code: crate::EventCode(format!("{surface_code}.{entry_code}")),
event_name: crate::EventName(entry_code.to_string()),
event: crate::MdDecodedProtocolEvent {
signature: crate::MdSignature("signature".to_string()),
slot: crate::MdSlot(42),
instruction_path: crate::MdInstructionPath("0".to_string()),
program_id: crate::MdProgramId("program-id".to_string()),
protocol_code: crate::MdProtocolCode("solana_native".to_string()),
surface_code: crate::MdSurfaceCode(surface_code.to_string()),
event_code: crate::MdEventCode(format!("{surface_code}.{entry_code}")),
event_name: crate::MdEventName(entry_code.to_string()),
event_family: family,
source_kind: crate::EventSourceKind::Instruction,
confidence: crate::DecoderConfidence::ManualExact,
source_kind: crate::MdEventSourceKind::Instruction,
confidence: crate::MdDecoderConfidence::ManualExact,
},
payload_json: serde_json::json!({"accounts": accounts, "parameters": parameters}),
transaction_failed,
@@ -621,9 +621,9 @@ mod tests {
std::option::Option::None
},
observation_committed: !transaction_failed,
proof: crate::DecoderProof {
kind: crate::DecoderProofKind::Manual,
confidence: crate::DecoderConfidence::ManualExact,
proof: crate::DcApiDecoderProof {
kind: crate::DcApiDecoderProofKind::Manual,
confidence: crate::MdDecoderConfidence::ManualExact,
evidence: std::vec!["fixture".to_string()],
},
};
@@ -635,18 +635,18 @@ mod tests {
#[test]
fn stake_initialize_creates_instructional_staking_projection() {
let materializer = crate::StakingMaterializer;
let materializer = crate::MtStakingMaterializer;
let observation = observation(
super::STAKE_SURFACE,
"initialize",
crate::EventFamily::Lifecycle,
crate::MdEventFamily::Lifecycle,
false,
serde_json::json!([account("stake_account", "stake111")]),
serde_json::json!({"authorized": {"staker": "staker111"}}),
);
let result = crate::EventMaterializer::materialize(&materializer, &observation);
assert_eq!(result.status, crate::MaterializerOutcomeStatus::Inserted);
assert_eq!(result.outputs[0].family, crate::MaterializedEventFamily::Staking);
let result = crate::MtApiEventMaterializer::materialize(&materializer, &observation);
assert_eq!(result.status, crate::MtApiMaterializerOutcomeStatus::Inserted);
assert_eq!(result.outputs[0].family, crate::MdMaterializedEventFamily::Staking);
assert_eq!(result.outputs[0].output_key, "stake_account:initialize:0");
assert_eq!(result.outputs[0].payload_json["projection"]["stakeAccount"], "stake111");
assert_eq!(
@@ -657,11 +657,11 @@ mod tests {
#[test]
fn stake_authority_and_lockup_events_are_projected() {
let materializer = crate::StakingMaterializer;
let materializer = crate::MtStakingMaterializer;
let authority = observation(
super::STAKE_SURFACE,
"authorize_with_seed",
crate::EventFamily::Admin,
crate::MdEventFamily::Admin,
false,
serde_json::json!([account("stake_account", "stake111")]),
serde_json::json!({
@@ -674,7 +674,7 @@ mod tests {
let lockup = observation(
super::STAKE_SURFACE,
"set_lockup_checked",
crate::EventFamily::Admin,
crate::MdEventFamily::Admin,
false,
serde_json::json!([
account("stake_account", "stake111"),
@@ -682,8 +682,9 @@ mod tests {
]),
serde_json::json!({"epoch": 42}),
);
let authority_result = crate::EventMaterializer::materialize(&materializer, &authority);
let lockup_result = crate::EventMaterializer::materialize(&materializer, &lockup);
let authority_result =
crate::MtApiEventMaterializer::materialize(&materializer, &authority);
let lockup_result = crate::MtApiEventMaterializer::materialize(&materializer, &lockup);
assert_eq!(authority_result.outputs[0].output_key, "stake_authority:authorize_with_seed:0");
assert_eq!(
authority_result.outputs[0].payload_json["projection"]["newAuthority"],
@@ -698,11 +699,11 @@ mod tests {
#[test]
fn stake_value_projection_does_not_duplicate_lamport_changes() {
let materializer = crate::StakingMaterializer;
let materializer = crate::MtStakingMaterializer;
let observation = observation(
super::STAKE_SURFACE,
"withdraw",
crate::EventFamily::Staking,
crate::MdEventFamily::Staking,
false,
serde_json::json!([
account("stake_account", "stake111"),
@@ -710,7 +711,7 @@ mod tests {
]),
serde_json::json!({"lamports": 99}),
);
let result = crate::EventMaterializer::materialize(&materializer, &observation);
let result = crate::MtApiEventMaterializer::materialize(&materializer, &observation);
assert_eq!(result.outputs[0].output_key, "stake_value:withdraw:0");
assert_eq!(result.outputs[0].payload_json["projection"]["lamports"], 99);
assert_eq!(
@@ -721,11 +722,11 @@ mod tests {
#[test]
fn vote_account_and_authority_events_are_projected() {
let materializer = crate::StakingMaterializer;
let materializer = crate::MtStakingMaterializer;
let initialize = observation(
super::VOTE_SURFACE,
"initialize_account_v2",
crate::EventFamily::Lifecycle,
crate::MdEventFamily::Lifecycle,
false,
serde_json::json!([
account("vote_account", "vote111"),
@@ -740,7 +741,7 @@ mod tests {
let authorize = observation(
super::VOTE_SURFACE,
"authorize_checked",
crate::EventFamily::Admin,
crate::MdEventFamily::Admin,
false,
serde_json::json!([
account("vote_account", "vote111"),
@@ -748,8 +749,10 @@ mod tests {
]),
serde_json::json!({"authorizationType": {"kind": "voter"}, "newAuthorityFromAccount": true}),
);
let initialize_result = crate::EventMaterializer::materialize(&materializer, &initialize);
let authorize_result = crate::EventMaterializer::materialize(&materializer, &authorize);
let initialize_result =
crate::MtApiEventMaterializer::materialize(&materializer, &initialize);
let authorize_result =
crate::MtApiEventMaterializer::materialize(&materializer, &authorize);
assert_eq!(initialize_result.outputs[0].output_key, "vote_account:initialize_account_v2:0");
assert_eq!(
initialize_result.outputs[0].payload_json["projection"]["validatorIdentity"],
@@ -764,11 +767,11 @@ mod tests {
#[test]
fn vote_state_and_reward_outputs_are_distinguished() {
let materializer = crate::StakingMaterializer;
let materializer = crate::MtStakingMaterializer;
let vote_state = observation(
super::VOTE_SURFACE,
"tower_sync_switch",
crate::EventFamily::Staking,
crate::MdEventFamily::Staking,
false,
serde_json::json!([
account("vote_account", "vote111"),
@@ -779,7 +782,7 @@ mod tests {
let reward = observation(
super::VOTE_SURFACE,
"deposit_delegator_rewards",
crate::EventFamily::Reward,
crate::MdEventFamily::Reward,
false,
serde_json::json!([
account("vote_account", "vote111"),
@@ -787,40 +790,40 @@ mod tests {
]),
serde_json::json!({"depositLamports": 123}),
);
let vote_result = crate::EventMaterializer::materialize(&materializer, &vote_state);
let reward_result = crate::EventMaterializer::materialize(&materializer, &reward);
let vote_result = crate::MtApiEventMaterializer::materialize(&materializer, &vote_state);
let reward_result = crate::MtApiEventMaterializer::materialize(&materializer, &reward);
assert_eq!(vote_result.outputs[0].output_key, "vote_state:tower_sync_switch:0");
assert_eq!(
vote_result.outputs[0].payload_json["projection"]["switchProofHash"],
"switch111"
);
assert_eq!(reward_result.outputs[0].family, crate::MaterializedEventFamily::Reward);
assert_eq!(reward_result.outputs[0].family, crate::MdMaterializedEventFamily::Reward);
assert_eq!(reward_result.outputs[0].output_key, "vote_reward:deposit_delegator_rewards:0");
}
#[test]
fn failed_staking_mutation_is_refused() {
let materializer = crate::StakingMaterializer;
let materializer = crate::MtStakingMaterializer;
let observation = observation(
super::STAKE_SURFACE,
"delegate_stake",
crate::EventFamily::Staking,
crate::MdEventFamily::Staking,
true,
serde_json::json!([account("stake_account", "stake111")]),
serde_json::json!({}),
);
let result = crate::EventMaterializer::materialize(&materializer, &observation);
assert_eq!(result.status, crate::MaterializerOutcomeStatus::Refused);
let result = crate::MtApiEventMaterializer::materialize(&materializer, &observation);
assert_eq!(result.status, crate::MtApiMaterializerOutcomeStatus::Refused);
assert!(result.outputs.is_empty());
}
#[test]
fn unrelated_or_disabled_observations_are_ignored() {
let materializer = crate::StakingMaterializer;
let materializer = crate::MtStakingMaterializer;
let unrelated = observation(
"solana_native_system",
"create_account",
crate::EventFamily::Lifecycle,
crate::MdEventFamily::Lifecycle,
false,
serde_json::json!([]),
serde_json::json!({}),
@@ -828,30 +831,32 @@ mod tests {
let redelegate = observation(
super::STAKE_SURFACE,
"redelegate",
crate::EventFamily::Staking,
crate::MdEventFamily::Staking,
false,
serde_json::json!([]),
serde_json::json!({"officiallyDisabled": true}),
);
let unrelated_result = crate::EventMaterializer::materialize(&materializer, &unrelated);
let redelegate_result = crate::EventMaterializer::materialize(&materializer, &redelegate);
assert_eq!(unrelated_result.status, crate::MaterializerOutcomeStatus::Ignored);
assert_eq!(redelegate_result.status, crate::MaterializerOutcomeStatus::Ignored);
let unrelated_result =
crate::MtApiEventMaterializer::materialize(&materializer, &unrelated);
let redelegate_result =
crate::MtApiEventMaterializer::materialize(&materializer, &redelegate);
assert_eq!(unrelated_result.status, crate::MtApiMaterializerOutcomeStatus::Ignored);
assert_eq!(redelegate_result.status, crate::MtApiMaterializerOutcomeStatus::Ignored);
}
#[test]
fn output_serialization_is_deterministic() {
let materializer = crate::StakingMaterializer;
let materializer = crate::MtStakingMaterializer;
let observation = observation(
super::VOTE_SURFACE,
"update_commission_bps",
crate::EventFamily::Admin,
crate::MdEventFamily::Admin,
false,
serde_json::json!([account("vote_account", "vote111")]),
serde_json::json!({"commissionBasisPoints": 250, "commissionKind": "inflation_rewards"}),
);
let first = crate::EventMaterializer::materialize(&materializer, &observation);
let second = crate::EventMaterializer::materialize(&materializer, &observation);
let first = crate::MtApiEventMaterializer::materialize(&materializer, &observation);
let second = crate::MtApiEventMaterializer::materialize(&materializer, &observation);
let first_json_result = serde_json::to_string(&first.outputs[0].payload_json);
let first_json = match first_json_result {
std::result::Result::Ok(value) => value,

View File

@@ -1,7 +1,20 @@
// file: kb-lib/src/materializer/token.rs
// version: 1
// version: 2
//! `token` materializer family.
pub mod accounts;
pub mod metadata_risk;
mod accounts;
mod constants;
mod metadata_risk;
/// Stable processor name for Token, ATA and Token-2022 account projections.
pub(crate) use self::constants::MT_TOKEN_ACCOUNTS_PROCESSOR_NAME;
/// Stable projection contract version for Token, ATA and Token-2022 account projections.
pub(crate) use self::constants::MT_TOKEN_ACCOUNTS_PROJECTION_VERSION;
/// Canonical tracing target for Token, ATA and Token-2022 account projections.
pub(crate) use self::constants::MT_TOKEN_ACCOUNTS_TRACING_TARGET;
/// Stable Token, ATA and Token-2022 account materializer.
pub use self::accounts::MtTokenAccountsMaterializer;
/// Materializes one bounded Token-2022 account state snapshot.
pub use self::accounts::materialize_token2022_state_snapshot;

View File

@@ -1,10 +1,831 @@
// file: kb-lib/src/materializer/token/accounts.rs
// version: 1
// version: 2
//! Migration boundary for legacy crate `kb_materializer_token_accounts`.
//! Stable committed SPL Token mutation and ATA lifecycle projections.
/// Legacy crate name retained for migration and compatibility tracking.
pub const LEGACY_CRATE: &str = "kb_materializer_token_accounts";
const ACCEPTED_FAMILIES: &[crate::MdEventFamily] = &[
crate::MdEventFamily::TokenAccount,
crate::MdEventFamily::TokenMint,
crate::MdEventFamily::TokenBurn,
crate::MdEventFamily::Lifecycle,
];
/// Current porting status.
pub const MIGRATION_STATUS: &str = "source-preserved-pending-port";
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<std::vec::Vec<crate::MdMaterializedEvent>> {
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 materialize_token2022_state_snapshot(
account_key: &str,
slot: u64,
state: &crate::DcToken2022State,
) -> std::result::Result<crate::MtApiMaterializedOutput, String> {
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::<std::vec::Vec<_>>();
let extension_names = state
.extensions
.iter()
.map(|entry| return entry.extension_name)
.collect::<std::vec::Vec<_>>();
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::vec::Vec<_>>(),
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::<std::vec::Vec<_>>();
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::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::materialize_token2022_state_snapshot("account111", 7, &state);
let second = crate::materialize_token2022_state_snapshot("account111", 7, &state);
assert_eq!(first, second);
assert!(crate::materialize_token2022_state_snapshot("", 7, &state).is_err());
}
}

View File

@@ -0,0 +1,11 @@
// file: kb-lib/src/materializer/token/constants.rs
// version: 1
//! Local constants for classic SPL Token account projections.
/// Stable processor name persisted by the replay ledger.
pub(crate) const MT_TOKEN_ACCOUNTS_PROCESSOR_NAME: &str = "spl_token_accounts";
/// Stable projection payload contract version.
pub(crate) const MT_TOKEN_ACCOUNTS_PROJECTION_VERSION: u32 = 3;
/// Canonical tracing target for this component.
pub(crate) const MT_TOKEN_ACCOUNTS_TRACING_TARGET: &str = "kb-lib.materializer.token";

View File

@@ -1,10 +1,10 @@
// file: kb-lib/src/materializer/token/metadata_risk.rs
// version: 1
// version: 2
//! Migration boundary for legacy crate `kb_materializer_token_metadata_risk`.
/// Legacy crate name retained for migration and compatibility tracking.
pub const LEGACY_CRATE: &str = "kb_materializer_token_metadata_risk";
const LEGACY_CRATE: &str = "kb_materializer_token_metadata_risk";
/// Current porting status.
pub const MIGRATION_STATUS: &str = "source-preserved-pending-port";
const MIGRATION_STATUS: &str = "source-preserved-pending-port";

View File

@@ -1,6 +1,6 @@
// file: kb-lib/src/materializer/trades.rs
// version: 1
// version: 2
//! `trades` materializer family.
pub mod core;
mod core;

View File

@@ -1,10 +1,10 @@
// file: kb-lib/src/materializer/trades/core.rs
// version: 1
// version: 2
//! Migration boundary for legacy crate `kb_materializer_trades`.
/// Legacy crate name retained for migration and compatibility tracking.
pub const LEGACY_CRATE: &str = "kb_materializer_trades";
const LEGACY_CRATE: &str = "kb_materializer_trades";
/// Current porting status.
pub const MIGRATION_STATUS: &str = "source-preserved-pending-port";
const MIGRATION_STATUS: &str = "source-preserved-pending-port";

View File

@@ -1,6 +1,19 @@
// file: kb-lib/src/materializer/transaction.rs
// version: 1
// version: 2
//! `transaction` materializer family.
pub mod annotations;
mod annotations;
mod constants;
/// Maximum Memo payload accepted by transaction annotations.
pub(crate) use self::constants::MT_TRANSACTION_ANNOTATIONS_MAX_MEMO_PAYLOAD_BYTES;
/// Stable processor name for transaction annotations.
pub(crate) use self::constants::MT_TRANSACTION_ANNOTATIONS_PROCESSOR_NAME;
/// Stable projection contract version for transaction annotations.
pub(crate) use self::constants::MT_TRANSACTION_ANNOTATIONS_PROJECTION_VERSION;
/// Canonical tracing target for transaction annotations.
pub(crate) use self::constants::MT_TRANSACTION_ANNOTATIONS_TRACING_TARGET;
/// Stable committed Memo transaction annotation materializer.
pub use self::annotations::MtTransactionAnnotationMaterializer;

View File

@@ -1,10 +1,614 @@
// file: kb-lib/src/materializer/transaction/annotations.rs
// version: 1
// version: 2
//! Migration boundary for legacy crate `kb_materializer_transaction_annotations`.
//! Exact committed SPL Memo transaction annotation projection.
/// Legacy crate name retained for migration and compatibility tracking.
pub const LEGACY_CRATE: &str = "kb_materializer_transaction_annotations";
const ACCEPTED_FAMILIES: &[crate::MdEventFamily] = &[crate::MdEventFamily::Audit];
const MEMO_ENTRY_NAMES: &[&str] = &["add_memo", "memo_intent", "invalid_memo_attempt"];
/// Current porting status.
pub const MIGRATION_STATUS: &str = "source-preserved-pending-port";
#[derive(Clone, Copy, Debug, Eq, PartialEq)]
enum MemoGeneration {
V1,
V3,
V4,
}
impl MemoGeneration {
fn from_event(event: &crate::MdDecodedProtocolEvent) -> std::option::Option<Self> {
if event.program_id.0 == kb_program_ids::SPL_MEMO_V1_PROGRAM_ID
&& event.surface_code.0 == "spl_memo_v1"
{
return std::option::Option::Some(Self::V1);
}
if event.program_id.0 == kb_program_ids::SPL_MEMO_V3_PROGRAM_ID
&& event.surface_code.0 == "spl_memo_v3"
{
return std::option::Option::Some(Self::V3);
}
if event.program_id.0 == kb_program_ids::SPL_MEMO_V4_PROGRAM_ID
&& event.surface_code.0 == "spl_memo_v4"
{
return std::option::Option::Some(Self::V4);
}
return std::option::Option::None;
}
fn code(self) -> &'static str {
return match self {
Self::V1 => "v1",
Self::V3 => "v3",
Self::V4 => "v4",
};
}
fn signer_status(self) -> &'static str {
return match self {
Self::V1 => "not_applicable",
Self::V3 | Self::V4 => "valid",
};
}
}
struct MemoProjection {
generation: MemoGeneration,
memo_text: std::string::String,
payload_length_bytes: u64,
payload_sha256: std::string::String,
required_signers: std::vec::Vec<std::string::String>,
observed_signers: std::vec::Vec<std::string::String>,
verified_signers: std::vec::Vec<std::string::String>,
signer_validation_status: std::string::String,
}
/// Stable transaction annotation materializer.
#[derive(Clone, Debug, Default)]
pub struct MtTransactionAnnotationMaterializer;
impl crate::MtMaterializer for crate::MtTransactionAnnotationMaterializer {
fn materializer_name(&self) -> &'static str {
return "kb_materializer_transaction_annotations";
}
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<std::vec::Vec<crate::MdMaterializedEvent>> {
return std::result::Result::Ok(std::vec::Vec::new());
}
}
impl crate::MtApiEventMaterializer for crate::MtTransactionAnnotationMaterializer {
fn identity(&self) -> crate::MtApiMaterializerIdentity {
return crate::MtApiMaterializerIdentity {
name: crate::MT_TRANSACTION_ANNOTATIONS_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_transaction_annotation_refused",
"failed or uncommitted Memo observations cannot create transaction annotations",
);
}
if observation.event.event_name.0 != "add_memo" {
return crate::MtApiMaterializerExecutionResult::refused(
"invalid_transaction_annotation_source_refused",
"only a committed add_memo observation can create a transaction annotation",
);
}
let projection_result = parse_projection(observation);
let projection = match projection_result {
std::result::Result::Ok(value) => value,
std::result::Result::Err(message) => {
return failed("memo_annotation_payload_invalid", message);
},
};
let idempotence_key = format!(
"spl_memo:{}:{}:{}:{}",
observation.event.signature.0,
observation.event.instruction_path.0,
observation.event.program_id.0,
projection.payload_sha256
);
let output = crate::MtApiMaterializedOutput {
output_key: "transaction_annotation:spl_memo:0".to_string(),
family: crate::MdMaterializedEventFamily::TransactionAnnotation,
payload_json: serde_json::json!({
"projectionVersion": crate::MT_TRANSACTION_ANNOTATIONS_PROJECTION_VERSION,
"domain": "transaction_annotation",
"annotationKind": "spl_memo",
"idempotenceKey": idempotence_key,
"signature": observation.event.signature.0.clone(),
"slot": observation.event.slot.0,
"instructionPath": observation.event.instruction_path.0.clone(),
"sourceKind": match observation.event.source_kind {
crate::MdEventSourceKind::InnerInstruction => "inner_instruction",
_ => "instruction"
},
"generation": projection.generation.code(),
"programId": observation.event.program_id.0.clone(),
"text": projection.memo_text,
"payloadLengthBytes": projection.payload_length_bytes,
"payloadSha256": projection.payload_sha256,
"signers": {
"validationStatus": projection.signer_validation_status,
"required": projection.required_signers,
"observed": projection.observed_signers,
"verified": projection.verified_signers
},
"committed": true,
"provenance": {
"processorName": crate::MT_TRANSACTION_ANNOTATIONS_PROCESSOR_NAME,
"processorVersion": env!("CARGO_PKG_VERSION"),
"sourceProtocol": observation.event.protocol_code.0.clone(),
"sourceSurface": observation.event.surface_code.0.clone(),
"sourceEventCode": observation.event.event_code.0.clone(),
"sourceEventKey": observation.event_key.clone()
}
}),
};
tracing::debug!(
target: crate::MT_TRANSACTION_ANNOTATIONS_TRACING_TARGET,
action = "materialize_transaction_annotation",
signature = %observation.event.signature.0,
instruction_path = %observation.event.instruction_path.0,
program_id = %observation.event.program_id.0,
generation = projection.generation.code(),
payload_length_bytes = projection.payload_length_bytes,
output_count = 1_usize,
"materialize committed SPL Memo transaction annotation"
);
return crate::MtApiMaterializerExecutionResult {
status: crate::MtApiMaterializerOutcomeStatus::Inserted,
outputs: std::vec![output],
diagnostics: std::vec::Vec::new(),
};
}
}
fn accepts_event(event: &crate::MdDecodedProtocolEvent) -> bool {
return event.event_family == crate::MdEventFamily::Audit
&& event.protocol_code.0 == "spl_memo"
&& (event.source_kind == crate::MdEventSourceKind::Instruction
|| event.source_kind == crate::MdEventSourceKind::InnerInstruction)
&& MemoGeneration::from_event(event).is_some()
&& MEMO_ENTRY_NAMES.contains(&event.event_name.0.as_str());
}
fn parse_projection(
observation: &crate::DcApiDecodedObservation,
) -> std::result::Result<MemoProjection, std::string::String> {
let generation = match MemoGeneration::from_event(&observation.event) {
std::option::Option::Some(value) => value,
std::option::Option::None => {
return std::result::Result::Err(
"Memo generation and Program ID do not match".to_string(),
);
},
};
let payload = &observation.payload_json;
if payload.get("committed").and_then(serde_json::Value::as_bool)
!= std::option::Option::Some(true)
{
return std::result::Result::Err("Memo payload is not marked committed".to_string());
}
if payload.get("transactionSucceeded").and_then(serde_json::Value::as_bool)
!= std::option::Option::Some(true)
|| payload.get("payloadComplete").and_then(serde_json::Value::as_bool)
!= std::option::Option::Some(true)
|| string_field(payload, "runtimeValidation")
!= std::option::Option::Some("proven_by_successful_transaction")
|| payload
.get("utf8Validation")
.and_then(|value| return string_field(value, "status"))
!= std::option::Option::Some("valid")
{
return std::result::Result::Err(
"Memo successful runtime validation proof is incomplete".to_string(),
);
}
if string_field(payload, "generation") != std::option::Option::Some(generation.code()) {
return std::result::Result::Err(
"Memo payload generation does not match its surface".to_string(),
);
}
if string_field(payload, "programId")
!= std::option::Option::Some(observation.event.program_id.0.as_str())
{
return std::result::Result::Err(
"Memo payload Program ID does not match its event".to_string(),
);
}
if string_field(payload, "instructionPath")
!= std::option::Option::Some(observation.event.instruction_path.0.as_str())
{
return std::result::Result::Err(
"Memo payload instruction path does not match its event".to_string(),
);
}
let memo_text = match string_field(payload, "memoText") {
std::option::Option::Some(value) => value.to_string(),
std::option::Option::None => {
return std::result::Result::Err("committed Memo has no UTF-8 text".to_string());
},
};
let payload_length_bytes =
match payload.get("payloadLengthBytes").and_then(serde_json::Value::as_u64) {
std::option::Option::Some(value) => value,
std::option::Option::None => {
return std::result::Result::Err("committed Memo has no byte length".to_string());
},
};
let measured_length = match u64::try_from(memo_text.len()) {
std::result::Result::Ok(value) => value,
std::result::Result::Err(error) => {
return std::result::Result::Err(format!(
"Memo text length conversion failed: {error}"
));
},
};
if measured_length != payload_length_bytes {
return std::result::Result::Err(
"Memo byte length does not match its UTF-8 text".to_string(),
);
}
if payload_length_bytes > crate::MT_TRANSACTION_ANNOTATIONS_MAX_MEMO_PAYLOAD_BYTES {
return std::result::Result::Err(
"Memo byte length exceeds the projection limit".to_string(),
);
}
let payload_sha256 = match string_field(payload, "payloadSha256") {
std::option::Option::Some(value) if is_sha256(value) => value.to_string(),
_ => return std::result::Result::Err("Memo payload SHA-256 is invalid".to_string()),
};
let signer_validation = match payload.get("signerValidation") {
std::option::Option::Some(value) => value,
std::option::Option::None => {
return std::result::Result::Err("Memo signer validation is absent".to_string());
},
};
let signer_validation_status = match string_field(signer_validation, "status") {
std::option::Option::Some(value) if value == generation.signer_status() => {
value.to_string()
},
_ => {
return std::result::Result::Err(
"Memo signer validation status is inconsistent".to_string(),
);
},
};
let missing_signers = match string_array(signer_validation, "missingRequiredSigners") {
std::result::Result::Ok(value) => value,
std::result::Result::Err(message) => return std::result::Result::Err(message),
};
if !missing_signers.is_empty() {
return std::result::Result::Err("committed Memo reports missing signers".to_string());
}
let required_signers = match string_array(signer_validation, "orderedRequiredSigners") {
std::result::Result::Ok(value) => value,
std::result::Result::Err(message) => return std::result::Result::Err(message),
};
let observed_signers = match string_array(signer_validation, "orderedObservedSigners") {
std::result::Result::Ok(value) => value,
std::result::Result::Err(message) => return std::result::Result::Err(message),
};
let verified_signers = if generation == MemoGeneration::V1 {
if !required_signers.is_empty() {
return std::result::Result::Err("Memo v1 cannot require signers".to_string());
}
std::vec::Vec::new()
} else {
if required_signers != observed_signers {
return std::result::Result::Err(
"committed Memo required and observed signers differ".to_string(),
);
}
observed_signers.clone()
};
return std::result::Result::Ok(MemoProjection {
generation,
memo_text,
payload_length_bytes,
payload_sha256,
required_signers,
observed_signers,
verified_signers,
signer_validation_status,
});
}
fn string_field<'a>(value: &'a serde_json::Value, key: &str) -> std::option::Option<&'a str> {
return value.get(key).and_then(serde_json::Value::as_str);
}
fn string_array(
value: &serde_json::Value,
key: &str,
) -> std::result::Result<std::vec::Vec<std::string::String>, std::string::String> {
let values = match value.get(key).and_then(serde_json::Value::as_array) {
std::option::Option::Some(values) => values,
std::option::Option::None => {
return std::result::Result::Err(format!("Memo signer field {key} is not an array"));
},
};
let mut output = std::vec::Vec::with_capacity(values.len());
for item in values {
let text = match item.as_str() {
std::option::Option::Some(text) if !text.trim().is_empty() => text,
_ => {
return std::result::Result::Err(format!(
"Memo signer field {key} contains an invalid pubkey"
));
},
};
output.push(text.to_string());
}
return std::result::Result::Ok(output);
}
fn is_sha256(value: &str) -> bool {
return value.len() == 64
&& value
.as_bytes()
.iter()
.all(|byte| return byte.is_ascii_hexdigit() && !byte.is_ascii_uppercase());
}
fn failed(code: &str, message: std::string::String) -> crate::MtApiMaterializerExecutionResult {
return crate::MtApiMaterializerExecutionResult {
status: crate::MtApiMaterializerOutcomeStatus::Failed,
outputs: std::vec::Vec::new(),
diagnostics: std::vec![crate::MtApiMaterializerDiagnostic {
code: code.to_string(),
message,
retriable: false,
}],
};
}
#[cfg(test)]
mod tests {
fn observation(
program_id: &str,
surface_code: &str,
generation: &str,
event_name: &str,
failed: bool,
committed: bool,
required_signers: serde_json::Value,
observed_signers: serde_json::Value,
) -> crate::DcApiDecodedObservation {
return crate::DcApiDecodedObservation {
event_key: "memo:0".to_string(),
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(program_id.to_string()),
protocol_code: crate::MdProtocolCode("spl_memo".to_string()),
surface_code: crate::MdSurfaceCode(surface_code.to_string()),
event_code: crate::MdEventCode(format!("{surface_code}.{event_name}")),
event_name: crate::MdEventName(event_name.to_string()),
event_family: crate::MdEventFamily::Audit,
source_kind: crate::MdEventSourceKind::InnerInstruction,
confidence: crate::MdDecoderConfidence::ManualExact,
},
payload_json: serde_json::json!({
"generation": generation,
"programId": program_id,
"instructionPath": "1/0",
"transactionSucceeded": !failed,
"committed": committed,
"memoText": "memo",
"payloadLengthBytes": 4,
"payloadSha256": "1111111111111111111111111111111111111111111111111111111111111111",
"payloadComplete": true,
"utf8Validation": {"status":"valid","invalidFromByte":null},
"signerValidation": {
"status": if generation == "v1" { "not_applicable" } else { "valid" },
"orderedRequiredSigners": required_signers,
"orderedObservedSigners": observed_signers,
"missingRequiredSigners": []
},
"runtimeValidation": if failed {
"not_proven_transaction_failed"
} else {
"proven_by_successful_transaction"
}
}),
transaction_failed: failed,
transaction_error: if failed {
std::option::Option::Some(serde_json::json!({"InstructionError":[0,"Custom"]}))
} else {
std::option::Option::None
},
observation_committed: committed,
proof: crate::DcApiDecoderProof {
kind: crate::DcApiDecoderProofKind::Manual,
confidence: crate::MdDecoderConfidence::ManualExact,
evidence: std::vec!["memo".to_string()],
},
};
}
fn v4() -> crate::DcApiDecodedObservation {
return observation(
kb_program_ids::SPL_MEMO_V4_PROGRAM_ID,
"spl_memo_v4",
"v4",
"add_memo",
false,
true,
serde_json::json!(["signer", "signer"]),
serde_json::json!(["signer", "signer"]),
);
}
#[test]
fn committed_v4_memo_becomes_deterministic_transaction_annotation() {
let materializer = crate::MtTransactionAnnotationMaterializer;
let first = crate::MtApiEventMaterializer::materialize(&materializer, &v4());
let second = crate::MtApiEventMaterializer::materialize(&materializer, &v4());
assert_eq!(first, second);
assert_eq!(first.status, crate::MtApiMaterializerOutcomeStatus::Inserted);
assert_eq!(first.outputs.len(), 1);
assert_eq!(
first.outputs[0].family,
crate::MdMaterializedEventFamily::TransactionAnnotation
);
assert_eq!(
first.outputs[0].payload_json["signers"]["verified"],
serde_json::json!(["signer", "signer"])
);
assert_eq!(first.outputs[0].payload_json["committed"], true);
assert!(first.validate().is_ok());
}
#[test]
fn exact_v1_v3_and_v4_surfaces_are_accepted() {
for (program_id, surface, generation) in [
(kb_program_ids::SPL_MEMO_V1_PROGRAM_ID, "spl_memo_v1", "v1"),
(kb_program_ids::SPL_MEMO_V3_PROGRAM_ID, "spl_memo_v3", "v3"),
(kb_program_ids::SPL_MEMO_V4_PROGRAM_ID, "spl_memo_v4", "v4"),
] {
let observation = observation(
program_id,
surface,
generation,
"add_memo",
false,
true,
serde_json::json!([]),
serde_json::json!([]),
);
assert!(crate::MtApiEventMaterializer::accepts_observation(
&crate::MtTransactionAnnotationMaterializer,
&observation,
));
}
}
#[test]
fn v1_observed_accounts_are_not_claimed_as_runtime_verified() {
let observation = observation(
kb_program_ids::SPL_MEMO_V1_PROGRAM_ID,
"spl_memo_v1",
"v1",
"add_memo",
false,
true,
serde_json::json!([]),
serde_json::json!(["observed_only"]),
);
let result = crate::MtApiEventMaterializer::materialize(
&crate::MtTransactionAnnotationMaterializer,
&observation,
);
assert_eq!(result.outputs[0].payload_json["signers"]["verified"], serde_json::json!([]));
assert_eq!(
result.outputs[0].payload_json["signers"]["observed"],
serde_json::json!(["observed_only"])
);
}
#[test]
fn failed_or_uncommitted_memo_is_refused_without_output() {
let observation = observation(
kb_program_ids::SPL_MEMO_V4_PROGRAM_ID,
"spl_memo_v4",
"v4",
"memo_intent",
true,
false,
serde_json::json!([]),
serde_json::json!([]),
);
let materializer = crate::MtTransactionAnnotationMaterializer;
let policy =
crate::materializer_api_validate_materialization_policy(&materializer, &observation);
assert!(policy.is_err());
let result = crate::MtApiEventMaterializer::materialize(&materializer, &observation);
assert_eq!(result.status, crate::MtApiMaterializerOutcomeStatus::Refused);
assert!(result.outputs.is_empty());
}
#[test]
fn malformed_committed_payload_fails_closed() {
let mut observation = v4();
observation.payload_json["payloadSha256"] = serde_json::Value::String("bad".to_string());
let result = crate::MtApiEventMaterializer::materialize(
&crate::MtTransactionAnnotationMaterializer,
&observation,
);
assert_eq!(result.status, crate::MtApiMaterializerOutcomeStatus::Failed);
assert!(result.outputs.is_empty());
assert_eq!(result.diagnostics[0].code, "memo_annotation_payload_invalid");
}
#[test]
fn foreign_audit_observation_is_not_accepted() {
let mut observation = v4();
observation.event.protocol_code = crate::MdProtocolCode("foreign".to_string());
assert!(!crate::MtApiEventMaterializer::accepts_observation(
&crate::MtTransactionAnnotationMaterializer,
&observation,
));
let result = crate::MtApiEventMaterializer::materialize(
&crate::MtTransactionAnnotationMaterializer,
&observation,
);
assert_eq!(result.status, crate::MtApiMaterializerOutcomeStatus::Ignored);
}
#[test]
fn machine_readable_matrix_declares_transaction_annotation_policy() {
let parsed = serde_json::from_str::<serde_json::Value>(include_str!(
"../../../../docs/SPL_MEMO_MATRIX.json"
));
let matrix = match parsed {
std::result::Result::Ok(value) => value,
std::result::Result::Err(error) => panic!("memo matrix is invalid JSON: {error}"),
};
assert_eq!(matrix["matrixVersion"], 4);
assert_eq!(
matrix["materializationContract"]["crate"],
"kb_materializer_transaction_annotations"
);
assert_eq!(
matrix["materializationContract"]["transactionPolicy"],
"SuccessfulCommittedOnly"
);
assert_eq!(matrix["materializationContract"]["failedOrUncommittedOutput"], false);
}
}

View File

@@ -0,0 +1,14 @@
// file: kb-lib/src/materializer/transaction/constants.rs
// version: 1
//! Local constants for the transaction annotation materializer.
/// Maximum Memo payload accepted from the decoded contract.
pub(crate) const MT_TRANSACTION_ANNOTATIONS_MAX_MEMO_PAYLOAD_BYTES: u64 = 4_096;
/// Stable materializer processor name.
pub(crate) const MT_TRANSACTION_ANNOTATIONS_PROCESSOR_NAME: &str = "transaction_annotations";
/// Stable projection contract version.
pub(crate) const MT_TRANSACTION_ANNOTATIONS_PROJECTION_VERSION: u32 = 1;
/// Canonical tracing target for this component.
pub(crate) const MT_TRANSACTION_ANNOTATIONS_TRACING_TARGET: &str =
"kb-lib.materializer.transaction";

View File

@@ -1,6 +1,6 @@
// file: kb-lib/src/materializer/vault.rs
// version: 1
// version: 2
//! `vault` materializer family.
pub mod core;
mod core;

View File

@@ -1,10 +1,10 @@
// file: kb-lib/src/materializer/vault/core.rs
// version: 1
// version: 2
//! Migration boundary for legacy crate `kb_materializer_vault`.
/// Legacy crate name retained for migration and compatibility tracking.
pub const LEGACY_CRATE: &str = "kb_materializer_vault";
const LEGACY_CRATE: &str = "kb_materializer_vault";
/// Current porting status.
pub const MIGRATION_STATUS: &str = "source-preserved-pending-port";
const MIGRATION_STATUS: &str = "source-preserved-pending-port";