v0.4.8-pre.006

This commit is contained in:
2026-08-05 20:30:16 +02:00
parent 4187643148
commit 80c439c988
16 changed files with 997 additions and 131 deletions

View File

@@ -1,5 +1,5 @@
// file: kb-lib/src/lib.rs
// version: 32
// version: 33
//! Consolidated decoder, executor, materializer and shared model library.
#![warn(missing_docs)]
@@ -1391,7 +1391,7 @@ pub use self::materializer::MtLifecycleMaterializer;
pub use self::materializer::MtLiquidityMaterializer;
/// Exposes the legacy common business materializer trait.
pub use self::materializer::MtMaterializer;
/// Token-2022 and Metaplex metadata snapshot materializer.
/// Token-2022, Metaplex and Solana Program Metadata materializer.
pub use self::materializer::MtMetadataMaterializer;
/// Reserved NFT materializer.
pub use self::materializer::MtNftMaterializer;
@@ -1439,6 +1439,12 @@ pub use self::materializer::materializer_fees_materialize_token_2022_state_snaps
pub use self::materializer::materializer_metadata_materialize_metaplex_account_snapshot;
/// Materializes one canonical Metaplex Metadata account snapshot.
pub use self::materializer::materializer_metadata_materialize_metaplex_metadata_snapshot;
/// Materializes one decoded Solana Program Metadata account snapshot.
pub use self::materializer::materializer_metadata_materialize_solana_program_metadata_account_snapshot;
/// Materializes one bounded Solana Program Metadata Buffer account snapshot.
pub use self::materializer::materializer_metadata_materialize_solana_program_metadata_buffer_snapshot;
/// Materializes one bounded Solana Program Metadata Metadata account snapshot.
pub use self::materializer::materializer_metadata_materialize_solana_program_metadata_metadata_snapshot;
/// Materializes Token-2022 embedded metadata and group snapshots.
pub use self::materializer::materializer_metadata_materialize_token_2022_snapshot;
/// Materializes one bounded Token-2022 account state snapshot.
@@ -2048,6 +2054,12 @@ pub(crate) use self::executor::executor_spl_token_2022_build_prepared_plan;
pub(crate) use self::executor::executor_spl_token_build_prepared_plan;
/// Canonical processor name for the native and SPL administration materializer.
pub(crate) use self::materializer::MT_ADMIN_PROCESSOR_NAME;
/// Event families accepted by the Solana Program Metadata instruction materializer.
pub(crate) use self::materializer::MT_METADATA_SPM_ACCEPTED_FAMILIES;
/// Stable processor name for Solana Program Metadata projections.
pub(crate) use self::materializer::MT_METADATA_SPM_PROCESSOR_NAME;
/// Stable projection schema version for Solana Program Metadata outputs.
pub(crate) use self::materializer::MT_METADATA_SPM_PROJECTION_VERSION;
/// Stable processor name for SPL Token and ATA risk facts.
pub(crate) use self::materializer::MT_RISK_PROCESSOR_NAME;
/// Stable projection contract version for SPL Token and ATA risk facts.
@@ -2068,6 +2080,8 @@ pub(crate) use self::materializer::TRACING_TARGET_MATERIALIZER_ADMIN;
pub(crate) use self::materializer::TRACING_TARGET_MATERIALIZER_COMPLIANCE_AUDIT;
/// Canonical tracing target for the native lifecycle materializer.
pub(crate) use self::materializer::TRACING_TARGET_MATERIALIZER_LIFECYCLE;
/// Canonical tracing target for Solana Program Metadata materialization.
pub(crate) use self::materializer::TRACING_TARGET_MATERIALIZER_METADATA_SOLANA_PROGRAM_METADATA;
/// Canonical tracing target for SPL Token and ATA risk facts.
pub(crate) use self::materializer::TRACING_TARGET_MATERIALIZER_RISK;
/// Canonical tracing target for the native stake and vote materializer.

View File

