0.1.0-pre.004

This commit is contained in:
2026-07-23 18:25:10 +02:00
parent 0da75c1311
commit 149d4c6ef6
85 changed files with 25696 additions and 227 deletions

View File

@@ -1,13 +1,10 @@
// file: kb-lib/src/decoder/api.rs
// version: 3
// version: 4
//! Decoder contracts consolidated from `kb_decoder_api`.
/// Decoder API constants.
pub mod constants;
pub mod contracts;
pub mod decoder;
/// Canonical tracing target for this crate.
/// Current contextual core instruction input contract version.
pub use crate::decoder::api::contracts::CORE_INSTRUCTION_INPUT_CONTRACT_VERSION;
/// Stable contextual decoded observation.

View File

@@ -1,5 +0,0 @@
// file: kb-lib/src/decoder/api/constants.rs
// version: 1
/// Canonical tracing target for this crate.
pub(crate) const TRACING_TARGET: &str = "kb_decoder_api";

View File

@@ -1,5 +1,5 @@
// file: kb-lib/src/decoder/api/decoder.rs
// version: 4
// version: 5
//! Shared decoder contracts for protocol decoder crates.
@@ -68,7 +68,6 @@ impl ProtocolDecoder for InitialDecoder {
&self,
_observation: &crate::ProgramObservation,
) -> kb_core::Result<std::vec::Vec<crate::DecodedProtocolEvent>> {
let _target = crate::TRACING_TARGET;
return std::result::Result::Ok(std::vec::Vec::new());
}
}

View File

@@ -1,6 +1,95 @@
// file: kb-lib/src/decoder/solana.rs
// version: 1
// version: 3
//! `solana` decoder family.
pub mod core;
mod core;
pub(crate) use self::core::SOLANA_CORE_ADDRESS_LOOKUP_TABLE_SURFACE_CODE;
pub(crate) use self::core::SOLANA_CORE_BPF_LOADER_DEPRECATED_SURFACE_CODE;
pub(crate) use self::core::SOLANA_CORE_BPF_LOADER_SURFACE_CODE;
pub(crate) use self::core::SOLANA_CORE_BPF_LOADER_UPGRADEABLE_SURFACE_CODE;
pub(crate) use self::core::SOLANA_CORE_COMPUTE_BUDGET_SURFACE_CODE;
pub(crate) use self::core::SOLANA_CORE_CONFIG_SURFACE_CODE;
pub(crate) use self::core::SOLANA_CORE_ED25519_PUBLIC_KEY_BYTES;
pub(crate) use self::core::SOLANA_CORE_ED25519_SURFACE_CODE;
pub(crate) use self::core::SOLANA_CORE_FEATURE_SURFACE_CODE;
pub(crate) use self::core::SOLANA_CORE_LOADER_V4_SURFACE_CODE;
pub(crate) use self::core::SOLANA_CORE_MAX_NATIVE_INSTRUCTION_PAYLOAD_BYTES;
pub(crate) use self::core::SOLANA_CORE_NATIVE_EVENT_VERSION;
pub(crate) use self::core::SOLANA_CORE_NATIVE_LOADER_SURFACE_CODE;
pub(crate) use self::core::SOLANA_CORE_PRECOMPILE_COMPONENT_PREFIX_BYTES;
pub(crate) use self::core::SOLANA_CORE_PROTOCOL_CODE;
pub(crate) use self::core::SOLANA_CORE_SECP256K1_ETHEREUM_ADDRESS_BYTES;
pub(crate) use self::core::SOLANA_CORE_SECP256K1_SURFACE_CODE;
pub(crate) use self::core::SOLANA_CORE_SECP256R1_MAX_SIGNATURES;
pub(crate) use self::core::SOLANA_CORE_SECP256R1_PUBLIC_KEY_BYTES;
pub(crate) use self::core::SOLANA_CORE_SECP256R1_SURFACE_CODE;
pub(crate) use self::core::SOLANA_CORE_SIGNATURE_BYTES;
pub(crate) use self::core::SOLANA_CORE_SLASHING_SURFACE_CODE;
pub(crate) use self::core::SOLANA_CORE_STAKE_SURFACE_CODE;
pub(crate) use self::core::SOLANA_CORE_SYSTEM_SURFACE_CODE;
pub(crate) use self::core::SOLANA_CORE_TRACING_TARGET;
pub(crate) use self::core::SOLANA_CORE_U8_OFFSETS_BYTES;
pub(crate) use self::core::SOLANA_CORE_U16_OFFSETS_BYTES;
pub(crate) use self::core::SOLANA_CORE_VOTE_SURFACE_CODE;
pub(crate) use self::core::SOLANA_CORE_ZK_ELGAMAL_PROOF_SURFACE_CODE;
pub(crate) use self::core::SOLANA_CORE_ZK_TOKEN_PROOF_SURFACE_CODE;
pub(crate) use self::core::SolanaCoreAccountRole;
pub(crate) use self::core::SolanaCoreResolvedInstructionPayload;
pub(crate) use self::core::solana_core_address_lookup_table_coverage;
pub(crate) use self::core::solana_core_address_lookup_table_decode;
pub(crate) use self::core::solana_core_address_lookup_table_recognize;
pub(crate) use self::core::solana_core_bounded_hexadecimal_prefix;
pub(crate) use self::core::solana_core_bounded_slice;
pub(crate) use self::core::solana_core_compute_budget_coverage;
pub(crate) use self::core::solana_core_compute_budget_decode;
pub(crate) use self::core::solana_core_compute_budget_recognize;
pub(crate) use self::core::solana_core_config_coverage;
pub(crate) use self::core::solana_core_config_decode;
pub(crate) use self::core::solana_core_config_recognize;
pub(crate) use self::core::solana_core_decode_instruction_data;
pub(crate) use self::core::solana_core_decoded_payload_length;
pub(crate) use self::core::solana_core_decoded_payload_sha256;
pub(crate) use self::core::solana_core_decoded_result;
pub(crate) use self::core::solana_core_failed_result;
pub(crate) use self::core::solana_core_feature_coverage;
pub(crate) use self::core::solana_core_feature_decode;
pub(crate) use self::core::solana_core_feature_recognize;
pub(crate) use self::core::solana_core_hash_bytes;
pub(crate) use self::core::solana_core_hexadecimal_prefix;
pub(crate) use self::core::solana_core_loaders_coverage;
pub(crate) use self::core::solana_core_loaders_decode;
pub(crate) use self::core::solana_core_loaders_recognize;
pub(crate) use self::core::solana_core_payload_hash;
pub(crate) use self::core::solana_core_precompiles_coverage;
pub(crate) use self::core::solana_core_precompiles_decode;
pub(crate) use self::core::solana_core_precompiles_recognize;
pub(crate) use self::core::solana_core_read_u32_le;
pub(crate) use self::core::solana_core_read_u64_le;
pub(crate) use self::core::solana_core_resolve_accounts;
pub(crate) use self::core::solana_core_resolve_u8_instruction_payload;
pub(crate) use self::core::solana_core_resolve_u16_instruction_payload;
pub(crate) use self::core::solana_core_slashing_coverage;
pub(crate) use self::core::solana_core_slashing_decode;
pub(crate) use self::core::solana_core_slashing_recognize;
pub(crate) use self::core::solana_core_stake_coverage;
pub(crate) use self::core::solana_core_stake_decode;
pub(crate) use self::core::solana_core_stake_recognize;
pub(crate) use self::core::solana_core_system_coverage;
pub(crate) use self::core::solana_core_system_decode;
pub(crate) use self::core::solana_core_system_recognize;
pub(crate) use self::core::solana_core_target_outer_instruction_index;
pub(crate) use self::core::solana_core_unsupported_result;
pub(crate) use self::core::solana_core_vote_coverage;
pub(crate) use self::core::solana_core_vote_decode;
pub(crate) use self::core::solana_core_vote_recognize;
pub(crate) use self::core::solana_core_zk_elgamal_coverage;
pub(crate) use self::core::solana_core_zk_elgamal_decode;
pub(crate) use self::core::solana_core_zk_elgamal_recognize;
pub(crate) use self::core::solana_core_zk_token_proof_coverage;
pub(crate) use self::core::solana_core_zk_token_proof_decode;
pub(crate) use self::core::solana_core_zk_token_proof_recognize;
/// Runtime-native Solana decoder with phased maximal native coverage.
pub use self::core::SolanaCoreDecoder;

View File

@@ -1,10 +1,197 @@
// file: kb-lib/src/decoder/solana/core.rs
// version: 1
// version: 17
//! Migration boundary for legacy crate `kb_decoder_solana_core`.
//! Solana Core decoder component.
/// Legacy crate name retained for migration and compatibility tracking.
pub const LEGACY_CRATE: &str = "kb_decoder_solana_core";
mod accounts;
mod address_lookup_table;
mod compute_budget;
mod config;
mod constants;
mod decoder;
mod event;
mod feature;
mod loaders;
mod payload;
mod precompiles;
mod slashing;
mod stake;
mod system;
mod vote;
mod zk_elgamal;
mod zk_token_proof;
/// Current porting status.
pub const MIGRATION_STATUS: &str = "source-preserved-pending-port";
/// Expected role and privileges for one positional instruction account.
pub(crate) use self::accounts::AccountRole as SolanaCoreAccountRole;
/// Resolves positional instruction accounts and validates their core indexes.
pub(crate) use self::accounts::resolve_accounts as solana_core_resolve_accounts;
/// Returns declared Address Lookup Table instruction coverage.
pub(crate) use self::address_lookup_table::address_lookup_table_coverage as solana_core_address_lookup_table_coverage;
/// Decodes one Address Lookup Table instruction.
pub(crate) use self::address_lookup_table::address_lookup_table_decode as solana_core_address_lookup_table_decode;
/// Recognizes one Address Lookup Table instruction without producing an event.
pub(crate) use self::address_lookup_table::address_lookup_table_recognize as solana_core_address_lookup_table_recognize;
/// Returns declared Compute Budget instruction coverage.
pub(crate) use self::compute_budget::compute_budget_coverage as solana_core_compute_budget_coverage;
/// Decodes one Compute Budget instruction.
pub(crate) use self::compute_budget::compute_budget_decode as solana_core_compute_budget_decode;
/// Recognizes one Compute Budget instruction without producing an event.
pub(crate) use self::compute_budget::compute_budget_recognize as solana_core_compute_budget_recognize;
/// Returns declared Config Program instruction coverage.
pub(crate) use self::config::config_coverage as solana_core_config_coverage;
/// Decodes one generic Config Program store instruction.
pub(crate) use self::config::config_decode as solana_core_config_decode;
/// Recognizes one generic Config Program store instruction.
pub(crate) use self::config::config_recognize as solana_core_config_recognize;
/// Stable Address Lookup Table surface code.
pub(crate) use self::constants::ADDRESS_LOOKUP_TABLE_SURFACE_CODE as SOLANA_CORE_ADDRESS_LOOKUP_TABLE_SURFACE_CODE;
/// Stable deprecated immutable BPF Loader surface code.
pub(crate) use self::constants::BPF_LOADER_DEPRECATED_SURFACE_CODE as SOLANA_CORE_BPF_LOADER_DEPRECATED_SURFACE_CODE;
/// Stable immutable BPF Loader v2 surface code.
pub(crate) use self::constants::BPF_LOADER_SURFACE_CODE as SOLANA_CORE_BPF_LOADER_SURFACE_CODE;
/// Stable upgradeable BPF Loader surface code.
pub(crate) use self::constants::BPF_LOADER_UPGRADEABLE_SURFACE_CODE as SOLANA_CORE_BPF_LOADER_UPGRADEABLE_SURFACE_CODE;
/// Stable Compute Budget surface code.
pub(crate) use self::constants::COMPUTE_BUDGET_SURFACE_CODE as SOLANA_CORE_COMPUTE_BUDGET_SURFACE_CODE;
/// Stable Config Program surface code.
pub(crate) use self::constants::CONFIG_SURFACE_CODE as SOLANA_CORE_CONFIG_SURFACE_CODE;
/// Byte length of an Ed25519 public key.
pub(crate) use self::constants::ED25519_PUBLIC_KEY_BYTES as SOLANA_CORE_ED25519_PUBLIC_KEY_BYTES;
/// Stable Ed25519 signature precompile surface code.
pub(crate) use self::constants::ED25519_SURFACE_CODE as SOLANA_CORE_ED25519_SURFACE_CODE;
/// Stable Feature Gate surface code.
pub(crate) use self::constants::FEATURE_SURFACE_CODE as SOLANA_CORE_FEATURE_SURFACE_CODE;
/// Stable Loader v4 surface code.
pub(crate) use self::constants::LOADER_V4_SURFACE_CODE as SOLANA_CORE_LOADER_V4_SURFACE_CODE;
/// Maximum retained native instruction payload accepted by the first decoder phase.
pub(crate) use self::constants::MAX_NATIVE_INSTRUCTION_PAYLOAD_BYTES as SOLANA_CORE_MAX_NATIVE_INSTRUCTION_PAYLOAD_BYTES;
/// Current native event payload contract version.
pub(crate) use self::constants::NATIVE_EVENT_VERSION as SOLANA_CORE_NATIVE_EVENT_VERSION;
/// Stable Native Loader surface code.
pub(crate) use self::constants::NATIVE_LOADER_SURFACE_CODE as SOLANA_CORE_NATIVE_LOADER_SURFACE_CODE;
/// Maximum number of component bytes retained as a hexadecimal event prefix.
pub(crate) use self::constants::PRECOMPILE_COMPONENT_PREFIX_BYTES as SOLANA_CORE_PRECOMPILE_COMPONENT_PREFIX_BYTES;
/// Stable protocol code shared by native Solana events.
pub(crate) use self::constants::PROTOCOL_CODE as SOLANA_CORE_PROTOCOL_CODE;
/// Byte length of a secp256k1 Ethereum address.
pub(crate) use self::constants::SECP256K1_ETHEREUM_ADDRESS_BYTES as SOLANA_CORE_SECP256K1_ETHEREUM_ADDRESS_BYTES;
/// Stable secp256k1 signature precompile surface code.
pub(crate) use self::constants::SECP256K1_SURFACE_CODE as SOLANA_CORE_SECP256K1_SURFACE_CODE;
/// Runtime maximum number of secp256r1 signatures in one precompile instruction.
pub(crate) use self::constants::SECP256R1_MAX_SIGNATURES as SOLANA_CORE_SECP256R1_MAX_SIGNATURES;
/// Byte length of a compressed secp256r1 public key.
pub(crate) use self::constants::SECP256R1_PUBLIC_KEY_BYTES as SOLANA_CORE_SECP256R1_PUBLIC_KEY_BYTES;
/// Stable secp256r1 signature precompile surface code.
pub(crate) use self::constants::SECP256R1_SURFACE_CODE as SOLANA_CORE_SECP256R1_SURFACE_CODE;
/// Byte length shared by compact Ed25519, secp256k1 and secp256r1 signatures.
pub(crate) use self::constants::SIGNATURE_BYTES as SOLANA_CORE_SIGNATURE_BYTES;
/// Stable Slashing Program decoder surface code.
pub(crate) use self::constants::SLASHING_SURFACE_CODE as SOLANA_CORE_SLASHING_SURFACE_CODE;
/// Stable Stake Program surface code.
pub(crate) use self::constants::STAKE_SURFACE_CODE as SOLANA_CORE_STAKE_SURFACE_CODE;
/// Stable System Program surface code.
pub(crate) use self::constants::SYSTEM_SURFACE_CODE as SOLANA_CORE_SYSTEM_SURFACE_CODE;
/// Canonical tracing target for the Solana Core decoder component.
pub(crate) use self::constants::TRACING_TARGET as SOLANA_CORE_TRACING_TARGET;
/// Byte length of one secp256k1 offsets entry.
pub(crate) use self::constants::U8_OFFSETS_BYTES as SOLANA_CORE_U8_OFFSETS_BYTES;
/// Byte length of one Ed25519 or secp256r1 offsets entry.
pub(crate) use self::constants::U16_OFFSETS_BYTES as SOLANA_CORE_U16_OFFSETS_BYTES;
/// Stable Vote Program surface code.
pub(crate) use self::constants::VOTE_SURFACE_CODE as SOLANA_CORE_VOTE_SURFACE_CODE;
/// Stable native ZK ElGamal Proof surface code.
pub(crate) use self::constants::ZK_ELGAMAL_PROOF_SURFACE_CODE as SOLANA_CORE_ZK_ELGAMAL_PROOF_SURFACE_CODE;
/// Stable historical ZK Token Proof surface code.
pub(crate) use self::constants::ZK_TOKEN_PROOF_SURFACE_CODE as SOLANA_CORE_ZK_TOKEN_PROOF_SURFACE_CODE;
/// Builds one exact decoded native observation.
pub(crate) use self::event::decoded_result as solana_core_decoded_result;
/// Builds one failed native decode result.
pub(crate) use self::event::failed_result as solana_core_failed_result;
/// Builds one unsupported native decode result with a bounded diagnostic.
pub(crate) use self::event::unsupported_result as solana_core_unsupported_result;
/// Returns declared Feature Gate instruction coverage.
pub(crate) use self::feature::feature_coverage as solana_core_feature_coverage;
/// Decodes one Feature Gate instruction.
pub(crate) use self::feature::feature_decode as solana_core_feature_decode;
/// Recognizes one Feature Gate instruction without producing an event.
pub(crate) use self::feature::feature_recognize as solana_core_feature_recognize;
/// Returns declared loader instruction coverage.
pub(crate) use self::loaders::loaders_coverage as solana_core_loaders_coverage;
/// Decodes one loader instruction.
pub(crate) use self::loaders::loaders_decode as solana_core_loaders_decode;
/// Recognizes one loader instruction without producing an event.
pub(crate) use self::loaders::loaders_recognize as solana_core_loaders_recognize;
/// One resolved outer instruction payload and its provenance relative to the target instruction.
pub(crate) use self::payload::ResolvedInstructionPayload as SolanaCoreResolvedInstructionPayload;
/// Returns a hexadecimal prefix no longer than the available byte slice.
pub(crate) use self::payload::bounded_hexadecimal_prefix as solana_core_bounded_hexadecimal_prefix;
/// Extracts one exact bounded byte slice with checked arithmetic.
pub(crate) use self::payload::bounded_slice as solana_core_bounded_slice;
/// Decodes one retained base64 instruction payload with an explicit byte limit.
pub(crate) use self::payload::decode_instruction_data as solana_core_decode_instruction_data;
/// Returns the decoded target payload length when retained and valid.
pub(crate) use self::payload::decoded_payload_length as solana_core_decoded_payload_length;
/// Returns the SHA-256 of the decoded target instruction data when available.
pub(crate) use self::payload::decoded_payload_sha256 as solana_core_decoded_payload_sha256;
/// Returns the lowercase SHA-256 of one bounded byte slice.
pub(crate) use self::payload::hash_bytes as solana_core_hash_bytes;
/// Returns a normalized lowercase hexadecimal prefix.
pub(crate) use self::payload::hexadecimal_prefix as solana_core_hexadecimal_prefix;
/// Returns a stable payload hash from core or computes it from retained JSON.
pub(crate) use self::payload::payload_hash as solana_core_payload_hash;
/// Reads one little-endian `u32` from an exact byte range.
pub(crate) use self::payload::read_u32_le as solana_core_read_u32_le;
/// Reads one little-endian `u64` from an exact byte range.
pub(crate) use self::payload::read_u64_le as solana_core_read_u64_le;
/// Resolves a secp256k1 instruction reference. The runtime format has no current-instruction
/// sentinel: every `u8` value is an explicit outer instruction index.
pub(crate) use self::payload::resolve_u8_instruction_payload as solana_core_resolve_u8_instruction_payload;
/// Resolves an Ed25519 or secp256r1 instruction reference using the official `u16::MAX` sentinel.
pub(crate) use self::payload::resolve_u16_instruction_payload as solana_core_resolve_u16_instruction_payload;
/// Returns the numeric outer index of the target instruction.
pub(crate) use self::payload::target_outer_instruction_index as solana_core_target_outer_instruction_index;
/// Returns declared signature precompile coverage.
pub(crate) use self::precompiles::precompiles_coverage as solana_core_precompiles_coverage;
/// Decodes one signature precompile instruction structurally without cryptographic recomputation.
pub(crate) use self::precompiles::precompiles_decode as solana_core_precompiles_decode;
/// Recognizes one signature precompile instruction without resolving referenced data.
pub(crate) use self::precompiles::precompiles_recognize as solana_core_precompiles_recognize;
/// Returns declared Slashing Program instruction coverage.
pub(crate) use self::slashing::slashing_coverage as solana_core_slashing_coverage;
/// Decodes one Slashing Program instruction.
pub(crate) use self::slashing::slashing_decode as solana_core_slashing_decode;
/// Recognizes one Slashing Program instruction without producing an event.
pub(crate) use self::slashing::slashing_recognize as solana_core_slashing_recognize;
/// Returns declared Stake Program instruction coverage.
pub(crate) use self::stake::stake_coverage as solana_core_stake_coverage;
/// Decodes one Stake Program instruction.
pub(crate) use self::stake::stake_decode as solana_core_stake_decode;
/// Recognizes one Stake Program instruction without producing an event.
pub(crate) use self::stake::stake_recognize as solana_core_stake_recognize;
/// Returns declared System Program instruction coverage.
pub(crate) use self::system::system_coverage as solana_core_system_coverage;
/// Decodes one System Program instruction.
pub(crate) use self::system::system_decode as solana_core_system_decode;
/// Recognizes one System Program instruction without producing an event.
pub(crate) use self::system::system_recognize as solana_core_system_recognize;
/// Returns declared Vote Program instruction coverage.
pub(crate) use self::vote::vote_coverage as solana_core_vote_coverage;
/// Decodes one Vote Program instruction.
pub(crate) use self::vote::vote_decode as solana_core_vote_decode;
/// Recognizes one Vote Program instruction without producing an event.
pub(crate) use self::vote::vote_recognize as solana_core_vote_recognize;
/// Returns declared ZK ElGamal Proof instruction coverage.
pub(crate) use self::zk_elgamal::zk_elgamal_coverage as solana_core_zk_elgamal_coverage;
/// Decodes one native ZK ElGamal Proof instruction without recomputing the proof.
pub(crate) use self::zk_elgamal::zk_elgamal_decode as solana_core_zk_elgamal_decode;
/// Recognizes one native ZK ElGamal Proof instruction.
pub(crate) use self::zk_elgamal::zk_elgamal_recognize as solana_core_zk_elgamal_recognize;
/// Returns declared historical and current ZK Token Proof coverage.
pub(crate) use self::zk_token_proof::zk_token_proof_coverage as solana_core_zk_token_proof_coverage;
/// Decodes one historical ZK Token Proof layout without recomputing any proof.
pub(crate) use self::zk_token_proof::zk_token_proof_decode as solana_core_zk_token_proof_decode;
/// Recognizes one historical ZK Token Proof layout or the current no-op runtime fallback.
pub(crate) use self::zk_token_proof::zk_token_proof_recognize as solana_core_zk_token_proof_recognize;
/// Runtime-native Solana decoder with phased maximal native coverage.
pub use self::decoder::SolanaCoreDecoder;

View File

@@ -0,0 +1,137 @@
// file: kb-lib/src/decoder/solana/core/accounts.rs
// version: 4
//! Resolved account-role validation for native instructions.
/// Expected role and privileges for one positional instruction account.
#[derive(Clone, Copy, Debug)]
pub(crate) struct AccountRole {
/// Stable role code.
pub(crate) role: &'static str,
/// Whether the official instruction contract marks the account writable.
pub(crate) expected_writable: bool,
/// Whether the official instruction contract marks the account as a signer.
pub(crate) expected_signer: std::option::Option<bool>,
}
impl AccountRole {
/// Builds one account role declaration.
pub(crate) const fn new(
role: &'static str,
expected_writable: bool,
expected_signer: bool,
) -> Self {
return Self {
role,
expected_writable,
expected_signer: std::option::Option::Some(expected_signer),
};
}
/// Builds one role whose signer requirement differs across supported runtime generations.
pub(crate) const fn optional_signer(role: &'static str, expected_writable: bool) -> Self {
return Self {
role,
expected_writable,
expected_signer: std::option::Option::None,
};
}
}
/// Resolves positional instruction accounts and validates their core indexes.
pub(crate) fn resolve_accounts(
input: &crate::CoreInstructionReplayInput,
roles: &[crate::SolanaCoreAccountRole],
minimum_count: usize,
maximum_count: std::option::Option<usize>,
) -> kb_core::Result<serde_json::Value> {
let instruction_accounts = match input.instruction_accounts_json.as_array() {
std::option::Option::Some(value) => value,
std::option::Option::None => {
return std::result::Result::Err(kb_core::Error::invalid_state(
"native instruction accounts must be a JSON array",
));
},
};
if instruction_accounts.len() < minimum_count {
return std::result::Result::Err(kb_core::Error::invalid_state(format!(
"native instruction requires at least {minimum_count} accounts but received {}",
instruction_accounts.len()
)));
}
if maximum_count.is_some_and(|value| return instruction_accounts.len() > value) {
return std::result::Result::Err(kb_core::Error::invalid_state(format!(
"native instruction accepts at most {} accounts but received {}",
maximum_count.unwrap_or(0),
instruction_accounts.len()
)));
}
let account_keys = match input.account_keys_json.as_array() {
std::option::Option::Some(value) => value,
std::option::Option::None => {
return std::result::Result::Err(kb_core::Error::invalid_state(
"native transaction account keys must be a JSON array",
));
},
};
let mut output = std::vec::Vec::with_capacity(instruction_accounts.len());
for (position, instruction_account) in instruction_accounts.iter().enumerate() {
let account_index =
match instruction_account.get("accountIndex").and_then(serde_json::Value::as_u64) {
std::option::Option::Some(value) => value,
std::option::Option::None => {
return std::result::Result::Err(kb_core::Error::invalid_state(format!(
"native instruction account {position} has no valid accountIndex"
)));
},
};
let instruction_key =
match instruction_account.get("accountKey").and_then(serde_json::Value::as_str) {
std::option::Option::Some(value) => value,
std::option::Option::None => {
return std::result::Result::Err(kb_core::Error::invalid_state(format!(
"native instruction account {position} has no accountKey"
)));
},
};
let resolved = account_keys.iter().find(|candidate| {
return candidate.get("accountIndex").and_then(serde_json::Value::as_u64)
== std::option::Option::Some(account_index);
});
let resolved = match resolved {
std::option::Option::Some(value) => value,
std::option::Option::None => {
return std::result::Result::Err(kb_core::Error::invalid_state(format!(
"native instruction account index {account_index} is absent from resolved keys"
)));
},
};
let resolved_key = match resolved.get("accountKey").and_then(serde_json::Value::as_str) {
std::option::Option::Some(value) => value,
std::option::Option::None => {
return std::result::Result::Err(kb_core::Error::invalid_state(format!(
"resolved native account index {account_index} has no accountKey"
)));
},
};
if resolved_key != instruction_key {
return std::result::Result::Err(kb_core::Error::invalid_state(format!(
"native instruction account index {account_index} resolves to a different key"
)));
}
let role = roles.get(position);
output.push(serde_json::json!({
"position": position,
"accountIndex": account_index,
"accountKey": instruction_key,
"role": role.map(|value| return value.role).unwrap_or("additional_account"),
"expectedWritable": role.map(|value| return value.expected_writable),
"expectedSigner": role.map(|value| return value.expected_signer),
"writable": resolved.get("writable").and_then(serde_json::Value::as_bool),
"signer": resolved.get("signer").and_then(serde_json::Value::as_bool),
"executable": resolved.get("executable").and_then(serde_json::Value::as_bool),
"source": resolved.get("source").cloned().unwrap_or(serde_json::Value::Null),
}));
}
return std::result::Result::Ok(serde_json::Value::Array(output));
}

View File

