1152 lines
49 KiB
Rust
1152 lines
49 KiB
Rust
// file: kb_materializer_admin/src/materializer.rs
|
|
// version: 15
|
|
|
|
//! Stable administrative projections derived from exact committed observations.
|
|
|
|
const ACCEPTED_FAMILIES: &[kb_model::EventFamily] =
|
|
&[kb_model::EventFamily::Admin, kb_model::EventFamily::TokenAccount];
|
|
const SYSTEM_SURFACE: &str = "solana_native_system";
|
|
const SYSTEM_ENTRIES: &[&str] = &["assign", "assign_with_seed"];
|
|
const CONFIG_SURFACE: &str = "solana_native_config";
|
|
const CONFIG_ENTRIES: &[&str] = &["store"];
|
|
const BPF_LOADER_UPGRADEABLE_SURFACE: &str = "solana_native_bpf_loader_upgradeable";
|
|
const BPF_LOADER_UPGRADEABLE_ENTRIES: &[&str] = &["set_authority", "set_authority_checked"];
|
|
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_ELGAMAL_REGISTRY_SURFACE: &str = "spl_elgamal_registry";
|
|
const SPL_ELGAMAL_REGISTRY_ENTRIES: &[&str] = &["create_registry", "update_registry"];
|
|
const SPL_TOKEN_AUTHORITY_ENTRIES: &[&str] = &[
|
|
"set_authority",
|
|
"initialize_permanent_delegate",
|
|
"initialize_interest_bearing_mint",
|
|
"update_interest_bearing_rate",
|
|
];
|
|
const SPL_TOKEN_MULTISIG_ENTRIES: &[&str] = &["initialize_multisig", "initialize_multisig2"];
|
|
const SPL_TOKEN_2022_EXTENSION_ADMIN_ENTRIES: &[&str] = &[
|
|
"initialize_transfer_hook",
|
|
"update_transfer_hook_program_id",
|
|
"initialize_metadata_pointer",
|
|
"update_metadata_pointer",
|
|
"initialize_group_pointer",
|
|
"update_group_pointer",
|
|
"initialize_group_member_pointer",
|
|
"update_group_member_pointer",
|
|
"initialize_scaled_ui_amount",
|
|
"update_scaled_ui_amount_multiplier",
|
|
];
|
|
|
|
#[derive(Clone, Copy, Debug, Eq, PartialEq)]
|
|
enum ProjectionKind {
|
|
SystemAccountOwnership,
|
|
ConfigAccountUpdate,
|
|
ProgramLoaderAuthority(&'static str),
|
|
SplTokenAuthority,
|
|
SplTokenMultisig,
|
|
SplElgamalRegistry,
|
|
SplToken2022ExtensionAdmin,
|
|
}
|
|
|
|
impl ProjectionKind {
|
|
fn domain(self) -> &'static str {
|
|
return match self {
|
|
Self::SystemAccountOwnership => "system_account_ownership",
|
|
Self::ConfigAccountUpdate => "config_account_update",
|
|
Self::ProgramLoaderAuthority(_) => "program_loader_authority",
|
|
Self::SplTokenAuthority => "spl_token_authority",
|
|
Self::SplTokenMultisig => "spl_token_multisig",
|
|
Self::SplElgamalRegistry => "spl_elgamal_registry",
|
|
Self::SplToken2022ExtensionAdmin => "spl_token_2022_extension_admin",
|
|
};
|
|
}
|
|
|
|
fn loader_surface(self) -> std::option::Option<&'static str> {
|
|
return match self {
|
|
Self::ProgramLoaderAuthority(surface) => std::option::Option::Some(surface),
|
|
_ => std::option::Option::None,
|
|
};
|
|
}
|
|
|
|
fn output_key(self, surface_code: &str, operation: &str) -> std::string::String {
|
|
return match self {
|
|
Self::SystemAccountOwnership => format!("system_account_ownership:{operation}:0"),
|
|
Self::ConfigAccountUpdate => format!("config_account_update:{operation}:0"),
|
|
Self::ProgramLoaderAuthority(_) => {
|
|
format!("program_loader_authority:{surface_code}:{operation}:0")
|
|
},
|
|
Self::SplTokenAuthority => format!("spl_token_authority:{operation}:0"),
|
|
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")
|
|
},
|
|
};
|
|
}
|
|
}
|
|
|
|
/// Stable administrative materializer for supported native mutations.
|
|
#[derive(Clone, Debug, Default)]
|
|
pub struct AdminMaterializer;
|
|
|
|
impl kb_materializer_api::Materializer for crate::AdminMaterializer {
|
|
fn materializer_name(&self) -> &'static str {
|
|
return "kb_materializer_admin";
|
|
}
|
|
|
|
fn materializer_version(&self) -> &'static str {
|
|
return env!("CARGO_PKG_VERSION");
|
|
}
|
|
|
|
fn accepts_event(&self, event: &kb_model::DecodedProtocolEvent) -> 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();
|
|
}
|
|
|
|
fn materialize_event(
|
|
&self,
|
|
event: &kb_model::DecodedProtocolEvent,
|
|
) -> kb_core::Result<std::vec::Vec<kb_model::MaterializedEvent>> {
|
|
if !kb_materializer_api::Materializer::accepts_event(self, event) {
|
|
return std::result::Result::Ok(std::vec::Vec::new());
|
|
}
|
|
return std::result::Result::Ok(std::vec::Vec::new());
|
|
}
|
|
}
|
|
|
|
impl kb_materializer_api::EventMaterializer for crate::AdminMaterializer {
|
|
fn identity(&self) -> kb_materializer_api::MaterializerIdentity {
|
|
return kb_materializer_api::MaterializerIdentity {
|
|
name: "solana_native_admin".to_string(),
|
|
version: env!("CARGO_PKG_VERSION").to_string(),
|
|
};
|
|
}
|
|
|
|
fn accepted_families(&self) -> &'static [kb_model::EventFamily] {
|
|
return ACCEPTED_FAMILIES;
|
|
}
|
|
|
|
fn accepts_observation(&self, observation: &kb_decoder_api::DecodedObservation) -> bool {
|
|
return ACCEPTED_FAMILIES.contains(&observation.event.event_family)
|
|
&& program_matches_surface(&observation.event)
|
|
&& projection(
|
|
observation.event.surface_code.0.as_str(),
|
|
observation.event.event_name.0.as_str(),
|
|
)
|
|
.is_some();
|
|
}
|
|
|
|
fn transaction_policy(
|
|
&self,
|
|
_family: kb_model::EventFamily,
|
|
) -> kb_materializer_api::MaterializationTransactionPolicy {
|
|
return kb_materializer_api::MaterializationTransactionPolicy::SuccessfulCommittedOnly;
|
|
}
|
|
|
|
fn materialize(
|
|
&self,
|
|
observation: &kb_decoder_api::DecodedObservation,
|
|
) -> kb_materializer_api::MaterializerExecutionResult {
|
|
let projection = match projection(
|
|
observation.event.surface_code.0.as_str(),
|
|
observation.event.event_name.0.as_str(),
|
|
) {
|
|
std::option::Option::Some(value) => value,
|
|
std::option::Option::None => {
|
|
tracing::debug!(
|
|
target: crate::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 kb_materializer_api::MaterializerExecutionResult::ignored();
|
|
},
|
|
};
|
|
if !ACCEPTED_FAMILIES.contains(&observation.event.event_family) {
|
|
return kb_materializer_api::MaterializerExecutionResult::ignored();
|
|
}
|
|
if !program_matches_surface(&observation.event) {
|
|
return kb_materializer_api::MaterializerExecutionResult::ignored();
|
|
}
|
|
if observation.transaction_failed || !observation.observation_committed {
|
|
tracing::debug!(
|
|
target: crate::TRACING_TARGET,
|
|
action = "materialize_admin",
|
|
surface_code = %observation.event.surface_code.0.as_str(),
|
|
entry_code = %observation.event.event_name.0.as_str(),
|
|
committed = observation.observation_committed,
|
|
transaction_failed = observation.transaction_failed,
|
|
"refuse uncommitted native administrative observation"
|
|
);
|
|
return kb_materializer_api::MaterializerExecutionResult::refused(
|
|
"failed_transaction_admin_refused",
|
|
"failed or uncommitted native observations cannot create administrative mutation outputs",
|
|
);
|
|
}
|
|
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 operation = observation.event.event_name.0.clone();
|
|
let output = kb_materializer_api::MaterializedOutput {
|
|
output_key: projection
|
|
.output_key(observation.event.surface_code.0.as_str(), operation.as_str()),
|
|
family: kb_model::MaterializedEventFamily::Admin,
|
|
payload_json: serde_json::json!({
|
|
"projectionVersion": 1,
|
|
"projectionSemantics": "committed_instruction_admin_event",
|
|
"domain": projection.domain(),
|
|
"loaderSurface": projection.loader_surface(),
|
|
"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,
|
|
"projection": projection_details(
|
|
projection,
|
|
&accounts,
|
|
¶meters,
|
|
observation.event.event_name.0.as_str(),
|
|
),
|
|
}),
|
|
};
|
|
tracing::debug!(
|
|
target: crate::TRACING_TARGET,
|
|
action = "materialize_admin",
|
|
surface_code = %observation.event.surface_code.0.as_str(),
|
|
entry_code = %observation.event.event_name.0.as_str(),
|
|
projection_domain = projection.domain(),
|
|
output_count = 1_usize,
|
|
"materialize committed native administrative observation"
|
|
);
|
|
return kb_materializer_api::MaterializerExecutionResult {
|
|
status: kb_materializer_api::MaterializerOutcomeStatus::Inserted,
|
|
outputs: std::vec![output],
|
|
diagnostics: std::vec::Vec::new(),
|
|
};
|
|
}
|
|
}
|
|
|
|
fn projection(surface_code: &str, entry_code: &str) -> std::option::Option<ProjectionKind> {
|
|
if surface_code == SYSTEM_SURFACE && contains(SYSTEM_ENTRIES, entry_code) {
|
|
return std::option::Option::Some(ProjectionKind::SystemAccountOwnership);
|
|
}
|
|
if surface_code == CONFIG_SURFACE && contains(CONFIG_ENTRIES, entry_code) {
|
|
return std::option::Option::Some(ProjectionKind::ConfigAccountUpdate);
|
|
}
|
|
if surface_code == BPF_LOADER_UPGRADEABLE_SURFACE
|
|
&& contains(BPF_LOADER_UPGRADEABLE_ENTRIES, entry_code)
|
|
{
|
|
return std::option::Option::Some(ProjectionKind::ProgramLoaderAuthority(
|
|
BPF_LOADER_UPGRADEABLE_SURFACE,
|
|
));
|
|
}
|
|
if surface_code == LOADER_V4_SURFACE && contains(LOADER_V4_ENTRIES, entry_code) {
|
|
return std::option::Option::Some(ProjectionKind::ProgramLoaderAuthority(
|
|
LOADER_V4_SURFACE,
|
|
));
|
|
}
|
|
if matches!(surface_code, SPL_TOKEN_SURFACE | SPL_TOKEN_2022_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)
|
|
&& 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)
|
|
{
|
|
return std::option::Option::Some(ProjectionKind::SplToken2022ExtensionAdmin);
|
|
}
|
|
if surface_code == SPL_ELGAMAL_REGISTRY_SURFACE
|
|
&& contains(SPL_ELGAMAL_REGISTRY_ENTRIES, entry_code)
|
|
{
|
|
return std::option::Option::Some(ProjectionKind::SplElgamalRegistry);
|
|
}
|
|
return std::option::Option::None;
|
|
}
|
|
|
|
fn program_matches_surface(event: &kb_model::DecodedProtocolEvent) -> 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_ELGAMAL_REGISTRY_SURFACE {
|
|
return event.program_id.0 == kb_program_ids::SPL_TOKEN_2022_ELGAMAL_REGISTRY_PROGRAM_ID;
|
|
}
|
|
return true;
|
|
}
|
|
|
|
fn projection_details(
|
|
projection: ProjectionKind,
|
|
accounts: &serde_json::Value,
|
|
parameters: &serde_json::Value,
|
|
operation: &str,
|
|
) -> serde_json::Value {
|
|
return match projection {
|
|
ProjectionKind::SystemAccountOwnership => serde_json::json!({
|
|
"targetAccount": first_account_key_for_roles(
|
|
accounts,
|
|
&["assigned_account", "derived_account"],
|
|
),
|
|
"baseAccount": account_key_for_role(accounts, "base_account"),
|
|
"newOwner": parameters.get("owner").cloned().unwrap_or(serde_json::Value::Null),
|
|
"seed": parameters.get("seed").cloned().unwrap_or(serde_json::Value::Null),
|
|
"stateTransition": if operation == "assign_with_seed" {
|
|
"derived_account_owner_assigned"
|
|
} else {
|
|
"account_owner_assigned"
|
|
},
|
|
"transactionFinalAccountStateCaptured": false,
|
|
}),
|
|
ProjectionKind::ConfigAccountUpdate => serde_json::json!({
|
|
"targetAccount": account_key_for_role(accounts, "config_account"),
|
|
"configuredKeys": parameters.get("configuredKeys").cloned().unwrap_or(serde_json::Value::Null),
|
|
"configuredKeyCount": parameters.get("configuredKeyCount").cloned().unwrap_or(serde_json::Value::Null),
|
|
"configuredSignerCount": parameters.get("configuredSignerCount").cloned().unwrap_or(serde_json::Value::Null),
|
|
"configDataByteLength": parameters.get("configDataByteLength").cloned().unwrap_or(serde_json::Value::Null),
|
|
"configDataSha256": parameters.get("configDataSha256").cloned().unwrap_or(serde_json::Value::Null),
|
|
"configDataPrefixHex": parameters.get("configDataPrefixHex").cloned().unwrap_or(serde_json::Value::Null),
|
|
"configDataSemantics": "opaque_program_specific",
|
|
"stateTransition": "config_account_data_replaced",
|
|
"payloadMaterialized": false,
|
|
"transactionFinalAccountStateCaptured": false,
|
|
}),
|
|
ProjectionKind::ProgramLoaderAuthority(_) => serde_json::json!({
|
|
"targetAccount": first_account_key_for_roles(
|
|
accounts,
|
|
&["authority_target", "program_account"],
|
|
),
|
|
"currentAuthority": first_account_key_for_roles(
|
|
accounts,
|
|
&["current_authority", "authority"],
|
|
),
|
|
"newAuthority": account_key_for_role(accounts, "new_authority"),
|
|
"stateTransition": match operation {
|
|
"set_authority" => "authority_changed_or_revoked",
|
|
"set_authority_checked" => "authority_changed_with_new_authority_signature",
|
|
"transfer_authority" => "authority_transferred_with_new_authority_signature",
|
|
_ => "authority_changed",
|
|
},
|
|
"newAuthorityOptional": operation == "set_authority",
|
|
"transactionFinalAccountStateCaptured": false,
|
|
}),
|
|
ProjectionKind::SplTokenAuthority => serde_json::json!({
|
|
"targetAccount": account_key_for_role(accounts, "authority_target"),
|
|
"currentAuthority": account_key_for_role(accounts, "authority"),
|
|
"authorityType": parameters.get("authorityType").cloned().unwrap_or(serde_json::Value::Null),
|
|
"newAuthority": parameters.get("newAuthority").cloned().unwrap_or(serde_json::Value::Null),
|
|
"stateTransition": if parameters.get("newAuthority").is_some_and(serde_json::Value::is_null) {
|
|
"token_authority_revoked"
|
|
} else {
|
|
"token_authority_changed"
|
|
},
|
|
"transactionFinalAccountStateCaptured": false,
|
|
}),
|
|
ProjectionKind::SplTokenMultisig => serde_json::json!({
|
|
"multisigAccount": account_key_for_role(accounts, "multisig_account"),
|
|
"thresholdM": parameters.get("m").cloned().unwrap_or(serde_json::Value::Null),
|
|
"members": account_keys_for_role(accounts, "multisig_member"),
|
|
"stateTransition": "token_multisig_initialized",
|
|
"transactionFinalAccountStateCaptured": false,
|
|
}),
|
|
ProjectionKind::SplElgamalRegistry => serde_json::json!({
|
|
"registryAccount": account_key_at_position(accounts, 0),
|
|
"ownerAccount": account_key_at_position(
|
|
accounts,
|
|
if operation == "create_registry" { 1 } else { 2 },
|
|
),
|
|
"proofAccountOrInstructionsSysvar": account_key_at_position(
|
|
accounts,
|
|
if operation == "create_registry" { 3 } else { 1 },
|
|
),
|
|
"proofInstructionOffset": parameters
|
|
.get("proofInstructionOffset")
|
|
.cloned()
|
|
.unwrap_or(serde_json::Value::Null),
|
|
"proofLocationKind": parameters
|
|
.get("proofLocationKind")
|
|
.cloned()
|
|
.unwrap_or(serde_json::Value::Null),
|
|
"stateTransition": if operation == "create_registry" {
|
|
"elgamal_registry_created"
|
|
} else {
|
|
"elgamal_registry_key_updated"
|
|
},
|
|
"registeredElgamalPubkeyCaptured": false,
|
|
"proofValidatedByRegistryProgram": true,
|
|
"transactionFinalAccountStateCaptured": false,
|
|
}),
|
|
ProjectionKind::SplToken2022ExtensionAdmin => serde_json::json!({
|
|
"targetAccount": account_key_at_position(accounts, 0),
|
|
"authority": parameters.get("authority").cloned().unwrap_or(serde_json::Value::Null),
|
|
"programId": parameters.get("programId").cloned().unwrap_or(serde_json::Value::Null),
|
|
"metadataAddress": parameters.get("metadataAddress").cloned().unwrap_or(serde_json::Value::Null),
|
|
"groupAddress": parameters.get("groupAddress").cloned().unwrap_or(serde_json::Value::Null),
|
|
"memberAddress": parameters.get("memberAddress").cloned().unwrap_or(serde_json::Value::Null),
|
|
"multiplier": parameters.get("multiplier").cloned().unwrap_or(serde_json::Value::Null),
|
|
"effectiveTimestamp": parameters.get("effectiveTimestamp").cloned().unwrap_or(serde_json::Value::Null),
|
|
"stateTransition": operation,
|
|
"stateReadRequired": true,
|
|
"transactionFinalAccountStateCaptured": false,
|
|
}),
|
|
};
|
|
}
|
|
|
|
fn account_keys_for_role(accounts: &serde_json::Value, role: &str) -> serde_json::Value {
|
|
let values = accounts
|
|
.as_array()
|
|
.map(|rows| {
|
|
return rows
|
|
.iter()
|
|
.filter_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)
|
|
.map(|value| return serde_json::Value::String(value.to_string()));
|
|
})
|
|
.collect::<std::vec::Vec<_>>();
|
|
})
|
|
.unwrap_or_default();
|
|
return serde_json::Value::Array(values);
|
|
}
|
|
|
|
fn first_account_key_for_roles(accounts: &serde_json::Value, roles: &[&str]) -> serde_json::Value {
|
|
for role in roles {
|
|
let value = account_key_for_role(accounts, role);
|
|
if !value.is_null() {
|
|
return value;
|
|
}
|
|
}
|
|
return serde_json::Value::Null;
|
|
}
|
|
|
|
fn account_key_for_role(accounts: &serde_json::Value, role: &str) -> serde_json::Value {
|
|
let account_key = accounts.as_array().and_then(|values| {
|
|
return values.iter().find_map(|value| {
|
|
if value.get("role").and_then(serde_json::Value::as_str)
|
|
!= std::option::Option::Some(role)
|
|
{
|
|
return std::option::Option::None;
|
|
}
|
|
return value
|
|
.get("accountKey")
|
|
.and_then(serde_json::Value::as_str)
|
|
.map(|account_key| return account_key.to_string());
|
|
});
|
|
});
|
|
return match account_key {
|
|
std::option::Option::Some(value) => serde_json::Value::String(value),
|
|
std::option::Option::None => serde_json::Value::Null,
|
|
};
|
|
}
|
|
|
|
fn account_key_at_position(accounts: &serde_json::Value, position: usize) -> serde_json::Value {
|
|
let account_key = accounts.as_array().and_then(|values| {
|
|
return values.iter().find_map(|value| {
|
|
let row_position = value
|
|
.get("position")
|
|
.and_then(serde_json::Value::as_u64)
|
|
.map(|value| return value as usize);
|
|
if row_position != std::option::Option::Some(position) {
|
|
return std::option::Option::None;
|
|
}
|
|
return value
|
|
.get("accountKey")
|
|
.and_then(serde_json::Value::as_str)
|
|
.map(|account_key| return account_key.to_string());
|
|
});
|
|
});
|
|
return match account_key {
|
|
std::option::Option::Some(value) => serde_json::Value::String(value),
|
|
std::option::Option::None => serde_json::Value::Null,
|
|
};
|
|
}
|
|
|
|
fn contains(entries: &[&str], entry_code: &str) -> bool {
|
|
return entries.iter().any(|entry| return *entry == entry_code);
|
|
}
|
|
|
|
#[cfg(test)]
|
|
mod tests {
|
|
fn observation(
|
|
surface_code: &str,
|
|
entry_code: &str,
|
|
transaction_failed: bool,
|
|
accounts: serde_json::Value,
|
|
parameters: serde_json::Value,
|
|
) -> kb_decoder_api::DecodedObservation {
|
|
return kb_decoder_api::DecodedObservation {
|
|
event_key: format!("{entry_code}:0"),
|
|
event: kb_model::DecodedProtocolEvent {
|
|
signature: kb_model::Signature("signature".to_string()),
|
|
slot: kb_model::Slot(42),
|
|
instruction_path: kb_model::InstructionPath("0".to_string()),
|
|
program_id: kb_model::ProgramId("program-id".to_string()),
|
|
protocol_code: kb_model::ProtocolCode("solana_native".to_string()),
|
|
surface_code: kb_model::SurfaceCode(surface_code.to_string()),
|
|
event_code: kb_model::EventCode(format!("{surface_code}.{entry_code}")),
|
|
event_name: kb_model::EventName(entry_code.to_string()),
|
|
event_family: kb_model::EventFamily::Admin,
|
|
source_kind: kb_model::EventSourceKind::Instruction,
|
|
confidence: kb_model::DecoderConfidence::ManualExact,
|
|
},
|
|
payload_json: serde_json::json!({"accounts": accounts, "parameters": parameters}),
|
|
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: kb_decoder_api::DecoderProof {
|
|
kind: kb_decoder_api::DecoderProofKind::Manual,
|
|
confidence: kb_model::DecoderConfidence::ManualExact,
|
|
evidence: std::vec!["fixture".to_string()],
|
|
},
|
|
};
|
|
}
|
|
|
|
fn account(role: &str, account_key: &str) -> serde_json::Value {
|
|
return serde_json::json!({"role": role, "accountKey": account_key});
|
|
}
|
|
|
|
#[test]
|
|
fn system_assign_creates_owner_projection() {
|
|
let materializer = crate::AdminMaterializer;
|
|
let observation = observation(
|
|
super::SYSTEM_SURFACE,
|
|
"assign",
|
|
false,
|
|
serde_json::json!([account("assigned_account", "account111",)]),
|
|
serde_json::json!({"owner": "owner111"}),
|
|
);
|
|
let result =
|
|
kb_materializer_api::EventMaterializer::materialize(&materializer, &observation);
|
|
assert_eq!(result.status, kb_materializer_api::MaterializerOutcomeStatus::Inserted);
|
|
assert_eq!(result.outputs[0].family, kb_model::MaterializedEventFamily::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 observation = observation(
|
|
super::SYSTEM_SURFACE,
|
|
"assign_with_seed",
|
|
false,
|
|
serde_json::json!([
|
|
account("derived_account", "derived111"),
|
|
account("base_account", "base111"),
|
|
]),
|
|
serde_json::json!({"owner": "owner111", "seed": "seed"}),
|
|
);
|
|
let result =
|
|
kb_materializer_api::EventMaterializer::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");
|
|
}
|
|
|
|
#[test]
|
|
fn config_store_preserves_bounded_opaque_audit_fields() {
|
|
let materializer = crate::AdminMaterializer;
|
|
let observation = observation(
|
|
super::CONFIG_SURFACE,
|
|
"store",
|
|
false,
|
|
serde_json::json!([
|
|
account("config_account", "config111"),
|
|
account("configured_signer", "signer111"),
|
|
]),
|
|
serde_json::json!({
|
|
"configuredKeys": [{"pubkey": "signer111", "signer": true}],
|
|
"configuredKeyCount": 1,
|
|
"configuredSignerCount": 1,
|
|
"configDataByteLength": 64,
|
|
"configDataSha256": "abc",
|
|
"configDataPrefixHex": "0102",
|
|
}),
|
|
);
|
|
let result =
|
|
kb_materializer_api::EventMaterializer::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");
|
|
assert_eq!(result.outputs[0].payload_json["projection"]["payloadMaterialized"], false);
|
|
}
|
|
|
|
#[test]
|
|
fn loader_authority_variants_create_exact_admin_outputs() {
|
|
let materializer = crate::AdminMaterializer;
|
|
for (surface, entry, target_role) in [
|
|
(super::BPF_LOADER_UPGRADEABLE_SURFACE, "set_authority", "authority_target"),
|
|
(
|
|
super::BPF_LOADER_UPGRADEABLE_SURFACE,
|
|
"set_authority_checked",
|
|
"authority_target",
|
|
),
|
|
(super::LOADER_V4_SURFACE, "transfer_authority", "program_account"),
|
|
] {
|
|
let observation = observation(
|
|
surface,
|
|
entry,
|
|
false,
|
|
serde_json::json!([
|
|
account(target_role, "target111"),
|
|
account("current_authority", "current111"),
|
|
account("new_authority", "new111"),
|
|
]),
|
|
serde_json::json!({}),
|
|
);
|
|
let result =
|
|
kb_materializer_api::EventMaterializer::materialize(&materializer, &observation);
|
|
assert_eq!(result.status, kb_materializer_api::MaterializerOutcomeStatus::Inserted);
|
|
assert_eq!(result.outputs[0].payload_json["projection"]["targetAccount"], "target111");
|
|
assert_eq!(result.outputs[0].payload_json["projection"]["newAuthority"], "new111");
|
|
}
|
|
}
|
|
|
|
#[test]
|
|
fn classic_token_authority_and_multisig_create_distinct_admin_outputs() {
|
|
let mut authority = observation(
|
|
super::SPL_TOKEN_SURFACE,
|
|
"set_authority",
|
|
false,
|
|
serde_json::json!([
|
|
account("authority_target", "mint111"),
|
|
account("authority", "current111"),
|
|
]),
|
|
serde_json::json!({"authorityType":"mint_tokens","newAuthority":null}),
|
|
);
|
|
authority.event.program_id =
|
|
kb_model::ProgramId(kb_program_ids::SPL_TOKEN_PROGRAM_ID.to_string());
|
|
let authority_result = kb_materializer_api::EventMaterializer::materialize(
|
|
&crate::AdminMaterializer,
|
|
&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"],
|
|
"token_authority_revoked"
|
|
);
|
|
|
|
let mut multisig = observation(
|
|
super::SPL_TOKEN_SURFACE,
|
|
"initialize_multisig2",
|
|
false,
|
|
serde_json::json!([
|
|
account("multisig_account", "multisig111"),
|
|
account("multisig_member", "member1"),
|
|
account("multisig_member", "member2"),
|
|
]),
|
|
serde_json::json!({"m":2}),
|
|
);
|
|
multisig.event.program_id =
|
|
kb_model::ProgramId(kb_program_ids::SPL_TOKEN_PROGRAM_ID.to_string());
|
|
let multisig_result = kb_materializer_api::EventMaterializer::materialize(
|
|
&crate::AdminMaterializer,
|
|
&multisig,
|
|
);
|
|
assert_eq!(
|
|
multisig_result.outputs[0].output_key,
|
|
"spl_token_multisig:initialize_multisig2:0"
|
|
);
|
|
assert_eq!(
|
|
multisig_result.outputs[0].payload_json["projection"]["members"]
|
|
.as_array()
|
|
.map(std::vec::Vec::len),
|
|
std::option::Option::Some(2)
|
|
);
|
|
}
|
|
|
|
#[test]
|
|
fn token_2022_authority_change_uses_the_admin_owner_once() {
|
|
let mut observation = observation(
|
|
super::SPL_TOKEN_2022_SURFACE,
|
|
"set_authority",
|
|
false,
|
|
serde_json::json!([
|
|
account("owned_account", "mint111"),
|
|
account("current_authority", "authority111"),
|
|
]),
|
|
serde_json::json!({"authorityType":"mint_tokens","newAuthority":null}),
|
|
);
|
|
observation.event.program_id =
|
|
kb_model::ProgramId(kb_program_ids::SPL_TOKEN_2022_PROGRAM_ID.to_string());
|
|
let result = kb_materializer_api::EventMaterializer::materialize(
|
|
&crate::AdminMaterializer,
|
|
&observation,
|
|
);
|
|
assert_eq!(result.status, kb_materializer_api::MaterializerOutcomeStatus::Inserted);
|
|
assert_eq!(result.outputs.len(), 1);
|
|
assert_eq!(
|
|
result.outputs[0].payload_json["programId"],
|
|
kb_program_ids::SPL_TOKEN_2022_PROGRAM_ID
|
|
);
|
|
}
|
|
|
|
#[test]
|
|
fn failed_admin_mutation_is_refused() {
|
|
let materializer = crate::AdminMaterializer;
|
|
let observation = observation(
|
|
super::SYSTEM_SURFACE,
|
|
"assign",
|
|
true,
|
|
serde_json::json!([account("assigned_account", "account111",)]),
|
|
serde_json::json!({"owner": "owner111"}),
|
|
);
|
|
let result =
|
|
kb_materializer_api::EventMaterializer::materialize(&materializer, &observation);
|
|
assert_eq!(result.status, kb_materializer_api::MaterializerOutcomeStatus::Refused);
|
|
assert!(result.outputs.is_empty());
|
|
}
|
|
|
|
#[test]
|
|
fn ata_instructions_do_not_create_admin_outputs() {
|
|
let mut observation = observation(
|
|
"spl_associated_token_account",
|
|
"create",
|
|
false,
|
|
serde_json::json!([]),
|
|
serde_json::json!({}),
|
|
);
|
|
observation.event.program_id =
|
|
kb_model::ProgramId(kb_program_ids::ASSOCIATED_TOKEN_PROGRAM_ID.to_string());
|
|
observation.event.event_family = kb_model::EventFamily::Lifecycle;
|
|
assert!(!kb_materializer_api::EventMaterializer::accepts_observation(
|
|
&crate::AdminMaterializer,
|
|
&observation,
|
|
));
|
|
let result = kb_materializer_api::EventMaterializer::materialize(
|
|
&crate::AdminMaterializer,
|
|
&observation,
|
|
);
|
|
assert_eq!(result.status, kb_materializer_api::MaterializerOutcomeStatus::Ignored);
|
|
assert!(result.outputs.is_empty());
|
|
}
|
|
|
|
#[test]
|
|
fn unrelated_admin_observation_is_ignored() {
|
|
let materializer = crate::AdminMaterializer;
|
|
let observation = observation(
|
|
"other_surface",
|
|
"set_authority",
|
|
false,
|
|
serde_json::json!([]),
|
|
serde_json::json!({}),
|
|
);
|
|
let result =
|
|
kb_materializer_api::EventMaterializer::materialize(&materializer, &observation);
|
|
assert_eq!(result.status, kb_materializer_api::MaterializerOutcomeStatus::Ignored);
|
|
}
|
|
|
|
#[test]
|
|
fn output_serialization_is_deterministic() {
|
|
let materializer = crate::AdminMaterializer;
|
|
let observation = observation(
|
|
super::CONFIG_SURFACE,
|
|
"store",
|
|
false,
|
|
serde_json::json!([account("config_account", "config111",)]),
|
|
serde_json::json!({
|
|
"configuredKeys": [],
|
|
"configuredKeyCount": 0,
|
|
"configuredSignerCount": 0,
|
|
"configDataByteLength": 1,
|
|
"configDataSha256": "abc",
|
|
"configDataPrefixHex": "01",
|
|
}),
|
|
);
|
|
let first =
|
|
kb_materializer_api::EventMaterializer::materialize(&materializer, &observation);
|
|
let second =
|
|
kb_materializer_api::EventMaterializer::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}"),
|
|
};
|
|
let second_json = match serde_json::to_string(&second) {
|
|
std::result::Result::Ok(value) => value,
|
|
std::result::Result::Err(error) => panic!("second admin serialization failed: {error}"),
|
|
};
|
|
assert_eq!(first_json, second_json);
|
|
}
|
|
|
|
#[test]
|
|
fn elgamal_registry_create_and_update_have_one_admin_owner() {
|
|
let materializer = crate::AdminMaterializer;
|
|
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"),
|
|
] {
|
|
let mut observation = observation(
|
|
super::SPL_ELGAMAL_REGISTRY_SURFACE,
|
|
operation,
|
|
false,
|
|
serde_json::json!([
|
|
{"position": 0, "accountKey": "registry111"},
|
|
{"position": 1, "accountKey": "proof111"},
|
|
{"position": 2, "accountKey": "owner111"},
|
|
{"position": 3, "accountKey": "proofCreate111"}
|
|
]),
|
|
serde_json::json!({}),
|
|
);
|
|
observation.event.program_id = kb_model::ProgramId(
|
|
kb_program_ids::SPL_TOKEN_2022_ELGAMAL_REGISTRY_PROGRAM_ID.to_string(),
|
|
);
|
|
observation.payload_json = serde_json::json!({
|
|
"accounts": observation.payload_json["accounts"].clone(),
|
|
"proofInstructionOffset": offset,
|
|
"proofLocationKind": if offset == 0 {
|
|
"context_state_account"
|
|
} else {
|
|
"relative_instruction_offset"
|
|
},
|
|
});
|
|
let result =
|
|
kb_materializer_api::EventMaterializer::materialize(&materializer, &observation);
|
|
assert_eq!(result.status, kb_materializer_api::MaterializerOutcomeStatus::Inserted);
|
|
assert_eq!(result.outputs.len(), 1);
|
|
assert_eq!(
|
|
result.outputs[0].payload_json["projection"]["registryAccount"],
|
|
"registry111"
|
|
);
|
|
assert_eq!(
|
|
result.outputs[0].payload_json["projection"]["ownerAccount"],
|
|
if owner_position == 1 { "proof111" } else { "owner111" }
|
|
);
|
|
assert_eq!(result.outputs[0].payload_json["projection"]["stateTransition"], transition);
|
|
}
|
|
}
|
|
|
|
#[test]
|
|
fn elgamal_registry_foreign_program_and_failed_transaction_are_refused() {
|
|
let materializer = crate::AdminMaterializer;
|
|
let mut foreign = observation(
|
|
super::SPL_ELGAMAL_REGISTRY_SURFACE,
|
|
"create_registry",
|
|
false,
|
|
serde_json::json!([]),
|
|
serde_json::json!({}),
|
|
);
|
|
foreign.event.program_id =
|
|
kb_model::ProgramId(kb_program_ids::SPL_TOKEN_2022_PROGRAM_ID.to_string());
|
|
assert!(!kb_materializer_api::EventMaterializer::accepts_observation(
|
|
&materializer,
|
|
&foreign,
|
|
));
|
|
let mut failed = foreign.clone();
|
|
failed.event.program_id = kb_model::ProgramId(
|
|
kb_program_ids::SPL_TOKEN_2022_ELGAMAL_REGISTRY_PROGRAM_ID.to_string(),
|
|
);
|
|
failed.transaction_failed = true;
|
|
failed.observation_committed = false;
|
|
let result = kb_materializer_api::EventMaterializer::materialize(&materializer, &failed);
|
|
assert_eq!(result.status, kb_materializer_api::MaterializerOutcomeStatus::Refused);
|
|
}
|
|
|
|
#[test]
|
|
fn token_2022_pointer_and_hook_admin_events_have_one_owner() {
|
|
for (operation, field, value) in [
|
|
("initialize_transfer_hook", "programId", "hook111"),
|
|
("update_metadata_pointer", "metadataAddress", "metadata111"),
|
|
("update_group_pointer", "groupAddress", "group111"),
|
|
("update_group_member_pointer", "memberAddress", "member111"),
|
|
] {
|
|
let mut observation = observation(
|
|
super::SPL_TOKEN_2022_SURFACE,
|
|
operation,
|
|
false,
|
|
serde_json::json!([{"position":0,"accountKey":"mint111"}]),
|
|
serde_json::json!({(field):value,"authority":"authority111"}),
|
|
);
|
|
observation.event.event_family = kb_model::EventFamily::TokenAccount;
|
|
observation.event.program_id =
|
|
kb_model::ProgramId(kb_program_ids::SPL_TOKEN_2022_PROGRAM_ID.to_string());
|
|
let result = kb_materializer_api::EventMaterializer::materialize(
|
|
&crate::AdminMaterializer,
|
|
&observation,
|
|
);
|
|
assert_eq!(result.status, kb_materializer_api::MaterializerOutcomeStatus::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);
|
|
}
|
|
}
|
|
|
|
#[test]
|
|
fn token_2022_extension_admin_rejects_foreign_program_and_failed_transaction() {
|
|
let mut observation = observation(
|
|
super::SPL_TOKEN_2022_SURFACE,
|
|
"initialize_metadata_pointer",
|
|
false,
|
|
serde_json::json!([]),
|
|
serde_json::json!({"metadataAddress":"metadata111"}),
|
|
);
|
|
observation.event.event_family = kb_model::EventFamily::TokenAccount;
|
|
observation.event.program_id =
|
|
kb_model::ProgramId(kb_program_ids::SPL_TOKEN_PROGRAM_ID.to_string());
|
|
assert!(!kb_materializer_api::EventMaterializer::accepts_observation(
|
|
&crate::AdminMaterializer,
|
|
&observation,
|
|
));
|
|
observation.event.program_id =
|
|
kb_model::ProgramId(kb_program_ids::SPL_TOKEN_2022_PROGRAM_ID.to_string());
|
|
observation.transaction_failed = true;
|
|
observation.observation_committed = false;
|
|
let result = kb_materializer_api::EventMaterializer::materialize(
|
|
&crate::AdminMaterializer,
|
|
&observation,
|
|
);
|
|
assert_eq!(result.status, kb_materializer_api::MaterializerOutcomeStatus::Refused);
|
|
}
|
|
}
|
|
|
|
/// Materialize authoritative Token-2022 administrative extension snapshots.
|
|
pub fn materialize_token_2022_admin_state_snapshots(
|
|
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 admin state snapshot requires a non-empty account key".to_string(),
|
|
);
|
|
}
|
|
if state.kind != kb_decoder_spl_token_2022::state::Token2022StateKind::Mint {
|
|
for entry in &state.extensions {
|
|
if is_token_2022_admin_mint_extension(entry.extension_name) {
|
|
return std::result::Result::Err(format!(
|
|
"Token-2022 {} extension requires mint state",
|
|
entry.extension_name
|
|
));
|
|
}
|
|
}
|
|
return std::result::Result::Ok(std::vec::Vec::new());
|
|
}
|
|
let mut outputs = std::vec::Vec::new();
|
|
for entry in &state.extensions {
|
|
if !is_token_2022_admin_mint_extension(entry.extension_name) {
|
|
continue;
|
|
}
|
|
let output_key =
|
|
format!("token_2022_admin_state:{}:{account_key}:{slot}", entry.extension_name);
|
|
outputs.push(kb_materializer_api::MaterializedOutput {
|
|
output_key: output_key.clone(),
|
|
family: kb_model::MaterializedEventFamily::Admin,
|
|
payload_json: serde_json::json!({
|
|
"projectionVersion": 1,
|
|
"projectionSemantics": "authoritative_bounded_admin_state_snapshot",
|
|
"domain": "token_2022_extension_admin_state",
|
|
"idempotenceKey": output_key,
|
|
"accountKey": account_key,
|
|
"slot": slot,
|
|
"programId": kb_program_ids::SPL_TOKEN_2022_PROGRAM_ID,
|
|
"stateKind": "mint",
|
|
"extensionType": entry.extension_type,
|
|
"extensionName": entry.extension_name,
|
|
"valueHex": entry.value_hex,
|
|
"valueFields": entry.value_fields,
|
|
"finalAccountStateCaptured": true,
|
|
"provenance": {
|
|
"processorName": crate::PROCESSOR_NAME,
|
|
"processorVersion": env!("CARGO_PKG_VERSION"),
|
|
"source": "exact_token_2022_tlv_state_parser"
|
|
}
|
|
}),
|
|
});
|
|
}
|
|
return std::result::Result::Ok(outputs);
|
|
}
|
|
|
|
fn is_token_2022_admin_mint_extension(extension_name: &str) -> bool {
|
|
return matches!(
|
|
extension_name,
|
|
"mint_close_authority"
|
|
| "default_account_state"
|
|
| "transfer_hook"
|
|
| "permanent_delegate"
|
|
| "metadata_pointer"
|
|
| "group_pointer"
|
|
| "group_member_pointer"
|
|
| "interest_bearing_config"
|
|
| "scaled_ui_amount"
|
|
| "pausable"
|
|
| "permissioned_burn"
|
|
);
|
|
}
|
|
|
|
#[cfg(test)]
|
|
mod token_2022_admin_state_tests {
|
|
fn mint_state() -> kb_decoder_spl_token_2022::state::Token2022State {
|
|
return kb_decoder_spl_token_2022::state::Token2022State {
|
|
kind: kb_decoder_spl_token_2022::state::Token2022StateKind::Mint,
|
|
base_fields: serde_json::json!({}),
|
|
base_hex: String::new(),
|
|
account_type: std::option::Option::Some(1),
|
|
extensions: vec![
|
|
kb_decoder_spl_token_2022::state::Token2022TlvEntry {
|
|
extension_type: 6,
|
|
extension_name: "default_account_state",
|
|
value_hex: "02".to_string(),
|
|
value_fields: serde_json::json!({"state": 2}),
|
|
},
|
|
kb_decoder_spl_token_2022::state::Token2022TlvEntry {
|
|
extension_type: 26,
|
|
extension_name: "pausable",
|
|
value_hex: "00".repeat(33),
|
|
value_fields: serde_json::json!({"paused": false}),
|
|
},
|
|
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!({}),
|
|
},
|
|
],
|
|
};
|
|
}
|
|
|
|
#[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);
|
|
assert_eq!(first, second);
|
|
assert_eq!(first.as_ref().map(std::vec::Vec::len), std::result::Result::Ok(2));
|
|
assert_eq!(
|
|
first.as_ref().map(|outputs| {
|
|
return outputs
|
|
.iter()
|
|
.map(|output| return output.payload_json["extensionName"].clone())
|
|
.collect::<std::vec::Vec<_>>();
|
|
}),
|
|
std::result::Result::Ok(vec![
|
|
serde_json::json!("default_account_state"),
|
|
serde_json::json!("pausable"),
|
|
])
|
|
);
|
|
}
|
|
|
|
#[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 = kb_decoder_spl_token_2022::state::Token2022State {
|
|
kind: kb_decoder_spl_token_2022::state::Token2022StateKind::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,)
|
|
.is_err()
|
|
);
|
|
}
|
|
}
|
|
|
|
/// Materialize one authoritative SPL ElGamal registry account snapshot.
|
|
pub fn materialize_elgamal_registry_state_snapshot(
|
|
registry_account_key: &str,
|
|
slot: u64,
|
|
state: &kb_decoder_spl_elgamal_registry::state::ElGamalRegistryState,
|
|
) -> std::result::Result<kb_materializer_api::MaterializedOutput, 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(kb_materializer_api::MaterializedOutput {
|
|
output_key: format!("spl_elgamal_registry_state:{registry_account_key}:{slot}"),
|
|
family: kb_model::MaterializedEventFamily::Admin,
|
|
payload_json: serde_json::json!({
|
|
"projectionVersion": 1,
|
|
"domain": "spl_elgamal_registry_account_state",
|
|
"projectionSemantics": "authoritative_exact_account_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,
|
|
"owner": state.owner,
|
|
"elgamalPubkeyBase64": state.elgamal_pubkey_base64,
|
|
"wireHex": state.wire_hex,
|
|
"elgamalKeyValidityProvenByStateParser": false,
|
|
"confidentialValuesDecrypted": false,
|
|
"finalAccountStateCaptured": true,
|
|
"provenance": {
|
|
"processorName": crate::PROCESSOR_NAME,
|
|
"processorVersion": env!("CARGO_PKG_VERSION"),
|
|
"source": "exact_spl_elgamal_registry_state_parser"
|
|
}
|
|
}),
|
|
});
|
|
}
|
|
|
|
#[cfg(test)]
|
|
mod elgamal_registry_state_tests {
|
|
#[test]
|
|
fn exact_registry_snapshot_preserves_public_state_without_claiming_proof_validation() {
|
|
let state = kb_decoder_spl_elgamal_registry::state::ElGamalRegistryState {
|
|
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);
|
|
assert_eq!(
|
|
output.as_ref().map(|value| return value.payload_json["owner"].clone()),
|
|
std::result::Result::Ok(serde_json::json!("owner111"))
|
|
);
|
|
assert_eq!(
|
|
output
|
|
.as_ref()
|
|
.map(|value| return value.payload_json["elgamalKeyValidityProvenByStateParser"]
|
|
.clone()),
|
|
std::result::Result::Ok(serde_json::json!(false))
|
|
);
|
|
assert_eq!(
|
|
output
|
|
.as_ref()
|
|
.map(|value| return value.payload_json["confidentialValuesDecrypted"].clone()),
|
|
std::result::Result::Ok(serde_json::json!(false))
|
|
);
|
|
}
|
|
|
|
#[test]
|
|
fn registry_snapshot_is_idempotent_and_requires_account_identity() {
|
|
let state = kb_decoder_spl_elgamal_registry::state::ElGamalRegistryState {
|
|
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);
|
|
assert_eq!(first, second);
|
|
assert!(crate::materialize_elgamal_registry_state_snapshot("", 11, &state).is_err());
|
|
}
|
|
}
|