@@ -1,5 +1,5 @@
// file: kb-lib/src/materializer.rs
// version: 8
// version: 9
//! Consolidated materializer modules.
@@ -73,12 +73,18 @@ pub use self::lending::MtLendingMaterializer;
pub use self::lifecycle::MtLifecycleMaterializer;
/// Reserved liquidity materializer.
pub use self::liquidity::MtLiquidityMaterializer;
/// Token-2022 and Metaplex metadata snapshot materializer.
/// Token-2022, Metaplex and Solana Program Metadata materializer.
pub use self::metadata::MtMetadataMaterializer;
/// Materializes one canonical non-Metadata Metaplex account snapshot.
pub use self::metadata::materializer_metadata_materialize_metaplex_account_snapshot;
/// Materializes one canonical Metaplex Metadata account snapshot.
pub use self::metadata::materializer_metadata_materialize_metaplex_metadata_snapshot;
/// Materializes one decoded Solana Program Metadata account snapshot.
pub use self::metadata::materializer_metadata_materialize_solana_program_metadata_account_snapshot;
/// Materializes one bounded Solana Program Metadata Buffer account snapshot.
pub use self::metadata::materializer_metadata_materialize_solana_program_metadata_buffer_snapshot;
/// Materializes one bounded Solana Program Metadata Metadata account snapshot.
pub use self::metadata::materializer_metadata_materialize_solana_program_metadata_metadata_snapshot;
/// Materializes Token-2022 embedded metadata and group snapshots.
pub use self::metadata::materializer_metadata_materialize_token_2022_snapshot;
/// Reserved NFT materializer.
@@ -120,6 +126,14 @@ pub(crate) use self::admin::TRACING_TARGET_MATERIALIZER_ADMIN;
pub(crate) use self::compliance::TRACING_TARGET_MATERIALIZER_COMPLIANCE_AUDIT;
/// Canonical tracing target for the native lifecycle materializer.
pub(crate) use self::lifecycle::TRACING_TARGET_MATERIALIZER_LIFECYCLE;
/// Event families accepted by the Solana Program Metadata instruction materializer.
pub(crate) use self::metadata::MT_METADATA_SPM_ACCEPTED_FAMILIES;
/// Stable processor name for Solana Program Metadata projections.
pub(crate) use self::metadata::MT_METADATA_SPM_PROCESSOR_NAME;
/// Stable projection schema version for Solana Program Metadata outputs.
pub(crate) use self::metadata::MT_METADATA_SPM_PROJECTION_VERSION;
/// Canonical tracing target for Solana Program Metadata materialization.
pub(crate) use self::metadata::TRACING_TARGET_MATERIALIZER_METADATA_SOLANA_PROGRAM_METADATA;
/// Stable processor name for SPL Token and ATA risk facts.
pub(crate) use self::risk::MT_RISK_PROCESSOR_NAME;
/// Stable projection contract version for SPL Token and ATA risk facts.

View File

@@ -1,11 +1,12 @@
// file: kb-lib/src/materializer/metadata.rs
// version: 4
// version: 5
//! `metadata` materializer family.
mod core;
mod solana_program_metadata;
/// Token-2022 and Metaplex metadata snapshot materializer.
/// Token-2022, Metaplex and Solana Program Metadata materializer.
pub use self::core::MtMetadataMaterializer;
/// Materializes one canonical non-Metadata Metaplex account snapshot.
pub use self::core::materializer_metadata_materialize_metaplex_account_snapshot;
@@ -13,3 +14,18 @@ pub use self::core::materializer_metadata_materialize_metaplex_account_snapshot;
pub use self::core::materializer_metadata_materialize_metaplex_metadata_snapshot;
/// Materializes Token-2022 embedded metadata and group snapshots.
pub use self::core::materializer_metadata_materialize_token_2022_snapshot;
/// Materializes one decoded Solana Program Metadata account snapshot.
pub use self::solana_program_metadata::materializer_metadata_materialize_solana_program_metadata_account_snapshot;
/// Materializes one bounded Solana Program Metadata Buffer account snapshot.
pub use self::solana_program_metadata::materializer_metadata_materialize_solana_program_metadata_buffer_snapshot;
/// Materializes one bounded Solana Program Metadata Metadata account snapshot.
pub use self::solana_program_metadata::materializer_metadata_materialize_solana_program_metadata_metadata_snapshot;
/// Event families accepted by the Solana Program Metadata instruction materializer.
pub(crate) use self::solana_program_metadata::MT_METADATA_SPM_ACCEPTED_FAMILIES;
/// Stable processor name for Solana Program Metadata projections.
pub(crate) use self::solana_program_metadata::MT_METADATA_SPM_PROCESSOR_NAME;
/// Stable projection schema version for Solana Program Metadata outputs.
pub(crate) use self::solana_program_metadata::MT_METADATA_SPM_PROJECTION_VERSION;
/// Canonical tracing target for Solana Program Metadata materialization.
pub(crate) use self::solana_program_metadata::TRACING_TARGET_MATERIALIZER_METADATA_SOLANA_PROGRAM_METADATA;

View File