@@ -0,0 +1,505 @@
// file: kb-lib/src/decoder/solana/core/address_lookup_table.rs
// version: 5
//! Exact Address Lookup Table instruction decoding through the official interface schema.
const SOURCE: &str =
"solana-address-lookup-table-interface@3.1.0 ProgramInstruction wincode contract";
const CREATE_ROLES: &[crate::SolanaCoreAccountRole] = &[
crate::SolanaCoreAccountRole::new("lookup_table", true, false),
crate::SolanaCoreAccountRole::optional_signer("authority", false),
crate::SolanaCoreAccountRole::new("payer", true, true),
crate::SolanaCoreAccountRole::new("system_program", false, false),
];
const AUTHORITY_ROLES: &[crate::SolanaCoreAccountRole] = &[
crate::SolanaCoreAccountRole::new("lookup_table", true, false),
crate::SolanaCoreAccountRole::new("authority", false, true),
];
const EXTEND_ROLES: &[crate::SolanaCoreAccountRole] = &[
crate::SolanaCoreAccountRole::new("lookup_table", true, false),
crate::SolanaCoreAccountRole::new("authority", false, true),
crate::SolanaCoreAccountRole::new("payer", true, true),
crate::SolanaCoreAccountRole::new("system_program", false, false),
];
const CLOSE_ROLES: &[crate::SolanaCoreAccountRole] = &[
crate::SolanaCoreAccountRole::new("lookup_table", true, false),
crate::SolanaCoreAccountRole::new("authority", false, true),
crate::SolanaCoreAccountRole::new("recipient", true, false),
];
/// Returns declared Address Lookup Table instruction coverage.
pub(crate) fn address_lookup_table_coverage() -> std::vec::Vec<crate::DecoderCoverageDeclaration> {
return vec![
coverage_entry(
"create_lookup_table",
solana_address_lookup_table_interface::instruction::ProgramInstruction::CreateLookupTable {
recent_slot: 0,
bump_seed: 0,
},
),
coverage_entry(
"freeze_lookup_table",
solana_address_lookup_table_interface::instruction::ProgramInstruction::FreezeLookupTable,
),
coverage_entry(
"extend_lookup_table",
solana_address_lookup_table_interface::instruction::ProgramInstruction::ExtendLookupTable {
new_addresses: std::vec::Vec::new(),
},
),
coverage_entry(
"deactivate_lookup_table",
solana_address_lookup_table_interface::instruction::ProgramInstruction::DeactivateLookupTable,
),
coverage_entry(
"close_lookup_table",
solana_address_lookup_table_interface::instruction::ProgramInstruction::CloseLookupTable,
),
];
}
/// Recognizes one Address Lookup Table instruction without producing an event.
pub(crate) fn address_lookup_table_recognize(
input: &crate::CoreInstructionReplayInput,
priority: u16,
) -> crate::DecoderRecognition {
let bytes_result = crate::solana_core_decode_instruction_data(input);
let bytes = match bytes_result {
std::result::Result::Ok(value) => value,
std::result::Result::Err(_error) => {
return crate::DecoderRecognition::compatible(
false,
priority,
std::option::Option::Some(
crate::SOLANA_CORE_ADDRESS_LOOKUP_TABLE_SURFACE_CODE.to_string(),
),
std::option::Option::Some("malformed_address_lookup_table_instruction".to_string()),
std::option::Option::None,
);
},
};
let tag_result = crate::solana_core_read_u32_le(bytes.as_slice(), 0);
let tag = match tag_result {
std::result::Result::Ok(value) => value,
std::result::Result::Err(_error) => {
return crate::DecoderRecognition::compatible(
false,
priority,
std::option::Option::Some(
crate::SOLANA_CORE_ADDRESS_LOOKUP_TABLE_SURFACE_CODE.to_string(),
),
std::option::Option::Some("malformed_address_lookup_table_instruction".to_string()),
crate::solana_core_hexadecimal_prefix(bytes.as_slice(), bytes.len().min(4)),
);
},
};
let entry_code = entry_code(tag);
let exact = if entry_code.is_some() {
wincode::deserialize_exact::<
solana_address_lookup_table_interface::instruction::ProgramInstruction,
>(bytes.as_slice())
.is_ok()
} else {
false
};
return crate::DecoderRecognition::compatible(
exact,
priority,
std::option::Option::Some(crate::SOLANA_CORE_ADDRESS_LOOKUP_TABLE_SURFACE_CODE.to_string()),
entry_code.map(str::to_string).or_else(|| {
return std::option::Option::Some(
"unknown_address_lookup_table_instruction".to_string(),
);
}),
crate::solana_core_hexadecimal_prefix(bytes.as_slice(), 4),
);
}
/// Decodes one Address Lookup Table instruction.
pub(crate) fn address_lookup_table_decode(
input: &crate::CoreInstructionReplayInput,
) -> crate::DecoderExecutionResult {
let bytes_result = crate::solana_core_decode_instruction_data(input);
let bytes = match bytes_result {
std::result::Result::Ok(value) => value,
std::result::Result::Err(error) => {
return crate::solana_core_failed_result(
std::option::Option::Some("malformed_address_lookup_table_instruction"),
"address_lookup_table_payload_invalid",
error.to_string(),
);
},
};
let tag_result = crate::solana_core_read_u32_le(bytes.as_slice(), 0);
let tag = match tag_result {
std::result::Result::Ok(value) => value,
std::result::Result::Err(error) => {
return crate::solana_core_failed_result(
std::option::Option::Some("malformed_address_lookup_table_instruction"),
"address_lookup_table_tag_truncated",
error.to_string(),
);
},
};
let entry_code = match entry_code(tag) {
std::option::Option::Some(value) => value,
std::option::Option::None => {
return crate::solana_core_unsupported_result(
"unknown_address_lookup_table_instruction",
"address_lookup_table_tag_unknown",
format!(
"unknown Address Lookup Table instruction tag {tag}; payload_sha256={}",
crate::solana_core_payload_hash(input)
),
);
},
};
let instruction_result = wincode::deserialize_exact::<
solana_address_lookup_table_interface::instruction::ProgramInstruction,
>(bytes.as_slice());
let instruction = match instruction_result {
std::result::Result::Ok(value) => value,
std::result::Result::Err(error) => {
return crate::solana_core_failed_result(
std::option::Option::Some(entry_code),
"address_lookup_table_wincode_invalid",
format!("cannot decode exact Address Lookup Table {entry_code}: {error}"),
);
},
};
return decoded_instruction(input, instruction);
}
fn decoded_instruction(
input: &crate::CoreInstructionReplayInput,
instruction: solana_address_lookup_table_interface::instruction::ProgramInstruction,
) -> crate::DecoderExecutionResult {
return match instruction {
solana_address_lookup_table_interface::instruction::ProgramInstruction::CreateLookupTable {
recent_slot,
bump_seed,
} => build(
input,
"create_lookup_table",
CREATE_ROLES,
4,
std::option::Option::Some(4),
serde_json::json!({
"recentSlot": recent_slot,
"bumpSeed": bump_seed,
"authoritySignerPolicy": "optional_current_historical_required",
}),
),
solana_address_lookup_table_interface::instruction::ProgramInstruction::FreezeLookupTable => {
build(
input,
"freeze_lookup_table",
AUTHORITY_ROLES,
2,
std::option::Option::Some(2),
serde_json::json!({}),
)
},
solana_address_lookup_table_interface::instruction::ProgramInstruction::ExtendLookupTable {
new_addresses,
} => {
let account_count = input
.instruction_accounts_json
.as_array()
.map(std::vec::Vec::len)
.unwrap_or(0);
if account_count != 2 && account_count != 4 {
return crate::solana_core_failed_result(
std::option::Option::Some("extend_lookup_table"),
"address_lookup_table_extend_accounts_invalid",
format!(
"Address Lookup Table extend requires 2 accounts or the complete optional payer/system pair of 4 accounts but received {account_count}"
),
);
}
let addresses = new_addresses
.iter()
.map(std::string::ToString::to_string)
.collect::<std::vec::Vec<_>>();
build(
input,
"extend_lookup_table",
EXTEND_ROLES,
2,
std::option::Option::Some(4),
serde_json::json!({
"newAddresses": addresses,
"newAddressCount": new_addresses.len(),
"fundingAccountsPresent": account_count == 4,
}),
)
},
solana_address_lookup_table_interface::instruction::ProgramInstruction::DeactivateLookupTable => {
build(
input,
"deactivate_lookup_table",
AUTHORITY_ROLES,
2,
std::option::Option::Some(2),
serde_json::json!({}),
)
},
solana_address_lookup_table_interface::instruction::ProgramInstruction::CloseLookupTable => {
build(
input,
"close_lookup_table",
CLOSE_ROLES,
3,
std::option::Option::Some(3),
serde_json::json!({}),
)
},
};
}
fn build(
input: &crate::CoreInstructionReplayInput,
entry_code: &str,
roles: &[crate::SolanaCoreAccountRole],
minimum_count: usize,
maximum_count: std::option::Option<usize>,
parameters: serde_json::Value,
) -> crate::DecoderExecutionResult {
let accounts_result =
crate::solana_core_resolve_accounts(input, roles, minimum_count, maximum_count);
let accounts = match accounts_result {
std::result::Result::Ok(value) => value,
std::result::Result::Err(error) => {
return crate::solana_core_failed_result(
std::option::Option::Some(entry_code),
"address_lookup_table_accounts_invalid",
error.to_string(),
);
},
};
return crate::solana_core_decoded_result(
input,
crate::SOLANA_CORE_ADDRESS_LOOKUP_TABLE_SURFACE_CODE,
entry_code,
crate::EventFamily::Lifecycle,
false,
accounts,
parameters,
SOURCE,
);
}
fn entry_code(tag: u32) -> std::option::Option<&'static str> {
return match tag {
0 => std::option::Option::Some("create_lookup_table"),
1 => std::option::Option::Some("freeze_lookup_table"),
2 => std::option::Option::Some("extend_lookup_table"),
3 => std::option::Option::Some("deactivate_lookup_table"),
4 => std::option::Option::Some("close_lookup_table"),
_ => std::option::Option::None,
};
}
fn coverage_entry(
entry_code: &str,
instruction: solana_address_lookup_table_interface::instruction::ProgramInstruction,
) -> crate::DecoderCoverageDeclaration {
let discriminator_hex = match wincode::serialize(&instruction) {
std::result::Result::Ok(bytes) => {
crate::solana_core_hexadecimal_prefix(bytes.as_slice(), 4)
},
std::result::Result::Err(_error) => std::option::Option::None,
};
return crate::DecoderCoverageDeclaration {
program_id: kb_program_ids::ADDRESS_LOOKUP_TABLE_PROGRAM_ID.to_string(),
surface_code: std::option::Option::Some(
crate::SOLANA_CORE_ADDRESS_LOOKUP_TABLE_SURFACE_CODE.to_string(),
),
entry_kind: crate::DecoderCoverageEntryKind::Instruction,
entry_code: entry_code.to_string(),
discriminator_hex,
historical: false,
};
}
#[cfg(test)]
mod tests {
use base64::Engine; // rust-rules: trait-import
fn replay_input(
instruction: &solana_address_lookup_table_interface::instruction::ProgramInstruction,
account_count: usize,
transaction_failed: bool,
) -> crate::CoreInstructionReplayInput {
let bytes_result = wincode::serialize(instruction);
let bytes = match bytes_result {
std::result::Result::Ok(value) => value,
std::result::Result::Err(error) => panic!("ALT fixture serialization failed: {error}"),
};
return replay_input_bytes(bytes.as_slice(), account_count, transaction_failed);
}
fn replay_input_bytes(
bytes: &[u8],
account_count: usize,
transaction_failed: bool,
) -> crate::CoreInstructionReplayInput {
let encoded = base64::engine::general_purpose::STANDARD.encode(bytes);
let account_keys = (0..account_count)
.map(|index| {
return serde_json::json!({
"accountIndex": index,
"accountKey": format!("account{index}"),
"source": "static",
"writable": true,
"signer": true,
"executable": false,
});
})
.collect::<std::vec::Vec<_>>();
let instruction_accounts = (0..account_count)
.map(|index| {
return serde_json::json!({
"accountIndex": index,
"accountKey": format!("account{index}"),
});
})
.collect::<std::vec::Vec<_>>();
let result = crate::CoreInstructionReplayInput::new(
"signature:0",
"signature",
42,
"0",
kb_program_ids::ADDRESS_LOOKUP_TABLE_PROGRAM_ID,
transaction_failed,
if transaction_failed {
std::option::Option::Some(serde_json::json!({"InstructionError": [0, "Custom"]}))
} else {
std::option::Option::None
},
serde_json::Value::Array(account_keys),
serde_json::Value::Array(instruction_accounts),
std::option::Option::Some(serde_json::json!({"dataBase64": encoded})),
std::option::Option::Some("payload-hash".to_string()),
serde_json::json!([]),
serde_json::json!([]),
serde_json::json!([]),
serde_json::json!([]),
);
return match result {
std::result::Result::Ok(value) => value,
std::result::Result::Err(error) => panic!("ALT replay input failed: {error}"),
};
}
#[test]
fn every_official_variant_decodes() {
let address = solana_sdk_ids::address_lookup_table::id();
let fixtures = [
(
solana_address_lookup_table_interface::instruction::ProgramInstruction::CreateLookupTable {
recent_slot: u64::MAX,
bump_seed: u8::MAX,
},
4,
"create_lookup_table",
),
(
solana_address_lookup_table_interface::instruction::ProgramInstruction::FreezeLookupTable,
2,
"freeze_lookup_table",
),
(
solana_address_lookup_table_interface::instruction::ProgramInstruction::ExtendLookupTable {
new_addresses: vec![address, address],
},
4,
"extend_lookup_table",
),
(
solana_address_lookup_table_interface::instruction::ProgramInstruction::DeactivateLookupTable,
2,
"deactivate_lookup_table",
),
(
solana_address_lookup_table_interface::instruction::ProgramInstruction::CloseLookupTable,
3,
"close_lookup_table",
),
];
for (instruction, account_count, expected) in fixtures {
let result = crate::solana_core_address_lookup_table_decode(&replay_input(
&instruction,
account_count,
false,
));
assert_eq!(result.status, crate::DecoderOutcomeStatus::Decoded);
assert_eq!(
result.recognized_entry_code.as_deref(),
std::option::Option::Some(expected)
);
assert_eq!(result.observations.len(), 1);
}
}
#[test]
fn extend_accepts_only_absent_or_complete_funding_pair() {
let instruction = solana_address_lookup_table_interface::instruction::ProgramInstruction::ExtendLookupTable {
new_addresses: std::vec::Vec::new(),
};
let without_funding =
crate::solana_core_address_lookup_table_decode(&replay_input(&instruction, 2, false));
assert_eq!(without_funding.status, crate::DecoderOutcomeStatus::Decoded);
let incomplete =
crate::solana_core_address_lookup_table_decode(&replay_input(&instruction, 3, false));
assert_eq!(incomplete.status, crate::DecoderOutcomeStatus::Failed);
}
#[test]
fn truncated_unknown_and_trailing_payloads_are_distinguished() {
let truncated = crate::solana_core_address_lookup_table_decode(&replay_input_bytes(
&[0, 0, 0],
4,
false,
));
assert_eq!(truncated.status, crate::DecoderOutcomeStatus::Failed);
let unknown = crate::solana_core_address_lookup_table_decode(&replay_input_bytes(
&[255, 0, 0, 0],
0,
false,
));
assert_eq!(unknown.status, crate::DecoderOutcomeStatus::Unsupported);
let mut valid = match wincode::serialize(
&solana_address_lookup_table_interface::instruction::ProgramInstruction::FreezeLookupTable,
) {
std::result::Result::Ok(value) => value,
std::result::Result::Err(error) => panic!("ALT fixture serialization failed: {error}"),
};
valid.push(1);
let trailing = crate::solana_core_address_lookup_table_decode(&replay_input_bytes(
valid.as_slice(),
2,
false,
));
assert_eq!(trailing.status, crate::DecoderOutcomeStatus::Failed);
}
#[test]
fn failed_transaction_is_decoded_as_uncommitted_lifecycle_intent() {
let instruction = solana_address_lookup_table_interface::instruction::ProgramInstruction::DeactivateLookupTable;
let result =
crate::solana_core_address_lookup_table_decode(&replay_input(&instruction, 2, true));
assert_eq!(result.status, crate::DecoderOutcomeStatus::Decoded);
assert!(!result.observations[0].observation_committed);
assert_eq!(result.observations[0].payload_json["transactionSucceeded"], false);
}
#[test]
fn coverage_uses_official_encoder_discriminants() {
let coverage = crate::solana_core_address_lookup_table_coverage();
assert_eq!(coverage.len(), 5);
assert_eq!(coverage[0].discriminator_hex.as_deref(), std::option::Option::Some("00000000"));
assert_eq!(coverage[4].discriminator_hex.as_deref(), std::option::Option::Some("04000000"));
}
}

View File

@@ -0,0 +1,896 @@
// file: kb-lib/src/decoder/solana/core/compute_budget.rs
// version: 6
//! Maximal current and historical Compute Budget instruction decoding.
use base64::Engine; // rust-rules: trait-import
const SOURCE_CURRENT: &str = "solana-compute-budget-interface@3.0.0; solana-compute-budget-instruction@4.1.1::try_from_slice_unchecked";
const SOURCE_HISTORICAL: &str =
"solana-sdk compute_budget::ComputeBudgetInstruction RequestUnitsDeprecated";
/// Returns declared Compute Budget instruction coverage.
pub(crate) fn compute_budget_coverage() -> std::vec::Vec<crate::DecoderCoverageDeclaration> {
return vec![
coverage_entry("unused_reserved", "00", false),
coverage_entry("request_units_deprecated", "00", true),
coverage_entry("request_heap_frame", "01", false),
coverage_entry("set_compute_unit_limit", "02", false),
coverage_entry("set_compute_unit_price", "03", false),
coverage_entry("set_loaded_accounts_data_size_limit", "04", false),
];
}
/// Recognizes one Compute Budget instruction without producing an event.
pub(crate) fn compute_budget_recognize(
input: &crate::CoreInstructionReplayInput,
priority: u16,
) -> crate::DecoderRecognition {
let bytes_result = crate::solana_core_decode_instruction_data(input);
let bytes = match bytes_result {
std::result::Result::Ok(value) => value,
std::result::Result::Err(_error) => {
return crate::DecoderRecognition::compatible(
false,
priority,
std::option::Option::Some(
crate::SOLANA_CORE_COMPUTE_BUDGET_SURFACE_CODE.to_string(),
),
std::option::Option::Some("malformed_compute_budget_instruction".to_string()),
std::option::Option::None,
);
},
};
let entry_code = entry_code(bytes.as_slice());
return crate::DecoderRecognition::compatible(
entry_code.is_some(),
priority,
std::option::Option::Some(crate::SOLANA_CORE_COMPUTE_BUDGET_SURFACE_CODE.to_string()),
entry_code.map(str::to_string).or_else(|| {
return std::option::Option::Some("unknown_compute_budget_instruction".to_string());
}),
crate::solana_core_hexadecimal_prefix(bytes.as_slice(), 1),
);
}
/// Decodes one Compute Budget instruction.
pub(crate) fn compute_budget_decode(
input: &crate::CoreInstructionReplayInput,
) -> crate::DecoderExecutionResult {
let bytes_result = crate::solana_core_decode_instruction_data(input);
let bytes = match bytes_result {
std::result::Result::Ok(value) => value,
std::result::Result::Err(error) => {
return crate::solana_core_failed_result(
std::option::Option::Some("malformed_compute_budget_instruction"),
"compute_budget_payload_invalid",
error.to_string(),
);
},
};
if bytes.is_empty() {
return crate::solana_core_failed_result(
std::option::Option::Some("malformed_compute_budget_instruction"),
"compute_budget_payload_empty",
"Compute Budget instruction payload is empty",
);
}
let accounts_result =
crate::solana_core_resolve_accounts(input, &[], 0, std::option::Option::None);
let accounts = match accounts_result {
std::result::Result::Ok(value) => value,
std::result::Result::Err(error) => {
return crate::solana_core_failed_result(
entry_code(bytes.as_slice()),
"compute_budget_accounts_invalid",
error.to_string(),
);
},
};
return match bytes[0] {
0 if bytes.len() == 1 => crate::DecoderExecutionResult::ignored(std::option::Option::Some(
"unused_reserved".to_string(),
)),
0 if bytes.len() == 9 => {
let units_result = crate::solana_core_read_u32_le(bytes.as_slice(), 1);
let units = match units_result {
std::result::Result::Ok(value) => value,
std::result::Result::Err(error) => {
return crate::solana_core_failed_result(
std::option::Option::Some("request_units_deprecated"),
"compute_budget_request_units_truncated",
error.to_string(),
);
},
};
let additional_fee_result = crate::solana_core_read_u32_le(bytes.as_slice(), 5);
let additional_fee = match additional_fee_result {
std::result::Result::Ok(value) => value,
std::result::Result::Err(error) => {
return crate::solana_core_failed_result(
std::option::Option::Some("request_units_deprecated"),
"compute_budget_request_units_truncated",
error.to_string(),
);
},
};
let mut parameters = serde_json::Map::new();
parameters.insert("units".to_string(), serde_json::json!(units));
parameters.insert("additionalFee".to_string(), serde_json::json!(additional_fee));
append_transaction_profile(input, &mut parameters);
crate::solana_core_decoded_result(
input,
crate::SOLANA_CORE_COMPUTE_BUDGET_SURFACE_CODE,
"request_units_deprecated",
crate::EventFamily::Audit,
true,
accounts,
serde_json::Value::Object(parameters),
SOURCE_HISTORICAL,
)
},
0 => crate::solana_core_failed_result(
std::option::Option::Some("request_units_deprecated"),
"compute_budget_request_units_size_invalid",
format!(
"historical RequestUnitsDeprecated requires 9 bytes but received {}",
bytes.len()
),
),
1 => {
decode_u32_instruction(input, bytes.as_slice(), accounts, "request_heap_frame", "bytes")
},
2 => decode_u32_instruction(
input,
bytes.as_slice(),
accounts,
"set_compute_unit_limit",
"computeUnitLimit",
),
3 => decode_u64_instruction(
input,
bytes.as_slice(),
accounts,
"set_compute_unit_price",
"microLamports",
),
4 => decode_u32_instruction(
input,
bytes.as_slice(),
accounts,
"set_loaded_accounts_data_size_limit",
"bytes",
),
tag => crate::solana_core_unsupported_result(
"unknown_compute_budget_instruction",
"compute_budget_tag_unknown",
format!(
"unknown Compute Budget instruction tag {tag}; payload_sha256={}",
crate::solana_core_payload_hash(input)
),
),
};
}
fn decode_u32_instruction(
input: &crate::CoreInstructionReplayInput,
bytes: &[u8],
accounts: serde_json::Value,
entry_code: &str,
field_name: &str,
) -> crate::DecoderExecutionResult {
if bytes.len() < 5 {
return crate::solana_core_failed_result(
std::option::Option::Some(entry_code),
"compute_budget_instruction_size_invalid",
format!(
"Compute Budget {entry_code} requires at least 5 bytes but received {}",
bytes.len()
),
);
}
let value_result = crate::solana_core_read_u32_le(bytes, 1);
let value = match value_result {
std::result::Result::Ok(value) => value,
std::result::Result::Err(error) => {
return crate::solana_core_failed_result(
std::option::Option::Some(entry_code),
"compute_budget_instruction_truncated",
error.to_string(),
);
},
};
let mut parameters = serde_json::Map::new();
parameters.insert(field_name.to_string(), serde_json::json!(value));
append_runtime_ignored_trailing_data(&mut parameters, bytes, 5);
append_transaction_profile(input, &mut parameters);
return crate::solana_core_decoded_result(
input,
crate::SOLANA_CORE_COMPUTE_BUDGET_SURFACE_CODE,
entry_code,
crate::EventFamily::Audit,
false,
accounts,
serde_json::Value::Object(parameters),
SOURCE_CURRENT,
);
}
fn decode_u64_instruction(
input: &crate::CoreInstructionReplayInput,
bytes: &[u8],
accounts: serde_json::Value,
entry_code: &str,
field_name: &str,
) -> crate::DecoderExecutionResult {
if bytes.len() < 9 {
return crate::solana_core_failed_result(
std::option::Option::Some(entry_code),
"compute_budget_instruction_size_invalid",
format!(
"Compute Budget {entry_code} requires at least 9 bytes but received {}",
bytes.len()
),
);
}
let value_result = crate::solana_core_read_u64_le(bytes, 1);
let value = match value_result {
std::result::Result::Ok(value) => value,
std::result::Result::Err(error) => {
return crate::solana_core_failed_result(
std::option::Option::Some(entry_code),
"compute_budget_instruction_truncated",
error.to_string(),
);
},
};
let mut parameters = serde_json::Map::new();
parameters.insert(field_name.to_string(), serde_json::json!(value));
append_runtime_ignored_trailing_data(&mut parameters, bytes, 9);
append_transaction_profile(input, &mut parameters);
return crate::solana_core_decoded_result(
input,
crate::SOLANA_CORE_COMPUTE_BUDGET_SURFACE_CODE,
entry_code,
crate::EventFamily::Audit,
false,
accounts,
serde_json::Value::Object(parameters),
SOURCE_CURRENT,
);
}
fn append_transaction_profile(
input: &crate::CoreInstructionReplayInput,
parameters: &mut serde_json::Map<std::string::String, serde_json::Value>,
) {
parameters.insert("transactionProfile".to_string(), transaction_profile(input));
}
fn transaction_profile(input: &crate::CoreInstructionReplayInput) -> serde_json::Value {
let target_path = input.instruction_path.as_str();
let mut instruction_rows = std::vec::Vec::new();
let mut effective = serde_json::Map::new();
let mut decoded_count = 0_u64;
let mut unsupported_count = 0_u64;
let mut failed_count = 0_u64;
let mut trailing_count = 0_u64;
let outer_rows = compute_budget_outer_rows(input);
for row in outer_rows {
if row.status == "decoded" {
decoded_count = decoded_count.saturating_add(1);
} else if row.status == "unsupported" {
unsupported_count = unsupported_count.saturating_add(1);
} else if row.status == "failed" {
failed_count = failed_count.saturating_add(1);
}
if row.trailing_data_present {
trailing_count = trailing_count.saturating_add(1);
}
apply_effective_value(&mut effective, &row);
instruction_rows.push(row.to_json());
}
let emitter_path = profile_emitter_path(instruction_rows.as_slice());
let current_is_emitter = emitter_path.as_deref() == std::option::Option::Some(target_path);
return serde_json::json!({
"profileVersion": 1,
"profileSemantics": "transaction_compute_budget_last_write_wins",
"currentInstructionIsProfileEmitter": current_is_emitter,
"profileEmitterInstructionPath": emitter_path,
"computeBudgetInstructionCount": instruction_rows.len(),
"decodedInstructionCount": decoded_count,
"unsupportedInstructionCount": unsupported_count,
"failedInstructionCount": failed_count,
"runtimeIgnoredTrailingDataInstructionCount": trailing_count,
"effectiveValues": serde_json::Value::Object(effective),
"instructions": instruction_rows,
"transactionFinalRuntimeMetricsCaptured": false,
"computeUnitsConsumedCaptured": false,
});
}
#[derive(Clone, Debug)]
struct ComputeBudgetProfileRow {
instruction_index: u64,
instruction_path: std::string::String,
entry_code: std::string::String,
status: &'static str,
parameters: serde_json::Value,
payload_hash: serde_json::Value,
trailing_data_present: bool,
diagnostic: serde_json::Value,
}
impl ComputeBudgetProfileRow {
fn to_json(&self) -> serde_json::Value {
return serde_json::json!({
"instructionIndex": self.instruction_index,
"instructionPath": self.instruction_path,
"entryCode": self.entry_code,
"status": self.status,
"parameters": self.parameters,
"payloadHash": self.payload_hash,
"trailingDataPresent": self.trailing_data_present,
"diagnostic": self.diagnostic,
});
}
}
fn compute_budget_outer_rows(
input: &crate::CoreInstructionReplayInput,
) -> std::vec::Vec<ComputeBudgetProfileRow> {
let mut rows = std::vec::Vec::new();
let outer_values = match input.outer_instructions_json.as_array() {
std::option::Option::Some(value) => value,
std::option::Option::None => return rows,
};
for value in outer_values {
if value.get("programId").and_then(serde_json::Value::as_str)
!= std::option::Option::Some(kb_program_ids::COMPUTE_BUDGET_PROGRAM_ID)
{
continue;
}
let instruction_index =
value.get("instructionIndex").and_then(serde_json::Value::as_u64).unwrap_or(0);
let instruction_path = value
.get("instructionPath")
.and_then(serde_json::Value::as_str)
.unwrap_or("")
.to_string();
let payload_hash = value.get("payloadHash").cloned().unwrap_or(serde_json::Value::Null);
let payload_json = match value.get("payloadJson") {
std::option::Option::Some(payload) if !payload.is_null() => payload,
_ => {
rows.push(profile_failure_row(
instruction_index,
instruction_path,
payload_hash,
"payload_missing",
"outer Compute Budget instruction payload is not retained",
));
continue;
},
};
let bytes_result = decode_payload_json_for_profile(payload_json);
let bytes = match bytes_result {
std::result::Result::Ok(value) => value,
std::result::Result::Err(error) => {
rows.push(profile_failure_row(
instruction_index,
instruction_path,
payload_hash,
"payload_invalid",
error.as_str(),
));
continue;
},
};
rows.push(profile_row_from_bytes(
instruction_index,
instruction_path,
payload_hash,
bytes.as_slice(),
));
}
if rows.is_empty() && input.program_id == kb_program_ids::COMPUTE_BUDGET_PROGRAM_ID {
let bytes_result = crate::solana_core_decode_instruction_data(input);
let bytes = match bytes_result {
std::result::Result::Ok(value) => value,
std::result::Result::Err(error) => {
rows.push(profile_failure_row(
input.instruction_path.parse::<u64>().unwrap_or(0),
input.instruction_path.clone(),
serde_json::Value::String(crate::solana_core_payload_hash(input)),
"payload_invalid",
error.to_string().as_str(),
));
return rows;
},
};
rows.push(profile_row_from_bytes(
input.instruction_path.parse::<u64>().unwrap_or(0),
input.instruction_path.clone(),
serde_json::Value::String(crate::solana_core_payload_hash(input)),
bytes.as_slice(),
));
}
return rows;
}
fn profile_row_from_bytes(
instruction_index: u64,
instruction_path: std::string::String,
payload_hash: serde_json::Value,
bytes: &[u8],
) -> ComputeBudgetProfileRow {
let entry = entry_code(bytes).unwrap_or("unknown_compute_budget_instruction");
let mut diagnostic = serde_json::Value::Null;
let mut status = "decoded";
let mut parameters = serde_json::Map::new();
let mut trailing_data_present = false;
match entry {
"unused_reserved" => {},
"request_units_deprecated" => {
if bytes.len() == 9 {
if let std::result::Result::Ok(value) = crate::solana_core_read_u32_le(bytes, 1) {
parameters.insert("units".to_string(), serde_json::json!(value));
}
if let std::result::Result::Ok(value) = crate::solana_core_read_u32_le(bytes, 5) {
parameters.insert("additionalFee".to_string(), serde_json::json!(value));
}
} else {
status = "failed";
diagnostic = serde_json::json!("historical request units payload size is invalid");
}
},
"request_heap_frame" | "set_compute_unit_limit" | "set_loaded_accounts_data_size_limit" => {
if bytes.len() >= 5 {
if let std::result::Result::Ok(value) = crate::solana_core_read_u32_le(bytes, 1) {
let field_name = match entry {
"request_heap_frame" => "bytes",
"set_compute_unit_limit" => "computeUnitLimit",
_ => "bytes",
};
parameters.insert(field_name.to_string(), serde_json::json!(value));
}
trailing_data_present = bytes.len() > 5;
} else {
status = "failed";
diagnostic = serde_json::json!("current u32 Compute Budget payload is truncated");
}
},
"set_compute_unit_price" => {
if bytes.len() >= 9 {
if let std::result::Result::Ok(value) = crate::solana_core_read_u64_le(bytes, 1) {
parameters.insert("microLamports".to_string(), serde_json::json!(value));
}
trailing_data_present = bytes.len() > 9;
} else {
status = "failed";
diagnostic = serde_json::json!("current u64 Compute Budget payload is truncated");
}
},
_ => {
status = "unsupported";
diagnostic = serde_json::json!("unknown Compute Budget instruction tag");
},
}
return ComputeBudgetProfileRow {
instruction_index,
instruction_path,
entry_code: entry.to_string(),
status,
parameters: serde_json::Value::Object(parameters),
payload_hash,
trailing_data_present,
diagnostic,
};
}
fn profile_failure_row(
instruction_index: u64,
instruction_path: std::string::String,
payload_hash: serde_json::Value,
code: &str,
message: &str,
) -> ComputeBudgetProfileRow {
return ComputeBudgetProfileRow {
instruction_index,
instruction_path,
entry_code: "malformed_compute_budget_instruction".to_string(),
status: "failed",
parameters: serde_json::Value::Null,
payload_hash,
trailing_data_present: false,
diagnostic: serde_json::json!({"code": code, "message": message}),
};
}
fn decode_payload_json_for_profile(
payload: &serde_json::Value,
) -> std::result::Result<std::vec::Vec<u8>, std::string::String> {
let encoded = match payload.get("dataBase64").and_then(serde_json::Value::as_str) {
std::option::Option::Some(value) => value,
std::option::Option::None => {
return std::result::Result::Err("payload does not contain dataBase64".to_string());
},
};
let maximum_encoded_length = crate::SOLANA_CORE_MAX_NATIVE_INSTRUCTION_PAYLOAD_BYTES
.saturating_mul(4)
.saturating_div(3)
.saturating_add(4);
if encoded.len() > maximum_encoded_length {
return std::result::Result::Err(format!(
"payload base64 exceeds {maximum_encoded_length} bytes"
));
}
let decoded_result = base64::engine::general_purpose::STANDARD.decode(encoded.as_bytes());
let decoded = match decoded_result {
std::result::Result::Ok(value) => value,
std::result::Result::Err(error) => {
return std::result::Result::Err(format!("payload is not valid base64: {error}"));
},
};
if decoded.len() > crate::SOLANA_CORE_MAX_NATIVE_INSTRUCTION_PAYLOAD_BYTES {
return std::result::Result::Err(format!(
"payload exceeds {} decoded bytes",
crate::SOLANA_CORE_MAX_NATIVE_INSTRUCTION_PAYLOAD_BYTES
));
}
return std::result::Result::Ok(decoded);
}
fn apply_effective_value(
effective: &mut serde_json::Map<std::string::String, serde_json::Value>,
row: &ComputeBudgetProfileRow,
) {
if row.status != "decoded" {
return;
}
let value = match row.entry_code.as_str() {
"request_units_deprecated" => serde_json::json!({
"units": row.parameters.get("units").cloned().unwrap_or(serde_json::Value::Null),
"additionalFee": row.parameters.get("additionalFee").cloned().unwrap_or(serde_json::Value::Null),
}),
"request_heap_frame" => {
row.parameters.get("bytes").cloned().unwrap_or(serde_json::Value::Null)
},
"set_compute_unit_limit" => row
.parameters
.get("computeUnitLimit")
.cloned()
.unwrap_or(serde_json::Value::Null),
"set_compute_unit_price" => {
row.parameters.get("microLamports").cloned().unwrap_or(serde_json::Value::Null)
},
"set_loaded_accounts_data_size_limit" => {
row.parameters.get("bytes").cloned().unwrap_or(serde_json::Value::Null)
},
_ => return,
};
effective.insert(
row.entry_code.clone(),
serde_json::json!({
"value": value,
"sourceInstructionIndex": row.instruction_index,
"sourceInstructionPath": row.instruction_path,
}),
);
}
fn profile_emitter_path(
instruction_rows: &[serde_json::Value],
) -> std::option::Option<std::string::String> {
for row in instruction_rows {
if row.get("status").and_then(serde_json::Value::as_str)
!= std::option::Option::Some("decoded")
{
continue;
}
if row.get("entryCode").and_then(serde_json::Value::as_str)
== std::option::Option::Some("unused_reserved")
{
continue;
}
return row
.get("instructionPath")
.and_then(serde_json::Value::as_str)
.map(|value| return value.to_string());
}
return std::option::Option::None;
}
fn append_runtime_ignored_trailing_data(
parameters: &mut serde_json::Map<std::string::String, serde_json::Value>,
bytes: &[u8],
decoded_byte_length: usize,
) {
let trailing_data = match bytes.get(decoded_byte_length..) {
std::option::Option::Some(value) if !value.is_empty() => value,
_ => return,
};
parameters.insert("trailingDataByteLength".to_string(), serde_json::json!(trailing_data.len()));
parameters.insert(
"trailingDataSha256".to_string(),
serde_json::json!(crate::solana_core_hash_bytes(trailing_data)),
);
parameters.insert(
"trailingDataPrefixHex".to_string(),
serde_json::json!(crate::solana_core_hexadecimal_prefix(
trailing_data,
trailing_data.len().min(16),
)),
);
parameters.insert(
"trailingDataSemantics".to_string(),
serde_json::json!("ignored_by_runtime_borsh_unchecked"),
);
}
fn entry_code(bytes: &[u8]) -> std::option::Option<&'static str> {
let tag = match bytes.first() {
std::option::Option::Some(value) => *value,
std::option::Option::None => return std::option::Option::None,
};
return match tag {
0 if bytes.len() == 1 => std::option::Option::Some("unused_reserved"),
0 if bytes.len() == 9 => std::option::Option::Some("request_units_deprecated"),
1 => std::option::Option::Some("request_heap_frame"),
2 => std::option::Option::Some("set_compute_unit_limit"),
3 => std::option::Option::Some("set_compute_unit_price"),
4 => std::option::Option::Some("set_loaded_accounts_data_size_limit"),
_ => std::option::Option::None,
};
}
fn coverage_entry(
entry_code: &str,
discriminator_hex: &str,
historical: bool,
) -> crate::DecoderCoverageDeclaration {
return crate::DecoderCoverageDeclaration {
program_id: kb_program_ids::COMPUTE_BUDGET_PROGRAM_ID.to_string(),
surface_code: std::option::Option::Some(
crate::SOLANA_CORE_COMPUTE_BUDGET_SURFACE_CODE.to_string(),
),
entry_kind: crate::DecoderCoverageEntryKind::Instruction,
entry_code: entry_code.to_string(),
discriminator_hex: std::option::Option::Some(discriminator_hex.to_string()),
historical,
};
}
#[cfg(test)]
mod tests {
use base64::Engine; // rust-rules: trait-import
fn replay_input(bytes: &[u8], transaction_failed: bool) -> crate::CoreInstructionReplayInput {
let encoded = base64::engine::general_purpose::STANDARD.encode(bytes);
let result = crate::CoreInstructionReplayInput::new(
"signature:0",
"signature",
42,
"0",
kb_program_ids::COMPUTE_BUDGET_PROGRAM_ID,
transaction_failed,
if transaction_failed {
std::option::Option::Some(serde_json::json!({"InstructionError": [0, "Custom"]}))
} else {
std::option::Option::None
},
serde_json::json!([]),
serde_json::json!([]),
std::option::Option::Some(serde_json::json!({"dataBase64": encoded})),
std::option::Option::Some("payload-hash".to_string()),
serde_json::json!([]),
serde_json::json!([]),
serde_json::json!([]),
serde_json::json!([]),
);
return match result {
std::result::Result::Ok(value) => value,
std::result::Result::Err(error) => panic!("replay input failed: {error}"),
};
}
fn replay_input_with_outer(
bytes: &[u8],
instruction_path: &str,
outer_instructions_json: serde_json::Value,
) -> crate::CoreInstructionReplayInput {
let encoded = base64::engine::general_purpose::STANDARD.encode(bytes);
let result = crate::CoreInstructionReplayInput::new(
"signature:0",
"signature",
42,
instruction_path,
kb_program_ids::COMPUTE_BUDGET_PROGRAM_ID,
false,
std::option::Option::None,
serde_json::json!([]),
serde_json::json!([]),
std::option::Option::Some(serde_json::json!({"dataBase64": encoded})),
std::option::Option::Some("payload-hash".to_string()),
outer_instructions_json,
serde_json::json!([]),
serde_json::json!([]),
serde_json::json!([]),
);
return match result {
std::result::Result::Ok(value) => value,
std::result::Result::Err(error) => panic!("replay input failed: {error}"),
};
}
fn outer_instruction(
index: u64,
path: &str,
program_id: &str,
bytes: &[u8],
) -> serde_json::Value {
let encoded = base64::engine::general_purpose::STANDARD.encode(bytes);
return serde_json::json!({
"instructionIndex": index,
"instructionPath": path,
"programId": program_id,
"payloadJson": {"dataBase64": encoded},
"payloadHash": format!("hash-{path}"),
});
}
#[test]
fn transaction_profile_aggregates_outer_compute_budget_instructions_once() {
let outer = serde_json::json!([
outer_instruction(
0,
"0",
kb_program_ids::COMPUTE_BUDGET_PROGRAM_ID,
&[2, 64, 66, 15, 0],
),
outer_instruction(1, "1", kb_program_ids::SYSTEM_PROGRAM_ID, &[2, 1, 0, 0, 0],),
outer_instruction(
2,
"2",
kb_program_ids::COMPUTE_BUDGET_PROGRAM_ID,
&[3, 7, 0, 0, 0, 0, 0, 0, 0],
),
outer_instruction(
3,
"3",
kb_program_ids::COMPUTE_BUDGET_PROGRAM_ID,
&[2, 128, 132, 30, 0],
),
]);
let result = crate::solana_core_compute_budget_decode(&replay_input_with_outer(
&[2, 64, 66, 15, 0],
"0",
outer,
));
assert_eq!(result.status, crate::DecoderOutcomeStatus::Decoded);
let profile = &result.observations[0].payload_json["parameters"]["transactionProfile"];
assert_eq!(profile["currentInstructionIsProfileEmitter"], true);
assert_eq!(profile["computeBudgetInstructionCount"], 3);
assert_eq!(profile["effectiveValues"]["set_compute_unit_limit"]["value"], 2_000_000);
assert_eq!(
profile["effectiveValues"]["set_compute_unit_limit"]["sourceInstructionPath"],
"3"
);
assert_eq!(profile["effectiveValues"]["set_compute_unit_price"]["value"], 7);
}
#[test]
fn official_current_encoder_matches_documented_layout() {
let instruction =
solana_compute_budget_interface::ComputeBudgetInstruction::SetComputeUnitPrice(7);
let bytes_result = borsh::to_vec(&instruction);
let bytes = match bytes_result {
std::result::Result::Ok(value) => value,
std::result::Result::Err(error) => {
panic!("compute budget fixture serialization failed: {error}")
},
};
assert_eq!(bytes, [3, 7, 0, 0, 0, 0, 0, 0, 0]);
let result =
crate::solana_core_compute_budget_decode(&replay_input(bytes.as_slice(), false));
assert_eq!(result.status, crate::DecoderOutcomeStatus::Decoded);
assert_eq!(
result.recognized_entry_code.as_deref(),
std::option::Option::Some("set_compute_unit_price")
);
}
#[test]
fn all_current_variants_decode_with_exact_numeric_values() {
let fixtures = [
(vec![1, 0, 0, 4, 0], "request_heap_frame"),
(vec![2, 255, 255, 255, 255], "set_compute_unit_limit"),
(vec![3, 255, 255, 255, 255, 255, 255, 255, 255], "set_compute_unit_price"),
(vec![4, 0, 0, 0, 1], "set_loaded_accounts_data_size_limit"),
];
for (bytes, expected) in fixtures {
let result =
crate::solana_core_compute_budget_decode(&replay_input(bytes.as_slice(), false));
assert_eq!(result.status, crate::DecoderOutcomeStatus::Decoded);
assert_eq!(
result.recognized_entry_code.as_deref(),
std::option::Option::Some(expected)
);
assert_eq!(result.observations.len(), 1);
}
}
#[test]
fn historical_request_units_is_decoded() {
let result = crate::solana_core_compute_budget_decode(&replay_input(
&[0, 64, 13, 3, 0, 7, 0, 0, 0],
false,
));
assert_eq!(result.status, crate::DecoderOutcomeStatus::Decoded);
assert_eq!(
result.recognized_entry_code.as_deref(),
std::option::Option::Some("request_units_deprecated")
);
assert_eq!(result.observations[0].payload_json["parameters"]["units"], 200_000);
assert_eq!(result.observations[0].payload_json["parameters"]["additionalFee"], 7);
}
#[test]
fn reserved_unused_tag_is_ignored_without_false_event() {
let result = crate::solana_core_compute_budget_decode(&replay_input(&[0], false));
assert_eq!(result.status, crate::DecoderOutcomeStatus::Ignored);
assert!(result.observations.is_empty());
}
#[test]
fn truncated_and_unknown_payloads_are_distinguished() {
let truncated = crate::solana_core_compute_budget_decode(&replay_input(&[2, 1, 2], false));
assert_eq!(truncated.status, crate::DecoderOutcomeStatus::Failed);
let unknown =
crate::solana_core_compute_budget_decode(&replay_input(&[255, 1, 2, 3], false));
assert_eq!(unknown.status, crate::DecoderOutcomeStatus::Unsupported);
}
#[test]
fn current_variants_accept_and_preserve_runtime_ignored_trailing_bytes() {
let result = crate::solana_core_compute_budget_decode(&replay_input(
&[2, 64, 13, 3, 0, 1, 2, 3, 4, 5, 6, 7],
true,
));
assert_eq!(result.status, crate::DecoderOutcomeStatus::Decoded);
assert_eq!(
result.recognized_entry_code.as_deref(),
std::option::Option::Some("set_compute_unit_limit")
);
assert_eq!(result.observations[0].payload_json["parameters"]["computeUnitLimit"], 200_000);
assert_eq!(result.observations[0].payload_json["parameters"]["trailingDataByteLength"], 7);
assert_eq!(
result.observations[0].payload_json["parameters"]["trailingDataPrefixHex"],
"01020304050607"
);
assert_eq!(
result.observations[0].payload_json["parameters"]["trailingDataSemantics"],
"ignored_by_runtime_borsh_unchecked"
);
assert!(result.observations[0].transaction_failed);
assert!(!result.observations[0].observation_committed);
}
#[test]
fn failed_transaction_event_is_not_committed() {
let result =
crate::solana_core_compute_budget_decode(&replay_input(&[2, 64, 13, 3, 0], true));
assert_eq!(result.status, crate::DecoderOutcomeStatus::Decoded);
assert!(result.observations[0].transaction_failed);
assert!(!result.observations[0].observation_committed);
assert_eq!(result.observations[0].payload_json["transactionSucceeded"], false);
}
#[test]
fn coverage_declares_current_and_historical_entries() {
let coverage = crate::solana_core_compute_budget_coverage();
assert_eq!(coverage.len(), 6);
assert!(coverage.iter().any(|entry| {
return entry.entry_code == "request_units_deprecated" && entry.historical;
}));
}
}

