v0.1.0-pre.034
This commit is contained in:
@@ -13,8 +13,11 @@ mod core_extraction;
|
||||
mod decode_replay;
|
||||
mod plan;
|
||||
mod solana_elgamal_registry_stateful;
|
||||
mod solana_token2022_correlation;
|
||||
mod solana_token2022_crypto_preflight;
|
||||
mod solana_token2022_devnet_scenarios;
|
||||
mod solana_token2022_preflight;
|
||||
mod solana_token2022_proof_orchestration;
|
||||
mod solana_token2022_stateful;
|
||||
mod solana_token2022_validation;
|
||||
|
||||
@@ -100,6 +103,26 @@ pub use self::solana_elgamal_registry_stateful::materialize_elgamal_registry_acc
|
||||
pub use self::solana_elgamal_registry_stateful::materialize_elgamal_registry_stateful_snapshot;
|
||||
/// Migrated read_elgamal_registry_stateful_snapshot contract.
|
||||
pub use self::solana_elgamal_registry_stateful::read_elgamal_registry_stateful_snapshot;
|
||||
/// Correlation outcome between one committed instruction fact and one final state snapshot.
|
||||
pub use self::solana_token2022_correlation::Token2022CorrelationStatus;
|
||||
/// Deterministic correlation report for one instruction output and one final snapshot.
|
||||
pub use self::solana_token2022_correlation::Token2022StateCorrelationReport;
|
||||
/// Correlates one materialized instruction fact with one authoritative Token-2022 snapshot.
|
||||
pub use self::solana_token2022_correlation::correlate_token2022_instruction_with_snapshot;
|
||||
/// Maximum number of distinct proof context-state accounts accepted by one preflight.
|
||||
pub use self::solana_token2022_crypto_preflight::MAX_TOKEN2022_PROOF_CONTEXTS;
|
||||
/// Token-2022 cryptographic preflight report.
|
||||
pub use self::solana_token2022_crypto_preflight::Token2022CryptographicPreflightReport;
|
||||
/// Token-2022 cryptographic preflight request.
|
||||
pub use self::solana_token2022_crypto_preflight::Token2022CryptographicPreflightRequest;
|
||||
/// One validated proof context report.
|
||||
pub use self::solana_token2022_crypto_preflight::Token2022ProofContextReport;
|
||||
/// One required proof context-state account.
|
||||
pub use self::solana_token2022_crypto_preflight::Token2022ProofContextRequirement;
|
||||
/// Encoded proof context metadata bytes.
|
||||
pub use self::solana_token2022_crypto_preflight::ZK_PROOF_CONTEXT_META_BYTES;
|
||||
/// Inspects bounded Token-2022 cryptographic proof contexts.
|
||||
pub use self::solana_token2022_crypto_preflight::inspect_token2022_cryptographic_preflight;
|
||||
/// Stable category of one independent Devnet validation scenario.
|
||||
pub use self::solana_token2022_devnet_scenarios::DevnetSplValidationFamily;
|
||||
/// Current implementation status of one Devnet validation scenario.
|
||||
@@ -122,6 +145,14 @@ pub use self::solana_token2022_preflight::Token2022PreflightRequest;
|
||||
pub use self::solana_token2022_preflight::Token2022PreflightRequirement;
|
||||
/// Migrated inspect_token2022_preflight contract.
|
||||
pub use self::solana_token2022_preflight::inspect_token2022_preflight;
|
||||
/// Maximum number of proofs accepted for one Token-2022 operation.
|
||||
pub use self::solana_token2022_proof_orchestration::MAX_TOKEN2022_OPERATION_PROOFS;
|
||||
/// Token-2022 proof orchestration report.
|
||||
pub use self::solana_token2022_proof_orchestration::Token2022ProofOrchestrationReport;
|
||||
/// Token-2022 proof orchestration request.
|
||||
pub use self::solana_token2022_proof_orchestration::Token2022ProofOrchestrationRequest;
|
||||
/// Validates mixed inline and context-state proof orchestration.
|
||||
pub use self::solana_token2022_proof_orchestration::orchestrate_token2022_proofs;
|
||||
/// Migrated MAX_TOKEN2022_STATEFUL_ACCOUNT_BYTES contract.
|
||||
pub use self::solana_token2022_stateful::MAX_TOKEN2022_STATEFUL_ACCOUNT_BYTES;
|
||||
/// Migrated Token2022StatefulContext contract.
|
||||
|
||||
242
kb-pipeline/src/solana_token2022_correlation.rs
Normal file
242
kb-pipeline/src/solana_token2022_correlation.rs
Normal file
@@ -0,0 +1,242 @@
|
||||
// file: kb-pipeline/src/solana_token2022_correlation.rs
|
||||
// version: 1
|
||||
|
||||
//! Deterministic Token-2022 instruction-to-state correlation.
|
||||
|
||||
/// Correlation outcome between one committed instruction fact and one final state snapshot.
|
||||
#[derive(Clone, Copy, Debug, Eq, PartialEq, serde::Deserialize, serde::Serialize)]
|
||||
pub enum Token2022CorrelationStatus {
|
||||
/// The final snapshot contains the extension expected by the instruction fact.
|
||||
Confirmed,
|
||||
/// The final snapshot does not contain the extension expected by the instruction fact.
|
||||
Contradicted,
|
||||
/// The instruction output is outside the supported correlation inventory.
|
||||
NotApplicable,
|
||||
}
|
||||
|
||||
/// Deterministic correlation report for one instruction output and one final snapshot.
|
||||
#[derive(Clone, Debug, PartialEq, serde::Deserialize, serde::Serialize)]
|
||||
pub struct Token2022StateCorrelationReport {
|
||||
/// Stable caller-provided correlation identity.
|
||||
pub correlation_key: std::string::String,
|
||||
/// Canonical account identity checked against the snapshot.
|
||||
pub account: kb_lib::MdPubkey,
|
||||
/// Instruction operation or risk fact used for correlation.
|
||||
pub fact_code: std::string::String,
|
||||
/// Extension expected from the fact when the fact is supported.
|
||||
pub expected_extension: std::option::Option<std::string::String>,
|
||||
/// Final correlation outcome.
|
||||
pub status: Token2022CorrelationStatus,
|
||||
/// Snapshot context slot.
|
||||
pub snapshot_slot: u64,
|
||||
/// Whether the snapshot contains the expected extension.
|
||||
pub extension_present: std::option::Option<bool>,
|
||||
/// Explicit semantic limitation of this correlation.
|
||||
pub fact_only: bool,
|
||||
}
|
||||
|
||||
/// Correlates one materialized instruction fact with one authoritative Token-2022 snapshot.
|
||||
pub fn correlate_token2022_instruction_with_snapshot(
|
||||
correlation_key: &str,
|
||||
account: &kb_lib::MdPubkey,
|
||||
instruction_output: &kb_lib::MtApiMaterializedOutput,
|
||||
snapshot: &crate::Token2022StatefulSnapshotBundle,
|
||||
) -> kb_core::Result<Token2022StateCorrelationReport> {
|
||||
if correlation_key.trim().is_empty() {
|
||||
return std::result::Result::Err(kb_core::Error::config(
|
||||
"Token-2022 correlation key must not be empty",
|
||||
));
|
||||
}
|
||||
if snapshot.account_key != account.0 {
|
||||
return std::result::Result::Err(kb_core::Error::new(
|
||||
"token2022_correlation_account_mismatch",
|
||||
format!(
|
||||
"Token-2022 correlation expected account {}, got snapshot {}",
|
||||
account.0, snapshot.account_key
|
||||
),
|
||||
));
|
||||
}
|
||||
let fact_code = match instruction_output.payload_json.get("riskKind") {
|
||||
std::option::Option::Some(value) => value.as_str().unwrap_or("").to_string(),
|
||||
std::option::Option::None => instruction_output
|
||||
.payload_json
|
||||
.get("operation")
|
||||
.and_then(serde_json::Value::as_str)
|
||||
.unwrap_or("")
|
||||
.to_string(),
|
||||
};
|
||||
if fact_code.is_empty() {
|
||||
return std::result::Result::Err(kb_core::Error::new(
|
||||
"token2022_correlation_fact_missing",
|
||||
"Token-2022 correlation requires riskKind or operation in the instruction output",
|
||||
));
|
||||
}
|
||||
let expected_extension = expected_extension(fact_code.as_str());
|
||||
let extension_present = expected_extension.map(|extension| {
|
||||
return snapshot.extension_names.iter().any(|candidate| return candidate == extension);
|
||||
});
|
||||
let status = match extension_present {
|
||||
std::option::Option::Some(true) => Token2022CorrelationStatus::Confirmed,
|
||||
std::option::Option::Some(false) => Token2022CorrelationStatus::Contradicted,
|
||||
std::option::Option::None => Token2022CorrelationStatus::NotApplicable,
|
||||
};
|
||||
return std::result::Result::Ok(Token2022StateCorrelationReport {
|
||||
correlation_key: correlation_key.to_string(),
|
||||
account: account.clone(),
|
||||
fact_code,
|
||||
expected_extension: expected_extension.map(str::to_string),
|
||||
status,
|
||||
snapshot_slot: snapshot.slot,
|
||||
extension_present,
|
||||
fact_only: true,
|
||||
});
|
||||
}
|
||||
|
||||
fn expected_extension(fact_code: &str) -> std::option::Option<&'static str> {
|
||||
return match fact_code {
|
||||
"default_account_state_configured"
|
||||
| "initialize_default_account_state"
|
||||
| "update_default_account_state" => std::option::Option::Some("default_account_state"),
|
||||
"required_transfer_memos_enabled"
|
||||
| "required_transfer_memos_disabled"
|
||||
| "enable_required_transfer_memos"
|
||||
| "disable_required_transfer_memos" => std::option::Option::Some("memo_transfer"),
|
||||
"cpi_guard_enabled" | "cpi_guard_disabled" | "enable_cpi_guard" | "disable_cpi_guard" => {
|
||||
std::option::Option::Some("cpi_guard")
|
||||
},
|
||||
"non_transferable_mint_configured" | "initialize_non_transferable_mint" => {
|
||||
std::option::Option::Some("non_transferable")
|
||||
},
|
||||
"pausable_mint_configured"
|
||||
| "mint_activity_paused"
|
||||
| "mint_activity_resumed"
|
||||
| "initialize_pausable_config"
|
||||
| "pause"
|
||||
| "resume" => std::option::Option::Some("pausable"),
|
||||
"permissioned_burn_configured" | "initialize_permissioned_burn" => {
|
||||
std::option::Option::Some("permissioned_burn")
|
||||
},
|
||||
"confidential_credits_enabled"
|
||||
| "confidential_credits_disabled"
|
||||
| "non_confidential_credits_enabled"
|
||||
| "non_confidential_credits_disabled"
|
||||
| "enable_confidential_credits"
|
||||
| "disable_confidential_credits"
|
||||
| "enable_non_confidential_credits"
|
||||
| "disable_non_confidential_credits" => {
|
||||
std::option::Option::Some("confidential_transfer_account")
|
||||
},
|
||||
"initialize_transfer_fee_config"
|
||||
| "set_transfer_fee"
|
||||
| "withdraw_withheld_tokens_from_mint"
|
||||
| "harvest_withheld_tokens_to_mint" => std::option::Option::Some("transfer_fee_config"),
|
||||
"withdraw_withheld_tokens_from_accounts" | "transfer_checked_with_fee" => {
|
||||
std::option::Option::Some("transfer_fee_amount")
|
||||
},
|
||||
"initialize_confidential_transfer_fee_config"
|
||||
| "withdraw_confidential_withheld_tokens_from_mint"
|
||||
| "harvest_confidential_withheld_tokens_to_mint"
|
||||
| "enable_confidential_harvest_to_mint"
|
||||
| "disable_confidential_harvest_to_mint" => {
|
||||
std::option::Option::Some("confidential_transfer_fee_config")
|
||||
},
|
||||
"withdraw_confidential_withheld_tokens_from_accounts"
|
||||
| "transfer_confidential_tokens_with_fee" => {
|
||||
std::option::Option::Some("confidential_transfer_fee_amount")
|
||||
},
|
||||
_ => std::option::Option::None,
|
||||
};
|
||||
}
|
||||
|
||||
#[cfg(test)]
|
||||
mod tests {
|
||||
fn snapshot(extension_names: &[&str]) -> crate::Token2022StatefulSnapshotBundle {
|
||||
return crate::Token2022StatefulSnapshotBundle {
|
||||
account_key: solana_pubkey::Pubkey::new_from_array([7u8; 32]).to_string(),
|
||||
slot: 42,
|
||||
state_kind: "mint".to_string(),
|
||||
extension_names: extension_names
|
||||
.iter()
|
||||
.map(|value| return (*value).to_string())
|
||||
.collect(),
|
||||
outputs: std::vec::Vec::new(),
|
||||
};
|
||||
}
|
||||
|
||||
fn output(field: &str, value: &str) -> kb_lib::MtApiMaterializedOutput {
|
||||
let mut payload = serde_json::Map::new();
|
||||
payload.insert(field.to_string(), serde_json::Value::String(value.to_string()));
|
||||
return kb_lib::MtApiMaterializedOutput {
|
||||
output_key: "fact:0".to_string(),
|
||||
family: kb_lib::MdMaterializedEventFamily::Risk,
|
||||
payload_json: serde_json::Value::Object(payload),
|
||||
};
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn known_fact_is_confirmed_or_contradicted_by_the_final_extension_inventory() {
|
||||
let snapshot = snapshot(&["pausable"]);
|
||||
let account = kb_lib::MdPubkey(snapshot.account_key.clone());
|
||||
let confirmed = super::correlate_token2022_instruction_with_snapshot(
|
||||
"corr:pause",
|
||||
&account,
|
||||
&output("riskKind", "mint_activity_paused"),
|
||||
&snapshot,
|
||||
);
|
||||
assert!(confirmed.is_ok());
|
||||
if let std::result::Result::Ok(confirmed) = confirmed {
|
||||
assert_eq!(confirmed.status, super::Token2022CorrelationStatus::Confirmed);
|
||||
assert_eq!(confirmed.extension_present, std::option::Option::Some(true));
|
||||
}
|
||||
let contradicted = super::correlate_token2022_instruction_with_snapshot(
|
||||
"corr:fee",
|
||||
&account,
|
||||
&output("operation", "initialize_transfer_fee_config"),
|
||||
&snapshot,
|
||||
);
|
||||
assert!(contradicted.is_ok());
|
||||
if let std::result::Result::Ok(contradicted) = contradicted {
|
||||
assert_eq!(contradicted.status, super::Token2022CorrelationStatus::Contradicted);
|
||||
assert_eq!(contradicted.extension_present, std::option::Option::Some(false));
|
||||
}
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn unsupported_fact_is_explicitly_not_applicable_without_inventing_a_conclusion() {
|
||||
let snapshot = snapshot(&[]);
|
||||
let account = kb_lib::MdPubkey(snapshot.account_key.clone());
|
||||
let report = super::correlate_token2022_instruction_with_snapshot(
|
||||
"corr:unknown",
|
||||
&account,
|
||||
&output("operation", "unknown_future_operation"),
|
||||
&snapshot,
|
||||
);
|
||||
assert!(report.is_ok());
|
||||
if let std::result::Result::Ok(report) = report {
|
||||
assert_eq!(report.status, super::Token2022CorrelationStatus::NotApplicable);
|
||||
assert_eq!(report.extension_present, std::option::Option::None);
|
||||
assert!(report.fact_only);
|
||||
}
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn correlation_identity_and_account_mismatch_fail_closed() {
|
||||
let snapshot = snapshot(&["memo_transfer"]);
|
||||
let account = kb_lib::MdPubkey(snapshot.account_key.clone());
|
||||
let empty = super::correlate_token2022_instruction_with_snapshot(
|
||||
"",
|
||||
&account,
|
||||
&output("riskKind", "required_transfer_memos_enabled"),
|
||||
&snapshot,
|
||||
);
|
||||
assert!(empty.is_err());
|
||||
let wrong = kb_lib::MdPubkey(solana_pubkey::Pubkey::new_from_array([8u8; 32]).to_string());
|
||||
let mismatch = super::correlate_token2022_instruction_with_snapshot(
|
||||
"corr:mismatch",
|
||||
&wrong,
|
||||
&output("riskKind", "required_transfer_memos_enabled"),
|
||||
&snapshot,
|
||||
);
|
||||
assert!(mismatch.is_err());
|
||||
}
|
||||
}
|
||||
319
kb-pipeline/src/solana_token2022_crypto_preflight.rs
Normal file
319
kb-pipeline/src/solana_token2022_crypto_preflight.rs
Normal file
@@ -0,0 +1,319 @@
|
||||
// file: kb-pipeline/src/solana_token2022_crypto_preflight.rs
|
||||
// version: 2
|
||||
|
||||
//! Bounded cryptographic preflight for Token-2022 proof context-state accounts.
|
||||
|
||||
/// Maximum number of distinct proof context-state accounts accepted by one request.
|
||||
pub const MAX_TOKEN2022_PROOF_CONTEXTS: usize = 8;
|
||||
/// Generic metadata bytes retained by every ZK ElGamal proof context-state account.
|
||||
pub const ZK_PROOF_CONTEXT_META_BYTES: usize = 33;
|
||||
|
||||
/// One exact pre-verified proof context-state requirement.
|
||||
#[derive(Clone, Debug, Eq, PartialEq)]
|
||||
pub struct Token2022ProofContextRequirement {
|
||||
/// Stable semantic role used in diagnostics.
|
||||
pub role: std::string::String,
|
||||
/// Canonical proof context-state account.
|
||||
pub account: kb_lib::MdPubkey,
|
||||
/// Exact proof kind expected by the Token-2022 builder.
|
||||
pub proof_type: kb_lib::ExSolanaCoreZkElGamalProofType,
|
||||
/// Optional authority retained by the context-state account.
|
||||
pub expected_authority: std::option::Option<kb_lib::MdPubkey>,
|
||||
}
|
||||
|
||||
/// One bounded cryptographic preflight request.
|
||||
#[derive(Clone, Debug, Eq, PartialEq)]
|
||||
pub struct Token2022CryptographicPreflightRequest {
|
||||
/// Endpoint role used by every account read.
|
||||
pub query_role: std::string::String,
|
||||
/// Optional minimum RPC context slot.
|
||||
pub min_context_slot: std::option::Option<u64>,
|
||||
/// Ordered proof context-state requirements.
|
||||
pub proof_contexts: std::vec::Vec<Token2022ProofContextRequirement>,
|
||||
}
|
||||
|
||||
/// One validated proof context-state result.
|
||||
#[derive(Clone, Debug, Eq, PartialEq, serde::Deserialize, serde::Serialize)]
|
||||
pub struct Token2022ProofContextReport {
|
||||
/// Stable semantic role.
|
||||
pub role: std::string::String,
|
||||
/// Canonical proof context-state account.
|
||||
pub account: kb_lib::MdPubkey,
|
||||
/// Official one-byte proof discriminator.
|
||||
pub proof_discriminator: u8,
|
||||
/// Exact context-state size for the proof type.
|
||||
pub context_state_bytes: usize,
|
||||
/// Context slot returned by the endpoint.
|
||||
pub context_slot: u64,
|
||||
/// Ordered successful checks.
|
||||
pub checks: std::vec::Vec<std::string::String>,
|
||||
}
|
||||
|
||||
/// Complete cryptographic preflight report.
|
||||
#[derive(Clone, Debug, Eq, PartialEq, serde::Deserialize, serde::Serialize)]
|
||||
pub struct Token2022CryptographicPreflightReport {
|
||||
/// Commitment used for all reads.
|
||||
pub commitment: std::string::String,
|
||||
/// Highest context slot observed across all reads.
|
||||
pub context_slot: u64,
|
||||
/// Ordered validated proof contexts.
|
||||
pub proof_contexts: std::vec::Vec<Token2022ProofContextReport>,
|
||||
}
|
||||
|
||||
/// Reads and validates all pre-verified proof context-state accounts required by one operation.
|
||||
pub async fn inspect_token2022_cryptographic_preflight(
|
||||
pool: &kb_onchain_transport::HttpEndpointPool,
|
||||
request: &crate::Token2022CryptographicPreflightRequest,
|
||||
) -> kb_core::Result<crate::Token2022CryptographicPreflightReport> {
|
||||
let requirements = match validate_proof_context_requirements(request) {
|
||||
std::result::Result::Ok(value) => value,
|
||||
std::result::Result::Err(error) => return std::result::Result::Err(error),
|
||||
};
|
||||
let mut context_slot = request.min_context_slot.unwrap_or(0);
|
||||
let mut reports = std::vec::Vec::with_capacity(requirements.len());
|
||||
for requirement in requirements {
|
||||
let expected_size = requirement.proof_type.context_state_size();
|
||||
let config = match kb_onchain_transport::GetAccountInfoConfig::new_with_data(
|
||||
kb_onchain_transport::RpcCommitmentLevel::Confirmed,
|
||||
request.min_context_slot,
|
||||
expected_size,
|
||||
) {
|
||||
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(), &requirement.account, &config)
|
||||
.await
|
||||
{
|
||||
std::result::Result::Ok(value) => value,
|
||||
std::result::Result::Err(error) => return std::result::Result::Err(error),
|
||||
};
|
||||
let report = match validate_proof_context_account(&requirement, &result) {
|
||||
std::result::Result::Ok(value) => value,
|
||||
std::result::Result::Err(error) => return std::result::Result::Err(error),
|
||||
};
|
||||
context_slot = context_slot.max(report.context_slot);
|
||||
reports.push(report);
|
||||
}
|
||||
return std::result::Result::Ok(crate::Token2022CryptographicPreflightReport {
|
||||
commitment: "confirmed".to_string(),
|
||||
context_slot,
|
||||
proof_contexts: reports,
|
||||
});
|
||||
}
|
||||
|
||||
fn validate_proof_context_requirements(
|
||||
request: &crate::Token2022CryptographicPreflightRequest,
|
||||
) -> kb_core::Result<std::vec::Vec<crate::Token2022ProofContextRequirement>> {
|
||||
if request.query_role.trim().is_empty() {
|
||||
return std::result::Result::Err(kb_core::Error::config(
|
||||
"Token-2022 cryptographic preflight query_role must not be empty",
|
||||
));
|
||||
}
|
||||
if request.proof_contexts.len() > crate::MAX_TOKEN2022_PROOF_CONTEXTS {
|
||||
return std::result::Result::Err(kb_core::Error::new(
|
||||
"token2022_proof_context_limit_exceeded",
|
||||
format!(
|
||||
"Token-2022 cryptographic preflight accepts at most {} proof contexts",
|
||||
crate::MAX_TOKEN2022_PROOF_CONTEXTS
|
||||
),
|
||||
));
|
||||
}
|
||||
let mut indexes = std::collections::BTreeMap::<std::string::String, usize>::new();
|
||||
let mut unique = std::vec::Vec::<crate::Token2022ProofContextRequirement>::new();
|
||||
for requirement in &request.proof_contexts {
|
||||
if requirement.role.trim().is_empty() {
|
||||
return std::result::Result::Err(kb_core::Error::config(
|
||||
"Token-2022 proof context role must not be empty",
|
||||
));
|
||||
}
|
||||
if let std::option::Option::Some(index) = indexes.get(requirement.account.0.as_str()) {
|
||||
if &unique[*index] != requirement {
|
||||
return std::result::Result::Err(kb_core::Error::new(
|
||||
"token2022_proof_context_conflicting_duplicate",
|
||||
format!(
|
||||
"Token-2022 proof context {} has conflicting requirements",
|
||||
requirement.account.0
|
||||
),
|
||||
));
|
||||
}
|
||||
continue;
|
||||
}
|
||||
indexes.insert(requirement.account.0.clone(), unique.len());
|
||||
unique.push(requirement.clone());
|
||||
}
|
||||
return std::result::Result::Ok(unique);
|
||||
}
|
||||
|
||||
fn validate_proof_context_account(
|
||||
requirement: &crate::Token2022ProofContextRequirement,
|
||||
result: &kb_onchain_transport::AccountInfoResult,
|
||||
) -> kb_core::Result<crate::Token2022ProofContextReport> {
|
||||
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(
|
||||
"token2022_proof_context_missing",
|
||||
format!(
|
||||
"Token-2022 proof context account {} does not exist",
|
||||
requirement.account.0
|
||||
),
|
||||
));
|
||||
},
|
||||
};
|
||||
if account.owner.0 != kb_program_ids::ZK_ELGAMAL_PROOF_PROGRAM_ID {
|
||||
return std::result::Result::Err(kb_core::Error::new(
|
||||
"token2022_proof_context_owner_mismatch",
|
||||
format!(
|
||||
"Token-2022 proof context owner must be {}, got {}",
|
||||
kb_program_ids::ZK_ELGAMAL_PROOF_PROGRAM_ID,
|
||||
account.owner.0
|
||||
),
|
||||
));
|
||||
}
|
||||
if account.executable {
|
||||
return std::result::Result::Err(kb_core::Error::new(
|
||||
"token2022_proof_context_executable",
|
||||
"Token-2022 proof context account must not be executable",
|
||||
));
|
||||
}
|
||||
let expected_size = requirement.proof_type.context_state_size();
|
||||
if account.space != expected_size as u64 || account.data.len() != expected_size {
|
||||
return std::result::Result::Err(kb_core::Error::new(
|
||||
"token2022_proof_context_size_mismatch",
|
||||
format!(
|
||||
"Token-2022 proof context {} must contain exactly {expected_size} bytes, got space {} and data {}",
|
||||
requirement.account.0,
|
||||
account.space,
|
||||
account.data.len()
|
||||
),
|
||||
));
|
||||
}
|
||||
if account.data.len() < crate::ZK_PROOF_CONTEXT_META_BYTES {
|
||||
return std::result::Result::Err(kb_core::Error::invalid_state(
|
||||
"Token-2022 proof context is shorter than its generic metadata",
|
||||
));
|
||||
}
|
||||
let discriminator = account.data[32];
|
||||
if discriminator != requirement.proof_type.discriminator() {
|
||||
return std::result::Result::Err(kb_core::Error::new(
|
||||
"token2022_proof_context_type_mismatch",
|
||||
format!(
|
||||
"Token-2022 proof context {} discriminator {} does not match expected {}",
|
||||
requirement.account.0,
|
||||
discriminator,
|
||||
requirement.proof_type.discriminator()
|
||||
),
|
||||
));
|
||||
}
|
||||
let mut checks = std::vec![
|
||||
"zk_program_owner".to_string(),
|
||||
"not_executable".to_string(),
|
||||
"exact_context_state_size".to_string(),
|
||||
"proof_type_discriminator".to_string(),
|
||||
];
|
||||
if let std::option::Option::Some(expected_authority) = requirement.expected_authority.as_ref() {
|
||||
let retained_authority = bs58::encode(&account.data[..32]).into_string();
|
||||
if retained_authority != expected_authority.0 {
|
||||
return std::result::Result::Err(kb_core::Error::new(
|
||||
"token2022_proof_context_authority_mismatch",
|
||||
format!(
|
||||
"Token-2022 proof context {} retains authority {}, expected {}",
|
||||
requirement.account.0, retained_authority, expected_authority.0
|
||||
),
|
||||
));
|
||||
}
|
||||
checks.push("retained_authority".to_string());
|
||||
}
|
||||
return std::result::Result::Ok(crate::Token2022ProofContextReport {
|
||||
role: requirement.role.clone(),
|
||||
account: requirement.account.clone(),
|
||||
proof_discriminator: discriminator,
|
||||
context_state_bytes: expected_size,
|
||||
context_slot: result.context.slot,
|
||||
checks,
|
||||
});
|
||||
}
|
||||
|
||||
#[cfg(test)]
|
||||
mod tests {
|
||||
fn pubkey(byte: u8) -> kb_lib::MdPubkey {
|
||||
return kb_lib::MdPubkey(bs58::encode([byte; 32]).into_string());
|
||||
}
|
||||
|
||||
fn requirement(
|
||||
account: kb_lib::MdPubkey,
|
||||
proof_type: kb_lib::ExSolanaCoreZkElGamalProofType,
|
||||
) -> crate::Token2022ProofContextRequirement {
|
||||
return crate::Token2022ProofContextRequirement {
|
||||
role: "equality_proof".to_string(),
|
||||
account,
|
||||
proof_type,
|
||||
expected_authority: std::option::Option::Some(pubkey(9)),
|
||||
};
|
||||
}
|
||||
|
||||
fn account_result(
|
||||
proof_type: kb_lib::ExSolanaCoreZkElGamalProofType,
|
||||
) -> kb_onchain_transport::AccountInfoResult {
|
||||
let mut data = std::vec![0u8; proof_type.context_state_size()];
|
||||
data[..32].copy_from_slice(&[9u8; 32]);
|
||||
data[32] = proof_type.discriminator();
|
||||
return kb_onchain_transport::AccountInfoResult {
|
||||
context: kb_onchain_transport::RpcResponseContext {
|
||||
slot: 77,
|
||||
api_version: std::option::Option::None,
|
||||
},
|
||||
account: std::option::Option::Some(kb_onchain_transport::AccountInfoValue {
|
||||
lamports: 1,
|
||||
owner: kb_lib::MdProgramId(kb_program_ids::ZK_ELGAMAL_PROOF_PROGRAM_ID.to_string()),
|
||||
executable: false,
|
||||
rent_epoch: 0,
|
||||
space: data.len() as u64,
|
||||
data,
|
||||
}),
|
||||
};
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn exact_duplicates_are_deduplicated_and_conflicts_fail_closed() {
|
||||
let item = requirement(
|
||||
pubkey(1),
|
||||
kb_lib::ExSolanaCoreZkElGamalProofType::CiphertextCiphertextEquality,
|
||||
);
|
||||
let request = crate::Token2022CryptographicPreflightRequest {
|
||||
query_role: "query".to_string(),
|
||||
min_context_slot: std::option::Option::Some(7),
|
||||
proof_contexts: std::vec![item.clone(), item.clone()],
|
||||
};
|
||||
let unique = super::validate_proof_context_requirements(&request);
|
||||
assert_eq!(unique.as_ref().map(std::vec::Vec::len), std::result::Result::Ok(1));
|
||||
let conflict = crate::Token2022CryptographicPreflightRequest {
|
||||
query_role: "query".to_string(),
|
||||
min_context_slot: std::option::Option::None,
|
||||
proof_contexts: std::vec![
|
||||
item,
|
||||
requirement(
|
||||
pubkey(1),
|
||||
kb_lib::ExSolanaCoreZkElGamalProofType::BatchedRangeProofU128,
|
||||
),
|
||||
],
|
||||
};
|
||||
assert!(super::validate_proof_context_requirements(&conflict).is_err());
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn proof_context_validation_checks_owner_size_type_and_authority() {
|
||||
let proof_type = kb_lib::ExSolanaCoreZkElGamalProofType::CiphertextCiphertextEquality;
|
||||
let context_requirement = requirement(pubkey(1), proof_type);
|
||||
let result = account_result(proof_type);
|
||||
let report = super::validate_proof_context_account(&context_requirement, &result);
|
||||
assert_eq!(
|
||||
report.as_ref().map(|value| return value.checks.len()),
|
||||
std::result::Result::Ok(5)
|
||||
);
|
||||
let wrong_type =
|
||||
requirement(pubkey(1), kb_lib::ExSolanaCoreZkElGamalProofType::BatchedRangeProofU128);
|
||||
assert!(super::validate_proof_context_account(&wrong_type, &result,).is_err());
|
||||
}
|
||||
}
|
||||
373
kb-pipeline/src/solana_token2022_proof_orchestration.rs
Normal file
373
kb-pipeline/src/solana_token2022_proof_orchestration.rs
Normal file
@@ -0,0 +1,373 @@
|
||||
// file: kb-pipeline/src/solana_token2022_proof_orchestration.rs
|
||||
// version: 2
|
||||
|
||||
//! Deterministic orchestration contract for mixed Token-2022 proof locations.
|
||||
|
||||
/// Maximum proof references accepted by one confidential Token-2022 operation.
|
||||
pub const MAX_TOKEN2022_OPERATION_PROOFS: usize = 5;
|
||||
|
||||
/// One bounded proof-orchestration request prepared before transaction assembly.
|
||||
#[derive(Clone, Debug, Eq, PartialEq)]
|
||||
pub struct Token2022ProofOrchestrationRequest {
|
||||
/// Stable executor operation code.
|
||||
pub operation_code: std::string::String,
|
||||
/// Exact ordered proof references required by the operation.
|
||||
pub proofs: std::vec::Vec<kb_lib::ExSplTokenConfidentialProofReference>,
|
||||
/// Optional authority expected on every context-state account.
|
||||
pub expected_context_authority: std::option::Option<kb_lib::MdPubkey>,
|
||||
/// Maximum compute-unit limit accepted for the future transaction.
|
||||
pub compute_unit_limit: u32,
|
||||
/// Maximum total fee accepted for the future transaction.
|
||||
pub max_fee_lamports: u64,
|
||||
/// Whether a future submission was explicitly requested.
|
||||
pub submit: bool,
|
||||
/// Whether the operator explicitly confirmed the future submission.
|
||||
pub operator_confirmed: bool,
|
||||
}
|
||||
|
||||
/// One deterministic mixed-proof orchestration result.
|
||||
#[derive(Clone, Debug, Eq, PartialEq)]
|
||||
pub struct Token2022ProofOrchestrationReport {
|
||||
/// Stable executor operation code.
|
||||
pub operation_code: std::string::String,
|
||||
/// Whether the instructions sysvar must be present.
|
||||
pub instructions_sysvar_required: bool,
|
||||
/// Ordered non-zero inline offsets.
|
||||
pub inline_offsets: std::vec::Vec<i8>,
|
||||
/// Ordered validated context-state requirements.
|
||||
pub context_requirements: std::vec::Vec<crate::Token2022ProofContextRequirement>,
|
||||
/// Simulation is always mandatory.
|
||||
pub simulation_required: bool,
|
||||
/// Whether the future send path is authorized by request-local policy.
|
||||
pub send_authorized: bool,
|
||||
/// Ordered successful checks.
|
||||
pub checks: std::vec::Vec<std::string::String>,
|
||||
}
|
||||
|
||||
/// Validates mixed inline/context-state proof orchestration before transaction assembly.
|
||||
pub fn orchestrate_token2022_proofs(
|
||||
request: &crate::Token2022ProofOrchestrationRequest,
|
||||
cryptographic_preflight: &crate::Token2022CryptographicPreflightReport,
|
||||
) -> kb_core::Result<crate::Token2022ProofOrchestrationReport> {
|
||||
if request.operation_code.trim().is_empty() {
|
||||
return std::result::Result::Err(kb_core::Error::config(
|
||||
"Token-2022 proof orchestration operation_code must not be empty",
|
||||
));
|
||||
}
|
||||
if request.proofs.len() > crate::MAX_TOKEN2022_OPERATION_PROOFS {
|
||||
return std::result::Result::Err(kb_core::Error::new(
|
||||
"token2022_operation_proof_limit_exceeded",
|
||||
format!(
|
||||
"Token-2022 proof orchestration accepts at most {} proofs",
|
||||
crate::MAX_TOKEN2022_OPERATION_PROOFS
|
||||
),
|
||||
));
|
||||
}
|
||||
if request.compute_unit_limit == 0 || request.max_fee_lamports == 0 {
|
||||
return std::result::Result::Err(kb_core::Error::config(
|
||||
"Token-2022 proof orchestration requires non-zero compute and fee ceilings",
|
||||
));
|
||||
}
|
||||
if request.submit && !request.operator_confirmed {
|
||||
return std::result::Result::Err(kb_core::Error::new(
|
||||
"token2022_submission_confirmation_required",
|
||||
"Token-2022 submission requires explicit operator confirmation",
|
||||
));
|
||||
}
|
||||
let mut kinds = std::collections::BTreeSet::<std::string::String>::new();
|
||||
let mut offsets = std::collections::BTreeSet::<i8>::new();
|
||||
let mut context_accounts = std::collections::BTreeSet::<std::string::String>::new();
|
||||
let mut inline_offsets = std::vec::Vec::<i8>::new();
|
||||
let mut context_requirements = std::vec::Vec::<crate::Token2022ProofContextRequirement>::new();
|
||||
for proof in &request.proofs {
|
||||
let kind_code = proof_kind_code(proof.kind).to_string();
|
||||
if !kinds.insert(kind_code.clone()) {
|
||||
return std::result::Result::Err(kb_core::Error::new(
|
||||
"token2022_duplicate_proof_kind",
|
||||
format!(
|
||||
"Token-2022 operation {} contains duplicate proof kind {kind_code}",
|
||||
request.operation_code
|
||||
),
|
||||
));
|
||||
}
|
||||
if let std::result::Result::Err(error) = proof.location.validate() {
|
||||
return std::result::Result::Err(kb_core::Error::invalid_state(error));
|
||||
}
|
||||
match &proof.location {
|
||||
kb_lib::ExSplTokenConfidentialProofLocation::InstructionOffset { offset } => {
|
||||
if !offsets.insert(*offset) {
|
||||
return std::result::Result::Err(kb_core::Error::new(
|
||||
"token2022_duplicate_inline_proof_offset",
|
||||
format!(
|
||||
"Token-2022 operation {} reuses inline proof offset {offset}",
|
||||
request.operation_code
|
||||
),
|
||||
));
|
||||
}
|
||||
inline_offsets.push(*offset);
|
||||
},
|
||||
kb_lib::ExSplTokenConfidentialProofLocation::ContextStateAccount { account } => {
|
||||
if !context_accounts.insert(account.0.clone()) {
|
||||
return std::result::Result::Err(kb_core::Error::new(
|
||||
"token2022_duplicate_context_state_account",
|
||||
format!(
|
||||
"Token-2022 operation {} reuses context-state account {}",
|
||||
request.operation_code, account.0
|
||||
),
|
||||
));
|
||||
}
|
||||
context_requirements.push(crate::Token2022ProofContextRequirement {
|
||||
role: kind_code,
|
||||
account: account.clone(),
|
||||
proof_type: proof_kind_to_zk_type(proof.kind),
|
||||
expected_authority: request.expected_context_authority.clone(),
|
||||
});
|
||||
},
|
||||
}
|
||||
}
|
||||
if let std::result::Result::Err(error) =
|
||||
validate_context_reports(context_requirements.as_slice(), cryptographic_preflight)
|
||||
{
|
||||
return std::result::Result::Err(error);
|
||||
}
|
||||
return std::result::Result::Ok(crate::Token2022ProofOrchestrationReport {
|
||||
operation_code: request.operation_code.clone(),
|
||||
instructions_sysvar_required: !inline_offsets.is_empty(),
|
||||
inline_offsets,
|
||||
context_requirements,
|
||||
simulation_required: true,
|
||||
send_authorized: request.submit && request.operator_confirmed,
|
||||
checks: vec![
|
||||
"ordered_unique_proof_kinds".to_string(),
|
||||
"non_zero_unique_inline_offsets".to_string(),
|
||||
"unique_context_state_accounts".to_string(),
|
||||
"context_preflight_matches_operation".to_string(),
|
||||
"simulation_required".to_string(),
|
||||
"bounded_compute_and_fee".to_string(),
|
||||
],
|
||||
});
|
||||
}
|
||||
|
||||
fn validate_context_reports(
|
||||
requirements: &[crate::Token2022ProofContextRequirement],
|
||||
report: &crate::Token2022CryptographicPreflightReport,
|
||||
) -> kb_core::Result<()> {
|
||||
if requirements.len() != report.proof_contexts.len() {
|
||||
return std::result::Result::Err(kb_core::Error::new(
|
||||
"token2022_context_preflight_count_mismatch",
|
||||
format!(
|
||||
"Token-2022 operation requires {} context states, preflight contains {}",
|
||||
requirements.len(),
|
||||
report.proof_contexts.len()
|
||||
),
|
||||
));
|
||||
}
|
||||
for (requirement, observed) in requirements.iter().zip(report.proof_contexts.iter()) {
|
||||
if requirement.account != observed.account
|
||||
|| requirement.proof_type.discriminator() != observed.proof_discriminator
|
||||
{
|
||||
return std::result::Result::Err(kb_core::Error::new(
|
||||
"token2022_context_preflight_mismatch",
|
||||
format!(
|
||||
"Token-2022 proof context {} does not match the ordered operation requirement",
|
||||
observed.account.0
|
||||
),
|
||||
));
|
||||
}
|
||||
}
|
||||
return std::result::Result::Ok(());
|
||||
}
|
||||
|
||||
fn proof_kind_code(kind: kb_lib::ExSplTokenConfidentialProofKind) -> &'static str {
|
||||
return match kind {
|
||||
kb_lib::ExSplTokenConfidentialProofKind::PubkeyValidity => "pubkey_validity",
|
||||
kb_lib::ExSplTokenConfidentialProofKind::ZeroCiphertext => "zero_ciphertext",
|
||||
kb_lib::ExSplTokenConfidentialProofKind::CiphertextCommitmentEquality => {
|
||||
"ciphertext_commitment_equality"
|
||||
},
|
||||
kb_lib::ExSplTokenConfidentialProofKind::CiphertextCiphertextEquality => {
|
||||
"ciphertext_ciphertext_equality"
|
||||
},
|
||||
kb_lib::ExSplTokenConfidentialProofKind::BatchedGroupedCiphertext3HandlesValidity => {
|
||||
"batched_grouped_ciphertext_3_handles_validity"
|
||||
},
|
||||
kb_lib::ExSplTokenConfidentialProofKind::BatchedGroupedCiphertext2HandlesValidity => {
|
||||
"batched_grouped_ciphertext_2_handles_validity"
|
||||
},
|
||||
kb_lib::ExSplTokenConfidentialProofKind::PercentageWithFee => "percentage_with_fee",
|
||||
kb_lib::ExSplTokenConfidentialProofKind::BatchedRangeProofU64 => "batched_range_proof_u64",
|
||||
kb_lib::ExSplTokenConfidentialProofKind::BatchedRangeProofU128 => {
|
||||
"batched_range_proof_u128"
|
||||
},
|
||||
kb_lib::ExSplTokenConfidentialProofKind::BatchedRangeProofU256 => {
|
||||
"batched_range_proof_u256"
|
||||
},
|
||||
};
|
||||
}
|
||||
|
||||
fn proof_kind_to_zk_type(
|
||||
kind: kb_lib::ExSplTokenConfidentialProofKind,
|
||||
) -> kb_lib::ExSolanaCoreZkElGamalProofType {
|
||||
return match kind {
|
||||
kb_lib::ExSplTokenConfidentialProofKind::PubkeyValidity => {
|
||||
kb_lib::ExSolanaCoreZkElGamalProofType::PubkeyValidity
|
||||
},
|
||||
kb_lib::ExSplTokenConfidentialProofKind::ZeroCiphertext => {
|
||||
kb_lib::ExSolanaCoreZkElGamalProofType::ZeroCiphertext
|
||||
},
|
||||
kb_lib::ExSplTokenConfidentialProofKind::CiphertextCommitmentEquality => {
|
||||
kb_lib::ExSolanaCoreZkElGamalProofType::CiphertextCommitmentEquality
|
||||
},
|
||||
kb_lib::ExSplTokenConfidentialProofKind::CiphertextCiphertextEquality => {
|
||||
kb_lib::ExSolanaCoreZkElGamalProofType::CiphertextCiphertextEquality
|
||||
},
|
||||
kb_lib::ExSplTokenConfidentialProofKind::BatchedGroupedCiphertext3HandlesValidity => {
|
||||
kb_lib::ExSolanaCoreZkElGamalProofType::BatchedGroupedCiphertext3HandlesValidity
|
||||
},
|
||||
kb_lib::ExSplTokenConfidentialProofKind::BatchedGroupedCiphertext2HandlesValidity => {
|
||||
kb_lib::ExSolanaCoreZkElGamalProofType::BatchedGroupedCiphertext2HandlesValidity
|
||||
},
|
||||
kb_lib::ExSplTokenConfidentialProofKind::PercentageWithFee => {
|
||||
kb_lib::ExSolanaCoreZkElGamalProofType::PercentageWithCap
|
||||
},
|
||||
kb_lib::ExSplTokenConfidentialProofKind::BatchedRangeProofU64 => {
|
||||
kb_lib::ExSolanaCoreZkElGamalProofType::BatchedRangeProofU64
|
||||
},
|
||||
kb_lib::ExSplTokenConfidentialProofKind::BatchedRangeProofU128 => {
|
||||
kb_lib::ExSolanaCoreZkElGamalProofType::BatchedRangeProofU128
|
||||
},
|
||||
kb_lib::ExSplTokenConfidentialProofKind::BatchedRangeProofU256 => {
|
||||
kb_lib::ExSolanaCoreZkElGamalProofType::BatchedRangeProofU256
|
||||
},
|
||||
};
|
||||
}
|
||||
|
||||
#[cfg(test)]
|
||||
mod tests {
|
||||
fn pubkey(byte: u8) -> kb_lib::MdPubkey {
|
||||
return kb_lib::MdPubkey(bs58::encode([byte; 32]).into_string());
|
||||
}
|
||||
|
||||
fn context_report(
|
||||
account: kb_lib::MdPubkey,
|
||||
proof_type: kb_lib::ExSolanaCoreZkElGamalProofType,
|
||||
) -> crate::Token2022ProofContextReport {
|
||||
return crate::Token2022ProofContextReport {
|
||||
role: "proof".to_string(),
|
||||
account,
|
||||
proof_discriminator: proof_type.discriminator(),
|
||||
context_state_bytes: proof_type.context_state_size(),
|
||||
context_slot: 100,
|
||||
checks: vec!["validated".to_string()],
|
||||
};
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn mixed_proofs_require_sysvar_and_preserve_ordered_contexts() {
|
||||
let context = pubkey(1);
|
||||
let request = crate::Token2022ProofOrchestrationRequest {
|
||||
operation_code: "spl_token2022.confidential_transfer".to_string(),
|
||||
proofs: vec![
|
||||
kb_lib::ExSplTokenConfidentialProofReference {
|
||||
kind: kb_lib::ExSplTokenConfidentialProofKind::CiphertextCommitmentEquality,
|
||||
location: kb_lib::ExSplTokenConfidentialProofLocation::InstructionOffset {
|
||||
offset: 1,
|
||||
},
|
||||
},
|
||||
kb_lib::ExSplTokenConfidentialProofReference {
|
||||
kind: kb_lib::ExSplTokenConfidentialProofKind::BatchedRangeProofU128,
|
||||
location: kb_lib::ExSplTokenConfidentialProofLocation::ContextStateAccount {
|
||||
account: context.clone(),
|
||||
},
|
||||
},
|
||||
],
|
||||
expected_context_authority: std::option::Option::None,
|
||||
compute_unit_limit: 400_000,
|
||||
max_fee_lamports: 50_000,
|
||||
submit: false,
|
||||
operator_confirmed: false,
|
||||
};
|
||||
let preflight = crate::Token2022CryptographicPreflightReport {
|
||||
commitment: "confirmed".to_string(),
|
||||
context_slot: 100,
|
||||
proof_contexts: vec![context_report(
|
||||
context,
|
||||
kb_lib::ExSolanaCoreZkElGamalProofType::BatchedRangeProofU128,
|
||||
)],
|
||||
};
|
||||
let report = crate::orchestrate_token2022_proofs(&request, &preflight)
|
||||
.expect("mixed proof orchestration must succeed");
|
||||
assert!(report.instructions_sysvar_required);
|
||||
assert_eq!(report.inline_offsets, vec![1]);
|
||||
assert_eq!(report.context_requirements.len(), 1);
|
||||
assert!(report.simulation_required);
|
||||
assert!(!report.send_authorized);
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn context_only_proofs_do_not_require_instructions_sysvar() {
|
||||
let context = pubkey(2);
|
||||
let request = crate::Token2022ProofOrchestrationRequest {
|
||||
operation_code: "spl_token2022.empty_confidential_account".to_string(),
|
||||
proofs: vec![kb_lib::ExSplTokenConfidentialProofReference {
|
||||
kind: kb_lib::ExSplTokenConfidentialProofKind::ZeroCiphertext,
|
||||
location: kb_lib::ExSplTokenConfidentialProofLocation::ContextStateAccount {
|
||||
account: context.clone(),
|
||||
},
|
||||
}],
|
||||
expected_context_authority: std::option::Option::None,
|
||||
compute_unit_limit: 200_000,
|
||||
max_fee_lamports: 20_000,
|
||||
submit: true,
|
||||
operator_confirmed: true,
|
||||
};
|
||||
let preflight = crate::Token2022CryptographicPreflightReport {
|
||||
commitment: "confirmed".to_string(),
|
||||
context_slot: 100,
|
||||
proof_contexts: vec![context_report(
|
||||
context,
|
||||
kb_lib::ExSolanaCoreZkElGamalProofType::ZeroCiphertext,
|
||||
)],
|
||||
};
|
||||
let report = crate::orchestrate_token2022_proofs(&request, &preflight)
|
||||
.expect("context-only orchestration must succeed");
|
||||
assert!(!report.instructions_sysvar_required);
|
||||
assert!(report.send_authorized);
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn duplicate_offsets_contexts_and_unconfirmed_submission_fail_closed() {
|
||||
let duplicate_offset = crate::Token2022ProofOrchestrationRequest {
|
||||
operation_code: "operation".to_string(),
|
||||
proofs: vec![
|
||||
kb_lib::ExSplTokenConfidentialProofReference {
|
||||
kind: kb_lib::ExSplTokenConfidentialProofKind::ZeroCiphertext,
|
||||
location: kb_lib::ExSplTokenConfidentialProofLocation::InstructionOffset {
|
||||
offset: 1,
|
||||
},
|
||||
},
|
||||
kb_lib::ExSplTokenConfidentialProofReference {
|
||||
kind: kb_lib::ExSplTokenConfidentialProofKind::BatchedRangeProofU64,
|
||||
location: kb_lib::ExSplTokenConfidentialProofLocation::InstructionOffset {
|
||||
offset: 1,
|
||||
},
|
||||
},
|
||||
],
|
||||
expected_context_authority: std::option::Option::None,
|
||||
compute_unit_limit: 1,
|
||||
max_fee_lamports: 1,
|
||||
submit: false,
|
||||
operator_confirmed: false,
|
||||
};
|
||||
let empty = crate::Token2022CryptographicPreflightReport {
|
||||
commitment: "confirmed".to_string(),
|
||||
context_slot: 0,
|
||||
proof_contexts: vec![],
|
||||
};
|
||||
assert!(crate::orchestrate_token2022_proofs(&duplicate_offset, &empty).is_err());
|
||||
let mut unconfirmed = duplicate_offset;
|
||||
unconfirmed.proofs.clear();
|
||||
unconfirmed.submit = true;
|
||||
assert!(crate::orchestrate_token2022_proofs(&unconfirmed, &empty).is_err());
|
||||
}
|
||||
}
|
||||
Reference in New Issue
Block a user