@@ -1,7 +1,7 @@
// file: kb-lib/src/materializer/metadata/core.rs
// version: 6
// version: 7
//! Stable Token-2022 and Metaplex metadata snapshot materializer.
//! Stable Token-2022, Metaplex and Solana Program Metadata materializer.
/// Initial materializer implementation placeholder.
#[derive(Clone, Debug, Default)]
@@ -16,8 +16,13 @@ impl crate::MtMaterializer for crate::MtMetadataMaterializer {
return env!("CARGO_PKG_VERSION");
}
fn accepts_event(&self, _event: &crate::MdDecodedProtocolEvent) -> bool {
return false;
fn accepts_event(&self, event: &crate::MdDecodedProtocolEvent) -> bool {
return event.event_family == crate::MdEventFamily::Metadata
&& event.program_id.0 == kb_program_ids::METADATA_SOLANA_PROGRAM_METADATA_PROGRAM_ID
&& event.surface_code.0 == crate::DC_METADATA_SPM_SURFACE_CODE
&& crate::DC_METADATA_SPM_INSTRUCTION_ENTRIES
.iter()
.any(|(_, code)| return *code == event.event_name.0.as_str());
}
fn materialize_event(

View File

@@ -0,0 +1,24 @@
// file: kb-lib/src/materializer/metadata/solana_program_metadata.rs
// version: 1
//! Solana Program Metadata materialization surface.
mod constants;
mod instruction;
mod state;
/// Materializes one decoded Solana Program Metadata account snapshot.
pub use self::state::materializer_metadata_materialize_solana_program_metadata_account_snapshot;
/// Materializes one bounded Solana Program Metadata Buffer account snapshot.
pub use self::state::materializer_metadata_materialize_solana_program_metadata_buffer_snapshot;
/// Materializes one bounded Solana Program Metadata Metadata account snapshot.
pub use self::state::materializer_metadata_materialize_solana_program_metadata_metadata_snapshot;
/// Event families accepted by the Solana Program Metadata instruction materializer.
pub(crate) use self::constants::MT_METADATA_SPM_ACCEPTED_FAMILIES;
/// Stable processor name for Solana Program Metadata projections.
pub(crate) use self::constants::MT_METADATA_SPM_PROCESSOR_NAME;
/// Stable projection schema version for Solana Program Metadata outputs.
pub(crate) use self::constants::MT_METADATA_SPM_PROJECTION_VERSION;
/// Canonical tracing target for Solana Program Metadata materialization.
pub(crate) use self::constants::TRACING_TARGET_MATERIALIZER_METADATA_SOLANA_PROGRAM_METADATA;

View File

@@ -0,0 +1,16 @@
// file: kb-lib/src/materializer/metadata/solana_program_metadata/constants.rs
// version: 1
//! Solana Program Metadata materializer constants.
/// Event families accepted by the Solana Program Metadata instruction materializer.
pub(crate) const MT_METADATA_SPM_ACCEPTED_FAMILIES: &[crate::MdEventFamily] =
&[crate::MdEventFamily::Metadata];
/// Stable processor name for Solana Program Metadata projections.
pub(crate) const MT_METADATA_SPM_PROCESSOR_NAME: &str =
"kb-lib.materializer.metadata.solana_program_metadata";
/// Stable projection schema version for Solana Program Metadata outputs.
pub(crate) const MT_METADATA_SPM_PROJECTION_VERSION: u16 = 1;
/// Canonical tracing target for Solana Program Metadata materialization.
pub(crate) const TRACING_TARGET_MATERIALIZER_METADATA_SOLANA_PROGRAM_METADATA: &str =
"kb-lib.materializer.metadata.solana_program_metadata";

View File

@@ -0,0 +1,325 @@
// file: kb-lib/src/materializer/metadata/solana_program_metadata/instruction.rs
// version: 1
//! Committed Solana Program Metadata instruction projections.
impl crate::MtApiEventMaterializer for crate::MtMetadataMaterializer {
fn identity(&self) -> crate::MtApiMaterializerIdentity {
return crate::MtApiMaterializerIdentity {
name: crate::MT_METADATA_SPM_PROCESSOR_NAME.to_string(),
version: env!("CARGO_PKG_VERSION").to_string(),
};
}
fn accepted_families(&self) -> &'static [crate::MdEventFamily] {
return crate::MT_METADATA_SPM_ACCEPTED_FAMILIES;
}
fn accepts_observation(&self, observation: &crate::DcApiDecodedObservation) -> bool {
return observation.event.event_family == crate::MdEventFamily::Metadata
&& observation.event.program_id.0
== kb_program_ids::METADATA_SOLANA_PROGRAM_METADATA_PROGRAM_ID
&& observation.event.surface_code.0 == crate::DC_METADATA_SPM_SURFACE_CODE
&& payload_contract_matches(observation)
&& crate::DC_METADATA_SPM_INSTRUCTION_ENTRIES
.iter()
.any(|(_, code)| return *code == observation.event.event_name.0.as_str());
}
fn transaction_policy(
&self,
_family: crate::MdEventFamily,
) -> crate::MtApiMaterializationTransactionPolicy {
return crate::MtApiMaterializationTransactionPolicy::SuccessfulCommittedOnly;
}
fn materialize(
&self,
observation: &crate::DcApiDecodedObservation,
) -> crate::MtApiMaterializerExecutionResult {
if !crate::MtApiEventMaterializer::accepts_observation(self, observation) {
return crate::MtApiMaterializerExecutionResult::ignored();
}
if observation.transaction_failed || !observation.observation_committed {
return crate::MtApiMaterializerExecutionResult::refused(
"failed_transaction_solana_program_metadata_refused",
"failed or uncommitted Solana Program Metadata instructions cannot create mutation outputs",
);
}
let operation = observation.event.event_name.0.as_str();
let projection = instruction_projection(operation, &observation.payload_json);
let output = crate::MtApiMaterializedOutput {
output_key: format!(
"solana_program_metadata_instruction:{}:{}:{}",
observation.event.signature.0, observation.event.instruction_path.0, operation
),
family: crate::MdMaterializedEventFamily::Metadata,
payload_json: serde_json::json!({
"projectionVersion": crate::MT_METADATA_SPM_PROJECTION_VERSION,
"domain": "solana_program_metadata",
"projectionKind": "committed_instruction_mutation",
"projectionSemantics": "committed_instruction_fact_non_authoritative_state",
"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(),
"sourceKind": observation.event.source_kind,
"transactionSucceeded": true,
"stateProjectionAuthority": "post_execution_account_snapshot",
"authoritativeFinalStateCaptured": false,
"projection": projection,
"decodedPayload": observation.payload_json.clone(),
"externalContentFetched": false,
"externalAccountRead": false,
"provenance": {
"processorName": crate::MT_METADATA_SPM_PROCESSOR_NAME,
"processorVersion": env!("CARGO_PKG_VERSION"),
"decoderProof": observation.proof.clone()
}
}),
};
tracing::debug!(
target: crate::TRACING_TARGET_MATERIALIZER_METADATA_SOLANA_PROGRAM_METADATA,
action = "materialize_solana_program_metadata_instruction",
operation = operation,
signature = %observation.event.signature.0,
instruction_path = %observation.event.instruction_path.0,
output_count = 1_usize,
"materialize committed Solana Program Metadata instruction"
);
return crate::MtApiMaterializerExecutionResult {
status: crate::MtApiMaterializerOutcomeStatus::Inserted,
outputs: std::vec![output],
diagnostics: std::vec::Vec::new(),
};
}
}
fn payload_contract_matches(observation: &crate::DcApiDecodedObservation) -> bool {
let instruction =
observation.payload_json.get("instruction").and_then(serde_json::Value::as_str);
let program_id = observation.payload_json.get("programId").and_then(serde_json::Value::as_str);
return instruction == std::option::Option::Some(observation.event.event_name.0.as_str())
&& program_id
== std::option::Option::Some(
kb_program_ids::METADATA_SOLANA_PROGRAM_METADATA_PROGRAM_ID,
);
}
fn instruction_projection(operation: &str, payload: &serde_json::Value) -> serde_json::Value {
let parameters = match payload.get("parameters") {
std::option::Option::Some(value) => value.clone(),
std::option::Option::None => serde_json::Value::Null,
};
let accounts = match payload.get("accounts") {
std::option::Option::Some(value) => value.clone(),
std::option::Option::None => serde_json::Value::Null,
};
return serde_json::json!({
"stateTransition": state_transition(operation),
"targetAccount": target_account(operation, &accounts),
"authority": account_for_role(&accounts, "authority"),
"destination": account_for_role(&accounts, "destination"),
"sourceBuffer": account_for_role(&accounts, "source_buffer"),
"buffer": account_for_role(&accounts, "buffer"),
"program": account_for_role(&accounts, "program"),
"programData": account_for_role(&accounts, "program_data"),
"parameters": parameters,
"accounts": accounts,
"currentStableInstruction": true,
"deprecated": false,
"replaced": false,
"executionPolicy": "current_candidate",
"preStableAlias": if operation == "trim" {
serde_json::Value::String("withdraw_excess_lamports".to_string())
} else {
serde_json::Value::Null
},
"preStableAliasDecodedSeparately": false
});
}
fn state_transition(operation: &str) -> &'static str {
match operation {
"write" => return "buffer_content_written",
"initialize" => return "metadata_account_initialized",
"set_authority" => return "account_authority_updated",
"set_data" => return "metadata_data_configuration_updated",
"set_immutable" => return "metadata_account_marked_immutable",
"trim" => return "account_capacity_trimmed",
"close" => return "account_closed",
"allocate" => return "buffer_account_allocated",
"extend" => return "account_capacity_extended",
_ => return "unknown",
}
}
fn target_account(operation: &str, accounts: &serde_json::Value) -> serde_json::Value {
let roles: &[&str] = match operation {
"write" | "allocate" => &["buffer"],
"initialize" | "set_data" | "set_immutable" => &["metadata"],
"set_authority" | "trim" | "close" | "extend" => &["account"],
_ => &[],
};
for role in roles {
let value = account_for_role(accounts, role);
if !value.is_null() {
return value;
}
}
return serde_json::Value::Null;
}
fn account_for_role(accounts: &serde_json::Value, role: &str) -> serde_json::Value {
let values = match accounts.as_array() {
std::option::Option::Some(value) => value,
std::option::Option::None => return serde_json::Value::Null,
};
for value in values {
let candidate = value.get("role").and_then(serde_json::Value::as_str);
let present = match value.get("present").and_then(serde_json::Value::as_bool) {
std::option::Option::Some(candidate) => candidate,
std::option::Option::None => true,
};
if candidate == std::option::Option::Some(role) && present {
return match value.get("accountKey") {
std::option::Option::Some(candidate) => candidate.clone(),
std::option::Option::None => serde_json::Value::Null,
};
}
}
return serde_json::Value::Null;
}
#[cfg(test)]
mod tests {
const MATRIX_JSON: &str = include_str!(
"../../../../../test-fixtures/contract-matrices/SOLANA_PROGRAM_METADATA_MATERIALIZATION_MATRIX.json"
);
fn observation(operation: &str, committed: bool) -> crate::DcApiDecodedObservation {
return crate::DcApiDecodedObservation {
event_key: format!("solana_program_metadata:{operation}:0"),
event: crate::MdDecodedProtocolEvent {
signature: crate::MdSignature("signature111".to_string()),
slot: crate::MdSlot(42),
instruction_path: crate::MdInstructionPath("0".to_string()),
program_id: crate::MdProgramId(
kb_program_ids::METADATA_SOLANA_PROGRAM_METADATA_PROGRAM_ID.to_string(),
),
protocol_code: crate::MdProtocolCode(
crate::DC_METADATA_SPM_SURFACE_CODE.to_string(),
),
surface_code: crate::MdSurfaceCode(crate::DC_METADATA_SPM_SURFACE_CODE.to_string()),
event_code: crate::MdEventCode(format!(
"{}.{}",
crate::DC_METADATA_SPM_SURFACE_CODE,
operation
)),
event_name: crate::MdEventName(operation.to_string()),
event_family: crate::MdEventFamily::Metadata,
source_kind: crate::MdEventSourceKind::Instruction,
confidence: crate::MdDecoderConfidence::ManualExact,
},
payload_json: serde_json::json!({
"programId": kb_program_ids::METADATA_SOLANA_PROGRAM_METADATA_PROGRAM_ID,
"instruction": operation,
"parameters": {"length": 8},
"accounts": [
{"role":"account","accountKey":"account111","present":true},
{"role":"authority","accountKey":"authority111","present":true}
],
"wire": {"sha256":"00"}
}),
transaction_failed: !committed,
transaction_error: if committed {
std::option::Option::None
} else {
std::option::Option::Some(serde_json::json!({"InstructionError":[0,"Custom"]}))
},
observation_committed: committed,
proof: crate::DcApiDecoderProof {
kind: crate::DcApiDecoderProofKind::Manual,
confidence: crate::MdDecoderConfidence::ManualExact,
evidence: std::vec!["wire".to_string()],
},
};
}
#[test]
fn every_stable_instruction_has_one_materialized_mutation_fact() {
let materializer = crate::MtMetadataMaterializer;
for (_, operation) in crate::DC_METADATA_SPM_INSTRUCTION_ENTRIES.iter().copied() {
let value = observation(operation, true);
assert!(crate::MtApiEventMaterializer::accepts_observation(&materializer, &value));
let result = crate::MtApiEventMaterializer::materialize(&materializer, &value);
assert_eq!(result.status, crate::MtApiMaterializerOutcomeStatus::Inserted);
assert_eq!(result.outputs.len(), 1);
assert_eq!(result.outputs[0].payload_json["operation"], *operation);
assert_eq!(result.outputs[0].payload_json["projection"]["deprecated"], false);
assert_eq!(result.outputs[0].payload_json["projection"]["replaced"], false);
}
}
#[test]
fn failed_instruction_is_refused_and_foreign_surface_is_ignored() {
let materializer = crate::MtMetadataMaterializer;
let failed = observation("set_data", false);
let failure = crate::MtApiEventMaterializer::materialize(&materializer, &failed);
assert_eq!(failure.status, crate::MtApiMaterializerOutcomeStatus::Refused);
let mut foreign = observation("set_data", true);
foreign.event.program_id =
crate::MdProgramId(kb_program_ids::SPL_TOKEN_2022_PROGRAM_ID.to_string());
let ignored = crate::MtApiEventMaterializer::materialize(&materializer, &foreign);
assert_eq!(ignored.status, crate::MtApiMaterializerOutcomeStatus::Ignored);
}
#[test]
fn materialization_matrix_matches_two_account_states_and_nine_instructions() {
let parsed = serde_json::from_str::<serde_json::Value>(MATRIX_JSON);
assert!(parsed.is_ok());
let matrix = match parsed {
std::result::Result::Ok(value) => value,
std::result::Result::Err(_) => return,
};
let accounts = match matrix.get("accountSnapshots").and_then(serde_json::Value::as_array) {
std::option::Option::Some(value) => value,
std::option::Option::None => return,
};
let instructions =
match matrix.get("instructionFacts").and_then(serde_json::Value::as_array) {
std::option::Option::Some(value) => value,
std::option::Option::None => return,
};
assert_eq!(accounts.len(), 2);
assert_eq!(instructions.len(), crate::DC_METADATA_SPM_INSTRUCTION_ENTRIES.len());
for (position, (tag, operation)) in
crate::DC_METADATA_SPM_INSTRUCTION_ENTRIES.iter().copied().enumerate()
{
assert_eq!(instructions[position]["tag"], tag);
assert_eq!(instructions[position]["entryCode"], operation);
assert_eq!(instructions[position]["deprecated"], false);
assert_eq!(instructions[position]["replaced"], false);
assert_eq!(
instructions[position]["stateTransition"],
super::state_transition(operation)
);
}
}
#[test]
fn trim_keeps_prestable_alias_as_provenance_only() {
let materializer = crate::MtMetadataMaterializer;
let value = observation("trim", true);
let result = crate::MtApiEventMaterializer::materialize(&materializer, &value);
assert_eq!(
result.outputs[0].payload_json["projection"]["preStableAlias"],
"withdraw_excess_lamports"
);
assert_eq!(
result.outputs[0].payload_json["projection"]["preStableAliasDecodedSeparately"],
false
);
}
}