View File

@@ -0,0 +1,491 @@
// file: kb-lib/src/decoder/solana/core/config.rs
// version: 4
//! Generic bounded Config Program store decoding without inventing payload semantics.
const SOURCE: &str =
"solana-config-interface@2.0.0 plus solana-config-program@2.2.20 processor contract";
const MAX_CONFIG_KEYS: usize = 128;
#[derive(Clone, Debug, Eq, PartialEq)]
struct ParsedConfigPayload {
keys: std::vec::Vec<(std::string::String, bool)>,
data_offset: usize,
}
/// Returns declared Config Program instruction coverage.
pub(crate) fn config_coverage() -> std::vec::Vec<crate::DecoderCoverageDeclaration> {
return vec![crate::DecoderCoverageDeclaration {
program_id: kb_program_ids::CONFIG_PROGRAM_ID.to_string(),
surface_code: std::option::Option::Some(crate::SOLANA_CORE_CONFIG_SURFACE_CODE.to_string()),
entry_kind: crate::DecoderCoverageEntryKind::Instruction,
entry_code: "store".to_string(),
discriminator_hex: std::option::Option::None,
historical: false,
}];
}
/// Recognizes one generic Config Program store instruction.
pub(crate) fn config_recognize(
input: &crate::CoreInstructionReplayInput,
priority: u16,
) -> crate::DecoderRecognition {
let bytes_result = crate::solana_core_decode_instruction_data(input);
let bytes = match bytes_result {
std::result::Result::Ok(value) => value,
std::result::Result::Err(_error) => {
return crate::DecoderRecognition::compatible(
false,
priority,
std::option::Option::Some(crate::SOLANA_CORE_CONFIG_SURFACE_CODE.to_string()),
std::option::Option::Some("malformed_config_store".to_string()),
std::option::Option::None,
);
},
};
let exact = parse_payload(bytes.as_slice()).is_ok();
return crate::DecoderRecognition::compatible(
exact,
priority,
std::option::Option::Some(crate::SOLANA_CORE_CONFIG_SURFACE_CODE.to_string()),
std::option::Option::Some(if exact {
"store".to_string()
} else {
"malformed_config_store".to_string()
}),
crate::solana_core_hexadecimal_prefix(bytes.as_slice(), bytes.len().min(3)),
);
}
/// Decodes one generic Config Program store instruction.
pub(crate) fn config_decode(
input: &crate::CoreInstructionReplayInput,
) -> crate::DecoderExecutionResult {
let bytes_result = crate::solana_core_decode_instruction_data(input);
let bytes = match bytes_result {
std::result::Result::Ok(value) => value,
std::result::Result::Err(error) => {
return crate::solana_core_failed_result(
std::option::Option::Some("malformed_config_store"),
"config_payload_invalid",
error.to_string(),
);
},
};
let parsed_result = parse_payload(bytes.as_slice());
let parsed = match parsed_result {
std::result::Result::Ok(value) => value,
std::result::Result::Err(error) => {
return crate::solana_core_failed_result(
std::option::Option::Some("store"),
"config_keys_invalid",
error.to_string(),
);
},
};
let accounts_result = resolve_and_validate_accounts(input, &parsed);
let accounts = match accounts_result {
std::result::Result::Ok(value) => value,
std::result::Result::Err(error) => {
return crate::solana_core_failed_result(
std::option::Option::Some("store"),
"config_accounts_invalid",
error.to_string(),
);
},
};
let data = match bytes.get(parsed.data_offset..) {
std::option::Option::Some(value) => value,
std::option::Option::None => {
return crate::solana_core_failed_result(
std::option::Option::Some("store"),
"config_data_offset_invalid",
"Config data offset exceeds retained payload",
);
},
};
let keys_json = parsed
.keys
.iter()
.map(|(pubkey, signer)| {
return serde_json::json!({"pubkey": pubkey, "signer": signer});
})
.collect::<std::vec::Vec<_>>();
let signer_count = parsed.keys.iter().filter(|(_, signer)| return *signer).count();
return crate::solana_core_decoded_result(
input,
crate::SOLANA_CORE_CONFIG_SURFACE_CODE,
"store",
crate::EventFamily::Admin,
false,
accounts,
serde_json::json!({
"configuredKeys": keys_json,
"configuredKeyCount": parsed.keys.len(),
"configuredSignerCount": signer_count,
"configDataByteLength": data.len(),
"configDataSha256": crate::solana_core_hash_bytes(data),
"configDataPrefixHex": crate::solana_core_hexadecimal_prefix(data, data.len().min(16)),
"configDataSemantics": "opaque_program_specific",
}),
SOURCE,
);
}
fn parse_payload(bytes: &[u8]) -> kb_core::Result<ParsedConfigPayload> {
let length_result = read_compact_u16(bytes);
let (key_count, mut offset) = match length_result {
std::result::Result::Ok(value) => value,
std::result::Result::Err(error) => return std::result::Result::Err(error),
};
let key_count_usize = usize::from(key_count);
if key_count_usize > MAX_CONFIG_KEYS {
return std::result::Result::Err(kb_core::Error::invalid_state(format!(
"Config key count {key_count_usize} exceeds bounded maximum {}",
MAX_CONFIG_KEYS
)));
}
let required = offset.saturating_add(key_count_usize.saturating_mul(33));
if required > bytes.len() {
return std::result::Result::Err(kb_core::Error::invalid_state(format!(
"Config key vector requires {required} bytes but payload contains {}",
bytes.len()
)));
}
let mut keys = std::vec::Vec::with_capacity(key_count_usize);
for index in 0..key_count_usize {
let pubkey_end = offset.saturating_add(32);
let pubkey_bytes = match bytes.get(offset..pubkey_end) {
std::option::Option::Some(value) => value,
std::option::Option::None => {
return std::result::Result::Err(kb_core::Error::invalid_state(format!(
"Config key {index} pubkey is truncated"
)));
},
};
let signer_byte = match bytes.get(pubkey_end) {
std::option::Option::Some(value) => *value,
std::option::Option::None => {
return std::result::Result::Err(kb_core::Error::invalid_state(format!(
"Config key {index} signer flag is truncated"
)));
},
};
let signer = match signer_byte {
0 => false,
1 => true,
value => {
return std::result::Result::Err(kb_core::Error::invalid_state(format!(
"Config key {index} signer flag {value} is not a canonical bool"
)));
},
};
let pubkey = bs58::encode(pubkey_bytes).into_string();
if keys.iter().any(|(existing_pubkey, existing_signer)| {
return existing_pubkey == &pubkey && *existing_signer == signer;
}) {
return std::result::Result::Err(kb_core::Error::invalid_state(format!(
"Config key {index} duplicates an earlier key/signature pair"
)));
}
keys.push((pubkey, signer));
offset = pubkey_end.saturating_add(1);
}
return std::result::Result::Ok(ParsedConfigPayload { keys, data_offset: offset });
}
fn read_compact_u16(bytes: &[u8]) -> kb_core::Result<(u16, usize)> {
let first = match bytes.first() {
std::option::Option::Some(value) => *value,
std::option::Option::None => {
return std::result::Result::Err(kb_core::Error::invalid_state(
"Config compact-u16 key count is missing",
));
},
};
let mut value = u16::from(first & 0x7f);
if first & 0x80 == 0 {
return std::result::Result::Ok((value, 1));
}
let second = match bytes.get(1) {
std::option::Option::Some(value) => *value,
std::option::Option::None => {
return std::result::Result::Err(kb_core::Error::invalid_state(
"Config compact-u16 key count is truncated after one byte",
));
},
};
value |= u16::from(second & 0x7f) << 7;
if second & 0x80 == 0 {
if value < 128 {
return std::result::Result::Err(kb_core::Error::invalid_state(
"Config compact-u16 key count uses a non-canonical two-byte encoding",
));
}
return std::result::Result::Ok((value, 2));
}
let third = match bytes.get(2) {
std::option::Option::Some(value) => *value,
std::option::Option::None => {
return std::result::Result::Err(kb_core::Error::invalid_state(
"Config compact-u16 key count is truncated after two bytes",
));
},
};
if third & 0xfc != 0 {
return std::result::Result::Err(kb_core::Error::invalid_state(
"Config compact-u16 key count exceeds u16 or has a continuation bit",
));
}
value |= u16::from(third) << 14;
if value < 16_384 {
return std::result::Result::Err(kb_core::Error::invalid_state(
"Config compact-u16 key count uses a non-canonical three-byte encoding",
));
}
return std::result::Result::Ok((value, 3));
}
fn resolve_and_validate_accounts(
input: &crate::CoreInstructionReplayInput,
parsed: &ParsedConfigPayload,
) -> kb_core::Result<serde_json::Value> {
let instruction_accounts = match input.instruction_accounts_json.as_array() {
std::option::Option::Some(value) => value,
std::option::Option::None => {
return std::result::Result::Err(kb_core::Error::invalid_state(
"Config instruction accounts must be an array",
));
},
};
let config_key = match instruction_accounts
.first()
.and_then(|value| return value.get("accountKey"))
.and_then(serde_json::Value::as_str)
{
std::option::Option::Some(value) => value,
std::option::Option::None => {
return std::result::Result::Err(kb_core::Error::invalid_state(
"Config instruction requires a config account at position zero",
));
},
};
let required_signers = parsed
.keys
.iter()
.filter(|(pubkey, signer)| return *signer && pubkey != config_key)
.map(|(pubkey, _signer)| return pubkey.as_str())
.collect::<std::vec::Vec<_>>();
let mut roles = std::vec::Vec::with_capacity(required_signers.len().saturating_add(1));
roles.push(crate::SolanaCoreAccountRole::optional_signer("config_account", true));
for _signer in &required_signers {
roles.push(crate::SolanaCoreAccountRole::new("configured_signer", true, true));
}
let minimum_count = required_signers.len().saturating_add(1);
let accounts = match crate::solana_core_resolve_accounts(
input,
roles.as_slice(),
minimum_count,
std::option::Option::None,
) {
std::result::Result::Ok(value) => value,
std::result::Result::Err(error) => return std::result::Result::Err(error),
};
let resolved = match accounts.as_array() {
std::option::Option::Some(value) => value,
std::option::Option::None => {
return std::result::Result::Err(kb_core::Error::invalid_state(
"resolved Config accounts must be an array",
));
},
};
for (index, expected) in required_signers.iter().enumerate() {
let position = index.saturating_add(1);
let actual = resolved
.get(position)
.and_then(|value| return value.get("accountKey"))
.and_then(serde_json::Value::as_str);
if actual != std::option::Option::Some(*expected) {
return std::result::Result::Err(kb_core::Error::invalid_state(format!(
"Config signer account {position} must match encoded key {expected}"
)));
}
}
return std::result::Result::Ok(accounts);
}
#[cfg(test)]
mod tests {
use base64::Engine; // rust-rules: trait-import
fn encode_payload(keys: &[([u8; 32], bool)], data: &[u8]) -> std::vec::Vec<u8> {
let mut output = std::vec::Vec::new();
let count = keys.len();
if count < 128 {
output.push(count as u8);
} else {
panic!("test fixture key count exceeds one-byte compact encoding");
}
for (pubkey, signer) in keys {
output.extend_from_slice(pubkey);
output.push(u8::from(*signer));
}
output.extend_from_slice(data);
return output;
}
fn replay_input(
bytes: &[u8],
extra_accounts: &[std::string::String],
transaction_failed: bool,
) -> crate::CoreInstructionReplayInput {
let mut keys = vec!["ConfigAccount1111111111111111111111111111".to_string()];
keys.extend_from_slice(extra_accounts);
let account_keys = keys
.iter()
.enumerate()
.map(|(index, key)| {
return serde_json::json!({
"accountIndex": index,
"accountKey": key,
"source": "static",
"writable": true,
"signer": true,
"executable": false,
});
})
.collect::<std::vec::Vec<_>>();
let instruction_accounts = keys
.iter()
.enumerate()
.map(|(index, key)| {
return serde_json::json!({"accountIndex": index, "accountKey": key});
})
.collect::<std::vec::Vec<_>>();
let result = crate::CoreInstructionReplayInput::new(
"signature:0",
"signature",
42,
"0",
kb_program_ids::CONFIG_PROGRAM_ID,
transaction_failed,
if transaction_failed {
std::option::Option::Some(serde_json::json!({"InstructionError": [0, "Custom"]}))
} else {
std::option::Option::None
},
serde_json::Value::Array(account_keys),
serde_json::Value::Array(instruction_accounts),
std::option::Option::Some(serde_json::json!({
"dataBase64": base64::engine::general_purpose::STANDARD.encode(bytes),
})),
std::option::Option::Some("payload-hash".to_string()),
serde_json::json!([]),
serde_json::json!([]),
serde_json::json!([]),
serde_json::json!([]),
);
return match result {
std::result::Result::Ok(value) => value,
std::result::Result::Err(error) => panic!("Config replay input failed: {error}"),
};
}
#[test]
fn valid_generic_store_is_recognized_exactly() {
let payload = encode_payload(&[], &[1, 2, 3]);
let input = replay_input(payload.as_slice(), &[], false);
let recognition = crate::solana_core_config_recognize(&input, 100);
assert!(recognition.compatible);
assert!(recognition.exact);
assert_eq!(recognition.entry_code.as_deref(), std::option::Option::Some("store"));
let malformed = crate::solana_core_config_recognize(&replay_input(&[], &[], false), 100);
assert!(malformed.compatible);
assert!(!malformed.exact);
}
#[test]
fn empty_key_set_and_opaque_data_decode_without_invented_semantics() {
let payload = encode_payload(&[], &[1, 2, 3, 4]);
let result =
crate::solana_core_config_decode(&replay_input(payload.as_slice(), &[], false));
assert_eq!(result.status, crate::DecoderOutcomeStatus::Decoded);
assert_eq!(result.observations[0].payload_json["parameters"]["configuredKeyCount"], 0);
assert_eq!(result.observations[0].payload_json["parameters"]["configDataByteLength"], 4);
assert_eq!(
result.observations[0].payload_json["parameters"]["configDataSemantics"],
"opaque_program_specific"
);
}
#[test]
fn encoded_signer_keys_are_resolved_in_official_account_order() {
let signer_bytes = [7_u8; 32];
let signer = bs58::encode(signer_bytes).into_string();
let payload = encode_payload(&[([3_u8; 32], false), (signer_bytes, true)], &[9]);
let result =
crate::solana_core_config_decode(&replay_input(payload.as_slice(), &[signer], false));
assert_eq!(result.status, crate::DecoderOutcomeStatus::Decoded);
assert_eq!(result.observations[0].payload_json["parameters"]["configuredSignerCount"], 1);
assert_eq!(result.observations[0].payload_json["accounts"][1]["role"], "configured_signer");
}
#[test]
fn malformed_compact_length_keys_and_bool_fail_safely() {
for payload in [
vec![],
vec![0x80],
vec![1, 2, 3],
{
let mut value = vec![1];
value.extend_from_slice(&[0_u8; 32]);
value.push(2);
value
},
vec![0x80, 0],
{
let mut value = vec![2];
value.extend_from_slice(&[4_u8; 32]);
value.push(1);
value.extend_from_slice(&[4_u8; 32]);
value.push(1);
value
},
] {
let result =
crate::solana_core_config_decode(&replay_input(payload.as_slice(), &[], false));
assert_eq!(result.status, crate::DecoderOutcomeStatus::Failed);
}
}
#[test]
fn missing_or_mismatched_signer_accounts_fail() {
let signer_bytes = [8_u8; 32];
let payload = encode_payload(&[(signer_bytes, true)], &[]);
let missing =
crate::solana_core_config_decode(&replay_input(payload.as_slice(), &[], false));
assert_eq!(missing.status, crate::DecoderOutcomeStatus::Failed);
let mismatch = crate::solana_core_config_decode(&replay_input(
payload.as_slice(),
&[bs58::encode([9_u8; 32]).into_string()],
false,
));
assert_eq!(mismatch.status, crate::DecoderOutcomeStatus::Failed);
}
#[test]
fn failed_config_store_is_decoded_as_uncommitted_intent() {
let payload = encode_payload(&[], &[5]);
let result = crate::solana_core_config_decode(&replay_input(payload.as_slice(), &[], true));
assert_eq!(result.status, crate::DecoderOutcomeStatus::Decoded);
assert!(!result.observations[0].observation_committed);
}
#[test]
fn coverage_declares_one_generic_store_surface() {
let coverage = crate::solana_core_config_coverage();
assert_eq!(coverage.len(), 1);
assert_eq!(coverage[0].entry_code, "store");
assert!(coverage[0].discriminator_hex.is_none());
}
}

View File

@@ -0,0 +1,66 @@
// file: kb-lib/src/decoder/solana/core/constants.rs
// version: 13
//! Local constants for the Solana Core decoder component.
/// Stable protocol code shared by native Solana events.
pub(crate) const PROTOCOL_CODE: &str = "solana_native";
/// Stable System Program surface code.
pub(crate) const SYSTEM_SURFACE_CODE: &str = "solana_native_system";
/// Stable deprecated immutable BPF Loader surface code.
pub(crate) const BPF_LOADER_DEPRECATED_SURFACE_CODE: &str = "solana_native_bpf_loader_deprecated";
/// Stable immutable BPF Loader v2 surface code.
pub(crate) const BPF_LOADER_SURFACE_CODE: &str = "solana_native_bpf_loader";
/// Stable upgradeable BPF Loader surface code.
pub(crate) const BPF_LOADER_UPGRADEABLE_SURFACE_CODE: &str = "solana_native_bpf_loader_upgradeable";
/// Stable Loader v4 surface code.
pub(crate) const LOADER_V4_SURFACE_CODE: &str = "solana_native_loader_v4";
/// Stable Native Loader surface code.
pub(crate) const NATIVE_LOADER_SURFACE_CODE: &str = "solana_native_loader";
/// Stable Compute Budget surface code.
pub(crate) const COMPUTE_BUDGET_SURFACE_CODE: &str = "solana_native_compute_budget";
/// Stable Address Lookup Table surface code.
pub(crate) const ADDRESS_LOOKUP_TABLE_SURFACE_CODE: &str = "solana_native_address_lookup_table";
/// Stable Vote Program surface code.
pub(crate) const VOTE_SURFACE_CODE: &str = "solana_native_vote";
/// Stable Stake Program surface code.
pub(crate) const STAKE_SURFACE_CODE: &str = "solana_native_stake";
/// Stable Config Program surface code.
pub(crate) const CONFIG_SURFACE_CODE: &str = "solana_native_config";
/// Stable Feature Gate surface code.
pub(crate) const FEATURE_SURFACE_CODE: &str = "solana_native_feature";
/// Stable Ed25519 signature precompile surface code.
pub(crate) const ED25519_SURFACE_CODE: &str = "solana_native_ed25519";
/// Stable secp256k1 signature precompile surface code.
pub(crate) const SECP256K1_SURFACE_CODE: &str = "solana_native_secp256k1";
/// Stable secp256r1 signature precompile surface code.
pub(crate) const SECP256R1_SURFACE_CODE: &str = "solana_native_secp256r1";
/// Stable native ZK ElGamal Proof surface code.
pub(crate) const ZK_ELGAMAL_PROOF_SURFACE_CODE: &str = "solana_native_zk_elgamal_proof";
/// Stable historical ZK Token Proof surface code.
pub(crate) const ZK_TOKEN_PROOF_SURFACE_CODE: &str = "solana_native_zk_token_proof";
/// Byte length shared by compact Ed25519, secp256k1 and secp256r1 signatures.
pub(crate) const SIGNATURE_BYTES: usize = 64;
/// Byte length of an Ed25519 public key.
pub(crate) const ED25519_PUBLIC_KEY_BYTES: usize = 32;
/// Byte length of a compressed secp256r1 public key.
pub(crate) const SECP256R1_PUBLIC_KEY_BYTES: usize = 33;
/// Byte length of a secp256k1 Ethereum address.
pub(crate) const SECP256K1_ETHEREUM_ADDRESS_BYTES: usize = 20;
/// Byte length of one Ed25519 or secp256r1 offsets entry.
pub(crate) const U16_OFFSETS_BYTES: usize = 14;
/// Byte length of one secp256k1 offsets entry.
pub(crate) const U8_OFFSETS_BYTES: usize = 11;
/// Stable Slashing Program decoder surface code.
pub(crate) const SLASHING_SURFACE_CODE: &str = "solana_native_slashing";
/// Runtime maximum number of secp256r1 signatures in one precompile instruction.
pub(crate) const SECP256R1_MAX_SIGNATURES: usize = 8;
/// Maximum number of component bytes retained as a hexadecimal event prefix.
pub(crate) const PRECOMPILE_COMPONENT_PREFIX_BYTES: usize = 16;
/// Maximum retained native instruction payload accepted by the first decoder phase.
pub(crate) const MAX_NATIVE_INSTRUCTION_PAYLOAD_BYTES: usize = 4_096;
/// Current native event payload contract version.
pub(crate) const NATIVE_EVENT_VERSION: u32 = 1;
/// Canonical tracing target for the Solana Core decoder component.
pub(crate) const TRACING_TARGET: &str = "kb-lib.decoder.solana.core";

View File

