0.1.0
This commit is contained in:
@@ -0,0 +1,18 @@
|
||||
// file: kb_materializer_metadata/src/lib.rs
|
||||
// version: 5
|
||||
|
||||
//! Materializer crate for `metadata`.
|
||||
#![warn(missing_docs)]
|
||||
#![deny(unreachable_pub)]
|
||||
#![forbid(unsafe_code)]
|
||||
|
||||
mod materializer;
|
||||
|
||||
/// Exposes the materializer type implemented by this crate.
|
||||
pub use crate::materializer::MetadataMaterializer;
|
||||
/// Materialize one canonical non-Metadata Metaplex account snapshot.
|
||||
pub use crate::materializer::materialize_metaplex_account_snapshot;
|
||||
/// Materialize one canonical Metaplex Metadata account snapshot without instruction-state duplication.
|
||||
pub use crate::materializer::materialize_metaplex_metadata_snapshot;
|
||||
/// Materialize Token-2022 embedded metadata and group state from one parsed mint snapshot.
|
||||
pub use crate::materializer::materialize_token_2022_metadata_snapshot;
|
||||
@@ -0,0 +1,585 @@
|
||||
// file: kb_materializer_metadata/src/materializer.rs
|
||||
// version: 7
|
||||
|
||||
//! Materializer scaffold for the `kb_materializer_metadata` crate.
|
||||
|
||||
/// Initial materializer implementation placeholder.
|
||||
#[derive(Clone, Debug, Default)]
|
||||
pub struct MetadataMaterializer;
|
||||
|
||||
impl kb_materializer_api::Materializer for crate::MetadataMaterializer {
|
||||
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: &kb_model::DecodedProtocolEvent) -> bool {
|
||||
return false;
|
||||
}
|
||||
|
||||
fn materialize_event(
|
||||
&self,
|
||||
event: &kb_model::DecodedProtocolEvent,
|
||||
) -> kb_core::Result<std::vec::Vec<kb_model::MaterializedEvent>> {
|
||||
let materialized = kb_model::MaterializedEvent {
|
||||
signature: event.signature.clone(),
|
||||
slot: event.slot,
|
||||
decoded_family: event.event_family,
|
||||
materialized_family: kb_model::MaterializedEventFamily::Metadata,
|
||||
};
|
||||
return std::result::Result::Ok(std::vec![materialized]);
|
||||
}
|
||||
}
|
||||
|
||||
/// Materialize embedded Token-2022 metadata, group, and group-member extensions.
|
||||
pub fn materialize_token_2022_metadata_snapshot(
|
||||
account_key: &str,
|
||||
slot: u64,
|
||||
state: &kb_decoder_spl_token_2022::state::Token2022State,
|
||||
) -> std::result::Result<std::vec::Vec<kb_materializer_api::MaterializedOutput>, 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 != kb_decoder_spl_token_2022::state::Token2022StateKind::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(kb_materializer_api::MaterializedOutput {
|
||||
output_key: format!("token_2022_metadata_snapshot:{account_key}:{slot}:{kind}"),
|
||||
family: kb_model::MaterializedEventFamily::Metadata,
|
||||
payload_json: serde_json::json!({
|
||||
"projectionVersion": 1,
|
||||
"domain": "spl_token_2022_embedded_metadata",
|
||||
"projectionSemantics": "authoritative_bounded_mint_extension_snapshot",
|
||||
"idempotenceKey": format!("token_2022_metadata:{account_key}:{slot}:{kind}"),
|
||||
"accountKey": account_key,
|
||||
"slot": slot,
|
||||
"programId": kb_program_ids::SPL_TOKEN_2022_PROGRAM_ID,
|
||||
"metadataSource": "token_2022_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_token_2022_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: &kb_decoder_metadata_metaplex_token_metadata::CanonicalMetadataAccountSnapshot,
|
||||
) -> std::result::Result<kb_materializer_api::MaterializedOutput, String> {
|
||||
if !snapshot.provenance.committed {
|
||||
return std::result::Result::Err(
|
||||
"Metaplex metadata state materialization requires committed provenance".to_string(),
|
||||
);
|
||||
}
|
||||
if snapshot.identity.materialization
|
||||
!= kb_decoder_metadata_metaplex_token_metadata::CanonicalMaterializationPolicy::ActiveState
|
||||
{
|
||||
return std::result::Result::Err(
|
||||
"Metaplex MetadataV1 materialization requires active-state policy".to_string(),
|
||||
);
|
||||
}
|
||||
let metadata = match &snapshot.state {
|
||||
kb_decoder_metadata_metaplex_token_metadata::CanonicalMetadataAccountState::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(kb_materializer_api::MaterializedOutput {
|
||||
output_key: format!("metaplex_metadata_state:{}", metadata.account),
|
||||
family: kb_model::MaterializedEventFamily::Metadata,
|
||||
payload_json: payload,
|
||||
});
|
||||
}
|
||||
|
||||
#[cfg(test)]
|
||||
mod tests_metaplex_metadata_state {
|
||||
pub(super) fn snapshot(
|
||||
committed: bool,
|
||||
) -> kb_decoder_metadata_metaplex_token_metadata::CanonicalMetadataAccountSnapshot {
|
||||
let metadata = kb_decoder_metadata_metaplex_token_metadata::MetadataAccountSnapshot {
|
||||
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 kb_decoder_metadata_metaplex_token_metadata::CanonicalMetadataAccountSnapshot::from_metadata(
|
||||
metadata,
|
||||
kb_decoder_metadata_metaplex_token_metadata::CanonicalAccountProvenance {
|
||||
source: kb_decoder_metadata_metaplex_token_metadata::CanonicalObservationSource::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 = kb_decoder_metadata_metaplex_token_metadata::CanonicalMetadataAccountState::EditionMarker(
|
||||
kb_decoder_metadata_metaplex_token_metadata::EditionMarkerAccountSnapshot {
|
||||
account: "marker111".to_string(),
|
||||
bump: 1,
|
||||
mint: "mint111".to_string(),
|
||||
kind: kb_decoder_metadata_metaplex_token_metadata::EditionMarkerAccountKind::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: &kb_decoder_metadata_metaplex_token_metadata::CanonicalMetadataAccountSnapshot,
|
||||
) -> std::result::Result<kb_materializer_api::MaterializedOutput, 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 {
|
||||
kb_decoder_metadata_metaplex_token_metadata::CanonicalMetadataAccountState::Metadata(_) => {
|
||||
return std::result::Result::Err(
|
||||
"MetadataV1 must use the dedicated metadata-state materializer".to_string(),
|
||||
);
|
||||
},
|
||||
kb_decoder_metadata_metaplex_token_metadata::CanonicalMetadataAccountState::Edition(value) => (
|
||||
"edition_state",
|
||||
"kb_materializer_metadata",
|
||||
value.payload_json.clone(),
|
||||
),
|
||||
kb_decoder_metadata_metaplex_token_metadata::CanonicalMetadataAccountState::EditionMarker(value) => (
|
||||
"edition_marker_state",
|
||||
"kb_materializer_metadata",
|
||||
value.payload_json.clone(),
|
||||
),
|
||||
kb_decoder_metadata_metaplex_token_metadata::CanonicalMetadataAccountState::TokenRecord(value) => (
|
||||
"programmable_token_state",
|
||||
"kb_materializer_lifecycle",
|
||||
value.payload_json.clone(),
|
||||
),
|
||||
kb_decoder_metadata_metaplex_token_metadata::CanonicalMetadataAccountState::DelegateRecord(value) => (
|
||||
"delegate_admin_state",
|
||||
"kb_materializer_admin",
|
||||
value.payload_json.clone(),
|
||||
),
|
||||
kb_decoder_metadata_metaplex_token_metadata::CanonicalMetadataAccountState::AuthorityRecord(value) => (
|
||||
"authority_admin_state",
|
||||
"kb_materializer_admin",
|
||||
value.payload_json.clone(),
|
||||
),
|
||||
kb_decoder_metadata_metaplex_token_metadata::CanonicalMetadataAccountState::TokenOwnedEscrow(value) => (
|
||||
"token_owned_escrow_state",
|
||||
"kb_materializer_lifecycle",
|
||||
value.payload_json.clone(),
|
||||
),
|
||||
kb_decoder_metadata_metaplex_token_metadata::CanonicalMetadataAccountState::ReservationList(value) => (
|
||||
"historical_reservation_state",
|
||||
"kb_materializer_metadata",
|
||||
value.payload_json.clone(),
|
||||
),
|
||||
};
|
||||
let lifecycle = match snapshot.identity.lifecycle {
|
||||
kb_decoder_metadata_metaplex_token_metadata::CanonicalAccountLifecycle::Active => "active",
|
||||
kb_decoder_metadata_metaplex_token_metadata::CanonicalAccountLifecycle::ActiveExperimental => {
|
||||
"active_experimental"
|
||||
},
|
||||
kb_decoder_metadata_metaplex_token_metadata::CanonicalAccountLifecycle::Historical => {
|
||||
"historical"
|
||||
},
|
||||
};
|
||||
let materialization_policy = match snapshot.identity.materialization {
|
||||
kb_decoder_metadata_metaplex_token_metadata::CanonicalMaterializationPolicy::ActiveState => {
|
||||
"active_state"
|
||||
},
|
||||
kb_decoder_metadata_metaplex_token_metadata::CanonicalMaterializationPolicy::HistoricalState => {
|
||||
"historical_state"
|
||||
},
|
||||
};
|
||||
let execution_policy = match snapshot.identity.execution {
|
||||
kb_decoder_metadata_metaplex_token_metadata::CanonicalExecutionPolicy::CurrentCandidate => {
|
||||
"current_candidate"
|
||||
},
|
||||
kb_decoder_metadata_metaplex_token_metadata::CanonicalExecutionPolicy::ExperimentalCandidate => {
|
||||
"experimental_candidate"
|
||||
},
|
||||
kb_decoder_metadata_metaplex_token_metadata::CanonicalExecutionPolicy::NeverHistorical => {
|
||||
"never_historical"
|
||||
},
|
||||
};
|
||||
return std::result::Result::Ok(kb_materializer_api::MaterializedOutput {
|
||||
output_key: format!("metaplex_account_state:{}", snapshot.identity.account),
|
||||
family: kb_model::MaterializedEventFamily::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() -> kb_decoder_metadata_metaplex_token_metadata::CanonicalAccountProvenance {
|
||||
return kb_decoder_metadata_metaplex_token_metadata::CanonicalAccountProvenance {
|
||||
source:
|
||||
kb_decoder_metadata_metaplex_token_metadata::CanonicalObservationSource::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 = kb_decoder_metadata_metaplex_token_metadata::CanonicalMetadataAccountSnapshot::from_edition(
|
||||
kb_decoder_metadata_metaplex_token_metadata::EditionAccountSnapshot {
|
||||
account: "master-v1".to_string(),
|
||||
bump: 1,
|
||||
mint: "mint-v1".to_string(),
|
||||
kind: kb_decoder_metadata_metaplex_token_metadata::EditionAccountKind::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 = kb_decoder_metadata_metaplex_token_metadata::CanonicalMetadataAccountSnapshot::from_reservation_list(
|
||||
kb_decoder_metadata_metaplex_token_metadata::ReservationListAccountSnapshot {
|
||||
account: "reservation-v2".to_string(),
|
||||
bump: 2,
|
||||
kind: kb_decoder_metadata_metaplex_token_metadata::ReservationListAccountKind::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 = kb_decoder_metadata_metaplex_token_metadata::CanonicalMetadataAccountSnapshot::from_token_record(
|
||||
kb_decoder_metadata_metaplex_token_metadata::TokenRecordAccountSnapshot {
|
||||
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 = kb_decoder_metadata_metaplex_token_metadata::CanonicalMetadataAccountSnapshot::from_edition_marker(
|
||||
kb_decoder_metadata_metaplex_token_metadata::EditionMarkerAccountSnapshot {
|
||||
account: "marker".to_string(),
|
||||
bump: 1,
|
||||
mint: "mint".to_string(),
|
||||
kind: kb_decoder_metadata_metaplex_token_metadata::EditionMarkerAccountKind::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_token_2022_state {
|
||||
#[test]
|
||||
fn embedded_metadata_group_and_member_have_distinct_stable_outputs() {
|
||||
let state = kb_decoder_spl_token_2022::state::Token2022State {
|
||||
kind: kb_decoder_spl_token_2022::state::Token2022StateKind::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![
|
||||
kb_decoder_spl_token_2022::state::Token2022TlvEntry {
|
||||
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"}),
|
||||
},
|
||||
kb_decoder_spl_token_2022::state::Token2022TlvEntry {
|
||||
extension_type: 20,
|
||||
extension_name: "token_group",
|
||||
value_hex: "02".to_string(),
|
||||
value_fields: serde_json::json!({"mint":"mint111","size":"1","maxSize":"10"}),
|
||||
},
|
||||
kb_decoder_spl_token_2022::state::Token2022TlvEntry {
|
||||
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_token_2022_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!("token_2022_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 = kb_decoder_spl_token_2022::state::Token2022State {
|
||||
kind: kb_decoder_spl_token_2022::state::Token2022StateKind::Mint,
|
||||
base_fields: serde_json::json!({}),
|
||||
base_hex: "00".repeat(82),
|
||||
account_type: std::option::Option::Some(1),
|
||||
extensions: std::vec![kb_decoder_spl_token_2022::state::Token2022TlvEntry {
|
||||
extension_type: 1,
|
||||
extension_name: "transfer_fee_config",
|
||||
value_hex: "00".to_string(),
|
||||
value_fields: serde_json::json!({}),
|
||||
}],
|
||||
};
|
||||
let first = crate::materialize_token_2022_metadata_snapshot("mint111", 9, &state);
|
||||
let second = crate::materialize_token_2022_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_token_2022_metadata_snapshot("", 9, &state).is_err());
|
||||
let account_state = kb_decoder_spl_token_2022::state::Token2022State {
|
||||
kind: kb_decoder_spl_token_2022::state::Token2022StateKind::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_token_2022_metadata_snapshot("account111", 9, &account_state)
|
||||
.is_err()
|
||||
);
|
||||
}
|
||||
}
|
||||
Reference in New Issue
Block a user