View File

@@ -0,0 +1,343 @@
// file: kb-lib/src/materializer/metadata/solana_program_metadata/state.rs
// version: 2
//! Authoritative Solana Program Metadata account-state projections.
use base64::Engine; // rust-rules: trait-import
use sha2::Digest; // rust-rules: trait-import
/// Materializes one decoded Solana Program Metadata account snapshot.
pub fn materializer_metadata_materialize_solana_program_metadata_account_snapshot(
slot: u64,
snapshot: &crate::DcMetadataSpmAccountSnapshot,
) -> std::result::Result<crate::MtApiMaterializedOutput, String> {
match snapshot {
crate::DcMetadataSpmAccountSnapshot::Buffer(value) => {
return crate::materializer_metadata_materialize_solana_program_metadata_buffer_snapshot(
slot,
value,
);
},
crate::DcMetadataSpmAccountSnapshot::Metadata(value) => {
return crate::materializer_metadata_materialize_solana_program_metadata_metadata_snapshot(
slot,
value,
);
},
}
}
/// Materializes one bounded Solana Program Metadata Buffer account snapshot.
pub fn materializer_metadata_materialize_solana_program_metadata_buffer_snapshot(
slot: u64,
snapshot: &crate::DcMetadataSpmBufferAccountSnapshot,
) -> std::result::Result<crate::MtApiMaterializedOutput, String> {
if snapshot.account.trim().is_empty() {
return std::result::Result::Err(
"Solana Program Metadata Buffer materialization requires a non-empty account"
.to_string(),
);
}
if snapshot.program.as_ref().is_some_and(|value| return value.trim().is_empty())
|| snapshot.authority.as_ref().is_some_and(|value| return value.trim().is_empty())
{
return std::result::Result::Err(
"Solana Program Metadata Buffer optional identities must not be empty".to_string(),
);
}
if snapshot.data.len() != snapshot.allocated_data_bytes {
return std::result::Result::Err(
"Solana Program Metadata Buffer allocated byte count does not match decoded data"
.to_string(),
);
}
if snapshot.allocated_data_bytes
> crate::DC_METADATA_SPM_MAX_ACCOUNT_BYTES
.saturating_sub(crate::DC_METADATA_SPM_BUFFER_HEADER_BYTES)
{
return std::result::Result::Err(
"Solana Program Metadata Buffer exceeds the bounded account-data contract".to_string(),
);
}
let seed_hex = hex(snapshot.seed.as_bytes());
let data = bytes_projection(snapshot.data.as_slice());
return std::result::Result::Ok(crate::MtApiMaterializedOutput {
output_key: format!("solana_program_metadata_account_state:{}", snapshot.account),
family: crate::MdMaterializedEventFamily::Metadata,
payload_json: serde_json::json!({
"projectionVersion": crate::MT_METADATA_SPM_PROJECTION_VERSION,
"domain": "solana_program_metadata",
"projectionKind": "buffer_state",
"projectionSemantics": "authoritative_bounded_owned_account_snapshot",
"account": snapshot.account,
"slot": slot,
"programId": kb_program_ids::METADATA_SOLANA_PROGRAM_METADATA_PROGRAM_ID,
"accountDiscriminator": "buffer",
"metadataProgram": snapshot.program,
"authority": snapshot.authority,
"mutable": snapshot.authority.is_some(),
"canonical": snapshot.canonical,
"seedHex": seed_hex,
"allocatedDataBytes": snapshot.allocated_data_bytes,
"data": data,
"metadataSource": "solana_program_metadata_owned_buffer_account",
"externalContentFetched": false,
"externalAccountRead": false,
"instructionProjectionRole": "mutation_observation_only_no_duplicate_state",
"provenance": {
"processorName": crate::MT_METADATA_SPM_PROCESSOR_NAME,
"processorVersion": env!("CARGO_PKG_VERSION"),
"source": "bounded_solana_program_metadata_account_decoder"
}
}),
});
}
/// Materializes one bounded Solana Program Metadata Metadata account snapshot.
pub fn materializer_metadata_materialize_solana_program_metadata_metadata_snapshot(
slot: u64,
snapshot: &crate::DcMetadataSpmMetadataAccountSnapshot,
) -> std::result::Result<crate::MtApiMaterializedOutput, String> {
if snapshot.account.trim().is_empty() || snapshot.program.trim().is_empty() {
return std::result::Result::Err(
"Solana Program Metadata state materialization requires account and program identities"
.to_string(),
);
}
if snapshot.authority.as_ref().is_some_and(|value| return value.trim().is_empty()) {
return std::result::Result::Err(
"Solana Program Metadata authority must not be empty when present".to_string(),
);
}
if snapshot.data_source != snapshot.data.source() {
return std::result::Result::Err(
"Solana Program Metadata data source does not match decoded content".to_string(),
);
}
if let std::result::Result::Err(error) = snapshot.data.validate() {
return std::result::Result::Err(error.to_string());
}
let logical_data_bytes = match usize::try_from(snapshot.data_length) {
std::result::Result::Ok(value) => value,
std::result::Result::Err(_) => {
return std::result::Result::Err(
"Solana Program Metadata logical data length is not representable".to_string(),
);
},
};
if logical_data_bytes != snapshot.data.wire_byte_len() {
return std::result::Result::Err(
"Solana Program Metadata logical data length does not match decoded content"
.to_string(),
);
}
if snapshot.allocated_data_bytes
> crate::DC_METADATA_SPM_MAX_ACCOUNT_BYTES
.saturating_sub(crate::DC_METADATA_SPM_METADATA_HEADER_BYTES)
{
return std::result::Result::Err(
"Solana Program Metadata state exceeds the bounded account-data contract".to_string(),
);
}
if snapshot.allocated_data_bytes
!= logical_data_bytes.saturating_add(snapshot.trailing_capacity_bytes)
{
return std::result::Result::Err(
"Solana Program Metadata allocated capacity is inconsistent".to_string(),
);
}
let seed_hex = hex(snapshot.seed.as_bytes());
let data = data_projection(&snapshot.data);
return std::result::Result::Ok(crate::MtApiMaterializedOutput {
output_key: format!("solana_program_metadata_account_state:{}", snapshot.account),
family: crate::MdMaterializedEventFamily::Metadata,
payload_json: serde_json::json!({
"projectionVersion": crate::MT_METADATA_SPM_PROJECTION_VERSION,
"domain": "solana_program_metadata",
"projectionKind": "metadata_state",
"projectionSemantics": "authoritative_bounded_owned_account_snapshot",
"account": snapshot.account,
"slot": slot,
"programId": kb_program_ids::METADATA_SOLANA_PROGRAM_METADATA_PROGRAM_ID,
"accountDiscriminator": "metadata",
"bump": snapshot.bump,
"metadataProgram": snapshot.program,
"authority": snapshot.authority,
"mutable": snapshot.mutable,
"canonical": snapshot.canonical,
"seedHex": seed_hex,
"encoding": snapshot.encoding,
"compression": snapshot.compression,
"format": snapshot.format,
"dataSource": snapshot.data_source,
"dataLength": snapshot.data_length,
"allocatedDataBytes": snapshot.allocated_data_bytes,
"trailingCapacityBytes": snapshot.trailing_capacity_bytes,
"data": data,
"metadataSource": "solana_program_metadata_owned_metadata_account",
"externalContentFetched": false,
"externalAccountRead": false,
"instructionProjectionRole": "mutation_observation_only_no_duplicate_state",
"provenance": {
"processorName": crate::MT_METADATA_SPM_PROCESSOR_NAME,
"processorVersion": env!("CARGO_PKG_VERSION"),
"source": "bounded_solana_program_metadata_account_decoder"
}
}),
});
}
fn data_projection(data: &crate::DcMetadataSpmData) -> serde_json::Value {
match data {
crate::DcMetadataSpmData::Direct(value) => {
let mut projection = bytes_projection(value.as_slice());
projection["kind"] = serde_json::json!("direct");
return projection;
},
crate::DcMetadataSpmData::Url(value) => {
return serde_json::json!({
"kind": "url",
"value": value,
"lengthBytes": value.len(),
"sha256": hash(value.as_bytes()),
"fetched": false
});
},
crate::DcMetadataSpmData::External(value) => {
return serde_json::json!({
"kind": "external",
"address": value.address.to_string(),
"offset": value.offset,
"length": value.length.map(std::num::NonZeroU32::get),
"wireLengthBytes": crate::DC_METADATA_SPM_EXTERNAL_DATA_BYTES,
"accountRead": false
});
},
}
}
fn bytes_projection(bytes: &[u8]) -> serde_json::Value {
return serde_json::json!({
"lengthBytes": bytes.len(),
"sha256": hash(bytes),
"base64": base64::engine::general_purpose::STANDARD.encode(bytes)
});
}
fn hash(bytes: &[u8]) -> std::string::String {
let digest = sha2::Sha256::digest(bytes);
let mut output = std::string::String::with_capacity(64);
for byte in digest {
output.push_str(format!("{byte:02x}").as_str());
}
return output;
}
fn hex(bytes: &[u8]) -> std::string::String {
let mut output = std::string::String::with_capacity(bytes.len().saturating_mul(2));
for byte in bytes {
output.push_str(format!("{byte:02x}").as_str());
}
return output;
}
#[cfg(test)]
mod tests {
#[test]
fn buffer_snapshot_preserves_all_bounded_bytes_and_authority_state() {
let snapshot = crate::DcMetadataSpmBufferAccountSnapshot {
account: "buffer111".to_string(),
program: std::option::Option::Some("program111".to_string()),
authority: std::option::Option::Some("authority111".to_string()),
canonical: true,
seed: match crate::DcMetadataSpmSeed::from_wire_bytes(&[7_u8; 16]) {
std::result::Result::Ok(value) => value,
std::result::Result::Err(_) => crate::DcMetadataSpmSeed::default(),
},
data: std::vec![1, 2, 3],
allocated_data_bytes: 3,
};
let output =
crate::materializer_metadata_materialize_solana_program_metadata_buffer_snapshot(
42, &snapshot,
);
assert!(output.is_ok());
let payload = match output.as_ref() {
std::result::Result::Ok(value) => value.payload_json.clone(),
std::result::Result::Err(_) => serde_json::Value::Null,
};
assert_eq!(payload["projectionKind"], "buffer_state");
assert_eq!(payload["data"]["base64"], "AQID");
assert_eq!(payload["mutable"], true);
}
#[test]
fn metadata_snapshots_materialize_every_onchain_source_without_resolution() {
let direct = metadata_snapshot(crate::DcMetadataSpmData::Direct(std::vec![4, 5]));
let direct_output =
crate::materializer_metadata_materialize_solana_program_metadata_metadata_snapshot(
9, &direct,
);
assert_eq!(
direct_output
.as_ref()
.map(|value| return value.payload_json["data"]["kind"].clone()),
std::result::Result::Ok(serde_json::json!("direct"))
);
let url_value = "https://example.invalid/idl.json".to_string();
let mut url = metadata_snapshot(crate::DcMetadataSpmData::Url(url_value.clone()));
url.data_source = crate::DcMetadataSpmDataSource::Url;
url.data_length = match u32::try_from(url_value.len()) {
std::result::Result::Ok(value) => value,
std::result::Result::Err(_) => 0,
};
url.allocated_data_bytes = url_value.len();
let url_output =
crate::materializer_metadata_materialize_solana_program_metadata_metadata_snapshot(
10, &url,
);
assert_eq!(
url_output
.as_ref()
.map(|value| return value.payload_json["data"]["fetched"].clone()),
std::result::Result::Ok(serde_json::json!(false))
);
}
#[test]
fn inconsistent_snapshot_lengths_fail_closed() {
let mut snapshot = metadata_snapshot(crate::DcMetadataSpmData::Direct(std::vec![1, 2]));
snapshot.data_length = 1;
assert!(
crate::materializer_metadata_materialize_solana_program_metadata_metadata_snapshot(
1, &snapshot
)
.is_err()
);
}
fn metadata_snapshot(
data: crate::DcMetadataSpmData,
) -> crate::DcMetadataSpmMetadataAccountSnapshot {
return crate::DcMetadataSpmMetadataAccountSnapshot {
account: "metadata111".to_string(),
bump: 2,
program: "program111".to_string(),
authority: std::option::Option::Some("authority111".to_string()),
mutable: true,
canonical: true,
seed: crate::DcMetadataSpmSeed::default(),
encoding: crate::DcMetadataSpmEncoding::Utf8,
compression: crate::DcMetadataSpmCompression::None,
format: crate::DcMetadataSpmFormat::Json,
data_source: crate::DcMetadataSpmDataSource::Direct,
data_length: match u32::try_from(data.wire_byte_len()) {
std::result::Result::Ok(value) => value,
std::result::Result::Err(_) => 0,
},
allocated_data_bytes: data.wire_byte_len(),
trailing_capacity_bytes: 0,
data,
};
}
}