@@ -0,0 +1,605 @@
// file: kb-lib/src/decoder/solana/core/decoder.rs
// version: 24
//! Runtime-native Solana program classifier for the common decode pipeline.
const NATIVE_SURFACES: &[crate::DecoderSurface] = &[
crate::DecoderSurface {
program_id: kb_program_ids::ADDRESS_LOOKUP_TABLE_PROGRAM_ID,
surface_code: "solana_native_address_lookup_table",
priority: 100,
},
crate::DecoderSurface {
program_id: kb_program_ids::BPF_LOADER_DEPRECATED_PROGRAM_ID,
surface_code: "solana_native_bpf_loader_deprecated",
priority: 100,
},
crate::DecoderSurface {
program_id: kb_program_ids::BPF_LOADER_PROGRAM_ID,
surface_code: "solana_native_bpf_loader",
priority: 100,
},
crate::DecoderSurface {
program_id: kb_program_ids::BPF_LOADER_UPGRADEABLE_PROGRAM_ID,
surface_code: "solana_native_bpf_loader_upgradeable",
priority: 100,
},
crate::DecoderSurface {
program_id: kb_program_ids::COMPUTE_BUDGET_PROGRAM_ID,
surface_code: "solana_native_compute_budget",
priority: 100,
},
crate::DecoderSurface {
program_id: kb_program_ids::CONFIG_PROGRAM_ID,
surface_code: "solana_native_config",
priority: 100,
},
crate::DecoderSurface {
program_id: kb_program_ids::ED25519_PROGRAM_ID,
surface_code: "solana_native_ed25519",
priority: 100,
},
crate::DecoderSurface {
program_id: kb_program_ids::FEATURE_PROGRAM_ID,
surface_code: "solana_native_feature",
priority: 100,
},
crate::DecoderSurface {
program_id: kb_program_ids::LOADER_V4_PROGRAM_ID,
surface_code: "solana_native_loader_v4",
priority: 100,
},
crate::DecoderSurface {
program_id: kb_program_ids::NATIVE_LOADER_PROGRAM_ID,
surface_code: "solana_native_loader",
priority: 100,
},
crate::DecoderSurface {
program_id: kb_program_ids::SECP256K1_PROGRAM_ID,
surface_code: "solana_native_secp256k1",
priority: 100,
},
crate::DecoderSurface {
program_id: kb_program_ids::SECP256R1_PROGRAM_ID,
surface_code: "solana_native_secp256r1",
priority: 100,
},
crate::DecoderSurface {
program_id: kb_program_ids::SLASHING_PROGRAM_ID,
surface_code: "solana_native_slashing",
priority: 100,
},
crate::DecoderSurface {
program_id: kb_program_ids::STAKE_PROGRAM_ID,
surface_code: "solana_native_stake",
priority: 100,
},
crate::DecoderSurface {
program_id: kb_program_ids::SYSTEM_PROGRAM_ID,
surface_code: "solana_native_system",
priority: 100,
},
crate::DecoderSurface {
program_id: kb_program_ids::VOTE_PROGRAM_ID,
surface_code: "solana_native_vote",
priority: 100,
},
crate::DecoderSurface {
program_id: kb_program_ids::ZK_ELGAMAL_PROOF_PROGRAM_ID,
surface_code: "solana_native_zk_elgamal_proof",
priority: 100,
},
crate::DecoderSurface {
program_id: kb_program_ids::ZK_TOKEN_PROOF_PROGRAM_ID,
surface_code: "solana_native_zk_token_proof",
priority: 100,
},
];
const LEGACY_PROGRAM_IDS: &[&str] = &[
kb_program_ids::ADDRESS_LOOKUP_TABLE_PROGRAM_ID,
kb_program_ids::BPF_LOADER_DEPRECATED_PROGRAM_ID,
kb_program_ids::BPF_LOADER_PROGRAM_ID,
kb_program_ids::BPF_LOADER_UPGRADEABLE_PROGRAM_ID,
kb_program_ids::COMPUTE_BUDGET_PROGRAM_ID,
kb_program_ids::CONFIG_PROGRAM_ID,
kb_program_ids::ED25519_PROGRAM_ID,
kb_program_ids::FEATURE_PROGRAM_ID,
kb_program_ids::LOADER_V4_PROGRAM_ID,
kb_program_ids::NATIVE_LOADER_PROGRAM_ID,
kb_program_ids::SECP256K1_PROGRAM_ID,
kb_program_ids::SECP256R1_PROGRAM_ID,
kb_program_ids::SLASHING_PROGRAM_ID,
kb_program_ids::STAKE_PROGRAM_ID,
kb_program_ids::SYSTEM_PROGRAM_ID,
kb_program_ids::VOTE_PROGRAM_ID,
kb_program_ids::ZK_ELGAMAL_PROOF_PROGRAM_ID,
kb_program_ids::ZK_TOKEN_PROOF_PROGRAM_ID,
];
/// Runtime-native Solana decoder with phased maximal instruction coverage.
#[derive(Clone, Debug, Default)]
pub struct SolanaCoreDecoder;
impl crate::ProtocolDecoder for crate::SolanaCoreDecoder {
fn decoder_name(&self) -> &'static str {
return "kb_decoder_solana_core";
}
fn decoder_version(&self) -> &'static str {
return env!("CARGO_PKG_VERSION");
}
fn program_ids(&self) -> &'static [&'static str] {
return LEGACY_PROGRAM_IDS;
}
fn supports_observation(
&self,
observation: &crate::ProgramObservation,
) -> crate::DecoderSupport {
return if crate::ProtocolDecoder::handles_program_id(self, &observation.program_id) {
crate::DecoderSupport::Maybe
} else {
crate::DecoderSupport::No
};
}
fn decode_observation(
&self,
_observation: &crate::ProgramObservation,
) -> kb_core::Result<std::vec::Vec<crate::DecodedProtocolEvent>> {
tracing::debug!(target: crate::SOLANA_CORE_TRACING_TARGET, "legacy native decoder adapter classified an observation without maximal decoding");
return std::result::Result::Ok(std::vec::Vec::new());
}
}
impl crate::InstructionDecoder for crate::SolanaCoreDecoder {
fn identity(&self) -> crate::DecoderIdentity {
return crate::DecoderIdentity {
name: "solana_native_classifier".to_string(),
version: env!("CARGO_PKG_VERSION").to_string(),
};
}
fn surfaces(&self) -> &'static [crate::DecoderSurface] {
return NATIVE_SURFACES;
}
fn coverage(&self) -> std::vec::Vec<crate::DecoderCoverageDeclaration> {
let mut coverage = crate::solana_core_system_coverage();
coverage.extend(crate::solana_core_compute_budget_coverage());
coverage.extend(crate::solana_core_config_coverage());
coverage.extend(crate::solana_core_feature_coverage());
coverage.extend(crate::solana_core_vote_coverage());
coverage.extend(crate::solana_core_stake_coverage());
coverage.extend(crate::solana_core_address_lookup_table_coverage());
coverage.extend(crate::solana_core_loaders_coverage());
coverage.extend(crate::solana_core_precompiles_coverage());
coverage.extend(crate::solana_core_slashing_coverage());
coverage.extend(crate::solana_core_zk_elgamal_coverage());
coverage.extend(crate::solana_core_zk_token_proof_coverage());
for surface in NATIVE_SURFACES {
if surface.program_id == kb_program_ids::SYSTEM_PROGRAM_ID
|| surface.program_id == kb_program_ids::COMPUTE_BUDGET_PROGRAM_ID
|| surface.program_id == kb_program_ids::ADDRESS_LOOKUP_TABLE_PROGRAM_ID
|| surface.program_id == kb_program_ids::CONFIG_PROGRAM_ID
|| surface.program_id == kb_program_ids::FEATURE_PROGRAM_ID
|| surface.program_id == kb_program_ids::VOTE_PROGRAM_ID
|| surface.program_id == kb_program_ids::STAKE_PROGRAM_ID
|| is_signature_precompile(surface.program_id)
|| surface.program_id == kb_program_ids::SLASHING_PROGRAM_ID
|| surface.program_id == kb_program_ids::ZK_ELGAMAL_PROOF_PROGRAM_ID
|| surface.program_id == kb_program_ids::ZK_TOKEN_PROOF_PROGRAM_ID
|| is_loader_program(surface.program_id)
{
continue;
}
coverage.push(crate::DecoderCoverageDeclaration {
program_id: surface.program_id.to_string(),
surface_code: std::option::Option::Some(surface.surface_code.to_string()),
entry_kind: crate::DecoderCoverageEntryKind::Instruction,
entry_code: "unclassified_native_instruction".to_string(),
discriminator_hex: std::option::Option::None,
historical: is_historical_native_program(surface.program_id),
});
}
return coverage;
}
fn recognize(&self, input: &crate::CoreInstructionReplayInput) -> crate::DecoderRecognition {
tracing::debug!(target: crate::SOLANA_CORE_TRACING_TARGET, action = "recognize", signature = %input.signature, slot = input.slot, instruction_path = %input.instruction_path, program_id = %input.program_id, input_key = %input.replay_input_key, transaction_failed = input.transaction_failed, "recognize native Solana contextual instruction");
let surface = NATIVE_SURFACES
.iter()
.find(|surface| return surface.program_id == input.program_id);
let surface = match surface {
std::option::Option::Some(value) => value,
std::option::Option::None => {
tracing::debug!(target: crate::SOLANA_CORE_TRACING_TARGET, action = "recognize", signature = %input.signature, instruction_path = %input.instruction_path, program_id = %input.program_id, compatible = false, "native Solana decoder does not support program id");
return crate::DecoderRecognition::incompatible();
},
};
let recognition = if input.program_id == kb_program_ids::SYSTEM_PROGRAM_ID {
crate::solana_core_system_recognize(input, surface.priority)
} else if input.program_id == kb_program_ids::COMPUTE_BUDGET_PROGRAM_ID {
crate::solana_core_compute_budget_recognize(input, surface.priority)
} else if input.program_id == kb_program_ids::ADDRESS_LOOKUP_TABLE_PROGRAM_ID {
crate::solana_core_address_lookup_table_recognize(input, surface.priority)
} else if input.program_id == kb_program_ids::CONFIG_PROGRAM_ID {
crate::solana_core_config_recognize(input, surface.priority)
} else if input.program_id == kb_program_ids::FEATURE_PROGRAM_ID {
crate::solana_core_feature_recognize(input, surface.priority)
} else if input.program_id == kb_program_ids::VOTE_PROGRAM_ID {
crate::solana_core_vote_recognize(input, surface.priority)
} else if input.program_id == kb_program_ids::STAKE_PROGRAM_ID {
crate::solana_core_stake_recognize(input, surface.priority)
} else if input.program_id == kb_program_ids::SLASHING_PROGRAM_ID {
crate::solana_core_slashing_recognize(input, surface.priority)
} else if is_signature_precompile(input.program_id.as_str()) {
crate::solana_core_precompiles_recognize(input, surface.priority)
} else if input.program_id == kb_program_ids::ZK_ELGAMAL_PROOF_PROGRAM_ID {
crate::solana_core_zk_elgamal_recognize(input, surface.priority)
} else if input.program_id == kb_program_ids::ZK_TOKEN_PROOF_PROGRAM_ID {
crate::solana_core_zk_token_proof_recognize(input, surface.priority)
} else if is_loader_program(input.program_id.as_str()) {
crate::solana_core_loaders_recognize(input, surface.priority)
} else {
crate::DecoderRecognition::compatible(
false,
surface.priority,
std::option::Option::Some(surface.surface_code.to_string()),
std::option::Option::Some("unclassified_native_instruction".to_string()),
crate::discriminator_8_hex(input),
)
};
tracing::debug!(target: crate::SOLANA_CORE_TRACING_TARGET, action = "recognize", signature = %input.signature, instruction_path = %input.instruction_path, program_id = %input.program_id, surface_code = surface.surface_code, recognition = ?recognition, compatible = true, "native Solana decoder recognized contextual instruction");
return recognition;
}
fn decode(&self, input: &crate::CoreInstructionReplayInput) -> crate::DecoderExecutionResult {
let result = if input.program_id == kb_program_ids::SYSTEM_PROGRAM_ID {
crate::solana_core_system_decode(input)
} else if input.program_id == kb_program_ids::COMPUTE_BUDGET_PROGRAM_ID {
crate::solana_core_compute_budget_decode(input)
} else if input.program_id == kb_program_ids::ADDRESS_LOOKUP_TABLE_PROGRAM_ID {
crate::solana_core_address_lookup_table_decode(input)
} else if input.program_id == kb_program_ids::CONFIG_PROGRAM_ID {
crate::solana_core_config_decode(input)
} else if input.program_id == kb_program_ids::FEATURE_PROGRAM_ID {
crate::solana_core_feature_decode(input)
} else if input.program_id == kb_program_ids::VOTE_PROGRAM_ID {
crate::solana_core_vote_decode(input)
} else if input.program_id == kb_program_ids::STAKE_PROGRAM_ID {
crate::solana_core_stake_decode(input)
} else if input.program_id == kb_program_ids::SLASHING_PROGRAM_ID {
crate::solana_core_slashing_decode(input)
} else if is_signature_precompile(input.program_id.as_str()) {
crate::solana_core_precompiles_decode(input)
} else if input.program_id == kb_program_ids::ZK_ELGAMAL_PROOF_PROGRAM_ID {
crate::solana_core_zk_elgamal_decode(input)
} else if input.program_id == kb_program_ids::ZK_TOKEN_PROOF_PROGRAM_ID {
crate::solana_core_zk_token_proof_decode(input)
} else if is_loader_program(input.program_id.as_str()) {
crate::solana_core_loaders_decode(input)
} else {
crate::DecoderExecutionResult::unsupported(std::option::Option::Some(
"unclassified_native_instruction".to_string(),
))
};
if matches!(
result.status,
crate::DecoderOutcomeStatus::Failed | crate::DecoderOutcomeStatus::Unsupported
) {
tracing::error!(
target: crate::SOLANA_CORE_TRACING_TARGET,
action = "decode_failure",
signature = %input.signature,
slot = input.slot,
instruction_path = %input.instruction_path,
program_id = %input.program_id,
processor_name = "solana_native_classifier",
processor_version = env!("CARGO_PKG_VERSION"),
input_key = %input.replay_input_key,
payload_hash = ?input.instruction_payload_hash,
payload_length = ?crate::solana_core_decoded_payload_length(input),
transaction_failed = input.transaction_failed,
transaction_error = ?input.transaction_err_json,
result_status = ?result.status,
recognized_entry_code = ?result.recognized_entry_code,
diagnostics = ?result.diagnostics,
"native instruction was not decoded successfully"
);
}
tracing::debug!(target: crate::SOLANA_CORE_TRACING_TARGET, action = "decode", signature = %input.signature, slot = input.slot, instruction_path = %input.instruction_path, program_id = %input.program_id, input_key = %input.replay_input_key, payload_hash = ?input.instruction_payload_hash, transaction_failed = input.transaction_failed, transaction_error = ?input.transaction_err_json, result_status = ?result.status, recognized_entry_code = ?result.recognized_entry_code, observation_count = result.observations.len(), diagnostic_count = result.diagnostics.len(), "native instruction decode completed");
return result;
}
}
fn is_signature_precompile(program_id: &str) -> bool {
return program_id == kb_program_ids::ED25519_PROGRAM_ID
|| program_id == kb_program_ids::SECP256K1_PROGRAM_ID
|| program_id == kb_program_ids::SECP256R1_PROGRAM_ID;
}
fn is_loader_program(program_id: &str) -> bool {
return program_id == kb_program_ids::BPF_LOADER_DEPRECATED_PROGRAM_ID
|| program_id == kb_program_ids::BPF_LOADER_PROGRAM_ID
|| program_id == kb_program_ids::BPF_LOADER_UPGRADEABLE_PROGRAM_ID
|| program_id == kb_program_ids::LOADER_V4_PROGRAM_ID
|| program_id == kb_program_ids::NATIVE_LOADER_PROGRAM_ID;
}
fn is_historical_native_program(program_id: &str) -> bool {
return program_id == kb_program_ids::BPF_LOADER_DEPRECATED_PROGRAM_ID
|| program_id == kb_program_ids::BPF_LOADER_PROGRAM_ID
|| program_id == kb_program_ids::ZK_TOKEN_PROOF_PROGRAM_ID;
}
#[cfg(test)]
mod tests {
fn replay_input(program_id: &str) -> crate::CoreInstructionReplayInput {
let result = crate::CoreInstructionReplayInput::new(
"signature:0",
"signature",
42,
"0",
program_id,
false,
std::option::Option::None,
serde_json::json!([]),
serde_json::json!([]),
std::option::Option::Some(serde_json::json!({"dataBase64": "AQIDBAUGBwg="})),
std::option::Option::Some("payload-hash".to_string()),
serde_json::json!([]),
serde_json::json!([]),
serde_json::json!([]),
serde_json::json!([]),
);
return match result {
std::result::Result::Ok(value) => value,
std::result::Result::Err(error) => panic!("replay input failed: {error}"),
};
}
#[test]
fn vote_program_is_dispatched_without_false_decode_for_unknown_tag() {
let decoder = crate::SolanaCoreDecoder;
let input = replay_input(kb_program_ids::VOTE_PROGRAM_ID);
let recognition = crate::InstructionDecoder::recognize(&decoder, &input);
assert!(recognition.compatible);
assert_eq!(
recognition.surface_code.as_deref(),
std::option::Option::Some("solana_native_vote")
);
assert_eq!(
recognition.entry_code.as_deref(),
std::option::Option::Some("unknown_vote_instruction")
);
let result = crate::InstructionDecoder::decode(&decoder, &input);
assert_eq!(result.status, crate::DecoderOutcomeStatus::Unsupported);
assert!(result.observations.is_empty());
}
#[test]
fn stake_program_is_dispatched_without_false_decode_for_unknown_tag() {
let decoder = crate::SolanaCoreDecoder;
let input = replay_input(kb_program_ids::STAKE_PROGRAM_ID);
let recognition = crate::InstructionDecoder::recognize(&decoder, &input);
assert!(recognition.compatible);
assert_eq!(
recognition.surface_code.as_deref(),
std::option::Option::Some("solana_native_stake")
);
assert_eq!(
recognition.entry_code.as_deref(),
std::option::Option::Some("unknown_stake_instruction")
);
let result = crate::InstructionDecoder::decode(&decoder, &input);
assert_eq!(result.status, crate::DecoderOutcomeStatus::Unsupported);
assert!(result.observations.is_empty());
}
#[test]
fn zk_token_proof_program_dispatches_to_current_noop_fallback() {
let decoder = crate::SolanaCoreDecoder;
let input = replay_input(kb_program_ids::ZK_TOKEN_PROOF_PROGRAM_ID);
let recognition = crate::InstructionDecoder::recognize(&decoder, &input);
assert!(recognition.compatible);
assert_eq!(
recognition.surface_code.as_deref(),
std::option::Option::Some("solana_native_zk_token_proof")
);
assert_eq!(
recognition.entry_code.as_deref(),
std::option::Option::Some("current_runtime_noop_invocation")
);
let result = crate::InstructionDecoder::decode(&decoder, &input);
assert_eq!(result.status, crate::DecoderOutcomeStatus::Decoded);
assert_eq!(
result.recognized_entry_code.as_deref(),
std::option::Option::Some("current_runtime_noop_invocation")
);
assert_eq!(result.observations.len(), 1);
}
#[test]
fn every_native_registry_program_has_one_surface() {
let decoder = crate::SolanaCoreDecoder;
let surfaces = crate::InstructionDecoder::surfaces(&decoder);
assert_eq!(surfaces.len(), kb_program_ids::native_program_ids().len());
for entry in kb_program_ids::native_program_ids() {
assert!(surfaces.iter().any(|surface| return surface.program_id == entry.program_id()));
}
}
#[test]
fn every_native_surface_has_declared_coverage_without_fallback_entries() {
let decoder = crate::SolanaCoreDecoder;
let surfaces = crate::InstructionDecoder::surfaces(&decoder);
let coverage = crate::InstructionDecoder::coverage(&decoder);
for surface in surfaces {
assert!(coverage.iter().any(|entry| {
return entry.program_id == surface.program_id
&& entry.surface_code.as_deref()
== std::option::Option::Some(surface.surface_code);
}));
}
assert!(!coverage.iter().any(|entry| {
return entry.entry_code == "unclassified_native_instruction";
}));
}
#[test]
fn coverage_entries_are_unique_and_reference_declared_surfaces() {
let decoder = crate::SolanaCoreDecoder;
let surfaces = crate::InstructionDecoder::surfaces(&decoder);
let coverage = crate::InstructionDecoder::coverage(&decoder);
let mut identities = std::collections::BTreeSet::new();
for entry in coverage {
let surface_code = match entry.surface_code.as_deref() {
std::option::Option::Some(value) => value,
std::option::Option::None => panic!("native coverage entry has no surface code"),
};
assert!(surfaces.iter().any(|surface| {
return surface.program_id == entry.program_id
&& surface.surface_code == surface_code;
}));
assert!(identities.insert(format!(
"{}:{surface_code}:{}:{:?}",
entry.program_id, entry.entry_code, entry.entry_kind
)));
}
}
#[test]
fn slashing_program_is_dispatched_to_exact_decoder() {
let decoder = crate::SolanaCoreDecoder;
let input = replay_input(kb_program_ids::SLASHING_PROGRAM_ID);
let recognition = crate::InstructionDecoder::recognize(&decoder, &input);
assert!(recognition.compatible);
assert_eq!(
recognition.surface_code.as_deref(),
std::option::Option::Some("solana_native_slashing")
);
assert_eq!(
recognition.entry_code.as_deref(),
std::option::Option::Some("duplicate_block_proof")
);
}
#[test]
fn deprecated_stake_config_account_is_not_an_instruction_surface() {
let decoder = crate::SolanaCoreDecoder;
let surfaces = crate::InstructionDecoder::surfaces(&decoder);
assert!(!surfaces.iter().any(|surface| {
return surface.program_id == kb_program_ids::STAKE_CONFIG_ACCOUNT_ID;
}));
let input = replay_input(kb_program_ids::STAKE_CONFIG_ACCOUNT_ID);
let recognition = crate::InstructionDecoder::recognize(&decoder, &input);
assert!(!recognition.compatible);
}
#[test]
fn native_decoder_matrix_matches_registry_surfaces_and_coverage() {
let raw = include_str!("../../../../../docs/NATIVE_SOLANA_DECODER_MATRIX.json");
let parsed = match serde_json::from_str::<serde_json::Value>(raw) {
std::result::Result::Ok(value) => value,
std::result::Result::Err(error) => {
panic!("native decoder matrix parsing failed: {error}")
},
};
let matrix_surfaces = match parsed.get("surfaces").and_then(serde_json::Value::as_array) {
std::option::Option::Some(value) => value,
std::option::Option::None => panic!("native decoder matrix surfaces missing"),
};
assert_eq!(matrix_surfaces.len(), 18);
let declared_surface_count =
parsed.get("surface_count").and_then(serde_json::Value::as_u64);
assert_eq!(declared_surface_count, std::option::Option::Some(18));
let decoder = crate::SolanaCoreDecoder;
let compiled_surfaces = crate::InstructionDecoder::surfaces(&decoder);
let compiled_surface_map = compiled_surfaces
.iter()
.map(|surface| {
return (surface.program_id.to_string(), surface.surface_code.to_string());
})
.collect::<std::collections::BTreeMap<_, _>>();
assert_eq!(compiled_surface_map.len(), 18);
let native_registry = kb_program_ids::native_program_ids()
.iter()
.map(|entry| return entry.program_id().to_string())
.collect::<std::collections::BTreeSet<_>>();
assert_eq!(native_registry.len(), 18);
let coverage = crate::InstructionDecoder::coverage(&decoder);
let mut compiled_coverage_counts = std::collections::BTreeMap::new();
for entry in &coverage {
let count = compiled_coverage_counts.entry(entry.program_id.clone()).or_insert(0_usize);
*count = count.saturating_add(1);
}
let mut matrix_surface_map = std::collections::BTreeMap::new();
let mut matrix_program_ids = std::collections::BTreeSet::new();
let mut matrix_coverage_total = 0_usize;
for surface in matrix_surfaces {
let surface_code = match surface.get("surface_code").and_then(serde_json::Value::as_str)
{
std::option::Option::Some(value) if !value.is_empty() => value,
_ => panic!("native decoder matrix surface_code missing"),
};
let program_id = match surface.get("program_id").and_then(serde_json::Value::as_str) {
std::option::Option::Some(value) if !value.is_empty() => value,
_ => panic!("native decoder matrix program_id missing"),
};
for field in ["runtime_status", "source_contract", "completeness_test"] {
let value = surface.get(field).and_then(serde_json::Value::as_str);
assert!(matches!(value, std::option::Option::Some(text) if !text.is_empty()));
}
let expected_count_u64 = match surface
.get("expected_coverage_entries")
.and_then(serde_json::Value::as_u64)
{
std::option::Option::Some(value) => value,
std::option::Option::None => {
panic!("native decoder matrix expected coverage count missing")
},
};
let expected_count = match usize::try_from(expected_count_u64) {
std::result::Result::Ok(value) => value,
std::result::Result::Err(error) => {
panic!("native decoder matrix coverage count invalid: {error}")
},
};
assert!(expected_count > 0);
assert!(matrix_program_ids.insert(program_id.to_string()));
assert!(
matrix_surface_map
.insert(program_id.to_string(), surface_code.to_string())
.is_none()
);
assert_eq!(
compiled_coverage_counts.get(program_id).copied(),
std::option::Option::Some(expected_count)
);
matrix_coverage_total = matrix_coverage_total.saturating_add(expected_count);
}
let declared_coverage_count =
parsed.get("coverage_entry_count").and_then(serde_json::Value::as_u64);
assert_eq!(declared_coverage_count, std::option::Option::Some(121));
assert_eq!(matrix_coverage_total, 121);
assert_eq!(coverage.len(), matrix_coverage_total);
assert_eq!(matrix_surface_map, compiled_surface_map);
assert_eq!(matrix_program_ids, native_registry);
}
#[test]
fn unrelated_program_is_not_dispatched() {
let decoder = crate::SolanaCoreDecoder;
let input = replay_input(kb_program_ids::SPL_TOKEN_PROGRAM_ID);
let recognition = crate::InstructionDecoder::recognize(&decoder, &input);
assert!(!recognition.compatible);
}
}

View File

@@ -0,0 +1,101 @@
// file: kb-lib/src/decoder/solana/core/event.rs
// version: 3
//! Stable native event and decoder result builders.
/// Builds one exact decoded native observation.
pub(crate) fn decoded_result(
input: &crate::CoreInstructionReplayInput,
surface_code: &str,
entry_code: &str,
event_family: crate::EventFamily,
historical: bool,
accounts: serde_json::Value,
parameters: serde_json::Value,
source_evidence: &str,
) -> crate::DecoderExecutionResult {
let event = crate::DecodedProtocolEvent {
signature: crate::Signature(input.signature.clone()),
slot: crate::Slot(input.slot),
instruction_path: crate::InstructionPath(input.instruction_path.clone()),
program_id: crate::ProgramId(input.program_id.clone()),
protocol_code: crate::ProtocolCode(crate::SOLANA_CORE_PROTOCOL_CODE.to_string()),
surface_code: crate::SurfaceCode(surface_code.to_string()),
event_code: crate::EventCode(format!("{surface_code}.{entry_code}")),
event_name: crate::EventName(entry_code.to_string()),
event_family,
source_kind: if input.instruction_path.contains('/') {
crate::EventSourceKind::InnerInstruction
} else {
crate::EventSourceKind::Instruction
},
confidence: crate::DecoderConfidence::ManualExact,
};
let payload_hash = crate::solana_core_payload_hash(input);
let observation = crate::DecodedObservation {
event_key: format!("{entry_code}:0"),
event,
payload_json: serde_json::json!({
"eventVersion": crate::SOLANA_CORE_NATIVE_EVENT_VERSION,
"programId": input.program_id,
"surfaceCode": surface_code,
"entryCode": entry_code,
"historical": historical,
"instructionPath": input.instruction_path,
"transactionSucceeded": !input.transaction_failed,
"payloadHash": payload_hash,
"accounts": accounts,
"parameters": parameters,
}),
transaction_failed: input.transaction_failed,
transaction_error: input.transaction_err_json.clone(),
observation_committed: !input.transaction_failed,
proof: crate::DecoderProof {
kind: crate::DecoderProofKind::Manual,
confidence: crate::DecoderConfidence::ManualExact,
evidence: vec![source_evidence.to_string(), format!("payload_sha256:{payload_hash}")],
},
};
return crate::DecoderExecutionResult {
status: crate::DecoderOutcomeStatus::Decoded,
recognized_entry_code: std::option::Option::Some(entry_code.to_string()),
observations: vec![observation],
diagnostics: std::vec::Vec::new(),
};
}
/// Builds one failed native decode result.
pub(crate) fn failed_result(
entry_code: std::option::Option<&str>,
code: &str,
message: impl std::convert::Into<std::string::String>,
) -> crate::DecoderExecutionResult {
return crate::DecoderExecutionResult {
status: crate::DecoderOutcomeStatus::Failed,
recognized_entry_code: entry_code.map(str::to_string),
observations: std::vec::Vec::new(),
diagnostics: vec![crate::DecoderDiagnostic {
code: code.to_string(),
message: message.into(),
retriable: false,
}],
};
}
/// Builds one unsupported native decode result with a bounded diagnostic.
pub(crate) fn unsupported_result(
entry_code: &str,
code: &str,
message: impl std::convert::Into<std::string::String>,
) -> crate::DecoderExecutionResult {
return crate::DecoderExecutionResult {
status: crate::DecoderOutcomeStatus::Unsupported,
recognized_entry_code: std::option::Option::Some(entry_code.to_string()),
observations: std::vec::Vec::new(),
diagnostics: vec![crate::DecoderDiagnostic {
code: code.to_string(),
message: message.into(),
retriable: false,
}],
};
}

View File

@@ -0,0 +1,278 @@
// file: kb-lib/src/decoder/solana/core/feature.rs
// version: 4
//! Exact Feature Gate instruction decoding from the official interface contract.
const SOURCE: &str = "solana-feature-gate-interface@4.0.0 revoke_pending_activation contract";
const REVOKE_ROLES: &[crate::SolanaCoreAccountRole] = &[
crate::SolanaCoreAccountRole::new("feature_account", true, true),
crate::SolanaCoreAccountRole::new("incinerator", true, false),
crate::SolanaCoreAccountRole::new("system_program", false, false),
];
/// Returns declared Feature Gate instruction coverage.
pub(crate) fn feature_coverage() -> std::vec::Vec<crate::DecoderCoverageDeclaration> {
return vec![crate::DecoderCoverageDeclaration {
program_id: kb_program_ids::FEATURE_PROGRAM_ID.to_string(),
surface_code: std::option::Option::Some(
crate::SOLANA_CORE_FEATURE_SURFACE_CODE.to_string(),
),
entry_kind: crate::DecoderCoverageEntryKind::Instruction,
entry_code: "revoke_pending_activation".to_string(),
discriminator_hex: std::option::Option::Some("00".to_string()),
historical: false,
}];
}
/// Recognizes one Feature Gate instruction without producing an event.
pub(crate) fn feature_recognize(
input: &crate::CoreInstructionReplayInput,
priority: u16,
) -> crate::DecoderRecognition {
let bytes_result = crate::solana_core_decode_instruction_data(input);
let bytes = match bytes_result {
std::result::Result::Ok(value) => value,
std::result::Result::Err(_error) => {
return crate::DecoderRecognition::compatible(
false,
priority,
std::option::Option::Some(crate::SOLANA_CORE_FEATURE_SURFACE_CODE.to_string()),
std::option::Option::Some("malformed_feature_instruction".to_string()),
std::option::Option::None,
);
},
};
let entry_code = match bytes.first() {
std::option::Option::Some(0) => "revoke_pending_activation",
std::option::Option::Some(_value) => "unknown_feature_instruction",
std::option::Option::None => "malformed_feature_instruction",
};
return crate::DecoderRecognition::compatible(
bytes.as_slice() == [0],
priority,
std::option::Option::Some(crate::SOLANA_CORE_FEATURE_SURFACE_CODE.to_string()),
std::option::Option::Some(entry_code.to_string()),
crate::solana_core_hexadecimal_prefix(bytes.as_slice(), bytes.len().min(1)),
);
}
/// Decodes one Feature Gate instruction.
pub(crate) fn feature_decode(
input: &crate::CoreInstructionReplayInput,
) -> crate::DecoderExecutionResult {
let bytes_result = crate::solana_core_decode_instruction_data(input);
let bytes = match bytes_result {
std::result::Result::Ok(value) => value,
std::result::Result::Err(error) => {
return crate::solana_core_failed_result(
std::option::Option::Some("malformed_feature_instruction"),
"feature_payload_invalid",
error.to_string(),
);
},
};
if bytes.is_empty() {
return crate::solana_core_failed_result(
std::option::Option::Some("malformed_feature_instruction"),
"feature_payload_empty",
"Feature Gate instruction payload is empty",
);
}
if bytes[0] != 0 {
return crate::solana_core_unsupported_result(
"unknown_feature_instruction",
"feature_tag_unknown",
format!(
"unknown Feature Gate instruction tag {}; payload_sha256={}",
bytes[0],
crate::solana_core_payload_hash(input)
),
);
}
if bytes.len() != 1 {
return crate::solana_core_failed_result(
std::option::Option::Some("revoke_pending_activation"),
"feature_instruction_size_invalid",
format!(
"Feature Gate revoke_pending_activation requires 1 byte but received {}",
bytes.len()
),
);
}
let accounts_result =
crate::solana_core_resolve_accounts(input, REVOKE_ROLES, 3, std::option::Option::Some(3));
let accounts = match accounts_result {
std::result::Result::Ok(value) => value,
std::result::Result::Err(error) => {
return crate::solana_core_failed_result(
std::option::Option::Some("revoke_pending_activation"),
"feature_accounts_invalid",
error.to_string(),
);
},
};
let fixed_accounts_result = validate_fixed_accounts(&accounts);
if let std::result::Result::Err(error) = fixed_accounts_result {
return crate::solana_core_failed_result(
std::option::Option::Some("revoke_pending_activation"),
"feature_fixed_account_invalid",
error.to_string(),
);
}
return crate::solana_core_decoded_result(
input,
crate::SOLANA_CORE_FEATURE_SURFACE_CODE,
"revoke_pending_activation",
crate::EventFamily::Lifecycle,
false,
accounts,
serde_json::json!({
"burnsFeatureLamports": true,
"requiresPendingActivation": true,
}),
SOURCE,
);
}
fn validate_fixed_accounts(accounts: &serde_json::Value) -> kb_core::Result<()> {
let array = match accounts.as_array() {
std::option::Option::Some(value) => value,
std::option::Option::None => {
return std::result::Result::Err(kb_core::Error::invalid_state(
"resolved Feature Gate accounts must be an array",
));
},
};
for (position, expected) in [
(1_usize, kb_program_ids::INCINERATOR_PROGRAM_ID),
(2_usize, kb_program_ids::SYSTEM_PROGRAM_ID),
] {
let actual = array
.get(position)
.and_then(|value| return value.get("accountKey"))
.and_then(serde_json::Value::as_str);
if actual != std::option::Option::Some(expected) {
return std::result::Result::Err(kb_core::Error::invalid_state(format!(
"Feature Gate account {position} must be {expected}"
)));
}
}
return std::result::Result::Ok(());
}
#[cfg(test)]
mod tests {
use base64::Engine; // rust-rules: trait-import
fn replay_input(bytes: &[u8], transaction_failed: bool) -> crate::CoreInstructionReplayInput {
let keys = [
"FeatureAccount11111111111111111111111111111",
kb_program_ids::INCINERATOR_PROGRAM_ID,
kb_program_ids::SYSTEM_PROGRAM_ID,
];
let account_keys = keys
.iter()
.enumerate()
.map(|(index, key)| {
return serde_json::json!({
"accountIndex": index,
"accountKey": key,
"source": "static",
"writable": index != 2,
"signer": index == 0,
"executable": index == 2,
});
})
.collect::<std::vec::Vec<_>>();
let instruction_accounts = keys
.iter()
.enumerate()
.map(|(index, key)| {
return serde_json::json!({"accountIndex": index, "accountKey": key});
})
.collect::<std::vec::Vec<_>>();
let result = crate::CoreInstructionReplayInput::new(
"signature:0",
"signature",
42,
"0",
kb_program_ids::FEATURE_PROGRAM_ID,
transaction_failed,
if transaction_failed {
std::option::Option::Some(serde_json::json!({"InstructionError": [0, "Custom"]}))
} else {
std::option::Option::None
},
serde_json::Value::Array(account_keys),
serde_json::Value::Array(instruction_accounts),
std::option::Option::Some(serde_json::json!({
"dataBase64": base64::engine::general_purpose::STANDARD.encode(bytes),
})),
std::option::Option::Some("payload-hash".to_string()),
serde_json::json!([]),
serde_json::json!([]),
serde_json::json!([]),
serde_json::json!([]),
);
return match result {
std::result::Result::Ok(value) => value,
std::result::Result::Err(error) => panic!("Feature Gate replay input failed: {error}"),
};
}
#[test]
fn official_payload_is_recognized_exactly() {
let input = replay_input(&[0], false);
let recognition = crate::solana_core_feature_recognize(&input, 100);
assert!(recognition.compatible);
assert!(recognition.exact);
assert_eq!(
recognition.entry_code.as_deref(),
std::option::Option::Some("revoke_pending_activation")
);
let unknown = crate::solana_core_feature_recognize(&replay_input(&[7], false), 100);
assert!(unknown.compatible);
assert!(!unknown.exact);
}
#[test]
fn revoke_pending_activation_decodes_exact_official_layout() {
let result = crate::solana_core_feature_decode(&replay_input(&[0], false));
assert_eq!(result.status, crate::DecoderOutcomeStatus::Decoded);
assert_eq!(
result.recognized_entry_code.as_deref(),
std::option::Option::Some("revoke_pending_activation")
);
assert_eq!(result.observations[0].payload_json["parameters"]["burnsFeatureLamports"], true);
}
#[test]
fn unknown_truncated_trailing_and_wrong_fixed_accounts_are_safe() {
let empty = crate::solana_core_feature_decode(&replay_input(&[], false));
assert_eq!(empty.status, crate::DecoderOutcomeStatus::Failed);
let unknown = crate::solana_core_feature_decode(&replay_input(&[7], false));
assert_eq!(unknown.status, crate::DecoderOutcomeStatus::Unsupported);
let trailing = crate::solana_core_feature_decode(&replay_input(&[0, 1], false));
assert_eq!(trailing.status, crate::DecoderOutcomeStatus::Failed);
let mut wrong = replay_input(&[0], false);
wrong.instruction_accounts_json[1]["accountKey"] = serde_json::json!("wrong");
wrong.account_keys_json[1]["accountKey"] = serde_json::json!("wrong");
let invalid = crate::solana_core_feature_decode(&wrong);
assert_eq!(invalid.status, crate::DecoderOutcomeStatus::Failed);
}
#[test]
fn failed_feature_revoke_is_uncommitted() {
let result = crate::solana_core_feature_decode(&replay_input(&[0], true));
assert_eq!(result.status, crate::DecoderOutcomeStatus::Decoded);
assert!(!result.observations[0].observation_committed);
}
#[test]
fn coverage_declares_the_single_official_instruction() {
let coverage = crate::solana_core_feature_coverage();
assert_eq!(coverage.len(), 1);
assert_eq!(coverage[0].entry_code, "revoke_pending_activation");
assert_eq!(coverage[0].discriminator_hex.as_deref(), std::option::Option::Some("00"));
}
}

File diff suppressed because it is too large Load Diff

View File

