Files
khadhroony-bot3/migration/khadhroony-bot2-reference/kb_pipeline/src/solana_token_2022_stateful.rs
2026-07-23 16:37:12 +02:00

804 lines
32 KiB
Rust

// file: kb_pipeline/src/solana_token_2022_stateful.rs
// version: 5
//! Contextual Token-2022 account-state validation and materialization routing.
/// Optional external identities required to validate cross-account Token-2022 state.
#[derive(Clone, Debug, Default, PartialEq, serde::Deserialize, serde::Serialize)]
pub struct Token2022StatefulContext {
/// Expected group account for one TokenGroupMember extension.
pub expected_group_address: std::option::Option<std::string::String>,
}
/// Maximum complete Token-2022 account data accepted by one bounded RPC read.
pub const MAX_TOKEN_2022_STATEFUL_ACCOUNT_BYTES: usize = 65_536;
/// One bounded Token-2022 account read request.
#[derive(Clone, Debug, PartialEq)]
pub struct Token2022StatefulReadRequest {
/// Endpoint role used for the HTTP RPC request.
pub query_role: std::string::String,
/// Canonical account address.
pub account: kb_model::Pubkey,
/// Expected Token-2022 base-state category.
pub kind: kb_decoder_spl_token_2022::state::Token2022StateKind,
/// Optional minimum RPC context slot.
pub min_context_slot: std::option::Option<u64>,
/// Maximum decoded account bytes accepted from the endpoint.
pub max_data_bytes: usize,
/// Optional external identities needed by cross-account extensions.
pub context: crate::solana_token_2022_stateful::Token2022StatefulContext,
}
/// One bounded RPC read and its contextually validated projections.
#[derive(Clone, Debug, PartialEq, serde::Deserialize, serde::Serialize)]
pub struct Token2022StatefulReadResult {
/// Commitment used for the RPC read.
pub commitment: std::string::String,
/// Context slot returned by the endpoint.
pub context_slot: u64,
/// Complete validated snapshot bundle.
pub snapshot: crate::solana_token_2022_stateful::Token2022StatefulSnapshotBundle,
}
/// Reads, validates, parses, and routes one bounded Token-2022 account snapshot.
pub async fn read_token_2022_stateful_snapshot(
pool: &kb_rpc::HttpEndpointPool,
request: &crate::solana_token_2022_stateful::Token2022StatefulReadRequest,
) -> kb_core::Result<crate::solana_token_2022_stateful::Token2022StatefulReadResult> {
if request.query_role.trim().is_empty() {
return std::result::Result::Err(kb_core::Error::config(
"Token-2022 stateful read query_role must not be empty",
));
}
if request.max_data_bytes == 0
|| request.max_data_bytes
> crate::solana_token_2022_stateful::MAX_TOKEN_2022_STATEFUL_ACCOUNT_BYTES
{
return std::result::Result::Err(kb_core::Error::config(format!(
"Token-2022 stateful read max_data_bytes must be between 1 and {}",
crate::solana_token_2022_stateful::MAX_TOKEN_2022_STATEFUL_ACCOUNT_BYTES
)));
}
let config = match kb_rpc::GetAccountInfoConfig::new_with_data(
kb_rpc::RpcCommitmentLevel::Confirmed,
request.min_context_slot,
request.max_data_bytes,
) {
std::result::Result::Ok(value) => value,
std::result::Result::Err(error) => return std::result::Result::Err(error),
};
let result = match pool
.get_account_info_for_role(request.query_role.as_str(), &request.account, &config)
.await
{
std::result::Result::Ok(value) => value,
std::result::Result::Err(error) => return std::result::Result::Err(error),
};
return crate::solana_token_2022_stateful::materialize_token_2022_account_info_result(
request, &result,
);
}
/// Validates one complete RPC account response before Token-2022 parsing and routing.
pub fn materialize_token_2022_account_info_result(
request: &crate::solana_token_2022_stateful::Token2022StatefulReadRequest,
result: &kb_rpc::AccountInfoResult,
) -> kb_core::Result<crate::solana_token_2022_stateful::Token2022StatefulReadResult> {
if request.max_data_bytes == 0
|| request.max_data_bytes
> crate::solana_token_2022_stateful::MAX_TOKEN_2022_STATEFUL_ACCOUNT_BYTES
{
return std::result::Result::Err(kb_core::Error::config(format!(
"Token-2022 stateful read max_data_bytes must be between 1 and {}",
crate::solana_token_2022_stateful::MAX_TOKEN_2022_STATEFUL_ACCOUNT_BYTES
)));
}
if let std::option::Option::Some(min_context_slot) = request.min_context_slot {
if result.context.slot < min_context_slot {
return std::result::Result::Err(kb_core::Error::new(
"token_2022_stateful_context_slot_too_old",
format!(
"Token-2022 account context slot {} is below requested minimum {min_context_slot}",
result.context.slot
),
));
}
}
let account = match result.account.as_ref() {
std::option::Option::Some(value) => value,
std::option::Option::None => {
return std::result::Result::Err(kb_core::Error::new(
"token_2022_stateful_account_missing",
format!("Token-2022 account {} does not exist", request.account.0),
));
},
};
if account.executable {
return std::result::Result::Err(kb_core::Error::new(
"token_2022_stateful_account_executable",
format!("Token-2022 state account {} must not be executable", request.account.0),
));
}
if account.owner.0 != kb_program_ids::SPL_TOKEN_2022_PROGRAM_ID {
return std::result::Result::Err(kb_core::Error::new(
"token_2022_stateful_owner_mismatch",
format!(
"Token-2022 state account {} owner must be {}, got {}",
request.account.0,
kb_program_ids::SPL_TOKEN_2022_PROGRAM_ID,
account.owner.0
),
));
}
if account.space > request.max_data_bytes as u64 || account.data.len() > request.max_data_bytes
{
return std::result::Result::Err(kb_core::Error::new(
"token_2022_stateful_account_too_large",
format!(
"Token-2022 account {} reports {} bytes and returned {} bytes above limit {}",
request.account.0,
account.space,
account.data.len(),
request.max_data_bytes
),
));
}
if account.space != account.data.len() as u64 {
return std::result::Result::Err(kb_core::Error::new(
"token_2022_stateful_account_data_incomplete",
format!(
"Token-2022 account {} reports {} bytes but returned {} decoded bytes",
request.account.0,
account.space,
account.data.len()
),
));
}
let state = match kb_decoder_spl_token_2022::state::parse_token_2022_state(
request.kind,
account.data.as_slice(),
) {
std::result::Result::Ok(value) => value,
std::result::Result::Err(error) => {
return std::result::Result::Err(kb_core::Error::new(
"token_2022_stateful_parse_failed",
error,
));
},
};
let snapshot = match crate::solana_token_2022_stateful::materialize_parsed_token_2022_stateful_snapshot_with_context(
request.account.0.as_str(),
result.context.slot,
&state,
&request.context,
) {
std::result::Result::Ok(value) => value,
std::result::Result::Err(error) => {
return std::result::Result::Err(kb_core::Error::new(
"token_2022_stateful_projection_failed",
error,
));
},
};
return std::result::Result::Ok(
crate::solana_token_2022_stateful::Token2022StatefulReadResult {
commitment: "confirmed".to_string(),
context_slot: result.context.slot,
snapshot,
},
);
}
/// One contextually validated Token-2022 state snapshot and its owned projections.
#[derive(Clone, Debug, PartialEq, serde::Deserialize, serde::Serialize)]
pub struct Token2022StatefulSnapshotBundle {
/// Canonical account identity used for contextual checks and stable output keys.
pub account_key: std::string::String,
/// Context slot associated with the account read.
pub slot: u64,
/// Parsed base-state category.
pub state_kind: std::string::String,
/// Ordered published or future extension names retained by the parser.
pub extension_names: std::vec::Vec<std::string::String>,
/// Processor-owned projections routed without duplicate ownership.
pub outputs: std::vec::Vec<kb_materializer_api::MaterializedOutput>,
}
/// Parse, contextually validate, and materialize one bounded Token-2022 account snapshot.
pub fn materialize_token_2022_stateful_snapshot(
account_key: &str,
owner_program_id: &str,
slot: u64,
kind: kb_decoder_spl_token_2022::state::Token2022StateKind,
data: &[u8],
) -> std::result::Result<crate::solana_token_2022_stateful::Token2022StatefulSnapshotBundle, String>
{
if owner_program_id != kb_program_ids::SPL_TOKEN_2022_PROGRAM_ID {
return std::result::Result::Err(format!(
"Token-2022 state snapshot owner must be {}, got {owner_program_id}",
kb_program_ids::SPL_TOKEN_2022_PROGRAM_ID
));
}
let state = match kb_decoder_spl_token_2022::state::parse_token_2022_state(kind, data) {
std::result::Result::Ok(value) => value,
std::result::Result::Err(error) => return std::result::Result::Err(error),
};
return crate::solana_token_2022_stateful::materialize_parsed_token_2022_stateful_snapshot_with_context(
account_key,
slot,
&state,
&crate::solana_token_2022_stateful::Token2022StatefulContext::default(),
);
}
/// Contextually validate and materialize one already parsed Token-2022 account snapshot.
pub fn materialize_parsed_token_2022_stateful_snapshot(
account_key: &str,
slot: u64,
state: &kb_decoder_spl_token_2022::state::Token2022State,
) -> std::result::Result<crate::solana_token_2022_stateful::Token2022StatefulSnapshotBundle, String>
{
return crate::solana_token_2022_stateful::materialize_parsed_token_2022_stateful_snapshot_with_context(
account_key,
slot,
state,
&crate::solana_token_2022_stateful::Token2022StatefulContext::default(),
);
}
/// Contextually validate one parsed Token-2022 snapshot with external cross-account identities.
pub fn materialize_parsed_token_2022_stateful_snapshot_with_context(
account_key: &str,
slot: u64,
state: &kb_decoder_spl_token_2022::state::Token2022State,
context: &crate::solana_token_2022_stateful::Token2022StatefulContext,
) -> std::result::Result<crate::solana_token_2022_stateful::Token2022StatefulSnapshotBundle, String>
{
if account_key.trim().is_empty() {
return std::result::Result::Err(
"Token-2022 stateful snapshot requires a non-empty account key".to_string(),
);
}
let decoded = match bs58::decode(account_key).into_vec() {
std::result::Result::Ok(value) => value,
std::result::Result::Err(_) => {
return std::result::Result::Err(
"Token-2022 stateful snapshot account key must be valid base58".to_string(),
);
},
};
if decoded.len() != 32 {
return std::result::Result::Err(format!(
"Token-2022 stateful snapshot account key must decode to 32 bytes, got {}",
decoded.len()
));
}
match crate::solana_token_2022_stateful::validate_embedded_mint_identity(account_key, state) {
std::result::Result::Ok(()) => {},
std::result::Result::Err(error) => return std::result::Result::Err(error),
}
match crate::solana_token_2022_stateful::validate_group_member_identity(state, context) {
std::result::Result::Ok(()) => {},
std::result::Result::Err(error) => return std::result::Result::Err(error),
}
let account_output = match kb_materializer_token_accounts::materialize_token_2022_state_snapshot(
account_key,
slot,
state,
) {
std::result::Result::Ok(value) => value,
std::result::Result::Err(error) => return std::result::Result::Err(error),
};
let mut outputs = std::vec![account_output];
if state.kind == kb_decoder_spl_token_2022::state::Token2022StateKind::Mint {
let metadata_outputs =
match kb_materializer_metadata::materialize_token_2022_metadata_snapshot(
account_key,
slot,
state,
) {
std::result::Result::Ok(value) => value,
std::result::Result::Err(error) => return std::result::Result::Err(error),
};
outputs.extend(metadata_outputs);
}
let fee_outputs = match kb_materializer_fees::materialize_token_2022_fee_state_snapshots(
account_key,
slot,
state,
) {
std::result::Result::Ok(value) => value,
std::result::Result::Err(error) => return std::result::Result::Err(error),
};
outputs.extend(fee_outputs);
let admin_outputs = match kb_materializer_admin::materialize_token_2022_admin_state_snapshots(
account_key,
slot,
state,
) {
std::result::Result::Ok(value) => value,
std::result::Result::Err(error) => return std::result::Result::Err(error),
};
outputs.extend(admin_outputs);
let state_kind = match state.kind {
kb_decoder_spl_token_2022::state::Token2022StateKind::Mint => "mint",
kb_decoder_spl_token_2022::state::Token2022StateKind::Account => "account",
kb_decoder_spl_token_2022::state::Token2022StateKind::Multisig => "multisig",
};
return std::result::Result::Ok(
crate::solana_token_2022_stateful::Token2022StatefulSnapshotBundle {
account_key: account_key.to_string(),
slot,
state_kind: state_kind.to_string(),
extension_names: state
.extensions
.iter()
.map(|entry| return entry.extension_name.to_string())
.collect(),
outputs,
},
);
}
fn validate_embedded_mint_identity(
account_key: &str,
state: &kb_decoder_spl_token_2022::state::Token2022State,
) -> std::result::Result<(), String> {
for entry in &state.extensions {
if entry.extension_name != "token_metadata"
&& entry.extension_name != "token_group"
&& entry.extension_name != "token_group_member"
{
continue;
}
let embedded_mint = entry.value_fields.get("mint").and_then(serde_json::Value::as_str);
let embedded_mint = match embedded_mint {
std::option::Option::Some(value) => value,
std::option::Option::None => {
return std::result::Result::Err(format!(
"Token-2022 {} extension requires a structured mint field",
entry.extension_name
));
},
};
if embedded_mint != account_key {
return std::result::Result::Err(format!(
"Token-2022 {} mint {} does not match account {account_key}",
entry.extension_name, embedded_mint
));
}
}
return std::result::Result::Ok(());
}
fn validate_group_member_identity(
state: &kb_decoder_spl_token_2022::state::Token2022State,
context: &crate::solana_token_2022_stateful::Token2022StatefulContext,
) -> std::result::Result<(), String> {
for entry in &state.extensions {
if entry.extension_name != "token_group_member" {
continue;
}
let group = entry.value_fields.get("group").and_then(serde_json::Value::as_str);
let group = match group {
std::option::Option::Some(value) => value,
std::option::Option::None => {
return std::result::Result::Err(
"Token-2022 token_group_member extension requires a structured group field"
.to_string(),
);
},
};
let expected = match context.expected_group_address.as_deref() {
std::option::Option::Some(value) => value,
std::option::Option::None => {
return std::result::Result::Err(
"Token-2022 token_group_member validation requires an expected group address"
.to_string(),
);
},
};
if group != expected {
return std::result::Result::Err(format!(
"Token-2022 token_group_member group {group} does not match expected group {expected}"
));
}
}
return std::result::Result::Ok(());
}
#[cfg(test)]
mod tests {
fn account_key(byte: u8) -> String {
return bs58::encode([byte; 32]).into_string();
}
fn mint_state(account_key: &str) -> kb_decoder_spl_token_2022::state::Token2022State {
return kb_decoder_spl_token_2022::state::Token2022State {
kind: kb_decoder_spl_token_2022::state::Token2022StateKind::Mint,
base_fields: serde_json::json!({"supply":"1","decimals":0,"initialized":true}),
base_hex: "00".repeat(82),
account_type: std::option::Option::Some(1),
extensions: std::vec![
kb_decoder_spl_token_2022::state::Token2022TlvEntry {
extension_type: 19,
extension_name: "token_metadata",
value_hex: "01".to_string(),
value_fields: serde_json::json!({
"mint": account_key,
"name": "Token",
"symbol": "TOK",
"uri": "https://example.invalid/token.json"
}),
},
kb_decoder_spl_token_2022::state::Token2022TlvEntry {
extension_type: 20,
extension_name: "token_group",
value_hex: "02".to_string(),
value_fields: serde_json::json!({
"mint": account_key,
"size": "1",
"maxSize": "10"
}),
},
],
};
}
#[test]
fn mint_snapshot_routes_account_and_metadata_outputs_once() {
let account_key = account_key(7);
let state = mint_state(account_key.as_str());
let bundle =
crate::solana_token_2022_stateful::materialize_parsed_token_2022_stateful_snapshot(
account_key.as_str(),
42,
&state,
);
assert_eq!(
bundle.as_ref().map(|value| return value.outputs.len()),
std::result::Result::Ok(3)
);
assert_eq!(
bundle.as_ref().map(|value| return value.state_kind.clone()),
std::result::Result::Ok("mint".to_string())
);
assert_eq!(
bundle.as_ref().map(|value| return value.extension_names.clone()),
std::result::Result::Ok(std::vec![
"token_metadata".to_string(),
"token_group".to_string()
])
);
}
#[test]
fn embedded_metadata_mint_must_match_the_account_identity() {
let account_address = account_key(8);
let mut state = mint_state(account_address.as_str());
state.extensions[0].value_fields["mint"] = serde_json::json!(account_key(9));
let result =
crate::solana_token_2022_stateful::materialize_parsed_token_2022_stateful_snapshot(
account_address.as_str(),
42,
&state,
);
assert!(result.is_err());
}
#[test]
fn owner_and_account_identity_fail_closed_before_projection() {
let account_key = account_key(10);
let data = [0u8; 82];
let wrong_owner =
crate::solana_token_2022_stateful::materialize_token_2022_stateful_snapshot(
account_key.as_str(),
kb_program_ids::SPL_TOKEN_PROGRAM_ID,
1,
kb_decoder_spl_token_2022::state::Token2022StateKind::Mint,
&data,
);
assert!(wrong_owner.is_err());
let state = mint_state(account_key.as_str());
let malformed_key =
crate::solana_token_2022_stateful::materialize_parsed_token_2022_stateful_snapshot(
"not-base58-0",
1,
&state,
);
assert!(malformed_key.is_err());
}
#[test]
fn account_snapshot_has_no_metadata_projection() {
let account_key = account_key(11);
let state = kb_decoder_spl_token_2022::state::Token2022State {
kind: kb_decoder_spl_token_2022::state::Token2022StateKind::Account,
base_fields: serde_json::json!({"amount":"0","state":"initialized"}),
base_hex: "00".repeat(165),
account_type: std::option::Option::None,
extensions: std::vec::Vec::new(),
};
let bundle =
crate::solana_token_2022_stateful::materialize_parsed_token_2022_stateful_snapshot(
account_key.as_str(),
9,
&state,
);
assert_eq!(
bundle.as_ref().map(|value| return value.outputs.len()),
std::result::Result::Ok(1)
);
}
#[test]
fn group_member_requires_and_matches_external_group_identity() {
let account_address = account_key(12);
let group_address = account_key(13);
let state = kb_decoder_spl_token_2022::state::Token2022State {
kind: kb_decoder_spl_token_2022::state::Token2022StateKind::Mint,
base_fields: serde_json::json!({"supply":"1","decimals":0,"initialized":true}),
base_hex: "00".repeat(82),
account_type: std::option::Option::Some(1),
extensions: std::vec![kb_decoder_spl_token_2022::state::Token2022TlvEntry {
extension_type: 23,
extension_name: "token_group_member",
value_hex: "03".to_string(),
value_fields: serde_json::json!({
"mint": account_address,
"group": group_address,
"memberNumber": "1"
}),
}],
};
let missing =
crate::solana_token_2022_stateful::materialize_parsed_token_2022_stateful_snapshot(
account_address.as_str(),
44,
&state,
);
assert!(missing.is_err());
let wrong_context = crate::solana_token_2022_stateful::Token2022StatefulContext {
expected_group_address: std::option::Option::Some(account_key(14)),
};
let wrong = crate::solana_token_2022_stateful::materialize_parsed_token_2022_stateful_snapshot_with_context(
account_address.as_str(),
44,
&state,
&wrong_context,
);
assert!(wrong.is_err());
let context = crate::solana_token_2022_stateful::Token2022StatefulContext {
expected_group_address: std::option::Option::Some(group_address),
};
let valid = crate::solana_token_2022_stateful::materialize_parsed_token_2022_stateful_snapshot_with_context(
account_address.as_str(),
44,
&state,
&context,
);
assert_eq!(
valid.as_ref().map(|bundle| return bundle.outputs.len()),
std::result::Result::Ok(2)
);
}
#[test]
fn mint_and_account_fee_extensions_route_to_the_fee_owner_once() {
let mint_address = account_key(15);
let mint = kb_decoder_spl_token_2022::state::Token2022State {
kind: kb_decoder_spl_token_2022::state::Token2022StateKind::Mint,
base_fields: serde_json::json!({"supply":"1","decimals":0,"initialized":true}),
base_hex: "00".repeat(82),
account_type: std::option::Option::Some(1),
extensions: std::vec![kb_decoder_spl_token_2022::state::Token2022TlvEntry {
extension_type: 1,
extension_name: "transfer_fee_config",
value_hex: "01".to_string(),
value_fields: serde_json::json!({"withheldAmount":"7"}),
}],
};
let mint_bundle =
crate::solana_token_2022_stateful::materialize_parsed_token_2022_stateful_snapshot(
mint_address.as_str(),
50,
&mint,
);
assert_eq!(
mint_bundle.as_ref().map(|bundle| return bundle.outputs.len()),
std::result::Result::Ok(2)
);
assert_eq!(
mint_bundle
.as_ref()
.map(|bundle| return bundle.outputs[1].payload_json["provenance"]["processorName"]
.clone()),
std::result::Result::Ok(serde_json::json!("fees"))
);
let account_address = account_key(16);
let account = kb_decoder_spl_token_2022::state::Token2022State {
kind: kb_decoder_spl_token_2022::state::Token2022StateKind::Account,
base_fields: serde_json::json!({"amount":"0","state":"initialized"}),
base_hex: "00".repeat(165),
account_type: std::option::Option::Some(2),
extensions: std::vec![kb_decoder_spl_token_2022::state::Token2022TlvEntry {
extension_type: 17,
extension_name: "confidential_transfer_fee_amount",
value_hex: "02".to_string(),
value_fields: serde_json::json!({"withheldAmount":"ciphertext"}),
}],
};
let account_bundle =
crate::solana_token_2022_stateful::materialize_parsed_token_2022_stateful_snapshot(
account_address.as_str(),
51,
&account,
);
assert_eq!(
account_bundle.as_ref().map(|bundle| return bundle.outputs.len()),
std::result::Result::Ok(2)
);
assert_eq!(
account_bundle
.as_ref()
.map(|bundle| return bundle.outputs[1].payload_json["confidentialValuesDecrypted"]
.clone()),
std::result::Result::Ok(serde_json::json!(false))
);
}
#[test]
fn mint_admin_extensions_route_to_the_admin_owner_once() {
let account_key = account_key(12);
let mut state = mint_state(account_key.as_str());
state.extensions.push(kb_decoder_spl_token_2022::state::Token2022TlvEntry {
extension_type: 6,
extension_name: "default_account_state",
value_hex: "02".to_string(),
value_fields: serde_json::json!({"state": 2}),
});
let bundle =
crate::solana_token_2022_stateful::materialize_parsed_token_2022_stateful_snapshot(
account_key.as_str(),
17,
&state,
);
assert_eq!(
bundle.as_ref().map(|value| return value.outputs.len()),
std::result::Result::Ok(4)
);
assert_eq!(
bundle.as_ref().map(|value| {
return value
.outputs
.iter()
.filter(|output| {
return output.payload_json["domain"]
== serde_json::json!("token_2022_extension_admin_state");
})
.count();
}),
std::result::Result::Ok(1)
);
}
#[test]
fn complete_rpc_account_routes_only_after_owner_size_and_context_validation() {
let account = crate::solana_token_2022_stateful::tests::account_key(21);
let request = crate::solana_token_2022_stateful::Token2022StatefulReadRequest {
query_role: "execution".to_string(),
account: kb_model::Pubkey(account.clone()),
kind: kb_decoder_spl_token_2022::state::Token2022StateKind::Mint,
min_context_slot: std::option::Option::Some(40),
max_data_bytes: 82,
context: crate::solana_token_2022_stateful::Token2022StatefulContext::default(),
};
let result = kb_rpc::AccountInfoResult {
context: kb_rpc::RpcResponseContext {
slot: 41,
api_version: std::option::Option::None,
},
account: std::option::Option::Some(kb_rpc::AccountInfoValue {
lamports: 1,
owner: kb_model::ProgramId(kb_program_ids::SPL_TOKEN_2022_PROGRAM_ID.to_string()),
executable: false,
rent_epoch: 0,
space: 82,
data: std::vec![0; 82],
}),
};
let materialized =
crate::solana_token_2022_stateful::materialize_token_2022_account_info_result(
&request, &result,
);
assert_eq!(
materialized.as_ref().map(|value| return value.context_slot),
std::result::Result::Ok(41)
);
assert_eq!(
materialized.as_ref().map(|value| return value.snapshot.outputs.len()),
std::result::Result::Ok(1)
);
}
#[test]
fn incomplete_foreign_executable_and_stale_rpc_accounts_fail_closed() {
let account = crate::solana_token_2022_stateful::tests::account_key(22);
let request = crate::solana_token_2022_stateful::Token2022StatefulReadRequest {
query_role: "execution".to_string(),
account: kb_model::Pubkey(account),
kind: kb_decoder_spl_token_2022::state::Token2022StateKind::Mint,
min_context_slot: std::option::Option::Some(50),
max_data_bytes: 82,
context: crate::solana_token_2022_stateful::Token2022StatefulContext::default(),
};
let base = kb_rpc::AccountInfoValue {
lamports: 1,
owner: kb_model::ProgramId(kb_program_ids::SPL_TOKEN_2022_PROGRAM_ID.to_string()),
executable: false,
rent_epoch: 0,
space: 82,
data: std::vec![0; 82],
};
let stale = kb_rpc::AccountInfoResult {
context: kb_rpc::RpcResponseContext {
slot: 49,
api_version: std::option::Option::None,
},
account: std::option::Option::Some(base.clone()),
};
assert!(
crate::solana_token_2022_stateful::materialize_token_2022_account_info_result(
&request, &stale
)
.is_err()
);
let mut foreign = base.clone();
foreign.owner = kb_model::ProgramId(kb_program_ids::SPL_TOKEN_PROGRAM_ID.to_string());
let foreign = kb_rpc::AccountInfoResult {
context: kb_rpc::RpcResponseContext {
slot: 50,
api_version: std::option::Option::None,
},
account: std::option::Option::Some(foreign),
};
assert!(
crate::solana_token_2022_stateful::materialize_token_2022_account_info_result(
&request, &foreign
)
.is_err()
);
let mut executable = base.clone();
executable.executable = true;
let executable = kb_rpc::AccountInfoResult {
context: kb_rpc::RpcResponseContext {
slot: 50,
api_version: std::option::Option::None,
},
account: std::option::Option::Some(executable),
};
assert!(
crate::solana_token_2022_stateful::materialize_token_2022_account_info_result(
&request,
&executable
)
.is_err()
);
let mut incomplete = base;
incomplete.data.pop();
let incomplete = kb_rpc::AccountInfoResult {
context: kb_rpc::RpcResponseContext {
slot: 50,
api_version: std::option::Option::None,
},
account: std::option::Option::Some(incomplete),
};
assert!(
crate::solana_token_2022_stateful::materialize_token_2022_account_info_result(
&request,
&incomplete
)
.is_err()
);
}
}