@@ -0,0 +1,328 @@
// file: kb-lib/src/decoder/solana/core/payload.rs
// version: 6
//! Bounded native instruction payload helpers.
use base64::Engine; // rust-rules: trait-import
use sha2::Digest; // rust-rules: trait-import
/// One resolved outer instruction payload and its provenance relative to the target instruction.
pub(crate) struct ResolvedInstructionPayload {
/// Numeric outer instruction index in the transaction message.
pub(crate) instruction_index: usize,
/// Stable outer instruction path retained by the core store.
pub(crate) instruction_path: std::string::String,
/// Decoded instruction data.
pub(crate) bytes: std::vec::Vec<u8>,
/// Stable provenance code used by decoded precompile events.
pub(crate) provenance: &'static str,
}
/// Decodes one retained base64 instruction payload with an explicit byte limit.
pub(crate) fn decode_instruction_data(
input: &crate::CoreInstructionReplayInput,
) -> kb_core::Result<std::vec::Vec<u8>> {
let payload = match input.instruction_payload_json.as_ref() {
std::option::Option::Some(value) => value,
std::option::Option::None => {
return std::result::Result::Err(kb_core::Error::invalid_state(
"native instruction payload is not retained",
));
},
};
return decode_payload_json(payload, "native instruction");
}
/// Resolves an Ed25519 or secp256r1 instruction reference using the official `u16::MAX` sentinel.
pub(crate) fn resolve_u16_instruction_payload(
input: &crate::CoreInstructionReplayInput,
raw_instruction_index: u16,
) -> kb_core::Result<crate::SolanaCoreResolvedInstructionPayload> {
if raw_instruction_index == u16::MAX {
let target_index_result = crate::solana_core_target_outer_instruction_index(input);
let target_index = match target_index_result {
std::result::Result::Ok(value) => value,
std::result::Result::Err(error) => return std::result::Result::Err(error),
};
let bytes_result = crate::solana_core_decode_instruction_data(input);
let bytes = match bytes_result {
std::result::Result::Ok(value) => value,
std::result::Result::Err(error) => return std::result::Result::Err(error),
};
return std::result::Result::Ok(crate::SolanaCoreResolvedInstructionPayload {
instruction_index: target_index,
instruction_path: input.instruction_path.clone(),
bytes,
provenance: "current_instruction",
});
}
return resolve_explicit_outer_instruction(input, usize::from(raw_instruction_index));
}
/// Resolves a secp256k1 instruction reference. The runtime format has no current-instruction
/// sentinel: every `u8` value is an explicit outer instruction index.
pub(crate) fn resolve_u8_instruction_payload(
input: &crate::CoreInstructionReplayInput,
raw_instruction_index: u8,
) -> kb_core::Result<crate::SolanaCoreResolvedInstructionPayload> {
return resolve_explicit_outer_instruction(input, usize::from(raw_instruction_index));
}
/// Returns the numeric outer index of the target instruction.
pub(crate) fn target_outer_instruction_index(
input: &crate::CoreInstructionReplayInput,
) -> kb_core::Result<usize> {
if input.instruction_path.contains('/') {
return std::result::Result::Err(kb_core::Error::invalid_state(
"signature precompiles can only resolve outer instruction paths",
));
}
let parse_result = input.instruction_path.parse::<usize>();
return match parse_result {
std::result::Result::Ok(value) => std::result::Result::Ok(value),
std::result::Result::Err(error) => std::result::Result::Err(kb_core::Error::invalid_state(
format!("target outer instruction path is not numeric: {error}"),
)),
};
}
/// Extracts one exact bounded byte slice with checked arithmetic.
pub(crate) fn bounded_slice<'a>(
bytes: &'a [u8],
offset: usize,
length: usize,
label: &str,
) -> kb_core::Result<&'a [u8]> {
let end = match offset.checked_add(length) {
std::option::Option::Some(value) => value,
std::option::Option::None => {
return std::result::Result::Err(kb_core::Error::invalid_state(format!(
"{label} range overflows usize: offset={offset}, length={length}"
)));
},
};
return match bytes.get(offset..end) {
std::option::Option::Some(value) => std::result::Result::Ok(value),
std::option::Option::None => {
std::result::Result::Err(kb_core::Error::invalid_state(format!(
"{label} range is outside referenced instruction: offset={offset}, length={length}, instructionLength={}",
bytes.len()
)))
},
};
}
/// Returns the decoded target payload length when retained and valid.
pub(crate) fn decoded_payload_length(
input: &crate::CoreInstructionReplayInput,
) -> std::option::Option<usize> {
return crate::solana_core_decode_instruction_data(input)
.ok()
.map(|bytes| return bytes.len());
}
/// Returns the SHA-256 of the decoded target instruction data when available.
pub(crate) fn decoded_payload_sha256(
input: &crate::CoreInstructionReplayInput,
) -> std::option::Option<std::string::String> {
let bytes_result = crate::solana_core_decode_instruction_data(input);
return match bytes_result {
std::result::Result::Ok(bytes) => {
std::option::Option::Some(crate::solana_core_hash_bytes(bytes.as_slice()))
},
std::result::Result::Err(_error) => std::option::Option::None,
};
}
/// Returns a stable payload hash from core or computes it from retained JSON.
pub(crate) fn payload_hash(input: &crate::CoreInstructionReplayInput) -> std::string::String {
if let std::option::Option::Some(value) = input.instruction_payload_hash.as_ref() {
return value.clone();
}
let mut value = match input.instruction_payload_json.clone() {
std::option::Option::Some(value) => value,
std::option::Option::None => serde_json::Value::Null,
};
let hash_result = crate::deterministic_json_hash(&mut value);
return match hash_result {
std::result::Result::Ok(value) => value,
std::result::Result::Err(_error) => "payload_hash_unavailable".to_string(),
};
}
/// Reads one little-endian `u32` from an exact byte range.
pub(crate) fn read_u32_le(bytes: &[u8], offset: usize) -> kb_core::Result<u32> {
let slice_result = crate::solana_core_bounded_slice(bytes, offset, 4, "native instruction u32");
let slice = match slice_result {
std::result::Result::Ok(value) => value,
std::result::Result::Err(error) => return std::result::Result::Err(error),
};
let array_result = <[u8; 4]>::try_from(slice);
return match array_result {
std::result::Result::Ok(value) => std::result::Result::Ok(u32::from_le_bytes(value)),
std::result::Result::Err(_error) => std::result::Result::Err(
kb_core::Error::invalid_state("native instruction u32 slice has an invalid length"),
),
};
}
/// Reads one little-endian `u64` from an exact byte range.
pub(crate) fn read_u64_le(bytes: &[u8], offset: usize) -> kb_core::Result<u64> {
let slice_result = crate::solana_core_bounded_slice(bytes, offset, 8, "native instruction u64");
let slice = match slice_result {
std::result::Result::Ok(value) => value,
std::result::Result::Err(error) => return std::result::Result::Err(error),
};
let array_result = <[u8; 8]>::try_from(slice);
return match array_result {
std::result::Result::Ok(value) => std::result::Result::Ok(u64::from_le_bytes(value)),
std::result::Result::Err(_error) => std::result::Result::Err(
kb_core::Error::invalid_state("native instruction u64 slice has an invalid length"),
),
};
}
/// Returns the lowercase SHA-256 of one bounded byte slice.
pub(crate) fn hash_bytes(bytes: &[u8]) -> std::string::String {
let digest = sha2::Sha256::digest(bytes);
let mut output = std::string::String::with_capacity(digest.len().saturating_mul(2));
for byte in digest {
output.push_str(format!("{byte:02x}").as_str());
}
return output;
}
/// Returns a normalized lowercase hexadecimal prefix.
pub(crate) fn hexadecimal_prefix(
bytes: &[u8],
count: usize,
) -> std::option::Option<std::string::String> {
let selected = match bytes.get(..count) {
std::option::Option::Some(value) => value,
std::option::Option::None => return std::option::Option::None,
};
let mut output = std::string::String::with_capacity(count.saturating_mul(2));
for byte in selected {
output.push_str(format!("{byte:02x}").as_str());
}
return std::option::Option::Some(output);
}
/// Returns a hexadecimal prefix no longer than the available byte slice.
pub(crate) fn bounded_hexadecimal_prefix(
bytes: &[u8],
maximum_count: usize,
) -> std::string::String {
let count = std::cmp::min(bytes.len(), maximum_count);
return match crate::solana_core_hexadecimal_prefix(bytes, count) {
std::option::Option::Some(value) => value,
std::option::Option::None => std::string::String::new(),
};
}
fn resolve_explicit_outer_instruction(
input: &crate::CoreInstructionReplayInput,
instruction_index: usize,
) -> kb_core::Result<crate::SolanaCoreResolvedInstructionPayload> {
let entries = match input.outer_instructions_json.as_array() {
std::option::Option::Some(value) => value,
std::option::Option::None => {
return std::result::Result::Err(kb_core::Error::invalid_state(
"outer instruction context is not a JSON array",
));
},
};
let target_index_result = crate::solana_core_target_outer_instruction_index(input);
let target_index = match target_index_result {
std::result::Result::Ok(value) => value,
std::result::Result::Err(error) => return std::result::Result::Err(error),
};
let entry = entries.iter().find(|entry| {
let value = entry
.get("instructionIndex")
.and_then(serde_json::Value::as_u64)
.and_then(|value| return usize::try_from(value).ok());
return value == std::option::Option::Some(instruction_index);
});
let entry = match entry {
std::option::Option::Some(value) => value,
std::option::Option::None => {
return std::result::Result::Err(kb_core::Error::invalid_state(format!(
"referenced outer instruction index is absent: {instruction_index}"
)));
},
};
let instruction_path = match entry.get("instructionPath").and_then(serde_json::Value::as_str) {
std::option::Option::Some(value) if !value.trim().is_empty() => value.to_string(),
_ => {
return std::result::Result::Err(kb_core::Error::invalid_state(format!(
"referenced outer instruction {instruction_index} has no instructionPath"
)));
},
};
let payload_json = match entry.get("payloadJson") {
std::option::Option::Some(value) if !value.is_null() => value,
_ => {
return std::result::Result::Err(kb_core::Error::invalid_state(format!(
"referenced outer instruction {instruction_index} has no retained payloadJson"
)));
},
};
let bytes_result = decode_payload_json(
payload_json,
format!("referenced outer instruction {instruction_index}").as_str(),
);
let bytes = match bytes_result {
std::result::Result::Ok(value) => value,
std::result::Result::Err(error) => return std::result::Result::Err(error),
};
return std::result::Result::Ok(crate::SolanaCoreResolvedInstructionPayload {
instruction_index,
instruction_path,
bytes,
provenance: if instruction_index == target_index {
"current_instruction"
} else {
"outer_instruction"
},
});
}
fn decode_payload_json(
payload: &serde_json::Value,
label: &str,
) -> kb_core::Result<std::vec::Vec<u8>> {
let encoded = match payload.get("dataBase64").and_then(serde_json::Value::as_str) {
std::option::Option::Some(value) => value,
std::option::Option::None => {
return std::result::Result::Err(kb_core::Error::invalid_state(format!(
"{label} payload does not contain dataBase64"
)));
},
};
let maximum_encoded_length = crate::SOLANA_CORE_MAX_NATIVE_INSTRUCTION_PAYLOAD_BYTES
.saturating_mul(4)
.saturating_div(3)
.saturating_add(4);
if encoded.len() > maximum_encoded_length {
return std::result::Result::Err(kb_core::Error::invalid_state(format!(
"{label} base64 payload exceeds {maximum_encoded_length} bytes"
)));
}
let decoded_result = base64::engine::general_purpose::STANDARD.decode(encoded.as_bytes());
let decoded = match decoded_result {
std::result::Result::Ok(value) => value,
std::result::Result::Err(error) => {
return std::result::Result::Err(kb_core::Error::invalid_state(format!(
"{label} payload is not valid base64: {error}"
)));
},
};
if decoded.len() > crate::SOLANA_CORE_MAX_NATIVE_INSTRUCTION_PAYLOAD_BYTES {
return std::result::Result::Err(kb_core::Error::invalid_state(format!(
"{label} payload exceeds {} decoded bytes",
crate::SOLANA_CORE_MAX_NATIVE_INSTRUCTION_PAYLOAD_BYTES
)));
}
return std::result::Result::Ok(decoded);
}

File diff suppressed because it is too large Load Diff

View File

@@ -0,0 +1,685 @@
// file: kb-lib/src/decoder/solana/core/slashing.rs
// version: 3
//! Exact structural decoding for the enshrined stateless Slashing Program.
const SOURCE: &str = "solana-program/slashing@fe8da3a instruction contract and Agave v4.1.1 stateless builtin verified build";
const DUPLICATE_BLOCK_PROOF_DATA_BYTES: usize = 304;
const DUPLICATE_BLOCK_PROOF_INSTRUCTION_BYTES: usize = 305;
const SIGNATURE_BYTES: usize = 64;
const HASH_BYTES: usize = 32;
const PUBKEY_BYTES: usize = 32;
const CLOSE_ROLES: &[crate::SolanaCoreAccountRole] = &[
crate::SolanaCoreAccountRole::new("violation_report", true, false),
crate::SolanaCoreAccountRole::new("destination", true, false),
];
const DUPLICATE_BLOCK_ROLES: &[crate::SolanaCoreAccountRole] = &[
crate::SolanaCoreAccountRole::new("proof_account", false, false),
crate::SolanaCoreAccountRole::new("violation_report", true, false),
crate::SolanaCoreAccountRole::new("instructions_sysvar", false, false),
crate::SolanaCoreAccountRole::new("system_program", false, false),
];
/// Returns declared Slashing Program instruction coverage.
pub(crate) fn slashing_coverage() -> std::vec::Vec<crate::DecoderCoverageDeclaration> {
return vec![
crate::DecoderCoverageDeclaration {
program_id: kb_program_ids::SLASHING_PROGRAM_ID.to_string(),
surface_code: std::option::Option::Some(
crate::SOLANA_CORE_SLASHING_SURFACE_CODE.to_string(),
),
entry_kind: crate::DecoderCoverageEntryKind::Instruction,
entry_code: "close_violation_report".to_string(),
discriminator_hex: std::option::Option::Some("00".to_string()),
historical: false,
},
crate::DecoderCoverageDeclaration {
program_id: kb_program_ids::SLASHING_PROGRAM_ID.to_string(),
surface_code: std::option::Option::Some(
crate::SOLANA_CORE_SLASHING_SURFACE_CODE.to_string(),
),
entry_kind: crate::DecoderCoverageEntryKind::Instruction,
entry_code: "duplicate_block_proof".to_string(),
discriminator_hex: std::option::Option::Some("01".to_string()),
historical: false,
},
];
}
/// Recognizes one Slashing Program instruction without producing an event.
pub(crate) fn slashing_recognize(
input: &crate::CoreInstructionReplayInput,
priority: u16,
) -> crate::DecoderRecognition {
let bytes_result = crate::solana_core_decode_instruction_data(input);
let bytes = match bytes_result {
std::result::Result::Ok(value) => value,
std::result::Result::Err(_error) => {
return crate::DecoderRecognition::compatible(
false,
priority,
std::option::Option::Some(crate::SOLANA_CORE_SLASHING_SURFACE_CODE.to_string()),
std::option::Option::Some("malformed_slashing_instruction".to_string()),
std::option::Option::None,
);
},
};
let (entry_code, exact) = match bytes.first() {
std::option::Option::Some(0) => ("close_violation_report", bytes.len() == 1),
std::option::Option::Some(1) => {
("duplicate_block_proof", bytes.len() == DUPLICATE_BLOCK_PROOF_INSTRUCTION_BYTES)
},
std::option::Option::Some(_value) => ("unknown_slashing_instruction", false),
std::option::Option::None => ("malformed_slashing_instruction", false),
};
return crate::DecoderRecognition::compatible(
exact,
priority,
std::option::Option::Some(crate::SOLANA_CORE_SLASHING_SURFACE_CODE.to_string()),
std::option::Option::Some(entry_code.to_string()),
crate::solana_core_hexadecimal_prefix(bytes.as_slice(), bytes.len().min(1)),
);
}
/// Decodes one Slashing Program instruction.
pub(crate) fn slashing_decode(
input: &crate::CoreInstructionReplayInput,
) -> crate::DecoderExecutionResult {
let bytes_result = crate::solana_core_decode_instruction_data(input);
let bytes = match bytes_result {
std::result::Result::Ok(value) => value,
std::result::Result::Err(error) => {
return crate::solana_core_failed_result(
std::option::Option::Some("malformed_slashing_instruction"),
"slashing_payload_invalid",
error.to_string(),
);
},
};
let tag = match bytes.first() {
std::option::Option::Some(value) => *value,
std::option::Option::None => {
return crate::solana_core_failed_result(
std::option::Option::Some("malformed_slashing_instruction"),
"slashing_payload_empty",
"Slashing Program instruction payload is empty",
);
},
};
return match tag {
0 => decode_close_violation_report(input, bytes.as_slice()),
1 => decode_duplicate_block_proof(input, bytes.as_slice()),
_ => crate::solana_core_unsupported_result(
"unknown_slashing_instruction",
"slashing_tag_unknown",
format!(
"unknown Slashing Program instruction tag {tag}; payload_sha256={}",
crate::solana_core_payload_hash(input)
),
),
};
}
fn decode_close_violation_report(
input: &crate::CoreInstructionReplayInput,
bytes: &[u8],
) -> crate::DecoderExecutionResult {
if bytes.len() != 1 {
return crate::solana_core_failed_result(
std::option::Option::Some("close_violation_report"),
"slashing_close_size_invalid",
format!("close_violation_report requires exactly 1 byte but received {}", bytes.len()),
);
}
let accounts_result =
crate::solana_core_resolve_accounts(input, CLOSE_ROLES, 2, std::option::Option::None);
let accounts = match accounts_result {
std::result::Result::Ok(value) => value,
std::result::Result::Err(error) => {
return crate::solana_core_failed_result(
std::option::Option::Some("close_violation_report"),
"slashing_close_accounts_invalid",
error.to_string(),
);
},
};
return crate::solana_core_decoded_result(
input,
crate::SOLANA_CORE_SLASHING_SURFACE_CODE,
"close_violation_report",
crate::EventFamily::Lifecycle,
false,
accounts,
serde_json::json!({
"proofType": "duplicate_block",
"minimumRetentionEpochs": 3,
"runtimeMutation": runtime_mutation(
input,
"violation_report_closed_lamports_transferred_owner_reset_to_system_program",
),
"transactionFinalAccountStateCaptured": false,
}),
SOURCE,
);
}
fn decode_duplicate_block_proof(
input: &crate::CoreInstructionReplayInput,
bytes: &[u8],
) -> crate::DecoderExecutionResult {
if bytes.len() != DUPLICATE_BLOCK_PROOF_INSTRUCTION_BYTES {
return crate::solana_core_failed_result(
std::option::Option::Some("duplicate_block_proof"),
"slashing_duplicate_block_size_invalid",
format!(
"duplicate_block_proof requires {} bytes but received {}",
DUPLICATE_BLOCK_PROOF_INSTRUCTION_BYTES,
bytes.len()
),
);
}
let accounts_result = crate::solana_core_resolve_accounts(
input,
DUPLICATE_BLOCK_ROLES,
4,
std::option::Option::None,
);
let accounts = match accounts_result {
std::result::Result::Ok(value) => value,
std::result::Result::Err(error) => {
return crate::solana_core_failed_result(
std::option::Option::Some("duplicate_block_proof"),
"slashing_duplicate_block_accounts_invalid",
error.to_string(),
);
},
};
let fixed_accounts_result = validate_duplicate_block_fixed_accounts(&accounts);
if let std::result::Result::Err(error) = fixed_accounts_result {
return crate::solana_core_failed_result(
std::option::Option::Some("duplicate_block_proof"),
"slashing_duplicate_block_fixed_account_invalid",
error.to_string(),
);
}
let parsed_result = parse_duplicate_block_parameters(input, bytes);
let parameters = match parsed_result {
std::result::Result::Ok(value) => value,
std::result::Result::Err(error) => {
return crate::solana_core_failed_result(
std::option::Option::Some("duplicate_block_proof"),
"slashing_duplicate_block_data_invalid",
error.to_string(),
);
},
};
return crate::solana_core_decoded_result(
input,
crate::SOLANA_CORE_SLASHING_SURFACE_CODE,
"duplicate_block_proof",
crate::EventFamily::Audit,
false,
accounts,
parameters,
SOURCE,
);
}
fn parse_duplicate_block_parameters(
input: &crate::CoreInstructionReplayInput,
bytes: &[u8],
) -> kb_core::Result<serde_json::Value> {
let offset_result = crate::solana_core_read_u64_le(bytes, 1);
let offset = match offset_result {
std::result::Result::Ok(value) => value,
std::result::Result::Err(error) => return std::result::Result::Err(error),
};
let slot_result = crate::solana_core_read_u64_le(bytes, 9);
let slot = match slot_result {
std::result::Result::Ok(value) => value,
std::result::Result::Err(error) => return std::result::Result::Err(error),
};
let node_pubkey_result = read_pubkey(bytes, 17, "slashing node pubkey");
let node_pubkey = match node_pubkey_result {
std::result::Result::Ok(value) => value,
std::result::Result::Err(error) => return std::result::Result::Err(error),
};
let reporter_result = read_pubkey(bytes, 49, "slashing reporter");
let reporter = match reporter_result {
std::result::Result::Ok(value) => value,
std::result::Result::Err(error) => return std::result::Result::Err(error),
};
let destination_result = read_pubkey(bytes, 81, "slashing destination");
let destination = match destination_result {
std::result::Result::Ok(value) => value,
std::result::Result::Err(error) => return std::result::Result::Err(error),
};
let shred_1_root_result = crate::solana_core_bounded_slice(
bytes,
113,
HASH_BYTES,
"slashing first shred merkle root",
);
let shred_1_root = match shred_1_root_result {
std::result::Result::Ok(value) => value,
std::result::Result::Err(error) => return std::result::Result::Err(error),
};
let shred_1_signature_result = crate::solana_core_bounded_slice(
bytes,
145,
SIGNATURE_BYTES,
"slashing first shred signature",
);
let shred_1_signature = match shred_1_signature_result {
std::result::Result::Ok(value) => value,
std::result::Result::Err(error) => return std::result::Result::Err(error),
};
let shred_2_root_result = crate::solana_core_bounded_slice(
bytes,
209,
HASH_BYTES,
"slashing second shred merkle root",
);
let shred_2_root = match shred_2_root_result {
std::result::Result::Ok(value) => value,
std::result::Result::Err(error) => return std::result::Result::Err(error),
};
let shred_2_signature_result = crate::solana_core_bounded_slice(
bytes,
241,
SIGNATURE_BYTES,
"slashing second shred signature",
);
let shred_2_signature = match shred_2_signature_result {
std::result::Result::Ok(value) => value,
std::result::Result::Err(error) => return std::result::Result::Err(error),
};
return std::result::Result::Ok(serde_json::json!({
"proofType": "duplicate_block",
"proofAccountOffset": offset,
"violationSlot": slot,
"nodePubkey": node_pubkey,
"reporter": reporter,
"destination": destination,
"shred1MerkleRoot": hash_summary(shred_1_root),
"shred1Signature": signature_summary(shred_1_signature),
"shred2MerkleRoot": hash_summary(shred_2_root),
"shred2Signature": signature_summary(shred_2_signature),
"proofAccountDataCaptured": false,
"proofAccountDataSemantics": "external_account_not_captured_by_transaction_core",
"reportAccountMustBePrefunded": true,
"prefundingProvenanceInspectedByDecoder": false,
"violationReportPdaDerivationVerifiedByDecoder": false,
"precedingEd25519Instruction": preceding_ed25519_instruction(input),
"cryptographicVerificationPerformedByDecoder": false,
"runtimeVerification": if input.transaction_failed {
"not_asserted_transaction_failed"
} else {
"accepted_in_successful_transaction"
},
"runtimeMutation": runtime_mutation(
input,
"violation_report_pda_assigned_allocated_and_data_stored_after_duplicate_block_proof_acceptance",
),
"penaltyAppliedByProgram": false,
"programSemantics": "records_verified_violation_report_for_external_consensus_enforcement",
"instructionDataBytes": DUPLICATE_BLOCK_PROOF_DATA_BYTES,
}));
}
fn validate_duplicate_block_fixed_accounts(accounts: &serde_json::Value) -> kb_core::Result<()> {
let array = match accounts.as_array() {
std::option::Option::Some(value) => value,
std::option::Option::None => {
return std::result::Result::Err(kb_core::Error::invalid_state(
"resolved Slashing Program accounts must be an array",
));
},
};
for (position, expected) in [
(2_usize, kb_program_ids::SYSVAR_INSTRUCTIONS_PROGRAM_ID),
(3_usize, kb_program_ids::SYSTEM_PROGRAM_ID),
] {
let actual = array
.get(position)
.and_then(|value| return value.get("accountKey"))
.and_then(serde_json::Value::as_str);
if actual != std::option::Option::Some(expected) {
return std::result::Result::Err(kb_core::Error::invalid_state(format!(
"Slashing Program account {position} must be {expected}"
)));
}
}
return std::result::Result::Ok(());
}
fn read_pubkey(bytes: &[u8], offset: usize, label: &str) -> kb_core::Result<std::string::String> {
let slice_result = crate::solana_core_bounded_slice(bytes, offset, PUBKEY_BYTES, label);
return match slice_result {
std::result::Result::Ok(value) => {
std::result::Result::Ok(bs58::encode(value).into_string())
},
std::result::Result::Err(error) => std::result::Result::Err(error),
};
}
fn hash_summary(bytes: &[u8]) -> serde_json::Value {
return serde_json::json!({
"length": bytes.len(),
"hex": crate::solana_core_bounded_hexadecimal_prefix(bytes, bytes.len()),
"sha256": crate::solana_core_hash_bytes(bytes),
});
}
fn signature_summary(bytes: &[u8]) -> serde_json::Value {
return serde_json::json!({
"length": bytes.len(),
"prefixHex": crate::solana_core_bounded_hexadecimal_prefix(
bytes,
crate::SOLANA_CORE_PRECOMPILE_COMPONENT_PREFIX_BYTES,
),
"sha256": crate::solana_core_hash_bytes(bytes),
});
}
fn preceding_ed25519_instruction(input: &crate::CoreInstructionReplayInput) -> serde_json::Value {
let target_index = match crate::solana_core_target_outer_instruction_index(input) {
std::result::Result::Ok(value) => value,
std::result::Result::Err(error) => {
return serde_json::json!({
"requiredRelativeInstructionOffset": -1,
"expectedProgramId": kb_program_ids::ED25519_PROGRAM_ID,
"resolved": false,
"matchesExpectedProgram": false,
"diagnostic": error.to_string(),
});
},
};
let previous_index = match target_index.checked_sub(1) {
std::option::Option::Some(value) => value,
std::option::Option::None => {
return serde_json::json!({
"requiredRelativeInstructionOffset": -1,
"currentInstructionIndex": target_index,
"expectedProgramId": kb_program_ids::ED25519_PROGRAM_ID,
"resolved": false,
"matchesExpectedProgram": false,
"diagnostic": "target instruction has no preceding outer instruction",
});
},
};
let previous = input.outer_instructions_json.as_array().and_then(|instructions| {
return instructions.iter().find(|instruction| {
return instruction
.get("instructionIndex")
.and_then(serde_json::Value::as_u64)
.and_then(|value| return usize::try_from(value).ok())
== std::option::Option::Some(previous_index);
});
});
let program_id = previous
.and_then(|instruction| return instruction.get("programId"))
.and_then(serde_json::Value::as_str);
let instruction_path = previous
.and_then(|instruction| return instruction.get("instructionPath"))
.and_then(serde_json::Value::as_str);
return serde_json::json!({
"requiredRelativeInstructionOffset": -1,
"currentInstructionIndex": target_index,
"resolvedInstructionIndex": previous_index,
"resolvedInstructionPath": instruction_path,
"expectedProgramId": kb_program_ids::ED25519_PROGRAM_ID,
"resolvedProgramId": program_id,
"resolved": previous.is_some(),
"matchesExpectedProgram": program_id == std::option::Option::Some(kb_program_ids::ED25519_PROGRAM_ID),
"expectedSignatureCount": 2,
"signatureTableInspectedByDecoder": false,
});
}
fn runtime_mutation(
input: &crate::CoreInstructionReplayInput,
successful_value: &'static str,
) -> &'static str {
return if input.transaction_failed {
"not_asserted_transaction_failed"
} else {
successful_value
};
}
#[cfg(test)]
mod tests {
use base64::Engine; // rust-rules: trait-import
fn duplicate_payload() -> std::vec::Vec<u8> {
let mut bytes = vec![1_u8];
bytes.extend_from_slice(&34_u64.to_le_bytes());
bytes.extend_from_slice(&42_u64.to_le_bytes());
bytes.extend_from_slice(&[1_u8; 32]);
bytes.extend_from_slice(&[2_u8; 32]);
bytes.extend_from_slice(&[3_u8; 32]);
bytes.extend_from_slice(&[4_u8; 32]);
bytes.extend_from_slice(&[5_u8; 64]);
bytes.extend_from_slice(&[6_u8; 32]);
bytes.extend_from_slice(&[7_u8; 64]);
return bytes;
}
fn replay_input(
bytes: &[u8],
transaction_failed: bool,
instruction_path: &str,
) -> crate::CoreInstructionReplayInput {
let keys = [
"Proof1111111111111111111111111111111111111",
"Report111111111111111111111111111111111111",
kb_program_ids::SYSVAR_INSTRUCTIONS_PROGRAM_ID,
kb_program_ids::SYSTEM_PROGRAM_ID,
];
let account_keys = keys
.iter()
.enumerate()
.map(|(index, key)| {
return serde_json::json!({
"accountIndex": index,
"accountKey": key,
"source": "static",
"writable": index == 1,
"signer": false,
"executable": index == 3,
});
})
.collect::<std::vec::Vec<_>>();
let instruction_accounts = keys
.iter()
.enumerate()
.map(|(index, key)| {
return serde_json::json!({"accountIndex": index, "accountKey": key});
})
.collect::<std::vec::Vec<_>>();
let current_index = instruction_path.parse::<usize>().unwrap_or(2);
let previous_index = current_index.saturating_sub(1);
let outer = serde_json::json!([
{
"instructionIndex": previous_index,
"instructionPath": previous_index.to_string(),
"programId": kb_program_ids::ED25519_PROGRAM_ID,
"payloadJson": {"dataBase64": ""},
"payloadHash": "ed25519-hash"
},
{
"instructionIndex": current_index,
"instructionPath": instruction_path,
"programId": kb_program_ids::SLASHING_PROGRAM_ID,
"payloadJson": {
"dataBase64": base64::engine::general_purpose::STANDARD.encode(bytes),
},
"payloadHash": "slashing-hash"
}
]);
let result = crate::CoreInstructionReplayInput::new(
format!("signature:{instruction_path}"),
"signature",
42,
instruction_path,
kb_program_ids::SLASHING_PROGRAM_ID,
transaction_failed,
if transaction_failed {
std::option::Option::Some(serde_json::json!({"InstructionError": [2, "Custom"]}))
} else {
std::option::Option::None
},
serde_json::Value::Array(account_keys),
serde_json::Value::Array(instruction_accounts),
std::option::Option::Some(serde_json::json!({
"dataBase64": base64::engine::general_purpose::STANDARD.encode(bytes),
})),
std::option::Option::Some("payload-hash".to_string()),
outer,
serde_json::json!([]),
serde_json::json!([]),
serde_json::json!([]),
);
return match result {
std::result::Result::Ok(value) => value,
std::result::Result::Err(error) => panic!("Slashing replay input failed: {error}"),
};
}
fn close_replay_input(transaction_failed: bool) -> crate::CoreInstructionReplayInput {
let mut input = replay_input(&[0], transaction_failed, "0");
input.account_keys_json = serde_json::json!([
{
"accountIndex": 0,
"accountKey": "Report111111111111111111111111111111111111",
"source": "static",
"writable": true,
"signer": false,
"executable": false
},
{
"accountIndex": 1,
"accountKey": "Destination11111111111111111111111111111111",
"source": "static",
"writable": true,
"signer": false,
"executable": false
}
]);
input.instruction_accounts_json = serde_json::json!([
{"accountIndex": 0, "accountKey": "Report111111111111111111111111111111111111"},
{"accountIndex": 1, "accountKey": "Destination11111111111111111111111111111111"}
]);
return input;
}
#[test]
fn coverage_declares_exactly_two_official_instructions() {
let coverage = crate::solana_core_slashing_coverage();
assert_eq!(coverage.len(), 2);
assert_eq!(coverage[0].entry_code, "close_violation_report");
assert_eq!(coverage[1].entry_code, "duplicate_block_proof");
}
#[test]
fn close_violation_report_decodes_exact_runtime_contract() {
let result = crate::solana_core_slashing_decode(&close_replay_input(false));
assert_eq!(result.status, crate::DecoderOutcomeStatus::Decoded);
assert_eq!(
result.recognized_entry_code.as_deref(),
std::option::Option::Some("close_violation_report")
);
let parameters = &result.observations[0].payload_json["parameters"];
assert_eq!(parameters["minimumRetentionEpochs"], 3);
assert_eq!(
parameters["runtimeMutation"],
"violation_report_closed_lamports_transferred_owner_reset_to_system_program"
);
}
#[test]
fn duplicate_block_proof_decodes_exact_official_layout() {
let payload = duplicate_payload();
assert_eq!(payload.len(), super::DUPLICATE_BLOCK_PROOF_INSTRUCTION_BYTES);
let result =
crate::solana_core_slashing_decode(&replay_input(payload.as_slice(), false, "2"));
assert_eq!(result.status, crate::DecoderOutcomeStatus::Decoded);
let parameters = &result.observations[0].payload_json["parameters"];
assert_eq!(parameters["proofAccountOffset"], 34);
assert_eq!(parameters["violationSlot"], 42);
assert_eq!(parameters["shred1Signature"]["length"], 64);
assert_eq!(parameters["shred2MerkleRoot"]["length"], 32);
assert_eq!(parameters["proofAccountDataCaptured"], false);
assert_eq!(parameters["reportAccountMustBePrefunded"], true);
assert_eq!(parameters["prefundingProvenanceInspectedByDecoder"], false);
assert_eq!(parameters["violationReportPdaDerivationVerifiedByDecoder"], false);
assert_eq!(parameters["cryptographicVerificationPerformedByDecoder"], false);
assert_eq!(parameters["penaltyAppliedByProgram"], false);
}
#[test]
fn duplicate_block_proof_reports_preceding_ed25519_context() {
let result = crate::solana_core_slashing_decode(&replay_input(
duplicate_payload().as_slice(),
false,
"2",
));
let preceding =
&result.observations[0].payload_json["parameters"]["precedingEd25519Instruction"];
assert_eq!(preceding["resolvedInstructionIndex"], 1);
assert_eq!(preceding["matchesExpectedProgram"], true);
assert_eq!(preceding["expectedSignatureCount"], 2);
assert_eq!(preceding["signatureTableInspectedByDecoder"], false);
}
#[test]
fn malformed_unknown_and_trailing_payloads_fail_safely() {
for bytes in [vec![], vec![0, 7], vec![1, 2, 3]] {
let result =
crate::solana_core_slashing_decode(&replay_input(bytes.as_slice(), false, "2"));
assert_eq!(result.status, crate::DecoderOutcomeStatus::Failed);
}
let unknown = crate::solana_core_slashing_decode(&replay_input(&[9], false, "2"));
assert_eq!(unknown.status, crate::DecoderOutcomeStatus::Unsupported);
}
#[test]
fn wrong_fixed_accounts_fail_safely() {
let mut input = replay_input(duplicate_payload().as_slice(), false, "2");
input.instruction_accounts_json[2]["accountKey"] = serde_json::json!("wrong");
input.account_keys_json[2]["accountKey"] = serde_json::json!("wrong");
let result = crate::solana_core_slashing_decode(&input);
assert_eq!(result.status, crate::DecoderOutcomeStatus::Failed);
}
#[test]
fn failed_transaction_is_decoded_without_runtime_or_mutation_claim() {
let result = crate::solana_core_slashing_decode(&replay_input(
duplicate_payload().as_slice(),
true,
"2",
));
assert_eq!(result.status, crate::DecoderOutcomeStatus::Decoded);
assert!(!result.observations[0].observation_committed);
let parameters = &result.observations[0].payload_json["parameters"];
assert_eq!(parameters["runtimeVerification"], "not_asserted_transaction_failed");
assert_eq!(parameters["runtimeMutation"], "not_asserted_transaction_failed");
}
#[test]
fn serialization_is_deterministic() {
let input = replay_input(duplicate_payload().as_slice(), false, "2");
let first = crate::solana_core_slashing_decode(&input);
let second = crate::solana_core_slashing_decode(&input);
let first_json = match serde_json::to_string(&first) {
std::result::Result::Ok(value) => value,
std::result::Result::Err(error) => panic!("first 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 serialization failed: {error}"),
};
assert_eq!(first_json, second_json);
}
}

File diff suppressed because it is too large Load Diff

View File

@@ -0,0 +1,688 @@
// file: kb-lib/src/decoder/solana/core/system.rs
// version: 6
//! Maximal current System Program instruction decoding through the official interface enum and wincode schema.
const SOURCE: &str = "solana-system-interface@3.2.0 SystemInstruction wincode-compatible contract";
const CREATE_ACCOUNT_ROLES: &[crate::SolanaCoreAccountRole] = &[
crate::SolanaCoreAccountRole::new("funding_account", true, true),
crate::SolanaCoreAccountRole::new("new_account", true, true),
];
const ASSIGN_ROLES: &[crate::SolanaCoreAccountRole] =
&[crate::SolanaCoreAccountRole::new("assigned_account", true, true)];
const TRANSFER_ROLES: &[crate::SolanaCoreAccountRole] = &[
crate::SolanaCoreAccountRole::new("funding_account", true, true),
crate::SolanaCoreAccountRole::new("recipient_account", true, false),
];
const CREATE_ACCOUNT_WITH_SEED_ROLES: &[crate::SolanaCoreAccountRole] = &[
crate::SolanaCoreAccountRole::new("funding_account", true, true),
crate::SolanaCoreAccountRole::new("new_account", true, false),
crate::SolanaCoreAccountRole::new("base_account", false, true),
];
const ADVANCE_NONCE_ROLES: &[crate::SolanaCoreAccountRole] = &[
crate::SolanaCoreAccountRole::new("nonce_account", true, false),
crate::SolanaCoreAccountRole::new("recent_blockhashes_sysvar", false, false),
crate::SolanaCoreAccountRole::new("nonce_authority", false, true),
];
const WITHDRAW_NONCE_ROLES: &[crate::SolanaCoreAccountRole] = &[
crate::SolanaCoreAccountRole::new("nonce_account", true, false),
crate::SolanaCoreAccountRole::new("recipient_account", true, false),
crate::SolanaCoreAccountRole::new("recent_blockhashes_sysvar", false, false),
crate::SolanaCoreAccountRole::new("rent_sysvar", false, false),
crate::SolanaCoreAccountRole::new("nonce_authority", false, true),
];
const INITIALIZE_NONCE_ROLES: &[crate::SolanaCoreAccountRole] = &[
crate::SolanaCoreAccountRole::new("nonce_account", true, false),
crate::SolanaCoreAccountRole::new("recent_blockhashes_sysvar", false, false),
crate::SolanaCoreAccountRole::new("rent_sysvar", false, false),
];
const AUTHORIZE_NONCE_ROLES: &[crate::SolanaCoreAccountRole] = &[
crate::SolanaCoreAccountRole::new("nonce_account", true, false),
crate::SolanaCoreAccountRole::new("nonce_authority", false, true),
];
const ALLOCATE_ROLES: &[crate::SolanaCoreAccountRole] =
&[crate::SolanaCoreAccountRole::new("allocated_account", true, true)];
const SEEDED_ACCOUNT_ROLES: &[crate::SolanaCoreAccountRole] = &[
crate::SolanaCoreAccountRole::new("derived_account", true, false),
crate::SolanaCoreAccountRole::new("base_account", false, true),
];
const TRANSFER_WITH_SEED_ROLES: &[crate::SolanaCoreAccountRole] = &[
crate::SolanaCoreAccountRole::new("funding_account", true, false),
crate::SolanaCoreAccountRole::new("base_account", false, true),
crate::SolanaCoreAccountRole::new("recipient_account", true, false),
];
const UPGRADE_NONCE_ROLES: &[crate::SolanaCoreAccountRole] =
&[crate::SolanaCoreAccountRole::new("nonce_account", true, false)];
const CREATE_ACCOUNT_ALLOW_PREFUND_ROLES: &[crate::SolanaCoreAccountRole] = &[
crate::SolanaCoreAccountRole::new("new_account", true, true),
crate::SolanaCoreAccountRole::new("funding_account", true, true),
];
/// Returns declared System Program instruction coverage.
pub(crate) fn system_coverage() -> std::vec::Vec<crate::DecoderCoverageDeclaration> {
let entries = [
("create_account", 0_u32),
("assign", 1_u32),
("transfer", 2_u32),
("create_account_with_seed", 3_u32),
("advance_nonce_account", 4_u32),
("withdraw_nonce_account", 5_u32),
("initialize_nonce_account", 6_u32),
("authorize_nonce_account", 7_u32),
("allocate", 8_u32),
("allocate_with_seed", 9_u32),
("assign_with_seed", 10_u32),
("transfer_with_seed", 11_u32),
("upgrade_nonce_account", 12_u32),
("create_account_allow_prefund", 13_u32),
];
return entries
.into_iter()
.map(|(entry_code, tag)| {
return crate::DecoderCoverageDeclaration {
program_id: kb_program_ids::SYSTEM_PROGRAM_ID.to_string(),
surface_code: std::option::Option::Some(
crate::SOLANA_CORE_SYSTEM_SURFACE_CODE.to_string(),
),
entry_kind: crate::DecoderCoverageEntryKind::Instruction,
entry_code: entry_code.to_string(),
discriminator_hex: std::option::Option::Some(tag_hex(tag).to_string()),
historical: false,
};
})
.collect();
}
/// Recognizes one System Program instruction without producing an event.
pub(crate) fn system_recognize(
input: &crate::CoreInstructionReplayInput,
priority: u16,
) -> crate::DecoderRecognition {
let bytes_result = crate::solana_core_decode_instruction_data(input);
let bytes = match bytes_result {
std::result::Result::Ok(value) => value,
std::result::Result::Err(_error) => {
return crate::DecoderRecognition::compatible(
false,
priority,
std::option::Option::Some(crate::SOLANA_CORE_SYSTEM_SURFACE_CODE.to_string()),
std::option::Option::Some("malformed_system_instruction".to_string()),
std::option::Option::None,
);
},
};
let tag_result = crate::solana_core_read_u32_le(bytes.as_slice(), 0);
let tag = match tag_result {
std::result::Result::Ok(value) => value,
std::result::Result::Err(_error) => {
return crate::DecoderRecognition::compatible(
false,
priority,
std::option::Option::Some(crate::SOLANA_CORE_SYSTEM_SURFACE_CODE.to_string()),
std::option::Option::Some("malformed_system_instruction".to_string()),
crate::solana_core_hexadecimal_prefix(bytes.as_slice(), bytes.len().min(4)),
);
},
};
let entry_code = entry_code(tag);
return crate::DecoderRecognition::compatible(
entry_code.is_some(),
priority,
std::option::Option::Some(crate::SOLANA_CORE_SYSTEM_SURFACE_CODE.to_string()),
entry_code.map(str::to_string).or_else(|| {
return std::option::Option::Some("unknown_system_instruction".to_string());
}),
std::option::Option::Some(tag_hex(tag).to_string()),
);
}
/// Decodes one System Program instruction.
pub(crate) fn system_decode(
input: &crate::CoreInstructionReplayInput,
) -> crate::DecoderExecutionResult {
let bytes_result = crate::solana_core_decode_instruction_data(input);
let bytes = match bytes_result {
std::result::Result::Ok(value) => value,
std::result::Result::Err(error) => {
return crate::solana_core_failed_result(
std::option::Option::Some("malformed_system_instruction"),
"system_payload_invalid",
error.to_string(),
);
},
};
let tag_result = crate::solana_core_read_u32_le(bytes.as_slice(), 0);
let tag = match tag_result {
std::result::Result::Ok(value) => value,
std::result::Result::Err(error) => {
return crate::solana_core_failed_result(
std::option::Option::Some("malformed_system_instruction"),
"system_tag_truncated",
error.to_string(),
);
},
};
let entry_code = match entry_code(tag) {
std::option::Option::Some(value) => value,
std::option::Option::None => {
return crate::solana_core_unsupported_result(
"unknown_system_instruction",
"system_tag_unknown",
format!(
"unknown System Program instruction tag {tag}; payload_sha256={}",
crate::solana_core_payload_hash(input)
),
);
},
};
let instruction_result = wincode::deserialize_exact::<
solana_system_interface::instruction::SystemInstruction,
>(bytes.as_slice());
let instruction = match instruction_result {
std::result::Result::Ok(value) => value,
std::result::Result::Err(error) => {
return crate::solana_core_failed_result(
std::option::Option::Some(entry_code),
"system_wincode_invalid",
format!("cannot decode exact System Program {entry_code}: {error}"),
);
},
};
return decoded_instruction(input, instruction);
}
fn decoded_instruction(
input: &crate::CoreInstructionReplayInput,
instruction: solana_system_interface::instruction::SystemInstruction,
) -> crate::DecoderExecutionResult {
return match instruction {
solana_system_interface::instruction::SystemInstruction::CreateAccount {
lamports,
space,
owner,
} => build(
input,
"create_account",
crate::EventFamily::Lifecycle,
CREATE_ACCOUNT_ROLES,
2,
serde_json::json!({"lamports": lamports, "space": space, "owner": owner.to_string()}),
),
solana_system_interface::instruction::SystemInstruction::Assign { owner } => build(
input,
"assign",
crate::EventFamily::Admin,
ASSIGN_ROLES,
1,
serde_json::json!({"owner": owner.to_string()}),
),
solana_system_interface::instruction::SystemInstruction::Transfer { lamports } => build(
input,
"transfer",
crate::EventFamily::Audit,
TRANSFER_ROLES,
2,
serde_json::json!({"lamports": lamports}),
),
solana_system_interface::instruction::SystemInstruction::CreateAccountWithSeed {
base,
seed,
lamports,
space,
owner,
} => build(
input,
"create_account_with_seed",
crate::EventFamily::Lifecycle,
CREATE_ACCOUNT_WITH_SEED_ROLES,
2,
serde_json::json!({
"base": base.to_string(),
"seed": seed,
"lamports": lamports,
"space": space,
"owner": owner.to_string(),
}),
),
solana_system_interface::instruction::SystemInstruction::AdvanceNonceAccount => build(
input,
"advance_nonce_account",
crate::EventFamily::Lifecycle,
ADVANCE_NONCE_ROLES,
3,
serde_json::json!({}),
),
solana_system_interface::instruction::SystemInstruction::WithdrawNonceAccount(lamports) => {
build(
input,
"withdraw_nonce_account",
crate::EventFamily::Lifecycle,
WITHDRAW_NONCE_ROLES,
5,
serde_json::json!({"lamports": lamports}),
)
},
solana_system_interface::instruction::SystemInstruction::InitializeNonceAccount(
authority,
) => build(
input,
"initialize_nonce_account",
crate::EventFamily::Lifecycle,
INITIALIZE_NONCE_ROLES,
3,
serde_json::json!({"authority": authority.to_string()}),
),
solana_system_interface::instruction::SystemInstruction::AuthorizeNonceAccount(
authority,
) => build(
input,
"authorize_nonce_account",
crate::EventFamily::Admin,
AUTHORIZE_NONCE_ROLES,
2,
serde_json::json!({"authority": authority.to_string()}),
),
solana_system_interface::instruction::SystemInstruction::Allocate { space } => build(
input,
"allocate",
crate::EventFamily::Lifecycle,
ALLOCATE_ROLES,
1,
serde_json::json!({"space": space}),
),
solana_system_interface::instruction::SystemInstruction::AllocateWithSeed {
base,
seed,
space,
owner,
} => build(
input,
"allocate_with_seed",
crate::EventFamily::Lifecycle,
SEEDED_ACCOUNT_ROLES,
2,
serde_json::json!({
"base": base.to_string(),
"seed": seed,
"space": space,
"owner": owner.to_string(),
}),
),
solana_system_interface::instruction::SystemInstruction::AssignWithSeed {
base,
seed,
owner,
} => build(
input,
"assign_with_seed",
crate::EventFamily::Admin,
SEEDED_ACCOUNT_ROLES,
2,
serde_json::json!({
"base": base.to_string(),
"seed": seed,
"owner": owner.to_string(),
}),
),
solana_system_interface::instruction::SystemInstruction::TransferWithSeed {
lamports,
from_seed,
from_owner,
} => build(
input,
"transfer_with_seed",
crate::EventFamily::Audit,
TRANSFER_WITH_SEED_ROLES,
3,
serde_json::json!({
"lamports": lamports,
"fromSeed": from_seed,
"fromOwner": from_owner.to_string(),
}),
),
solana_system_interface::instruction::SystemInstruction::UpgradeNonceAccount => build(
input,
"upgrade_nonce_account",
crate::EventFamily::Lifecycle,
UPGRADE_NONCE_ROLES,
1,
serde_json::json!({}),
),
solana_system_interface::instruction::SystemInstruction::CreateAccountAllowPrefund {
lamports,
space,
owner,
} => build(
input,
"create_account_allow_prefund",
crate::EventFamily::Lifecycle,
CREATE_ACCOUNT_ALLOW_PREFUND_ROLES,
if lamports == 0 { 1 } else { 2 },
serde_json::json!({"lamports": lamports, "space": space, "owner": owner.to_string()}),
),
};
}
fn build(
input: &crate::CoreInstructionReplayInput,
entry_code: &str,
event_family: crate::EventFamily,
roles: &[crate::SolanaCoreAccountRole],
minimum_count: usize,
parameters: serde_json::Value,
) -> crate::DecoderExecutionResult {
let accounts_result =
crate::solana_core_resolve_accounts(input, roles, minimum_count, std::option::Option::None);
let accounts = match accounts_result {
std::result::Result::Ok(value) => value,
std::result::Result::Err(error) => {
return crate::solana_core_failed_result(
std::option::Option::Some(entry_code),
"system_accounts_invalid",
error.to_string(),
);
},
};
return crate::solana_core_decoded_result(
input,
crate::SOLANA_CORE_SYSTEM_SURFACE_CODE,
entry_code,
event_family,
false,
accounts,
parameters,
SOURCE,
);
}
fn entry_code(tag: u32) -> std::option::Option<&'static str> {
return match tag {
0 => std::option::Option::Some("create_account"),
1 => std::option::Option::Some("assign"),
2 => std::option::Option::Some("transfer"),
3 => std::option::Option::Some("create_account_with_seed"),
4 => std::option::Option::Some("advance_nonce_account"),
5 => std::option::Option::Some("withdraw_nonce_account"),
6 => std::option::Option::Some("initialize_nonce_account"),
7 => std::option::Option::Some("authorize_nonce_account"),
8 => std::option::Option::Some("allocate"),
9 => std::option::Option::Some("allocate_with_seed"),
10 => std::option::Option::Some("assign_with_seed"),
11 => std::option::Option::Some("transfer_with_seed"),
12 => std::option::Option::Some("upgrade_nonce_account"),
13 => std::option::Option::Some("create_account_allow_prefund"),
_ => std::option::Option::None,
};
}
fn tag_hex(tag: u32) -> std::string::String {
let bytes = tag.to_le_bytes();
return format!("{:02x}{:02x}{:02x}{:02x}", bytes[0], bytes[1], bytes[2], bytes[3]);
}
#[cfg(test)]
mod tests {
use base64::Engine; // rust-rules: trait-import
fn replay_input(
instruction: &solana_system_interface::instruction::SystemInstruction,
account_count: usize,
transaction_failed: bool,
) -> crate::CoreInstructionReplayInput {
let bytes_result = wincode::serialize(instruction);
let bytes = match bytes_result {
std::result::Result::Ok(value) => value,
std::result::Result::Err(error) => {
panic!("system fixture serialization failed: {error}")
},
};
return replay_input_bytes(bytes.as_slice(), account_count, transaction_failed);
}
fn replay_input_bytes(
bytes: &[u8],
account_count: usize,
transaction_failed: bool,
) -> crate::CoreInstructionReplayInput {
let encoded = base64::engine::general_purpose::STANDARD.encode(bytes);
let account_keys = (0..account_count)
.map(|index| {
return serde_json::json!({
"accountIndex": index,
"accountKey": format!("account{index}"),
"source": "static",
"writable": true,
"signer": true,
"executable": false,
});
})
.collect::<std::vec::Vec<_>>();
let instruction_accounts = (0..account_count)
.map(|index| {
return serde_json::json!({
"accountIndex": index,
"accountKey": format!("account{index}"),
});
})
.collect::<std::vec::Vec<_>>();
let result = crate::CoreInstructionReplayInput::new(
"signature:0",
"signature",
42,
"0",
kb_program_ids::SYSTEM_PROGRAM_ID,
transaction_failed,
if transaction_failed {
std::option::Option::Some(serde_json::json!({"InstructionError": [0, "Custom"]}))
} else {
std::option::Option::None
},
serde_json::Value::Array(account_keys),
serde_json::Value::Array(instruction_accounts),
std::option::Option::Some(serde_json::json!({"dataBase64": encoded})),
std::option::Option::Some("payload-hash".to_string()),
serde_json::json!([]),
serde_json::json!([]),
serde_json::json!([]),
serde_json::json!([]),
);
return match result {
std::result::Result::Ok(value) => value,
std::result::Result::Err(error) => panic!("replay input failed: {error}"),
};
}
#[test]
fn every_official_variant_decodes() {
let address = solana_sdk_ids::system_program::id();
let fixtures = [
(
solana_system_interface::instruction::SystemInstruction::CreateAccount {
lamports: u64::MAX,
space: u64::MAX,
owner: address,
},
2,
"create_account",
),
(
solana_system_interface::instruction::SystemInstruction::Assign { owner: address },
1,
"assign",
),
(
solana_system_interface::instruction::SystemInstruction::Transfer {
lamports: u64::MAX,
},
2,
"transfer",
),
(
solana_system_interface::instruction::SystemInstruction::CreateAccountWithSeed {
base: address,
seed: "seed".to_string(),
lamports: 1,
space: 2,
owner: address,
},
3,
"create_account_with_seed",
),
(
solana_system_interface::instruction::SystemInstruction::AdvanceNonceAccount,
3,
"advance_nonce_account",
),
(
solana_system_interface::instruction::SystemInstruction::WithdrawNonceAccount(1),
5,
"withdraw_nonce_account",
),
(
solana_system_interface::instruction::SystemInstruction::InitializeNonceAccount(
address,
),
3,
"initialize_nonce_account",
),
(
solana_system_interface::instruction::SystemInstruction::AuthorizeNonceAccount(
address,
),
2,
"authorize_nonce_account",
),
(
solana_system_interface::instruction::SystemInstruction::Allocate { space: 2 },
1,
"allocate",
),
(
solana_system_interface::instruction::SystemInstruction::AllocateWithSeed {
base: address,
seed: "seed".to_string(),
space: 2,
owner: address,
},
2,
"allocate_with_seed",
),
(
solana_system_interface::instruction::SystemInstruction::AssignWithSeed {
base: address,
seed: "seed".to_string(),
owner: address,
},
2,
"assign_with_seed",
),
(
solana_system_interface::instruction::SystemInstruction::TransferWithSeed {
lamports: 1,
from_seed: "seed".to_string(),
from_owner: address,
},
3,
"transfer_with_seed",
),
(
solana_system_interface::instruction::SystemInstruction::UpgradeNonceAccount,
1,
"upgrade_nonce_account",
),
(
solana_system_interface::instruction::SystemInstruction::CreateAccountAllowPrefund {
lamports: 1,
space: 2,
owner: address,
},
2,
"create_account_allow_prefund",
),
];
for (instruction, account_count, expected) in fixtures {
let input = replay_input(&instruction, account_count, false);
let result = crate::solana_core_system_decode(&input);
assert_eq!(result.status, crate::DecoderOutcomeStatus::Decoded);
assert_eq!(
result.recognized_entry_code.as_deref(),
std::option::Option::Some(expected)
);
assert_eq!(result.observations.len(), 1);
}
}
#[test]
fn additional_accounts_are_preserved_without_rejecting_valid_system_instructions() {
let instruction =
solana_system_interface::instruction::SystemInstruction::Transfer { lamports: 1 };
let result = crate::solana_core_system_decode(&replay_input(&instruction, 3, false));
assert_eq!(result.status, crate::DecoderOutcomeStatus::Decoded);
let accounts = match result.observations[0].payload_json["accounts"].as_array() {
std::option::Option::Some(value) => value,
std::option::Option::None => panic!("decoded System accounts are not an array"),
};
assert_eq!(accounts.len(), 3);
assert_eq!(accounts[2]["role"], "additional_account");
}
#[test]
fn missing_accounts_and_truncated_payload_fail_without_panic() {
let instruction =
solana_system_interface::instruction::SystemInstruction::Transfer { lamports: 1 };
let missing = crate::solana_core_system_decode(&replay_input(&instruction, 1, false));
assert_eq!(missing.status, crate::DecoderOutcomeStatus::Failed);
let truncated = crate::solana_core_system_decode(&replay_input_bytes(&[2, 0, 0], 2, false));
assert_eq!(truncated.status, crate::DecoderOutcomeStatus::Failed);
}
#[test]
fn unknown_tag_is_unsupported_and_hashable_for_replay() {
let result =
crate::solana_core_system_decode(&replay_input_bytes(&[255, 0, 0, 0], 0, false));
assert_eq!(result.status, crate::DecoderOutcomeStatus::Unsupported);
assert_eq!(
result.recognized_entry_code.as_deref(),
std::option::Option::Some("unknown_system_instruction")
);
}
#[test]
fn failed_transaction_is_decoded_as_uncommitted_intent() {
let instruction =
solana_system_interface::instruction::SystemInstruction::Transfer { lamports: 55 };
let result = crate::solana_core_system_decode(&replay_input(&instruction, 2, true));
assert_eq!(result.status, crate::DecoderOutcomeStatus::Decoded);
assert!(!result.observations[0].observation_committed);
assert_eq!(result.observations[0].payload_json["transactionSucceeded"], false);
}
#[test]
fn event_serialization_is_deterministic() {
let instruction =
solana_system_interface::instruction::SystemInstruction::Transfer { lamports: 55 };
let input = replay_input(&instruction, 2, false);
let first = crate::solana_core_system_decode(&input);
let second = crate::solana_core_system_decode(&input);
let first_json = match serde_json::to_string(&first) {
std::result::Result::Ok(value) => value,
std::result::Result::Err(error) => panic!("first event 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 event serialization failed: {error}"),
};
assert_eq!(first_json, second_json);
}
#[test]
fn coverage_declares_all_current_system_variants() {
let coverage = crate::solana_core_system_coverage();
assert_eq!(coverage.len(), 14);
assert!(coverage.iter().any(|entry| {
return entry.entry_code == "create_account_allow_prefund"
&& entry.discriminator_hex.as_deref() == std::option::Option::Some("0d000000");
}));
}
}

File diff suppressed because it is too large Load Diff

View File

@@ -0,0 +1,721 @@
// file: kb-lib/src/decoder/solana/core/zk_elgamal.rs
// version: 4
//! Exact structural decoding for the native ZK ElGamal Proof program.
const SOURCE: &str = "solana-zk-elgamal-proof-interface@^0.1 ProofInstruction and Pod layouts; Agave native runtime proof-account mode";
const PROOF_ACCOUNT_INSTRUCTION_BYTES: usize = 5;
const PROOF_COMPONENT_PREFIX_BYTES: usize = 16;
#[derive(Clone, Copy, Debug)]
struct ProofDescriptor {
entry_code: &'static str,
proof_data_size: usize,
context_data_size: usize,
}
/// Returns declared ZK ElGamal Proof instruction coverage.
pub(crate) fn zk_elgamal_coverage() -> std::vec::Vec<crate::DecoderCoverageDeclaration> {
return instruction_entries()
.iter()
.map(|(tag, entry_code)| {
return crate::DecoderCoverageDeclaration {
program_id: kb_program_ids::ZK_ELGAMAL_PROOF_PROGRAM_ID.to_string(),
surface_code: std::option::Option::Some(
crate::SOLANA_CORE_ZK_ELGAMAL_PROOF_SURFACE_CODE.to_string(),
),
entry_kind: crate::DecoderCoverageEntryKind::Instruction,
entry_code: (*entry_code).to_string(),
discriminator_hex: std::option::Option::Some(format!("{tag:02x}")),
historical: false,
};
})
.collect();
}
/// Recognizes one native ZK ElGamal Proof instruction.
pub(crate) fn zk_elgamal_recognize(
input: &crate::CoreInstructionReplayInput,
priority: u16,
) -> crate::DecoderRecognition {
let bytes = match crate::solana_core_decode_instruction_data(input) {
std::result::Result::Ok(value) => value,
std::result::Result::Err(_error) => {
return crate::DecoderRecognition::compatible(
false,
priority,
std::option::Option::Some(
crate::SOLANA_CORE_ZK_ELGAMAL_PROOF_SURFACE_CODE.to_string(),
),
std::option::Option::Some("malformed_zk_elgamal_proof_instruction".to_string()),
std::option::Option::None,
);
},
};
let instruction =
match solana_zk_elgamal_proof_interface::instruction::ProofInstruction::instruction_type(
bytes.as_slice(),
) {
std::option::Option::Some(value) => value,
std::option::Option::None => {
let entry_code = if bytes.is_empty() {
"malformed_zk_elgamal_proof_instruction"
} else {
"unknown_zk_elgamal_proof_instruction"
};
return crate::DecoderRecognition::compatible(
false,
priority,
std::option::Option::Some(
crate::SOLANA_CORE_ZK_ELGAMAL_PROOF_SURFACE_CODE.to_string(),
),
std::option::Option::Some(entry_code.to_string()),
crate::solana_core_hexadecimal_prefix(bytes.as_slice(), bytes.len().min(1)),
);
},
};
let (entry_code, exact) = match instruction {
solana_zk_elgamal_proof_interface::instruction::ProofInstruction::CloseContextState => {
("close_context_state", true)
},
_ => {
let descriptor = proof_descriptor(instruction);
let descriptor = match descriptor {
std::option::Option::Some(value) => value,
std::option::Option::None => {
return crate::DecoderRecognition::compatible(
false,
priority,
std::option::Option::Some(
crate::SOLANA_CORE_ZK_ELGAMAL_PROOF_SURFACE_CODE.to_string(),
),
std::option::Option::Some(
"unknown_zk_elgamal_proof_instruction".to_string(),
),
crate::solana_core_hexadecimal_prefix(bytes.as_slice(), 1),
);
},
};
let inline_size = descriptor.proof_data_size.saturating_add(1);
(
descriptor.entry_code,
bytes.len() == PROOF_ACCOUNT_INSTRUCTION_BYTES || bytes.len() == inline_size,
)
},
};
return crate::DecoderRecognition::compatible(
exact,
priority,
std::option::Option::Some(crate::SOLANA_CORE_ZK_ELGAMAL_PROOF_SURFACE_CODE.to_string()),
std::option::Option::Some(entry_code.to_string()),
crate::solana_core_hexadecimal_prefix(bytes.as_slice(), 1),
);
}
/// Decodes one native ZK ElGamal Proof instruction without recomputing the proof.
pub(crate) fn zk_elgamal_decode(
input: &crate::CoreInstructionReplayInput,
) -> crate::DecoderExecutionResult {
let bytes = match crate::solana_core_decode_instruction_data(input) {
std::result::Result::Ok(value) => value,
std::result::Result::Err(error) => {
return crate::solana_core_failed_result(
std::option::Option::Some("malformed_zk_elgamal_proof_instruction"),
"zk_elgamal_payload_invalid",
error.to_string(),
);
},
};
let instruction =
match solana_zk_elgamal_proof_interface::instruction::ProofInstruction::instruction_type(
bytes.as_slice(),
) {
std::option::Option::Some(value) => value,
std::option::Option::None => {
if bytes.is_empty() {
return crate::solana_core_failed_result(
std::option::Option::Some("malformed_zk_elgamal_proof_instruction"),
"zk_elgamal_payload_empty",
"ZK ElGamal Proof instruction payload is empty",
);
}
let tag = match bytes.first() {
std::option::Option::Some(value) => *value,
std::option::Option::None => {
return crate::solana_core_failed_result(
std::option::Option::Some("malformed_zk_elgamal_proof_instruction"),
"zk_elgamal_payload_empty",
"ZK ElGamal Proof instruction payload is empty",
);
},
};
return crate::solana_core_unsupported_result(
"unknown_zk_elgamal_proof_instruction",
"zk_elgamal_tag_unknown",
format!(
"unknown ZK ElGamal Proof instruction tag {tag}; payload_sha256={}",
crate::solana_core_payload_hash(input)
),
);
},
};
if instruction
== solana_zk_elgamal_proof_interface::instruction::ProofInstruction::CloseContextState
{
return decode_close_context_state(input, bytes.as_slice());
}
let descriptor = match proof_descriptor(instruction) {
std::option::Option::Some(value) => value,
std::option::Option::None => {
return crate::solana_core_unsupported_result(
"unknown_zk_elgamal_proof_instruction",
"zk_elgamal_instruction_unmapped",
"official ZK ElGamal Proof instruction has no local descriptor",
);
},
};
return decode_verify_proof(input, bytes.as_slice(), descriptor);
}
fn decode_close_context_state(
input: &crate::CoreInstructionReplayInput,
bytes: &[u8],
) -> crate::DecoderExecutionResult {
let roles = [
crate::SolanaCoreAccountRole::new("proof_context_state", true, false),
crate::SolanaCoreAccountRole::new("lamport_destination", true, false),
crate::SolanaCoreAccountRole::new("context_state_authority", false, true),
];
let accounts = match crate::solana_core_resolve_accounts(
input,
roles.as_slice(),
3,
std::option::Option::None,
) {
std::result::Result::Ok(value) => value,
std::result::Result::Err(error) => {
return crate::solana_core_failed_result(
std::option::Option::Some("close_context_state"),
"zk_elgamal_close_accounts_invalid",
error.to_string(),
);
},
};
let account_array = match accounts.as_array() {
std::option::Option::Some(value) => value,
std::option::Option::None => {
return crate::solana_core_failed_result(
std::option::Option::Some("close_context_state"),
"zk_elgamal_close_accounts_not_array",
"resolved ZK ElGamal close accounts are not an array",
);
},
};
let context_key = account_array
.first()
.and_then(|value| return value.get("accountKey"))
.and_then(serde_json::Value::as_str);
let destination_key = account_array
.get(1)
.and_then(|value| return value.get("accountKey"))
.and_then(serde_json::Value::as_str);
if context_key.is_some() && context_key == destination_key {
return crate::solana_core_failed_result(
std::option::Option::Some("close_context_state"),
"zk_elgamal_close_destination_matches_context",
"ZK ElGamal proof context account and lamport destination must differ",
);
}
let trailing = match bytes.get(1..) {
std::option::Option::Some(value) => value,
std::option::Option::None => {
return crate::solana_core_failed_result(
std::option::Option::Some("close_context_state"),
"zk_elgamal_close_payload_missing_tag",
"ZK ElGamal close-context instruction has no discriminator byte",
);
},
};
return crate::solana_core_decoded_result(
input,
crate::SOLANA_CORE_ZK_ELGAMAL_PROOF_SURFACE_CODE,
"close_context_state",
crate::EventFamily::Lifecycle,
false,
accounts,
serde_json::json!({
"instructionMode": "close_context_state",
"runtimeAcceptsTrailingBytes": true,
"trailingByteLength": trailing.len(),
"trailingSha256": crate::solana_core_hash_bytes(trailing),
"trailingPrefixHex": crate::solana_core_bounded_hexadecimal_prefix(
trailing,
PROOF_COMPONENT_PREFIX_BYTES,
),
"reclaimsLamports": true,
"resetsOwnerToSystemProgram": true,
"runtimeVerification": runtime_verification(input),
}),
SOURCE,
);
}
fn decode_verify_proof(
input: &crate::CoreInstructionReplayInput,
bytes: &[u8],
descriptor: ProofDescriptor,
) -> crate::DecoderExecutionResult {
let external_proof = bytes.len() == PROOF_ACCOUNT_INSTRUCTION_BYTES;
let expected_inline_size = descriptor.proof_data_size.saturating_add(1);
if !external_proof && bytes.len() != expected_inline_size {
return crate::solana_core_failed_result(
std::option::Option::Some(descriptor.entry_code),
"zk_elgamal_proof_data_size_invalid",
format!(
"{} requires either {} bytes for proof-account mode or {} bytes for inline proof data but received {}",
descriptor.entry_code,
PROOF_ACCOUNT_INSTRUCTION_BYTES,
expected_inline_size,
bytes.len()
),
);
}
let account_count = match input.instruction_accounts_json.as_array() {
std::option::Option::Some(value) => value.len(),
std::option::Option::None => {
return crate::solana_core_failed_result(
std::option::Option::Some(descriptor.entry_code),
"zk_elgamal_accounts_not_array",
"ZK ElGamal Proof instruction accounts are not an array",
);
},
};
let context_state_requested =
if external_proof { account_count >= 3 } else { account_count >= 2 };
let mut roles = std::vec::Vec::new();
if external_proof {
roles.push(crate::SolanaCoreAccountRole::new("proof_data_account", false, false));
}
if context_state_requested {
roles.push(crate::SolanaCoreAccountRole::new("proof_context_state", true, false));
roles.push(crate::SolanaCoreAccountRole::new("context_state_authority", false, false));
}
let minimum_accounts = if external_proof { 1 } else { 0 };
let accounts = match crate::solana_core_resolve_accounts(
input,
roles.as_slice(),
minimum_accounts,
std::option::Option::None,
) {
std::result::Result::Ok(value) => value,
std::result::Result::Err(error) => {
return crate::solana_core_failed_result(
std::option::Option::Some(descriptor.entry_code),
"zk_elgamal_accounts_invalid",
error.to_string(),
);
},
};
let proof_parameters = if external_proof {
let offset = match crate::solana_core_read_u32_le(bytes, 1) {
std::result::Result::Ok(value) => value,
std::result::Result::Err(error) => {
return crate::solana_core_failed_result(
std::option::Option::Some(descriptor.entry_code),
"zk_elgamal_proof_account_offset_invalid",
error.to_string(),
);
},
};
serde_json::json!({
"proofDataSource": "account",
"proofAccountOffset": offset,
"proofDataAvailability": "external_account_not_captured_by_transaction_core",
"proofDataByteLength": descriptor.proof_data_size,
"contextDataByteLength": descriptor.context_data_size,
"proofBodyByteLength": descriptor.proof_data_size.saturating_sub(descriptor.context_data_size),
"proofDataSha256": serde_json::Value::Null,
"contextDataSha256": serde_json::Value::Null,
"proofBodySha256": serde_json::Value::Null,
})
} else {
let proof_data = match bytes.get(1..) {
std::option::Option::Some(value) => value,
std::option::Option::None => {
return crate::solana_core_failed_result(
std::option::Option::Some(descriptor.entry_code),
"zk_elgamal_inline_proof_missing",
"inline ZK ElGamal proof data is missing",
);
},
};
let context_data = match proof_data.get(..descriptor.context_data_size) {
std::option::Option::Some(value) => value,
std::option::Option::None => {
return crate::solana_core_failed_result(
std::option::Option::Some(descriptor.entry_code),
"zk_elgamal_inline_context_truncated",
"inline ZK ElGamal proof context data is truncated",
);
},
};
let proof_body = match proof_data.get(descriptor.context_data_size..) {
std::option::Option::Some(value) => value,
std::option::Option::None => {
return crate::solana_core_failed_result(
std::option::Option::Some(descriptor.entry_code),
"zk_elgamal_inline_proof_body_truncated",
"inline ZK ElGamal proof body is truncated",
);
},
};
serde_json::json!({
"proofDataSource": "instruction_data",
"proofAccountOffset": serde_json::Value::Null,
"proofDataAvailability": "inline_hashed_not_retained",
"proofDataByteLength": proof_data.len(),
"contextDataByteLength": context_data.len(),
"proofBodyByteLength": proof_body.len(),
"proofDataSha256": crate::solana_core_hash_bytes(proof_data),
"proofDataPrefixHex": crate::solana_core_bounded_hexadecimal_prefix(
proof_data,
PROOF_COMPONENT_PREFIX_BYTES,
),
"contextDataSha256": crate::solana_core_hash_bytes(context_data),
"contextDataPrefixHex": crate::solana_core_bounded_hexadecimal_prefix(
context_data,
PROOF_COMPONENT_PREFIX_BYTES,
),
"proofBodySha256": crate::solana_core_hash_bytes(proof_body),
"proofBodyPrefixHex": crate::solana_core_bounded_hexadecimal_prefix(
proof_body,
PROOF_COMPONENT_PREFIX_BYTES,
),
})
};
return crate::solana_core_decoded_result(
input,
crate::SOLANA_CORE_ZK_ELGAMAL_PROOF_SURFACE_CODE,
descriptor.entry_code,
crate::EventFamily::Audit,
false,
accounts,
serde_json::json!({
"proofType": descriptor.entry_code,
"proof": proof_parameters,
"contextStateRequested": context_state_requested,
"contextStateMutation": if context_state_requested {
"initialize_after_successful_verification"
} else {
"none"
},
"cryptographicVerificationPerformedByDecoder": false,
"runtimeVerification": runtime_verification(input),
}),
SOURCE,
);
}
fn proof_descriptor(
instruction: solana_zk_elgamal_proof_interface::instruction::ProofInstruction,
) -> std::option::Option<ProofDescriptor> {
let descriptor = match instruction {
solana_zk_elgamal_proof_interface::instruction::ProofInstruction::CloseContextState => {
return std::option::Option::None;
},
solana_zk_elgamal_proof_interface::instruction::ProofInstruction::VerifyZeroCiphertext => {
ProofDescriptor {
entry_code: "verify_zero_ciphertext",
proof_data_size: std::mem::size_of::<solana_zk_elgamal_proof_interface::proof_data::ZeroCiphertextProofData>(),
context_data_size: std::mem::size_of::<solana_zk_elgamal_proof_interface::proof_data::ZeroCiphertextProofContext>(),
}
},
solana_zk_elgamal_proof_interface::instruction::ProofInstruction::VerifyCiphertextCiphertextEquality => ProofDescriptor {
entry_code: "verify_ciphertext_ciphertext_equality",
proof_data_size: std::mem::size_of::<solana_zk_elgamal_proof_interface::proof_data::CiphertextCiphertextEqualityProofData>(),
context_data_size: std::mem::size_of::<solana_zk_elgamal_proof_interface::proof_data::CiphertextCiphertextEqualityProofContext>(),
},
solana_zk_elgamal_proof_interface::instruction::ProofInstruction::VerifyCiphertextCommitmentEquality => ProofDescriptor {
entry_code: "verify_ciphertext_commitment_equality",
proof_data_size: std::mem::size_of::<solana_zk_elgamal_proof_interface::proof_data::CiphertextCommitmentEqualityProofData>(),
context_data_size: std::mem::size_of::<solana_zk_elgamal_proof_interface::proof_data::CiphertextCommitmentEqualityProofContext>(),
},
solana_zk_elgamal_proof_interface::instruction::ProofInstruction::VerifyPubkeyValidity => {
ProofDescriptor {
entry_code: "verify_pubkey_validity",
proof_data_size: std::mem::size_of::<solana_zk_elgamal_proof_interface::proof_data::PubkeyValidityProofData>(),
context_data_size: std::mem::size_of::<solana_zk_elgamal_proof_interface::proof_data::PubkeyValidityProofContext>(),
}
},
solana_zk_elgamal_proof_interface::instruction::ProofInstruction::VerifyPercentageWithCap => ProofDescriptor {
entry_code: "verify_percentage_with_cap",
proof_data_size: std::mem::size_of::<solana_zk_elgamal_proof_interface::proof_data::PercentageWithCapProofData>(),
context_data_size: std::mem::size_of::<solana_zk_elgamal_proof_interface::proof_data::PercentageWithCapProofContext>(),
},
solana_zk_elgamal_proof_interface::instruction::ProofInstruction::VerifyBatchedRangeProofU64 => ProofDescriptor {
entry_code: "verify_batched_range_proof_u64",
proof_data_size: std::mem::size_of::<solana_zk_elgamal_proof_interface::proof_data::BatchedRangeProofU64Data>(),
context_data_size: std::mem::size_of::<solana_zk_elgamal_proof_interface::proof_data::BatchedRangeProofContext>(),
},
solana_zk_elgamal_proof_interface::instruction::ProofInstruction::VerifyBatchedRangeProofU128 => ProofDescriptor {
entry_code: "verify_batched_range_proof_u128",
proof_data_size: std::mem::size_of::<solana_zk_elgamal_proof_interface::proof_data::BatchedRangeProofU128Data>(),
context_data_size: std::mem::size_of::<solana_zk_elgamal_proof_interface::proof_data::BatchedRangeProofContext>(),
},
solana_zk_elgamal_proof_interface::instruction::ProofInstruction::VerifyBatchedRangeProofU256 => ProofDescriptor {
entry_code: "verify_batched_range_proof_u256",
proof_data_size: std::mem::size_of::<solana_zk_elgamal_proof_interface::proof_data::BatchedRangeProofU256Data>(),
context_data_size: std::mem::size_of::<solana_zk_elgamal_proof_interface::proof_data::BatchedRangeProofContext>(),
},
solana_zk_elgamal_proof_interface::instruction::ProofInstruction::VerifyGroupedCiphertext2HandlesValidity => ProofDescriptor {
entry_code: "verify_grouped_ciphertext_2_handles_validity",
proof_data_size: std::mem::size_of::<solana_zk_elgamal_proof_interface::proof_data::GroupedCiphertext2HandlesValidityProofData>(),
context_data_size: std::mem::size_of::<solana_zk_elgamal_proof_interface::proof_data::GroupedCiphertext2HandlesValidityProofContext>(),
},
solana_zk_elgamal_proof_interface::instruction::ProofInstruction::VerifyBatchedGroupedCiphertext2HandlesValidity => ProofDescriptor {
entry_code: "verify_batched_grouped_ciphertext_2_handles_validity",
proof_data_size: std::mem::size_of::<solana_zk_elgamal_proof_interface::proof_data::BatchedGroupedCiphertext2HandlesValidityProofData>(),
context_data_size: std::mem::size_of::<solana_zk_elgamal_proof_interface::proof_data::BatchedGroupedCiphertext2HandlesValidityProofContext>(),
},
solana_zk_elgamal_proof_interface::instruction::ProofInstruction::VerifyGroupedCiphertext3HandlesValidity => ProofDescriptor {
entry_code: "verify_grouped_ciphertext_3_handles_validity",
proof_data_size: std::mem::size_of::<solana_zk_elgamal_proof_interface::proof_data::GroupedCiphertext3HandlesValidityProofData>(),
context_data_size: std::mem::size_of::<solana_zk_elgamal_proof_interface::proof_data::GroupedCiphertext3HandlesValidityProofContext>(),
},
solana_zk_elgamal_proof_interface::instruction::ProofInstruction::VerifyBatchedGroupedCiphertext3HandlesValidity => ProofDescriptor {
entry_code: "verify_batched_grouped_ciphertext_3_handles_validity",
proof_data_size: std::mem::size_of::<solana_zk_elgamal_proof_interface::proof_data::BatchedGroupedCiphertext3HandlesValidityProofData>(),
context_data_size: std::mem::size_of::<solana_zk_elgamal_proof_interface::proof_data::BatchedGroupedCiphertext3HandlesValidityProofContext>(),
},
};
return std::option::Option::Some(descriptor);
}
fn runtime_verification(input: &crate::CoreInstructionReplayInput) -> &'static str {
if input.transaction_failed {
return "not_asserted_transaction_failed";
}
return "accepted_in_successful_transaction";
}
fn instruction_entries() -> &'static [(u8, &'static str)] {
return &[
(0, "close_context_state"),
(1, "verify_zero_ciphertext"),
(2, "verify_ciphertext_ciphertext_equality"),
(3, "verify_ciphertext_commitment_equality"),
(4, "verify_pubkey_validity"),
(5, "verify_percentage_with_cap"),
(6, "verify_batched_range_proof_u64"),
(7, "verify_batched_range_proof_u128"),
(8, "verify_batched_range_proof_u256"),
(9, "verify_grouped_ciphertext_2_handles_validity"),
(10, "verify_batched_grouped_ciphertext_2_handles_validity"),
(11, "verify_grouped_ciphertext_3_handles_validity"),
(12, "verify_batched_grouped_ciphertext_3_handles_validity"),
];
}
#[cfg(test)]
mod tests {
use base64::Engine; // rust-rules: trait-import
fn replay_input(
bytes: &[u8],
account_count: usize,
transaction_failed: bool,
) -> crate::CoreInstructionReplayInput {
let account_keys = (0..account_count)
.map(|index| {
return serde_json::json!({
"accountIndex": index,
"accountKey": format!("ZkAccount{index:02}111111111111111111111111111"),
"source": "static",
"writable": true,
"signer": index == 2,
"executable": false,
});
})
.collect::<std::vec::Vec<_>>();
let instruction_accounts = account_keys
.iter()
.map(|account| {
return serde_json::json!({
"accountIndex": account["accountIndex"],
"accountKey": account["accountKey"],
});
})
.collect::<std::vec::Vec<_>>();
let result = crate::CoreInstructionReplayInput::new(
"signature:0",
"signature",
42,
"0",
kb_program_ids::ZK_ELGAMAL_PROOF_PROGRAM_ID,
transaction_failed,
if transaction_failed {
std::option::Option::Some(serde_json::json!({"InstructionError": [0, "Custom"]}))
} else {
std::option::Option::None
},
serde_json::Value::Array(account_keys),
serde_json::Value::Array(instruction_accounts),
std::option::Option::Some(serde_json::json!({
"dataBase64": base64::engine::general_purpose::STANDARD.encode(bytes),
})),
std::option::Option::Some("payload-hash".to_string()),
serde_json::json!([]),
serde_json::json!([]),
serde_json::json!([]),
serde_json::json!([]),
);
return match result {
std::result::Result::Ok(value) => value,
std::result::Result::Err(error) => panic!("ZK ElGamal replay input failed: {error}"),
};
}
fn inline_fixture(
tag: u8,
instruction: solana_zk_elgamal_proof_interface::instruction::ProofInstruction,
) -> std::vec::Vec<u8> {
let descriptor = match super::proof_descriptor(instruction) {
std::option::Option::Some(value) => value,
std::option::Option::None => panic!("proof descriptor missing"),
};
let mut bytes = vec![tag];
bytes.resize(descriptor.proof_data_size.saturating_add(1), tag);
return bytes;
}
#[test]
fn coverage_declares_all_thirteen_official_instructions() {
let coverage = crate::solana_core_zk_elgamal_coverage();
assert_eq!(coverage.len(), 13);
for (tag, entry_code) in super::instruction_entries() {
let expected_discriminator = format!("{tag:02x}");
assert!(coverage.iter().any(|entry| {
return entry.entry_code == *entry_code
&& entry.discriminator_hex.as_deref()
== std::option::Option::Some(expected_discriminator.as_str());
}));
}
}
#[test]
fn official_discriminants_map_to_stable_entry_codes() {
for (tag, entry_code) in super::instruction_entries() {
let recognition =
crate::solana_core_zk_elgamal_recognize(&replay_input(&[*tag], 3, false), 100);
assert!(recognition.compatible);
assert_eq!(recognition.entry_code.as_deref(), std::option::Option::Some(*entry_code));
}
}
#[test]
fn proof_account_mode_decodes_offset_and_context_request() {
let bytes = [1_u8, 44, 0, 0, 0];
let result = crate::solana_core_zk_elgamal_decode(&replay_input(&bytes, 3, false));
assert_eq!(result.status, crate::DecoderOutcomeStatus::Decoded);
let parameters = &result.observations[0].payload_json["parameters"];
assert_eq!(parameters["proof"]["proofDataSource"], "account");
assert_eq!(parameters["proof"]["proofAccountOffset"], 44);
assert_eq!(parameters["contextStateRequested"], true);
assert_eq!(parameters["cryptographicVerificationPerformedByDecoder"], false);
assert_eq!(parameters["runtimeVerification"], "accepted_in_successful_transaction");
}
#[test]
fn proof_account_mode_requires_the_proof_account() {
let result =
crate::solana_core_zk_elgamal_decode(&replay_input(&[1, 0, 0, 0, 0], 0, false));
assert_eq!(result.status, crate::DecoderOutcomeStatus::Failed);
}
#[test]
fn every_inline_proof_layout_is_bounded_by_official_pod_sizes() {
let variants = [
(1, solana_zk_elgamal_proof_interface::instruction::ProofInstruction::VerifyZeroCiphertext),
(2, solana_zk_elgamal_proof_interface::instruction::ProofInstruction::VerifyCiphertextCiphertextEquality),
(3, solana_zk_elgamal_proof_interface::instruction::ProofInstruction::VerifyCiphertextCommitmentEquality),
(4, solana_zk_elgamal_proof_interface::instruction::ProofInstruction::VerifyPubkeyValidity),
(5, solana_zk_elgamal_proof_interface::instruction::ProofInstruction::VerifyPercentageWithCap),
(6, solana_zk_elgamal_proof_interface::instruction::ProofInstruction::VerifyBatchedRangeProofU64),
(7, solana_zk_elgamal_proof_interface::instruction::ProofInstruction::VerifyBatchedRangeProofU128),
(8, solana_zk_elgamal_proof_interface::instruction::ProofInstruction::VerifyBatchedRangeProofU256),
(9, solana_zk_elgamal_proof_interface::instruction::ProofInstruction::VerifyGroupedCiphertext2HandlesValidity),
(10, solana_zk_elgamal_proof_interface::instruction::ProofInstruction::VerifyBatchedGroupedCiphertext2HandlesValidity),
(11, solana_zk_elgamal_proof_interface::instruction::ProofInstruction::VerifyGroupedCiphertext3HandlesValidity),
(12, solana_zk_elgamal_proof_interface::instruction::ProofInstruction::VerifyBatchedGroupedCiphertext3HandlesValidity),
];
for (tag, instruction) in variants {
let bytes = inline_fixture(tag, instruction);
let result =
crate::solana_core_zk_elgamal_decode(&replay_input(bytes.as_slice(), 0, true));
assert_eq!(result.status, crate::DecoderOutcomeStatus::Decoded);
assert_eq!(result.observations.len(), 1);
assert!(!result.observations[0].observation_committed);
assert_eq!(
result.observations[0].payload_json["parameters"]["proof"]["proofDataByteLength"],
bytes.len().saturating_sub(1)
);
}
}
#[test]
fn invalid_inline_sizes_unknown_tags_and_empty_payload_fail_safely() {
let truncated = crate::solana_core_zk_elgamal_decode(&replay_input(&[1, 2, 3], 0, false));
assert_eq!(truncated.status, crate::DecoderOutcomeStatus::Failed);
let unknown = crate::solana_core_zk_elgamal_decode(&replay_input(&[99], 0, false));
assert_eq!(unknown.status, crate::DecoderOutcomeStatus::Unsupported);
let empty = crate::solana_core_zk_elgamal_decode(&replay_input(&[], 0, false));
assert_eq!(empty.status, crate::DecoderOutcomeStatus::Failed);
}
#[test]
fn close_context_state_accepts_runtime_trailing_bytes_and_rejects_same_destination() {
let decoded = crate::solana_core_zk_elgamal_decode(&replay_input(&[0, 7, 8], 3, false));
assert_eq!(decoded.status, crate::DecoderOutcomeStatus::Decoded);
assert_eq!(decoded.observations[0].payload_json["parameters"]["trailingByteLength"], 2);
let mut same = replay_input(&[0], 3, false);
let accounts = match same.instruction_accounts_json.as_array_mut() {
std::option::Option::Some(value) => value,
std::option::Option::None => panic!("instruction accounts missing"),
};
accounts[1]["accountKey"] = accounts[0]["accountKey"].clone();
let keys = match same.account_keys_json.as_array_mut() {
std::option::Option::Some(value) => value,
std::option::Option::None => panic!("account keys missing"),
};
keys[1]["accountKey"] = keys[0]["accountKey"].clone();
let failed = crate::solana_core_zk_elgamal_decode(&same);
assert_eq!(failed.status, crate::DecoderOutcomeStatus::Failed);
}
#[test]
fn failed_transaction_never_claims_runtime_verification_or_commit() {
let result = crate::solana_core_zk_elgamal_decode(&replay_input(&[4, 0, 0, 0, 0], 1, true));
assert_eq!(result.status, crate::DecoderOutcomeStatus::Decoded);
assert!(!result.observations[0].observation_committed);
assert_eq!(
result.observations[0].payload_json["parameters"]["runtimeVerification"],
"not_asserted_transaction_failed"
);
}
#[test]
fn serialization_is_deterministic() {
let input = replay_input(&[1, 0, 0, 0, 0], 1, false);
let first = crate::solana_core_zk_elgamal_decode(&input);
let second = crate::solana_core_zk_elgamal_decode(&input);
let first_json = match serde_json::to_string(&first.observations[0].payload_json) {
std::result::Result::Ok(value) => value,
std::result::Result::Err(error) => panic!("first serialization failed: {error}"),
};
let second_json = match serde_json::to_string(&second.observations[0].payload_json) {
std::result::Result::Ok(value) => value,
std::result::Result::Err(error) => panic!("second serialization failed: {error}"),
};
assert_eq!(first_json, second_json);
}
}

View File

@@ -0,0 +1,973 @@
// file: kb-lib/src/decoder/solana/core/zk_token_proof.rs
// version: 4
//! Historical ZK Token Proof interface decoding with explicit current no-op runtime semantics.
const SOURCE: &str = "local bounded mirror of the historical ZK Token Proof wire contract audited against solana-zk-token-sdk 3.1.14 and Agave v2.0.0; Agave v4.1.1 no-op runtime stub";
const PROOF_ACCOUNT_INSTRUCTION_BYTES: usize = 5;
const PROOF_COMPONENT_PREFIX_BYTES: usize = 16;
#[derive(Clone, Copy, Debug, Eq, PartialEq)]
#[repr(u8)]
enum HistoricalProofInstruction {
CloseContextState = 0,
VerifyZeroBalance = 1,
VerifyWithdraw = 2,
VerifyCiphertextCiphertextEquality = 3,
VerifyTransfer = 4,
VerifyTransferWithFee = 5,
VerifyPubkeyValidity = 6,
VerifyRangeProofU64 = 7,
VerifyBatchedRangeProofU64 = 8,
VerifyBatchedRangeProofU128 = 9,
VerifyBatchedRangeProofU256 = 10,
VerifyCiphertextCommitmentEquality = 11,
VerifyGroupedCiphertext2HandlesValidity = 12,
VerifyBatchedGroupedCiphertext2HandlesValidity = 13,
VerifyFeeSigma = 14,
VerifyGroupedCiphertext3HandlesValidity = 15,
VerifyBatchedGroupedCiphertext3HandlesValidity = 16,
}
impl HistoricalProofInstruction {
fn from_tag(tag: u8) -> std::option::Option<Self> {
return match tag {
0 => std::option::Option::Some(Self::CloseContextState),
1 => std::option::Option::Some(Self::VerifyZeroBalance),
2 => std::option::Option::Some(Self::VerifyWithdraw),
3 => std::option::Option::Some(Self::VerifyCiphertextCiphertextEquality),
4 => std::option::Option::Some(Self::VerifyTransfer),
5 => std::option::Option::Some(Self::VerifyTransferWithFee),
6 => std::option::Option::Some(Self::VerifyPubkeyValidity),
7 => std::option::Option::Some(Self::VerifyRangeProofU64),
8 => std::option::Option::Some(Self::VerifyBatchedRangeProofU64),
9 => std::option::Option::Some(Self::VerifyBatchedRangeProofU128),
10 => std::option::Option::Some(Self::VerifyBatchedRangeProofU256),
11 => std::option::Option::Some(Self::VerifyCiphertextCommitmentEquality),
12 => std::option::Option::Some(Self::VerifyGroupedCiphertext2HandlesValidity),
13 => std::option::Option::Some(Self::VerifyBatchedGroupedCiphertext2HandlesValidity),
14 => std::option::Option::Some(Self::VerifyFeeSigma),
15 => std::option::Option::Some(Self::VerifyGroupedCiphertext3HandlesValidity),
16 => std::option::Option::Some(Self::VerifyBatchedGroupedCiphertext3HandlesValidity),
_ => std::option::Option::None,
};
}
}
fn historical_instruction(bytes: &[u8]) -> std::option::Option<HistoricalProofInstruction> {
return bytes.first().copied().and_then(HistoricalProofInstruction::from_tag);
}
#[derive(Clone, Copy, Debug)]
struct ProofDescriptor {
entry_code: &'static str,
proof_data_size: usize,
context_data_size: usize,
historical_inline_availability: &'static str,
}
/// Returns declared historical and current ZK Token Proof coverage.
pub(crate) fn zk_token_proof_coverage() -> std::vec::Vec<crate::DecoderCoverageDeclaration> {
let mut coverage = instruction_entries()
.iter()
.map(|(tag, entry_code)| {
return crate::DecoderCoverageDeclaration {
program_id: kb_program_ids::ZK_TOKEN_PROOF_PROGRAM_ID.to_string(),
surface_code: std::option::Option::Some(
crate::SOLANA_CORE_ZK_TOKEN_PROOF_SURFACE_CODE.to_string(),
),
entry_kind: crate::DecoderCoverageEntryKind::Instruction,
entry_code: (*entry_code).to_string(),
discriminator_hex: std::option::Option::Some(format!("{tag:02x}")),
historical: true,
};
})
.collect::<std::vec::Vec<_>>();
coverage.push(crate::DecoderCoverageDeclaration {
program_id: kb_program_ids::ZK_TOKEN_PROOF_PROGRAM_ID.to_string(),
surface_code: std::option::Option::Some(
crate::SOLANA_CORE_ZK_TOKEN_PROOF_SURFACE_CODE.to_string(),
),
entry_kind: crate::DecoderCoverageEntryKind::Instruction,
entry_code: "current_runtime_noop_invocation".to_string(),
discriminator_hex: std::option::Option::None,
historical: false,
});
return coverage;
}
/// Recognizes one historical ZK Token Proof layout or the current no-op runtime fallback.
pub(crate) fn zk_token_proof_recognize(
input: &crate::CoreInstructionReplayInput,
priority: u16,
) -> crate::DecoderRecognition {
let bytes = match crate::solana_core_decode_instruction_data(input) {
std::result::Result::Ok(value) => value,
std::result::Result::Err(_error) => {
return crate::DecoderRecognition::compatible(
false,
priority,
std::option::Option::Some(
crate::SOLANA_CORE_ZK_TOKEN_PROOF_SURFACE_CODE.to_string(),
),
std::option::Option::Some("malformed_zk_token_proof_instruction".to_string()),
std::option::Option::None,
);
},
};
let instruction = historical_instruction(bytes.as_slice());
let entry_code = match instruction {
std::option::Option::Some(HistoricalProofInstruction::CloseContextState) => {
"close_context_state"
},
std::option::Option::Some(value) => {
let descriptor = proof_descriptor(value);
match descriptor {
std::option::Option::Some(value) => {
let inline_size = value.proof_data_size.checked_add(1);
if bytes.len() == PROOF_ACCOUNT_INSTRUCTION_BYTES
|| inline_size.is_some_and(|size| return bytes.len() == size)
{
value.entry_code
} else {
"current_runtime_noop_invocation"
}
},
std::option::Option::None => "current_runtime_noop_invocation",
}
},
std::option::Option::None => "current_runtime_noop_invocation",
};
return crate::DecoderRecognition::compatible(
true,
priority,
std::option::Option::Some(crate::SOLANA_CORE_ZK_TOKEN_PROOF_SURFACE_CODE.to_string()),
std::option::Option::Some(entry_code.to_string()),
crate::solana_core_hexadecimal_prefix(bytes.as_slice(), bytes.len().min(1)),
);
}
/// Decodes one historical ZK Token Proof layout without recomputing any proof.
pub(crate) fn zk_token_proof_decode(
input: &crate::CoreInstructionReplayInput,
) -> crate::DecoderExecutionResult {
let bytes = match crate::solana_core_decode_instruction_data(input) {
std::result::Result::Ok(value) => value,
std::result::Result::Err(error) => {
return crate::solana_core_failed_result(
std::option::Option::Some("malformed_zk_token_proof_instruction"),
"zk_token_proof_payload_invalid",
error.to_string(),
);
},
};
let instruction = historical_instruction(bytes.as_slice());
let instruction = match instruction {
std::option::Option::Some(value) => value,
std::option::Option::None => {
return decode_current_noop(
input,
bytes.as_slice(),
"empty_or_unknown_historical_discriminator",
);
},
};
if instruction == HistoricalProofInstruction::CloseContextState {
return decode_close_context_state(input, bytes.as_slice());
}
let descriptor = match proof_descriptor(instruction) {
std::option::Option::Some(value) => value,
std::option::Option::None => {
return decode_current_noop(input, bytes.as_slice(), "unmapped_historical_instruction");
},
};
let inline_size = match descriptor.proof_data_size.checked_add(1) {
std::option::Option::Some(value) => value,
std::option::Option::None => {
return crate::solana_core_failed_result(
std::option::Option::Some(descriptor.entry_code),
"zk_token_proof_inline_size_overflow",
"historical ZK Token Proof inline size overflowed usize",
);
},
};
if bytes.len() != PROOF_ACCOUNT_INSTRUCTION_BYTES && bytes.len() != inline_size {
return decode_current_noop(input, bytes.as_slice(), "historical_proof_size_mismatch");
}
return decode_verify_proof(input, bytes.as_slice(), descriptor);
}
fn decode_current_noop(
input: &crate::CoreInstructionReplayInput,
bytes: &[u8],
historical_parse_status: &str,
) -> crate::DecoderExecutionResult {
let accounts =
match crate::solana_core_resolve_accounts(input, &[], 0, std::option::Option::None) {
std::result::Result::Ok(value) => value,
std::result::Result::Err(error) => {
return crate::solana_core_failed_result(
std::option::Option::Some("current_runtime_noop_invocation"),
"zk_token_proof_accounts_invalid",
error.to_string(),
);
},
};
return crate::solana_core_decoded_result(
input,
crate::SOLANA_CORE_ZK_TOKEN_PROOF_SURFACE_CODE,
"current_runtime_noop_invocation",
crate::EventFamily::Audit,
false,
accounts,
serde_json::json!({
"instructionMode": "opaque_current_runtime_noop",
"historicalParseStatus": historical_parse_status,
"historicalDiscriminator": bytes.first().copied(),
"payloadByteLength": bytes.len(),
"payloadSha256": crate::solana_core_hash_bytes(bytes),
"payloadPrefixHex": crate::solana_core_bounded_hexadecimal_prefix(
bytes,
PROOF_COMPONENT_PREFIX_BYTES,
),
"historicalActivationEvidence": "no_public_activation_epoch_documented_in_feature_gate_issue",
"decoderValidationBasis": "official_interface_runtime_sources_and_synthetic_fixtures",
"historicalRuntimeReference": "agave_v2_0_0_gated_verifier",
"currentRuntimeReference": "agave_v4_1_1_no_op_success_stub",
"currentRuntimeBehavior": "no_op_success_stub",
"cryptographicVerificationPerformedByDecoder": false,
"cryptographicVerificationClaim": verification_claim(input),
"runtimeExecutionOutcome": runtime_execution_outcome(input),
}),
SOURCE,
);
}
fn decode_close_context_state(
input: &crate::CoreInstructionReplayInput,
bytes: &[u8],
) -> crate::DecoderExecutionResult {
let roles = [
crate::SolanaCoreAccountRole::new("proof_context_state", true, false),
crate::SolanaCoreAccountRole::new("lamport_destination", true, false),
crate::SolanaCoreAccountRole::new("context_state_authority", false, true),
];
let accounts = match crate::solana_core_resolve_accounts(
input,
roles.as_slice(),
0,
std::option::Option::None,
) {
std::result::Result::Ok(value) => value,
std::result::Result::Err(error) => {
return crate::solana_core_failed_result(
std::option::Option::Some("close_context_state"),
"zk_token_proof_close_accounts_invalid",
error.to_string(),
);
},
};
let account_array = match accounts.as_array() {
std::option::Option::Some(value) => value,
std::option::Option::None => {
return crate::solana_core_failed_result(
std::option::Option::Some("close_context_state"),
"zk_token_proof_close_accounts_not_array",
"resolved ZK Token Proof close accounts are not an array",
);
},
};
let context_key = account_array
.first()
.and_then(|value| return value.get("accountKey"))
.and_then(serde_json::Value::as_str);
let destination_key = account_array
.get(1)
.and_then(|value| return value.get("accountKey"))
.and_then(serde_json::Value::as_str);
let historical_account_layout_valid = account_array.len() >= 3
&& context_key.is_some()
&& destination_key.is_some()
&& context_key != destination_key;
let trailing = match bytes.get(1..) {
std::option::Option::Some(value) => value,
std::option::Option::None => &[],
};
return crate::solana_core_decoded_result(
input,
crate::SOLANA_CORE_ZK_TOKEN_PROOF_SURFACE_CODE,
"close_context_state",
crate::EventFamily::Audit,
true,
accounts,
serde_json::json!({
"instructionMode": "historical_close_context_state",
"historicalAccountLayoutValid": historical_account_layout_valid,
"historicalRuntimeAcceptsTrailingBytes": true,
"trailingByteLength": trailing.len(),
"trailingSha256": crate::solana_core_hash_bytes(trailing),
"trailingPrefixHex": crate::solana_core_bounded_hexadecimal_prefix(
trailing,
PROOF_COMPONENT_PREFIX_BYTES,
),
"historicalIntendedMutation": "close_context_and_reclaim_lamports",
"historicalInnerInstructionSupport": true,
"historicalActivationEvidence": "no_public_activation_epoch_documented_in_feature_gate_issue",
"decoderValidationBasis": "official_interface_runtime_sources_and_synthetic_fixtures",
"historicalRuntimeReference": "agave_v2_0_0_gated_verifier",
"currentRuntimeReference": "agave_v4_1_1_no_op_success_stub",
"currentRuntimeBehavior": "no_op_success_stub",
"currentRuntimeMutation": "none",
"stateMutationClaim": "not_asserted",
"runtimeExecutionOutcome": runtime_execution_outcome(input),
}),
SOURCE,
);
}
fn decode_verify_proof(
input: &crate::CoreInstructionReplayInput,
bytes: &[u8],
descriptor: ProofDescriptor,
) -> crate::DecoderExecutionResult {
let external_proof = bytes.len() == PROOF_ACCOUNT_INSTRUCTION_BYTES;
let account_count = match input.instruction_accounts_json.as_array() {
std::option::Option::Some(value) => value.len(),
std::option::Option::None => {
return crate::solana_core_failed_result(
std::option::Option::Some(descriptor.entry_code),
"zk_token_proof_accounts_not_array",
"ZK Token Proof instruction accounts are not an array",
);
},
};
let historical_account_layout = historical_account_layout(external_proof, account_count);
let context_state_requested =
if external_proof { account_count >= 3 } else { account_count >= 2 };
let mut roles = std::vec::Vec::new();
if external_proof {
roles.push(crate::SolanaCoreAccountRole::new("proof_data_account", false, false));
}
if context_state_requested {
roles.push(crate::SolanaCoreAccountRole::new("proof_context_state", true, false));
roles.push(crate::SolanaCoreAccountRole::new("context_state_authority", false, false));
}
let accounts = match crate::solana_core_resolve_accounts(
input,
roles.as_slice(),
0,
std::option::Option::None,
) {
std::result::Result::Ok(value) => value,
std::result::Result::Err(error) => {
return crate::solana_core_failed_result(
std::option::Option::Some(descriptor.entry_code),
"zk_token_proof_accounts_invalid",
error.to_string(),
);
},
};
let proof_body_size = match descriptor.proof_data_size.checked_sub(descriptor.context_data_size)
{
std::option::Option::Some(value) => value,
std::option::Option::None => {
return crate::solana_core_failed_result(
std::option::Option::Some(descriptor.entry_code),
"zk_token_proof_official_size_inconsistent",
"historical ZK Token Proof context size exceeds proof data size",
);
},
};
let proof_parameters = if external_proof {
let offset = match crate::solana_core_read_u32_le(bytes, 1) {
std::result::Result::Ok(value) => value,
std::result::Result::Err(error) => {
return crate::solana_core_failed_result(
std::option::Option::Some(descriptor.entry_code),
"zk_token_proof_account_offset_invalid",
error.to_string(),
);
},
};
serde_json::json!({
"proofDataSource": "account",
"proofAccountOffset": offset,
"proofDataAvailability": "external_account_not_captured_by_transaction_core",
"proofDataByteLength": descriptor.proof_data_size,
"contextDataByteLength": descriptor.context_data_size,
"proofBodyByteLength": proof_body_size,
"proofDataSha256": serde_json::Value::Null,
"contextDataSha256": serde_json::Value::Null,
"proofBodySha256": serde_json::Value::Null,
})
} else {
let proof_data = match bytes.get(1..) {
std::option::Option::Some(value) => value,
std::option::Option::None => &[],
};
let context_data = match proof_data.get(..descriptor.context_data_size) {
std::option::Option::Some(value) => value,
std::option::Option::None => {
return crate::solana_core_failed_result(
std::option::Option::Some(descriptor.entry_code),
"zk_token_proof_inline_context_truncated",
"inline historical ZK Token Proof context data is truncated",
);
},
};
let proof_body = match proof_data.get(descriptor.context_data_size..) {
std::option::Option::Some(value) => value,
std::option::Option::None => {
return crate::solana_core_failed_result(
std::option::Option::Some(descriptor.entry_code),
"zk_token_proof_inline_body_truncated",
"inline historical ZK Token Proof body is truncated",
);
},
};
serde_json::json!({
"proofDataSource": "instruction_data",
"proofAccountOffset": serde_json::Value::Null,
"proofDataAvailability": "inline_hashed_not_retained",
"proofDataByteLength": proof_data.len(),
"contextDataByteLength": context_data.len(),
"proofBodyByteLength": proof_body.len(),
"proofDataSha256": crate::solana_core_hash_bytes(proof_data),
"proofDataPrefixHex": crate::solana_core_bounded_hexadecimal_prefix(
proof_data,
PROOF_COMPONENT_PREFIX_BYTES,
),
"contextDataSha256": crate::solana_core_hash_bytes(context_data),
"contextDataPrefixHex": crate::solana_core_bounded_hexadecimal_prefix(
context_data,
PROOF_COMPONENT_PREFIX_BYTES,
),
"proofBodySha256": crate::solana_core_hash_bytes(proof_body),
"proofBodyPrefixHex": crate::solana_core_bounded_hexadecimal_prefix(
proof_body,
PROOF_COMPONENT_PREFIX_BYTES,
),
})
};
let historical_variant_availability = if external_proof {
"gated_by_enable_zk_proof_from_account"
} else {
descriptor.historical_inline_availability
};
return crate::solana_core_decoded_result(
input,
crate::SOLANA_CORE_ZK_TOKEN_PROOF_SURFACE_CODE,
descriptor.entry_code,
crate::EventFamily::Audit,
true,
accounts,
serde_json::json!({
"proofType": descriptor.entry_code,
"proof": proof_parameters,
"historicalAccountLayout": historical_account_layout,
"historicalAccountLayoutValid": historical_account_layout != "invalid_for_historical_interface",
"contextStateRequested": context_state_requested,
"historicalContextStateMutation": if context_state_requested {
"initialize_after_successful_verification"
} else {
"none"
},
"historicalVariantAvailability": historical_variant_availability,
"historicalInnerInstructionSupport": false,
"historicalActivationEvidence": "no_public_activation_epoch_documented_in_feature_gate_issue",
"decoderValidationBasis": "official_interface_runtime_sources_and_synthetic_fixtures",
"historicalRuntimeReference": "agave_v2_0_0_gated_verifier",
"currentRuntimeReference": "agave_v4_1_1_no_op_success_stub",
"currentRuntimeBehavior": "no_op_success_stub",
"cryptographicVerificationPerformedByDecoder": false,
"cryptographicVerificationClaim": verification_claim(input),
"runtimeExecutionOutcome": runtime_execution_outcome(input),
}),
SOURCE,
);
}
fn historical_account_layout(external_proof: bool, account_count: usize) -> &'static str {
if external_proof {
return match account_count {
1 => "proof_account_without_context",
3.. => "proof_account_with_context",
_ => "invalid_for_historical_interface",
};
}
return match account_count {
0 => "inline_without_context",
2.. => "inline_with_context",
_ => "invalid_for_historical_interface",
};
}
fn runtime_execution_outcome(input: &crate::CoreInstructionReplayInput) -> &'static str {
if input.transaction_failed {
return "not_asserted_transaction_failed";
}
return "accepted_in_successful_transaction";
}
fn verification_claim(input: &crate::CoreInstructionReplayInput) -> &'static str {
if input.transaction_failed {
return "not_asserted_transaction_failed";
}
return "not_asserted_decoder_does_not_bind_transaction_to_runtime_version";
}
fn proof_descriptor(
instruction: HistoricalProofInstruction,
) -> std::option::Option<ProofDescriptor> {
let descriptor = match instruction {
HistoricalProofInstruction::CloseContextState => {
return std::option::Option::None;
},
HistoricalProofInstruction::VerifyZeroBalance => ProofDescriptor {
entry_code: "verify_zero_balance",
proof_data_size: 192,
context_data_size: 96,
historical_inline_availability: "historical_runtime_implementation_present_activation_unconfirmed",
},
HistoricalProofInstruction::VerifyWithdraw => ProofDescriptor {
entry_code: "verify_withdraw",
proof_data_size: 992,
context_data_size: 96,
historical_inline_availability: "historical_runtime_implementation_present_activation_unconfirmed",
},
HistoricalProofInstruction::VerifyCiphertextCiphertextEquality => ProofDescriptor {
entry_code: "verify_ciphertext_ciphertext_equality",
proof_data_size: 416,
context_data_size: 192,
historical_inline_availability: "historical_runtime_implementation_present_activation_unconfirmed",
},
HistoricalProofInstruction::VerifyTransfer => ProofDescriptor {
entry_code: "verify_transfer",
proof_data_size: 1536,
context_data_size: 416,
historical_inline_availability: "historical_runtime_implementation_present_activation_unconfirmed",
},
HistoricalProofInstruction::VerifyTransferWithFee => ProofDescriptor {
entry_code: "verify_transfer_with_fee",
proof_data_size: 2282,
context_data_size: 650,
historical_inline_availability: "gated_by_enable_zk_transfer_with_fee",
},
HistoricalProofInstruction::VerifyPubkeyValidity => ProofDescriptor {
entry_code: "verify_pubkey_validity",
proof_data_size: 96,
context_data_size: 32,
historical_inline_availability: "historical_runtime_implementation_present_activation_unconfirmed",
},
HistoricalProofInstruction::VerifyRangeProofU64 => ProofDescriptor {
entry_code: "verify_range_proof_u64",
proof_data_size: 704,
context_data_size: 32,
historical_inline_availability: "historical_runtime_implementation_present_activation_unconfirmed",
},
HistoricalProofInstruction::VerifyBatchedRangeProofU64 => ProofDescriptor {
entry_code: "verify_batched_range_proof_u64",
proof_data_size: 936,
context_data_size: 264,
historical_inline_availability: "historical_runtime_implementation_present_activation_unconfirmed",
},
HistoricalProofInstruction::VerifyBatchedRangeProofU128 => ProofDescriptor {
entry_code: "verify_batched_range_proof_u128",
proof_data_size: 1000,
context_data_size: 264,
historical_inline_availability: "historical_runtime_implementation_present_activation_unconfirmed",
},
HistoricalProofInstruction::VerifyBatchedRangeProofU256 => ProofDescriptor {
entry_code: "verify_batched_range_proof_u256",
proof_data_size: 1064,
context_data_size: 264,
historical_inline_availability: "gated_by_enable_zk_transfer_with_fee",
},
HistoricalProofInstruction::VerifyCiphertextCommitmentEquality => ProofDescriptor {
entry_code: "verify_ciphertext_commitment_equality",
proof_data_size: 320,
context_data_size: 128,
historical_inline_availability: "historical_runtime_implementation_present_activation_unconfirmed",
},
HistoricalProofInstruction::VerifyGroupedCiphertext2HandlesValidity => ProofDescriptor {
entry_code: "verify_grouped_ciphertext_2_handles_validity",
proof_data_size: 320,
context_data_size: 160,
historical_inline_availability: "historical_runtime_implementation_present_activation_unconfirmed",
},
HistoricalProofInstruction::VerifyBatchedGroupedCiphertext2HandlesValidity => {
ProofDescriptor {
entry_code: "verify_batched_grouped_ciphertext_2_handles_validity",
proof_data_size: 416,
context_data_size: 256,
historical_inline_availability: "historical_runtime_implementation_present_activation_unconfirmed",
}
},
HistoricalProofInstruction::VerifyFeeSigma => ProofDescriptor {
entry_code: "verify_fee_sigma",
proof_data_size: 360,
context_data_size: 104,
historical_inline_availability: "gated_by_enable_zk_transfer_with_fee",
},
HistoricalProofInstruction::VerifyGroupedCiphertext3HandlesValidity => ProofDescriptor {
entry_code: "verify_grouped_ciphertext_3_handles_validity",
proof_data_size: 416,
context_data_size: 224,
historical_inline_availability: "historical_runtime_implementation_present_activation_unconfirmed",
},
HistoricalProofInstruction::VerifyBatchedGroupedCiphertext3HandlesValidity => {
ProofDescriptor {
entry_code: "verify_batched_grouped_ciphertext_3_handles_validity",
proof_data_size: 544,
context_data_size: 352,
historical_inline_availability: "historical_runtime_implementation_present_activation_unconfirmed",
}
},
};
return std::option::Option::Some(descriptor);
}
fn instruction_entries() -> &'static [(u8, &'static str)] {
return &[
(0, "close_context_state"),
(1, "verify_zero_balance"),
(2, "verify_withdraw"),
(3, "verify_ciphertext_ciphertext_equality"),
(4, "verify_transfer"),
(5, "verify_transfer_with_fee"),
(6, "verify_pubkey_validity"),
(7, "verify_range_proof_u64"),
(8, "verify_batched_range_proof_u64"),
(9, "verify_batched_range_proof_u128"),
(10, "verify_batched_range_proof_u256"),
(11, "verify_ciphertext_commitment_equality"),
(12, "verify_grouped_ciphertext_2_handles_validity"),
(13, "verify_batched_grouped_ciphertext_2_handles_validity"),
(14, "verify_fee_sigma"),
(15, "verify_grouped_ciphertext_3_handles_validity"),
(16, "verify_batched_grouped_ciphertext_3_handles_validity"),
];
}
#[cfg(test)]
mod tests {
use base64::Engine; // rust-rules: trait-import
fn replay_input(
bytes: &[u8],
account_count: usize,
transaction_failed: bool,
instruction_path: &str,
) -> crate::CoreInstructionReplayInput {
let account_keys = (0..account_count)
.map(|index| {
return serde_json::json!({
"accountIndex": index,
"accountKey": format!("ZkToken{index:02}1111111111111111111111111"),
"source": "static",
"writable": index < 2,
"signer": index == 2,
"executable": false,
});
})
.collect::<std::vec::Vec<_>>();
let instruction_accounts = account_keys
.iter()
.map(|account| {
return serde_json::json!({
"accountIndex": account["accountIndex"],
"accountKey": account["accountKey"],
});
})
.collect::<std::vec::Vec<_>>();
let result = crate::CoreInstructionReplayInput::new(
format!("signature:{instruction_path}"),
"signature",
42,
instruction_path,
kb_program_ids::ZK_TOKEN_PROOF_PROGRAM_ID,
transaction_failed,
if transaction_failed {
std::option::Option::Some(serde_json::json!({"InstructionError": [0, "Custom"]}))
} else {
std::option::Option::None
},
serde_json::Value::Array(account_keys),
serde_json::Value::Array(instruction_accounts),
std::option::Option::Some(serde_json::json!({
"dataBase64": base64::engine::general_purpose::STANDARD.encode(bytes),
})),
std::option::Option::Some("payload-hash".to_string()),
serde_json::json!([]),
serde_json::json!([]),
serde_json::json!([]),
serde_json::json!([]),
);
return match result {
std::result::Result::Ok(value) => value,
std::result::Result::Err(error) => {
panic!("ZK Token Proof replay input failed: {error}")
},
};
}
fn inline_fixture(instruction: super::HistoricalProofInstruction) -> std::vec::Vec<u8> {
let descriptor = match super::proof_descriptor(instruction) {
std::option::Option::Some(value) => value,
std::option::Option::None => panic!("proof descriptor missing"),
};
let inline_size = match descriptor.proof_data_size.checked_add(1) {
std::option::Option::Some(value) => value,
std::option::Option::None => panic!("inline fixture size overflow"),
};
let tag = instruction as u8;
let mut bytes = vec![tag];
bytes.resize(inline_size, tag);
return bytes;
}
#[test]
fn coverage_declares_seventeen_historical_entries_and_current_noop_fallback() {
let coverage = crate::solana_core_zk_token_proof_coverage();
assert_eq!(coverage.len(), 18);
assert_eq!(coverage.iter().filter(|entry| return entry.historical).count(), 17);
assert!(coverage.iter().any(|entry| {
return entry.entry_code == "current_runtime_noop_invocation"
&& !entry.historical
&& entry.discriminator_hex.is_none();
}));
}
#[test]
fn official_discriminants_map_to_stable_entry_codes() {
for (tag, entry_code) in super::instruction_entries() {
let bytes = if *tag == 0 { vec![*tag] } else { vec![*tag, 0, 0, 0, 0] };
let recognition = crate::solana_core_zk_token_proof_recognize(
&replay_input(bytes.as_slice(), 3, false, "0"),
100,
);
assert!(recognition.compatible);
assert_eq!(recognition.entry_code.as_deref(), std::option::Option::Some(*entry_code));
}
}
#[test]
fn every_historical_inline_layout_matches_the_audited_wire_table() {
let variants = [
(super::HistoricalProofInstruction::VerifyZeroBalance, 192, 96),
(super::HistoricalProofInstruction::VerifyWithdraw, 992, 96),
(super::HistoricalProofInstruction::VerifyCiphertextCiphertextEquality, 416, 192),
(super::HistoricalProofInstruction::VerifyTransfer, 1536, 416),
(super::HistoricalProofInstruction::VerifyTransferWithFee, 2282, 650),
(super::HistoricalProofInstruction::VerifyPubkeyValidity, 96, 32),
(super::HistoricalProofInstruction::VerifyRangeProofU64, 704, 32),
(super::HistoricalProofInstruction::VerifyBatchedRangeProofU64, 936, 264),
(super::HistoricalProofInstruction::VerifyBatchedRangeProofU128, 1000, 264),
(super::HistoricalProofInstruction::VerifyBatchedRangeProofU256, 1064, 264),
(super::HistoricalProofInstruction::VerifyCiphertextCommitmentEquality, 320, 128),
(
super::HistoricalProofInstruction::VerifyGroupedCiphertext2HandlesValidity,
320,
160,
),
(
super::HistoricalProofInstruction::VerifyBatchedGroupedCiphertext2HandlesValidity,
416,
256,
),
(super::HistoricalProofInstruction::VerifyFeeSigma, 360, 104),
(
super::HistoricalProofInstruction::VerifyGroupedCiphertext3HandlesValidity,
416,
224,
),
(
super::HistoricalProofInstruction::VerifyBatchedGroupedCiphertext3HandlesValidity,
544,
352,
),
];
for (instruction, proof_data_size, context_data_size) in variants {
let descriptor = match super::proof_descriptor(instruction) {
std::option::Option::Some(value) => value,
std::option::Option::None => panic!("proof descriptor missing"),
};
assert_eq!(descriptor.proof_data_size, proof_data_size);
assert_eq!(descriptor.context_data_size, context_data_size);
let bytes = inline_fixture(instruction);
let result = crate::solana_core_zk_token_proof_decode(&replay_input(
bytes.as_slice(),
0,
true,
"0",
));
assert_eq!(result.status, crate::DecoderOutcomeStatus::Decoded);
assert_eq!(result.observations.len(), 1);
assert!(!result.observations[0].observation_committed);
assert_eq!(
result.observations[0].payload_json["parameters"]["proof"]["proofDataByteLength"],
bytes.len() - 1
);
}
}
#[test]
fn proof_account_mode_is_structured_and_historical_feature_gate_is_explicit() {
let result = crate::solana_core_zk_token_proof_decode(&replay_input(
&[1, 44, 0, 0, 0],
3,
false,
"0",
));
assert_eq!(result.status, crate::DecoderOutcomeStatus::Decoded);
let parameters = &result.observations[0].payload_json["parameters"];
assert_eq!(parameters["proof"]["proofDataSource"], "account");
assert_eq!(parameters["proof"]["proofAccountOffset"], 44);
assert_eq!(
parameters["historicalVariantAvailability"],
"gated_by_enable_zk_proof_from_account"
);
assert!(parameters["contextStateRequested"].as_bool().is_some_and(|value| return value));
}
#[test]
fn historical_transfer_fee_gates_are_explicit() {
let variants = [
super::HistoricalProofInstruction::VerifyTransferWithFee,
super::HistoricalProofInstruction::VerifyBatchedRangeProofU256,
super::HistoricalProofInstruction::VerifyFeeSigma,
];
for instruction in variants {
let bytes = inline_fixture(instruction);
let result = crate::solana_core_zk_token_proof_decode(&replay_input(
bytes.as_slice(),
0,
false,
"0",
));
assert_eq!(result.status, crate::DecoderOutcomeStatus::Decoded);
assert_eq!(
result.observations[0].payload_json["parameters"]["historicalVariantAvailability"],
"gated_by_enable_zk_transfer_with_fee"
);
}
}
#[test]
fn malformed_historical_shapes_decode_as_current_noop_instead_of_false_failure() {
let fixtures: &[&[u8]] = &[&[], &[99], &[1, 2, 3]];
for bytes in fixtures {
let result =
crate::solana_core_zk_token_proof_decode(&replay_input(bytes, 1, false, "0"));
assert_eq!(result.status, crate::DecoderOutcomeStatus::Decoded);
assert_eq!(
result.recognized_entry_code.as_deref(),
std::option::Option::Some("current_runtime_noop_invocation")
);
assert_eq!(
result.observations[0].payload_json["parameters"]["currentRuntimeReference"],
"agave_v4_1_1_no_op_success_stub"
);
assert_eq!(
result.observations[0].payload_json["parameters"]["currentRuntimeBehavior"],
"no_op_success_stub"
);
}
}
#[test]
fn historical_account_arity_is_reported_without_overriding_current_noop_acceptance() {
let inline = inline_fixture(super::HistoricalProofInstruction::VerifyZeroBalance);
let inline_result = crate::solana_core_zk_token_proof_decode(&replay_input(
inline.as_slice(),
1,
false,
"0",
));
assert_eq!(inline_result.status, crate::DecoderOutcomeStatus::Decoded);
assert!(inline_result.observations[0].payload_json["parameters"]["historicalAccountLayoutValid"]
.as_bool()
.is_some_and(|value| return !value));
let account_result = crate::solana_core_zk_token_proof_decode(&replay_input(
&[1, 0, 0, 0, 0],
2,
false,
"0",
));
assert_eq!(account_result.status, crate::DecoderOutcomeStatus::Decoded);
assert!(account_result.observations[0].payload_json["parameters"]["historicalAccountLayoutValid"]
.as_bool()
.is_some_and(|value| return !value));
}
#[test]
fn close_context_state_preserves_historical_intent_without_claiming_mutation() {
let result =
crate::solana_core_zk_token_proof_decode(&replay_input(&[0, 7, 8], 3, false, "0"));
assert_eq!(result.status, crate::DecoderOutcomeStatus::Decoded);
let parameters = &result.observations[0].payload_json["parameters"];
assert!(
parameters["historicalAccountLayoutValid"]
.as_bool()
.is_some_and(|value| return value)
);
assert_eq!(parameters["trailingByteLength"], 2);
assert!(
parameters["historicalInnerInstructionSupport"]
.as_bool()
.is_some_and(|value| return value)
);
assert_eq!(parameters["currentRuntimeMutation"], "none");
assert_eq!(parameters["stateMutationClaim"], "not_asserted");
}
#[test]
fn inner_invocation_never_claims_historical_verification() {
let bytes = inline_fixture(super::HistoricalProofInstruction::VerifyPubkeyValidity);
let result = crate::solana_core_zk_token_proof_decode(&replay_input(
bytes.as_slice(),
0,
false,
"0/1",
));
assert_eq!(result.status, crate::DecoderOutcomeStatus::Decoded);
assert!(
result.observations[0].payload_json["parameters"]["historicalInnerInstructionSupport"]
.as_bool()
.is_some_and(|value| return !value)
);
assert_eq!(
result.observations[0].payload_json["parameters"]["cryptographicVerificationClaim"],
"not_asserted_decoder_does_not_bind_transaction_to_runtime_version"
);
}
#[test]
fn failed_transaction_is_non_committed_and_never_claims_verification() {
let bytes = inline_fixture(super::HistoricalProofInstruction::VerifyZeroBalance);
let result =
crate::solana_core_zk_token_proof_decode(&replay_input(bytes.as_slice(), 0, true, "0"));
assert_eq!(result.status, crate::DecoderOutcomeStatus::Decoded);
assert!(!result.observations[0].observation_committed);
assert_eq!(
result.observations[0].payload_json["parameters"]["cryptographicVerificationClaim"],
"not_asserted_transaction_failed"
);
}
#[test]
fn serialization_is_deterministic() {
let input = replay_input(&[1, 0, 0, 0, 0], 1, false, "0");
let first = crate::solana_core_zk_token_proof_decode(&input);
let second = crate::solana_core_zk_token_proof_decode(&input);
let first_json = match serde_json::to_string(&first.observations[0].payload_json) {
std::result::Result::Ok(value) => value,
std::result::Result::Err(error) => panic!("first serialization failed: {error}"),
};
let second_json = match serde_json::to_string(&second.observations[0].payload_json) {
std::result::Result::Ok(value) => value,
std::result::Result::Err(error) => panic!("second serialization failed: {error}"),
};
assert_eq!(first_json, second_json);
}
}

View File

@@ -1,5 +1,5 @@
// file: kb-lib/src/lib.rs
// version: 3
// version: 6
//! Consolidated decoder, executor, materializer and shared model library.
#![warn(missing_docs)]
@@ -11,10 +11,179 @@ pub mod executor;
pub mod materializer;
pub mod model;
/// Canonical tracing target for the consolidated library.
pub(crate) use crate::decoder::api::constants::TRACING_TARGET;
/// Stable Address Lookup Table surface code.
pub(crate) use crate::decoder::solana::SOLANA_CORE_ADDRESS_LOOKUP_TABLE_SURFACE_CODE;
/// Stable deprecated immutable BPF Loader surface code.
pub(crate) use crate::decoder::solana::SOLANA_CORE_BPF_LOADER_DEPRECATED_SURFACE_CODE;
/// Stable immutable BPF Loader v2 surface code.
pub(crate) use crate::decoder::solana::SOLANA_CORE_BPF_LOADER_SURFACE_CODE;
/// Stable upgradeable BPF Loader surface code.
pub(crate) use crate::decoder::solana::SOLANA_CORE_BPF_LOADER_UPGRADEABLE_SURFACE_CODE;
/// Stable Compute Budget surface code.
pub(crate) use crate::decoder::solana::SOLANA_CORE_COMPUTE_BUDGET_SURFACE_CODE;
/// Stable Config Program surface code.
pub(crate) use crate::decoder::solana::SOLANA_CORE_CONFIG_SURFACE_CODE;
/// Byte length of an Ed25519 public key.
pub(crate) use crate::decoder::solana::SOLANA_CORE_ED25519_PUBLIC_KEY_BYTES;
/// Stable Ed25519 signature precompile surface code.
pub(crate) use crate::decoder::solana::SOLANA_CORE_ED25519_SURFACE_CODE;
/// Stable Feature Gate surface code.
pub(crate) use crate::decoder::solana::SOLANA_CORE_FEATURE_SURFACE_CODE;
/// Stable Loader v4 surface code.
pub(crate) use crate::decoder::solana::SOLANA_CORE_LOADER_V4_SURFACE_CODE;
/// Maximum retained native instruction payload accepted by the first decoder phase.
pub(crate) use crate::decoder::solana::SOLANA_CORE_MAX_NATIVE_INSTRUCTION_PAYLOAD_BYTES;
/// Current native event payload contract version.
pub(crate) use crate::decoder::solana::SOLANA_CORE_NATIVE_EVENT_VERSION;
/// Stable Native Loader surface code.
pub(crate) use crate::decoder::solana::SOLANA_CORE_NATIVE_LOADER_SURFACE_CODE;
/// Maximum number of component bytes retained as a hexadecimal event prefix.
pub(crate) use crate::decoder::solana::SOLANA_CORE_PRECOMPILE_COMPONENT_PREFIX_BYTES;
/// Expected role and privileges for one positional instruction account.
pub(crate) use crate::decoder::solana::SolanaCoreAccountRole;
/// Returns declared Address Lookup Table instruction coverage.
pub(crate) use crate::decoder::solana::solana_core_address_lookup_table_coverage;
/// Decodes one Address Lookup Table instruction.
pub(crate) use crate::decoder::solana::solana_core_address_lookup_table_decode;
/// Recognizes one Address Lookup Table instruction without producing an event.
pub(crate) use crate::decoder::solana::solana_core_address_lookup_table_recognize;
/// Returns declared Compute Budget instruction coverage.
pub(crate) use crate::decoder::solana::solana_core_compute_budget_coverage;
/// Decodes one Compute Budget instruction.
pub(crate) use crate::decoder::solana::solana_core_compute_budget_decode;
/// Recognizes one Compute Budget instruction without producing an event.
pub(crate) use crate::decoder::solana::solana_core_compute_budget_recognize;
/// Returns declared Config Program instruction coverage.
pub(crate) use crate::decoder::solana::solana_core_config_coverage;
/// Decodes one generic Config Program store instruction.
pub(crate) use crate::decoder::solana::solana_core_config_decode;
/// Recognizes one generic Config Program store instruction.
pub(crate) use crate::decoder::solana::solana_core_config_recognize;
/// Resolves positional instruction accounts and validates their core indexes.
pub(crate) use crate::decoder::solana::solana_core_resolve_accounts;
/// Stable protocol code shared by native Solana events.
pub(crate) use crate::decoder::solana::SOLANA_CORE_PROTOCOL_CODE;
/// Byte length of a secp256k1 Ethereum address.
pub(crate) use crate::decoder::solana::SOLANA_CORE_SECP256K1_ETHEREUM_ADDRESS_BYTES;
/// Stable secp256k1 signature precompile surface code.
pub(crate) use crate::decoder::solana::SOLANA_CORE_SECP256K1_SURFACE_CODE;
/// Runtime maximum number of secp256r1 signatures in one precompile instruction.
pub(crate) use crate::decoder::solana::SOLANA_CORE_SECP256R1_MAX_SIGNATURES;
/// Byte length of a compressed secp256r1 public key.
pub(crate) use crate::decoder::solana::SOLANA_CORE_SECP256R1_PUBLIC_KEY_BYTES;
/// Stable secp256r1 signature precompile surface code.
pub(crate) use crate::decoder::solana::SOLANA_CORE_SECP256R1_SURFACE_CODE;
/// Byte length shared by compact Ed25519, secp256k1 and secp256r1 signatures.
pub(crate) use crate::decoder::solana::SOLANA_CORE_SIGNATURE_BYTES;
/// Stable Slashing Program decoder surface code.
pub(crate) use crate::decoder::solana::SOLANA_CORE_SLASHING_SURFACE_CODE;
/// Stable Stake Program surface code.
pub(crate) use crate::decoder::solana::SOLANA_CORE_STAKE_SURFACE_CODE;
/// Stable System Program surface code.
pub(crate) use crate::decoder::solana::SOLANA_CORE_SYSTEM_SURFACE_CODE;
/// Canonical tracing target for this crate.
pub(crate) use crate::decoder::solana::SOLANA_CORE_TRACING_TARGET;
/// Byte length of one secp256k1 offsets entry.
pub(crate) use crate::decoder::solana::SOLANA_CORE_U8_OFFSETS_BYTES;
/// Byte length of one Ed25519 or secp256r1 offsets entry.
pub(crate) use crate::decoder::solana::SOLANA_CORE_U16_OFFSETS_BYTES;
/// Stable Vote Program surface code.
pub(crate) use crate::decoder::solana::SOLANA_CORE_VOTE_SURFACE_CODE;
/// Stable native ZK ElGamal Proof surface code.
pub(crate) use crate::decoder::solana::SOLANA_CORE_ZK_ELGAMAL_PROOF_SURFACE_CODE;
/// Stable historical ZK Token Proof surface code.
pub(crate) use crate::decoder::solana::SOLANA_CORE_ZK_TOKEN_PROOF_SURFACE_CODE;
/// One resolved outer instruction payload and its provenance relative to the target instruction.
pub(crate) use crate::decoder::solana::SolanaCoreResolvedInstructionPayload;
/// Returns a hexadecimal prefix no longer than the available byte slice.
pub(crate) use crate::decoder::solana::solana_core_bounded_hexadecimal_prefix;
/// Extracts one exact bounded byte slice with checked arithmetic.
pub(crate) use crate::decoder::solana::solana_core_bounded_slice;
/// Decodes one retained base64 instruction payload with an explicit byte limit.
pub(crate) use crate::decoder::solana::solana_core_decode_instruction_data;
/// Returns the decoded target payload length when retained and valid.
pub(crate) use crate::decoder::solana::solana_core_decoded_payload_length;
/// Returns the SHA-256 of the decoded target instruction data when available.
pub(crate) use crate::decoder::solana::solana_core_decoded_payload_sha256;
/// Builds one exact decoded native observation.
pub(crate) use crate::decoder::solana::solana_core_decoded_result;
/// Builds one failed native decode result.
pub(crate) use crate::decoder::solana::solana_core_failed_result;
/// Returns declared Feature Gate instruction coverage.
pub(crate) use crate::decoder::solana::solana_core_feature_coverage;
/// Decodes one Feature Gate instruction.
pub(crate) use crate::decoder::solana::solana_core_feature_decode;
/// Recognizes one Feature Gate instruction without producing an event.
pub(crate) use crate::decoder::solana::solana_core_feature_recognize;
/// Returns the lowercase SHA-256 of one bounded byte slice.
pub(crate) use crate::decoder::solana::solana_core_hash_bytes;
/// Returns a normalized lowercase hexadecimal prefix.
pub(crate) use crate::decoder::solana::solana_core_hexadecimal_prefix;
/// Returns declared loader instruction coverage.
pub(crate) use crate::decoder::solana::solana_core_loaders_coverage;
/// Decodes one loader instruction.
pub(crate) use crate::decoder::solana::solana_core_loaders_decode;
/// Recognizes one loader instruction without producing an event.
pub(crate) use crate::decoder::solana::solana_core_loaders_recognize;
/// Returns a stable payload hash from core or computes it from retained JSON.
pub(crate) use crate::decoder::solana::solana_core_payload_hash;
/// Returns declared signature precompile coverage.
pub(crate) use crate::decoder::solana::solana_core_precompiles_coverage;
/// Decodes one signature precompile instruction structurally without cryptographic recomputation.
pub(crate) use crate::decoder::solana::solana_core_precompiles_decode;
/// Recognizes one signature precompile instruction without resolving referenced data.
pub(crate) use crate::decoder::solana::solana_core_precompiles_recognize;
/// Reads one little-endian `u32` from an exact byte range.
pub(crate) use crate::decoder::solana::solana_core_read_u32_le;
/// Reads one little-endian `u64` from an exact byte range.
pub(crate) use crate::decoder::solana::solana_core_read_u64_le;
/// Resolves a secp256k1 instruction reference. The runtime format has no current-instruction
/// sentinel: every `u8` value is an explicit outer instruction index.
pub(crate) use crate::decoder::solana::solana_core_resolve_u8_instruction_payload;
/// Resolves an Ed25519 or secp256r1 instruction reference using the official `u16::MAX` sentinel.
pub(crate) use crate::decoder::solana::solana_core_resolve_u16_instruction_payload;
/// Returns declared Slashing Program instruction coverage.
pub(crate) use crate::decoder::solana::solana_core_slashing_coverage;
/// Decodes one Slashing Program instruction.
pub(crate) use crate::decoder::solana::solana_core_slashing_decode;
/// Recognizes one Slashing Program instruction without producing an event.
pub(crate) use crate::decoder::solana::solana_core_slashing_recognize;
/// Returns declared Stake Program instruction coverage.
pub(crate) use crate::decoder::solana::solana_core_stake_coverage;
/// Decodes one Stake Program instruction.
pub(crate) use crate::decoder::solana::solana_core_stake_decode;
/// Recognizes one Stake Program instruction without producing an event.
pub(crate) use crate::decoder::solana::solana_core_stake_recognize;
/// Returns declared System Program instruction coverage.
pub(crate) use crate::decoder::solana::solana_core_system_coverage;
/// Decodes one System Program instruction.
pub(crate) use crate::decoder::solana::solana_core_system_decode;
/// Recognizes one System Program instruction without producing an event.
pub(crate) use crate::decoder::solana::solana_core_system_recognize;
/// Returns the numeric outer index of the target instruction.
pub(crate) use crate::decoder::solana::solana_core_target_outer_instruction_index;
/// Builds one unsupported native decode result with a bounded diagnostic.
pub(crate) use crate::decoder::solana::solana_core_unsupported_result;
/// Returns declared Vote Program instruction coverage.
pub(crate) use crate::decoder::solana::solana_core_vote_coverage;
/// Decodes one Vote Program instruction.
pub(crate) use crate::decoder::solana::solana_core_vote_decode;
/// Recognizes one Vote Program instruction without producing an event.
pub(crate) use crate::decoder::solana::solana_core_vote_recognize;
/// Returns declared ZK ElGamal Proof instruction coverage.
pub(crate) use crate::decoder::solana::solana_core_zk_elgamal_coverage;
/// Decodes one native ZK ElGamal Proof instruction without recomputing the proof.
pub(crate) use crate::decoder::solana::solana_core_zk_elgamal_decode;
/// Recognizes one native ZK ElGamal Proof instruction.
pub(crate) use crate::decoder::solana::solana_core_zk_elgamal_recognize;
/// Returns declared historical and current ZK Token Proof coverage.
pub(crate) use crate::decoder::solana::solana_core_zk_token_proof_coverage;
/// Decodes one historical ZK Token Proof layout without recomputing any proof.
pub(crate) use crate::decoder::solana::solana_core_zk_token_proof_decode;
/// Recognizes one historical ZK Token Proof layout or the current no-op runtime fallback.
pub(crate) use crate::decoder::solana::solana_core_zk_token_proof_recognize;
/// Current contextual core instruction input contract version.
pub use crate::decoder::api::contracts::CORE_INSTRUCTION_INPUT_CONTRACT_VERSION;
/// Stable contextual decoded observation.
@@ -55,6 +224,8 @@ pub use crate::decoder::api::decoder::DecoderSupport;
pub use crate::decoder::api::decoder::InitialDecoder;
/// Exposes the common protocol decoder trait.
pub use crate::decoder::api::decoder::ProtocolDecoder;
/// Runtime-native Solana decoder with maximal native instruction coverage.
pub use crate::decoder::solana::SolanaCoreDecoder;
/// Exposes the blockhash policy kind.
pub use crate::executor::api::execution::ExecutionBlockhashKind;
/// Exposes the blockhash policy.

View File

@@ -1,5 +1,5 @@
// file: kb-lib/src/model/replay.rs
// version: 2
// version: 3
//! Source-neutral instruction replay input shared by decoders and stores.
@@ -123,4 +123,21 @@ impl CoreInstructionReplayInput {
}
return std::result::Result::Ok(());
}
/// Adds an optional stable surface hint after validation.
pub fn with_surface_code_hint(
mut self,
surface_code_hint: std::option::Option<std::string::String>,
) -> kb_core::Result<Self> {
if surface_code_hint
.as_deref()
.is_some_and(|surface| return surface.trim().is_empty())
{
return std::result::Result::Err(kb_core::Error::invalid_state(
"core replay input surface hint must not be empty when present",
));
}
self.surface_code_hint = surface_code_hint;
return std::result::Result::Ok(self);
}
}