762 lines
33 KiB
Rust
762 lines
33 KiB
Rust
// file: kb-pipeline/src/metadata_solana_program_preflight.rs
|
|
// version: 2
|
|
|
|
//! Stateful preflight for Solana Program Metadata execution plans.
|
|
|
|
/// Maximum number of bounded account snapshots accepted by one preflight.
|
|
pub const MAX_SOLANA_PROGRAM_METADATA_PREFLIGHT_ACCOUNTS: usize = 16;
|
|
|
|
/// One rent-exemption observation supplied by the caller for a growth operation.
|
|
#[derive(Clone, Debug, Eq, PartialEq, serde::Deserialize, serde::Serialize)]
|
|
pub struct SolanaProgramMetadataRentObservation {
|
|
/// Account whose allocation or growth is being validated.
|
|
pub account: kb_lib::MdPubkey,
|
|
/// Target account-data size used for the rent query.
|
|
pub target_space: u64,
|
|
/// Lamports observed on the account before execution.
|
|
pub observed_lamports: u64,
|
|
/// Minimum lamports returned for `target_space`.
|
|
pub required_lamports: u64,
|
|
}
|
|
|
|
impl crate::SolanaProgramMetadataRentObservation {
|
|
/// Returns whether the account is sufficiently pre-funded.
|
|
pub const fn is_satisfied(&self) -> bool {
|
|
return self.observed_lamports >= self.required_lamports;
|
|
}
|
|
}
|
|
|
|
/// Complete stateful preflight request for one prepared Program Metadata plan.
|
|
#[derive(Clone, Debug, PartialEq)]
|
|
pub struct SolanaProgramMetadataPreflightRequest {
|
|
/// Exact typed intent used to build the plan.
|
|
pub intent: kb_lib::ExMetadataSpmExecutionIntent,
|
|
/// Exact prepared plan produced by `kb-lib`.
|
|
pub plan: kb_lib::ExApiPreparedExecutionPlan,
|
|
/// Confirmed account snapshots observed before simulation.
|
|
pub before: std::vec::Vec<crate::SolanaProgramMetadataStatefulReadResult>,
|
|
/// Explicit rent observations required by account growth operations.
|
|
pub rent_observations: std::vec::Vec<crate::SolanaProgramMetadataRentObservation>,
|
|
}
|
|
|
|
/// Deterministic Program Metadata preflight report.
|
|
#[derive(Clone, Debug, Eq, PartialEq, serde::Deserialize, serde::Serialize)]
|
|
pub struct SolanaProgramMetadataPreflightReport {
|
|
/// Stable operation code.
|
|
pub operation_code: std::string::String,
|
|
/// Primary account mutated by the instruction.
|
|
pub target_account: kb_lib::MdPubkey,
|
|
/// Highest confirmed context slot across supplied snapshots.
|
|
pub context_slot: u64,
|
|
/// Canonical accounts inspected by the preflight.
|
|
pub inspected_accounts: std::vec::Vec<kb_lib::MdPubkey>,
|
|
/// Safety decision applied before simulation.
|
|
pub safety: kb_lib::ExSafetyEvaluation,
|
|
/// Whether the on-chain program must validate an upgrade authority at runtime.
|
|
pub runtime_authority_validation_required: bool,
|
|
/// Ordered successful checks.
|
|
pub checks: std::vec::Vec<std::string::String>,
|
|
}
|
|
|
|
/// Validates one Program Metadata plan against confirmed pre-execution state.
|
|
pub fn inspect_solana_program_metadata_preflight(
|
|
request: &crate::SolanaProgramMetadataPreflightRequest,
|
|
) -> kb_core::Result<crate::SolanaProgramMetadataPreflightReport> {
|
|
if request.before.len() > crate::MAX_SOLANA_PROGRAM_METADATA_PREFLIGHT_ACCOUNTS {
|
|
return std::result::Result::Err(kb_core::Error::new(
|
|
"metadata_solana_program_preflight_account_limit_exceeded",
|
|
"Solana Program Metadata preflight account limit exceeded",
|
|
));
|
|
}
|
|
let operation_code = request.intent.operation.operation_code();
|
|
if request.plan.operation_code != operation_code {
|
|
return std::result::Result::Err(kb_core::Error::new(
|
|
"metadata_solana_program_preflight_operation_mismatch",
|
|
"Program Metadata intent and plan operation codes must match",
|
|
));
|
|
}
|
|
if request.plan.instructions.len() != 1 {
|
|
return std::result::Result::Err(kb_core::Error::new(
|
|
"metadata_solana_program_preflight_instruction_count_invalid",
|
|
"Program Metadata plans must contain exactly one instruction",
|
|
));
|
|
}
|
|
let instruction = match request.plan.instructions.first() {
|
|
std::option::Option::Some(value) => value,
|
|
std::option::Option::None => {
|
|
return std::result::Result::Err(kb_core::Error::new(
|
|
"metadata_solana_program_preflight_instruction_missing",
|
|
"Program Metadata plan instruction is missing",
|
|
));
|
|
},
|
|
};
|
|
if instruction.program_id.0 != kb_program_ids::METADATA_SOLANA_PROGRAM_METADATA_PROGRAM_ID
|
|
|| instruction.operation_code != operation_code
|
|
|| !kb_lib::EX_METADATA_SPM_SUPPORTED_OPERATION_CODES.contains(&operation_code)
|
|
{
|
|
return std::result::Result::Err(kb_core::Error::new(
|
|
"metadata_solana_program_preflight_instruction_contract_mismatch",
|
|
"Program Metadata plan does not preserve the exact program and operation contract",
|
|
));
|
|
}
|
|
if request.intent.operation.requires_explicit_approval()
|
|
&& !request.intent.allow_destructive_operation
|
|
{
|
|
return std::result::Result::Err(kb_core::Error::new(
|
|
"metadata_solana_program_preflight_destructive_approval_required",
|
|
"destructive Program Metadata execution requires explicit approval",
|
|
));
|
|
}
|
|
let safety = match kb_lib::ExSafetyChecker.evaluate_prepared_plan(&request.plan) {
|
|
std::result::Result::Ok(value) => value,
|
|
std::result::Result::Err(error) => return std::result::Result::Err(error),
|
|
};
|
|
if safety.decision == kb_lib::ExSafetyDecision::Deny {
|
|
return std::result::Result::Err(kb_core::Error::new(
|
|
"metadata_solana_program_preflight_safety_denied",
|
|
safety_violation_codes(safety.violations.as_slice()),
|
|
));
|
|
}
|
|
let mut snapshots = std::collections::BTreeMap::<
|
|
std::string::String,
|
|
&crate::SolanaProgramMetadataStatefulReadResult,
|
|
>::new();
|
|
let mut context_slot = 0_u64;
|
|
for snapshot in &request.before {
|
|
if snapshot.commitment != "confirmed" {
|
|
return std::result::Result::Err(kb_core::Error::new(
|
|
"metadata_solana_program_preflight_commitment_mismatch",
|
|
"Program Metadata stateful snapshots must use confirmed commitment",
|
|
));
|
|
}
|
|
if snapshots.insert(snapshot.account.0.clone(), snapshot).is_some() {
|
|
return std::result::Result::Err(kb_core::Error::new(
|
|
"metadata_solana_program_preflight_duplicate_account",
|
|
format!("duplicate Program Metadata snapshot for {}", snapshot.account.0),
|
|
));
|
|
}
|
|
context_slot = context_slot.max(snapshot.context_slot);
|
|
}
|
|
let target_account = operation_target(&request.intent.operation).clone();
|
|
let target = match snapshots.get(target_account.0.as_str()) {
|
|
std::option::Option::Some(value) => *value,
|
|
std::option::Option::None => {
|
|
return std::result::Result::Err(kb_core::Error::new(
|
|
"metadata_solana_program_preflight_target_snapshot_missing",
|
|
format!("missing preflight snapshot for {}", target_account.0),
|
|
));
|
|
},
|
|
};
|
|
if let std::result::Result::Err(error) =
|
|
validate_target_state(&request.intent.operation, target)
|
|
{
|
|
return std::result::Result::Err(error);
|
|
}
|
|
if let std::result::Result::Err(error) =
|
|
validate_source_state(&request.intent.operation, &snapshots, target)
|
|
{
|
|
return std::result::Result::Err(error);
|
|
}
|
|
let runtime_authority_validation_required =
|
|
match validate_authority_state(&request.intent.operation, target) {
|
|
std::result::Result::Ok(value) => value,
|
|
std::result::Result::Err(error) => return std::result::Result::Err(error),
|
|
};
|
|
if let std::result::Result::Err(error) = validate_rent_observations(
|
|
&request.intent.operation,
|
|
target,
|
|
&snapshots,
|
|
request.rent_observations.as_slice(),
|
|
) {
|
|
return std::result::Result::Err(error);
|
|
}
|
|
let inspected_accounts = snapshots
|
|
.values()
|
|
.map(|value| return value.account.clone())
|
|
.collect::<std::vec::Vec<_>>();
|
|
tracing::debug!(
|
|
target: crate::TRACING_TARGET,
|
|
action = "inspect_solana_program_metadata_preflight",
|
|
operation_code,
|
|
target_account = %target_account.0,
|
|
context_slot,
|
|
inspected_account_count = inspected_accounts.len(),
|
|
runtime_authority_validation_required,
|
|
safety_decision = ?safety.decision,
|
|
"validated Solana Program Metadata stateful preflight"
|
|
);
|
|
return std::result::Result::Ok(crate::SolanaProgramMetadataPreflightReport {
|
|
operation_code: operation_code.to_string(),
|
|
target_account,
|
|
context_slot,
|
|
inspected_accounts,
|
|
safety,
|
|
runtime_authority_validation_required,
|
|
checks: vec![
|
|
"program_id_exact".to_string(),
|
|
"operation_code_exact".to_string(),
|
|
"single_instruction_plan".to_string(),
|
|
"confirmed_state_snapshots".to_string(),
|
|
"target_state_compatible".to_string(),
|
|
"source_state_compatible".to_string(),
|
|
"authority_contract_checked".to_string(),
|
|
"rent_prefunding_checked_when_required".to_string(),
|
|
"common_safety_checked_before_simulation".to_string(),
|
|
],
|
|
});
|
|
}
|
|
|
|
fn operation_target(operation: &kb_lib::ExMetadataSpmOperation) -> &kb_lib::MdPubkey {
|
|
return match operation {
|
|
kb_lib::ExMetadataSpmOperation::Write { buffer, .. }
|
|
| kb_lib::ExMetadataSpmOperation::Allocate { buffer, .. } => buffer,
|
|
kb_lib::ExMetadataSpmOperation::Initialize { metadata, .. }
|
|
| kb_lib::ExMetadataSpmOperation::SetData { metadata, .. }
|
|
| kb_lib::ExMetadataSpmOperation::SetImmutable { metadata, .. } => metadata,
|
|
kb_lib::ExMetadataSpmOperation::SetAuthority { account, .. }
|
|
| kb_lib::ExMetadataSpmOperation::Trim { account, .. }
|
|
| kb_lib::ExMetadataSpmOperation::Close { account, .. }
|
|
| kb_lib::ExMetadataSpmOperation::Extend { account, .. } => account,
|
|
};
|
|
}
|
|
|
|
fn validate_target_state(
|
|
operation: &kb_lib::ExMetadataSpmOperation,
|
|
target: &crate::SolanaProgramMetadataStatefulReadResult,
|
|
) -> kb_core::Result<()> {
|
|
let valid = match operation {
|
|
kb_lib::ExMetadataSpmOperation::Write { .. } => {
|
|
matches!(&target.state, crate::SolanaProgramMetadataObservedAccountState::Buffer(_))
|
|
},
|
|
kb_lib::ExMetadataSpmOperation::Initialize { data, .. } => {
|
|
if data.is_some() {
|
|
is_prefunded_uninitialized(target)
|
|
} else {
|
|
matches!(&target.state, crate::SolanaProgramMetadataObservedAccountState::Buffer(_))
|
|
}
|
|
},
|
|
kb_lib::ExMetadataSpmOperation::SetData { .. }
|
|
| kb_lib::ExMetadataSpmOperation::SetImmutable { .. } => {
|
|
matches!(&target.state, crate::SolanaProgramMetadataObservedAccountState::Metadata(_))
|
|
},
|
|
kb_lib::ExMetadataSpmOperation::Allocate { .. } => is_prefunded_uninitialized(target),
|
|
kb_lib::ExMetadataSpmOperation::SetAuthority { .. }
|
|
| kb_lib::ExMetadataSpmOperation::Trim { .. }
|
|
| kb_lib::ExMetadataSpmOperation::Close { .. }
|
|
| kb_lib::ExMetadataSpmOperation::Extend { .. } => matches!(
|
|
&target.state,
|
|
crate::SolanaProgramMetadataObservedAccountState::Buffer(_)
|
|
| crate::SolanaProgramMetadataObservedAccountState::Metadata(_)
|
|
),
|
|
};
|
|
if !valid {
|
|
return std::result::Result::Err(kb_core::Error::new(
|
|
"metadata_solana_program_preflight_target_state_invalid",
|
|
format!(
|
|
"operation {} cannot use target state {}",
|
|
operation.operation_code(),
|
|
target.state.code()
|
|
),
|
|
));
|
|
}
|
|
return std::result::Result::Ok(());
|
|
}
|
|
|
|
fn is_prefunded_uninitialized(target: &crate::SolanaProgramMetadataStatefulReadResult) -> bool {
|
|
return match &target.state {
|
|
crate::SolanaProgramMetadataObservedAccountState::Uninitialized {
|
|
owner,
|
|
lamports,
|
|
space: _,
|
|
} => {
|
|
*lamports > 0
|
|
&& (owner.0 == kb_program_ids::SYSTEM_PROGRAM_ID
|
|
|| owner.0 == kb_program_ids::METADATA_SOLANA_PROGRAM_METADATA_PROGRAM_ID)
|
|
},
|
|
_ => false,
|
|
};
|
|
}
|
|
|
|
fn validate_source_state(
|
|
operation: &kb_lib::ExMetadataSpmOperation,
|
|
snapshots: &std::collections::BTreeMap<
|
|
std::string::String,
|
|
&crate::SolanaProgramMetadataStatefulReadResult,
|
|
>,
|
|
target: &crate::SolanaProgramMetadataStatefulReadResult,
|
|
) -> kb_core::Result<()> {
|
|
match operation {
|
|
kb_lib::ExMetadataSpmOperation::Write { offset, source, .. } => {
|
|
let target_buffer = match &target.state {
|
|
crate::SolanaProgramMetadataObservedAccountState::Buffer(value) => value,
|
|
_ => return std::result::Result::Ok(()),
|
|
};
|
|
let source_length = match source {
|
|
kb_lib::ExMetadataSpmWriteSource::Inline { data } => data.len(),
|
|
kb_lib::ExMetadataSpmWriteSource::Buffer { source_buffer } => {
|
|
let source_snapshot = match snapshots.get(source_buffer.0.as_str()) {
|
|
std::option::Option::Some(value) => *value,
|
|
std::option::Option::None => {
|
|
return std::result::Result::Err(kb_core::Error::new(
|
|
"metadata_solana_program_preflight_source_buffer_missing",
|
|
format!("missing source Buffer snapshot for {}", source_buffer.0),
|
|
));
|
|
},
|
|
};
|
|
match &source_snapshot.state {
|
|
crate::SolanaProgramMetadataObservedAccountState::Buffer(value) => {
|
|
value.data.len()
|
|
},
|
|
_ => {
|
|
return std::result::Result::Err(kb_core::Error::new(
|
|
"metadata_solana_program_preflight_source_buffer_invalid",
|
|
"Write source account must be an initialized Buffer",
|
|
));
|
|
},
|
|
}
|
|
},
|
|
};
|
|
let offset = match usize::try_from(*offset) {
|
|
std::result::Result::Ok(value) => value,
|
|
std::result::Result::Err(error) => {
|
|
return std::result::Result::Err(kb_core::Error::new(
|
|
"metadata_solana_program_preflight_write_offset_invalid",
|
|
error.to_string(),
|
|
));
|
|
},
|
|
};
|
|
if offset.saturating_add(source_length) > target_buffer.allocated_data_bytes {
|
|
return std::result::Result::Err(kb_core::Error::new(
|
|
"metadata_solana_program_preflight_write_out_of_bounds",
|
|
"Write source exceeds the allocated Buffer capacity",
|
|
));
|
|
}
|
|
},
|
|
kb_lib::ExMetadataSpmOperation::SetData {
|
|
source: kb_lib::ExMetadataSpmSetDataSource::Buffer { buffer, .. },
|
|
..
|
|
} => {
|
|
let source_snapshot = match snapshots.get(buffer.0.as_str()) {
|
|
std::option::Option::Some(value) => *value,
|
|
std::option::Option::None => {
|
|
return std::result::Result::Err(kb_core::Error::new(
|
|
"metadata_solana_program_preflight_source_buffer_missing",
|
|
format!("missing source Buffer snapshot for {}", buffer.0),
|
|
));
|
|
},
|
|
};
|
|
if !matches!(
|
|
&source_snapshot.state,
|
|
crate::SolanaProgramMetadataObservedAccountState::Buffer(_)
|
|
) {
|
|
return std::result::Result::Err(kb_core::Error::new(
|
|
"metadata_solana_program_preflight_source_buffer_invalid",
|
|
"SetData source account must be an initialized Buffer",
|
|
));
|
|
}
|
|
},
|
|
_ => {},
|
|
}
|
|
return std::result::Result::Ok(());
|
|
}
|
|
|
|
fn validate_authority_state(
|
|
operation: &kb_lib::ExMetadataSpmOperation,
|
|
target: &crate::SolanaProgramMetadataStatefulReadResult,
|
|
) -> kb_core::Result<bool> {
|
|
let (authority, has_program_context) = match operation {
|
|
kb_lib::ExMetadataSpmOperation::Write { authority: _, .. } => {
|
|
return std::result::Result::Ok(false);
|
|
},
|
|
kb_lib::ExMetadataSpmOperation::Initialize { canonical, .. } => {
|
|
return std::result::Result::Ok(*canonical);
|
|
},
|
|
kb_lib::ExMetadataSpmOperation::Allocate { seed, canonical, .. } => {
|
|
return std::result::Result::Ok(seed.is_some() && *canonical);
|
|
},
|
|
kb_lib::ExMetadataSpmOperation::SetAuthority { authority, program_context, .. }
|
|
| kb_lib::ExMetadataSpmOperation::SetData { authority, program_context, .. }
|
|
| kb_lib::ExMetadataSpmOperation::SetImmutable { authority, program_context, .. }
|
|
| kb_lib::ExMetadataSpmOperation::Trim { authority, program_context, .. }
|
|
| kb_lib::ExMetadataSpmOperation::Close { authority, program_context, .. }
|
|
| kb_lib::ExMetadataSpmOperation::Extend { authority, program_context, .. } => {
|
|
(authority, program_context.is_some())
|
|
},
|
|
};
|
|
let (current_authority, canonical) = match &target.state {
|
|
crate::SolanaProgramMetadataObservedAccountState::Buffer(value) => {
|
|
(value.authority.as_deref(), value.canonical)
|
|
},
|
|
crate::SolanaProgramMetadataObservedAccountState::Metadata(value) => {
|
|
(value.authority.as_deref(), value.canonical)
|
|
},
|
|
_ => return std::result::Result::Ok(false),
|
|
};
|
|
if current_authority == std::option::Option::Some(authority.0.as_str()) {
|
|
return std::result::Result::Ok(false);
|
|
}
|
|
if canonical && has_program_context {
|
|
return std::result::Result::Ok(true);
|
|
}
|
|
return std::result::Result::Err(kb_core::Error::new(
|
|
"metadata_solana_program_preflight_authority_mismatch",
|
|
"the supplied authority does not match account state and no canonical program context was provided",
|
|
));
|
|
}
|
|
|
|
fn validate_rent_observations(
|
|
operation: &kb_lib::ExMetadataSpmOperation,
|
|
target: &crate::SolanaProgramMetadataStatefulReadResult,
|
|
snapshots: &std::collections::BTreeMap<
|
|
std::string::String,
|
|
&crate::SolanaProgramMetadataStatefulReadResult,
|
|
>,
|
|
observations: &[crate::SolanaProgramMetadataRentObservation],
|
|
) -> kb_core::Result<()> {
|
|
let expected_space = match expected_rent_target_space(operation, target, snapshots) {
|
|
std::result::Result::Ok(value) => value,
|
|
std::result::Result::Err(error) => return std::result::Result::Err(error),
|
|
};
|
|
let expected_space = match expected_space {
|
|
std::option::Option::Some(value) => value,
|
|
std::option::Option::None => return std::result::Result::Ok(()),
|
|
};
|
|
let observation = match observations.iter().find(|value| return value.account == target.account)
|
|
{
|
|
std::option::Option::Some(value) => value,
|
|
std::option::Option::None => {
|
|
return std::result::Result::Err(kb_core::Error::new(
|
|
"metadata_solana_program_preflight_rent_observation_missing",
|
|
format!("missing rent observation for {}", target.account.0),
|
|
));
|
|
},
|
|
};
|
|
let observed_lamports = match target.lamports {
|
|
std::option::Option::Some(value) => value,
|
|
std::option::Option::None => 0,
|
|
};
|
|
if observation.target_space != expected_space
|
|
|| observation.observed_lamports != observed_lamports
|
|
|| !observation.is_satisfied()
|
|
{
|
|
return std::result::Result::Err(kb_core::Error::new(
|
|
"metadata_solana_program_preflight_rent_not_satisfied",
|
|
format!(
|
|
"account {} has {} lamports but requires {} for exact target space {}",
|
|
observation.account.0,
|
|
observation.observed_lamports,
|
|
observation.required_lamports,
|
|
expected_space
|
|
),
|
|
));
|
|
}
|
|
return std::result::Result::Ok(());
|
|
}
|
|
|
|
fn expected_rent_target_space(
|
|
operation: &kb_lib::ExMetadataSpmOperation,
|
|
target: &crate::SolanaProgramMetadataStatefulReadResult,
|
|
snapshots: &std::collections::BTreeMap<
|
|
std::string::String,
|
|
&crate::SolanaProgramMetadataStatefulReadResult,
|
|
>,
|
|
) -> kb_core::Result<std::option::Option<u64>> {
|
|
let header = match u64::try_from(kb_lib::DC_METADATA_SPM_METADATA_HEADER_BYTES) {
|
|
std::result::Result::Ok(value) => value,
|
|
std::result::Result::Err(error) => {
|
|
return std::result::Result::Err(kb_core::Error::new(
|
|
"metadata_solana_program_preflight_header_size_overflow",
|
|
error.to_string(),
|
|
));
|
|
},
|
|
};
|
|
return match operation {
|
|
kb_lib::ExMetadataSpmOperation::Initialize {
|
|
data: std::option::Option::Some(data),
|
|
..
|
|
} => add_metadata_header(header, metadata_input_wire_len(data)),
|
|
kb_lib::ExMetadataSpmOperation::SetData {
|
|
source: kb_lib::ExMetadataSpmSetDataSource::PreserveExisting,
|
|
..
|
|
} => std::result::Result::Ok(std::option::Option::None),
|
|
kb_lib::ExMetadataSpmOperation::SetData {
|
|
source: kb_lib::ExMetadataSpmSetDataSource::Inline { data },
|
|
..
|
|
} => add_metadata_header(header, metadata_input_wire_len(data)),
|
|
kb_lib::ExMetadataSpmOperation::SetData {
|
|
source: kb_lib::ExMetadataSpmSetDataSource::Buffer { buffer, .. },
|
|
..
|
|
} => {
|
|
let source = match snapshots.get(buffer.0.as_str()) {
|
|
std::option::Option::Some(value) => *value,
|
|
std::option::Option::None => {
|
|
return std::result::Result::Err(kb_core::Error::new(
|
|
"metadata_solana_program_preflight_source_buffer_missing",
|
|
format!("missing source Buffer snapshot for {}", buffer.0),
|
|
));
|
|
},
|
|
};
|
|
let length = match &source.state {
|
|
crate::SolanaProgramMetadataObservedAccountState::Buffer(value) => {
|
|
match u64::try_from(value.data.len()) {
|
|
std::result::Result::Ok(length) => length,
|
|
std::result::Result::Err(error) => {
|
|
return std::result::Result::Err(kb_core::Error::new(
|
|
"metadata_solana_program_preflight_source_length_overflow",
|
|
error.to_string(),
|
|
));
|
|
},
|
|
}
|
|
},
|
|
_ => {
|
|
return std::result::Result::Err(kb_core::Error::new(
|
|
"metadata_solana_program_preflight_source_buffer_invalid",
|
|
"SetData source account must be an initialized Buffer",
|
|
));
|
|
},
|
|
};
|
|
return add_metadata_header(header, std::result::Result::Ok(length));
|
|
},
|
|
kb_lib::ExMetadataSpmOperation::Allocate { .. } => {
|
|
let current = match target.space {
|
|
std::option::Option::Some(value) => value,
|
|
std::option::Option::None => 0,
|
|
};
|
|
std::result::Result::Ok(std::option::Option::Some(current.max(header)))
|
|
},
|
|
kb_lib::ExMetadataSpmOperation::Extend { length, .. } => {
|
|
let current = match target.space {
|
|
std::option::Option::Some(value) => value,
|
|
std::option::Option::None => {
|
|
return std::result::Result::Err(kb_core::Error::new(
|
|
"metadata_solana_program_preflight_target_space_missing",
|
|
"Extend requires the current account space",
|
|
));
|
|
},
|
|
};
|
|
return match current.checked_add(u64::from(*length)) {
|
|
std::option::Option::Some(value) => {
|
|
std::result::Result::Ok(std::option::Option::Some(value))
|
|
},
|
|
std::option::Option::None => std::result::Result::Err(kb_core::Error::new(
|
|
"metadata_solana_program_preflight_target_space_overflow",
|
|
"Extend target space overflowed u64",
|
|
)),
|
|
};
|
|
},
|
|
_ => std::result::Result::Ok(std::option::Option::None),
|
|
};
|
|
}
|
|
|
|
fn metadata_input_wire_len(data: &kb_lib::ExMetadataSpmDataInput) -> kb_core::Result<u64> {
|
|
let length = match data {
|
|
kb_lib::ExMetadataSpmDataInput::Direct { bytes } => bytes.len(),
|
|
kb_lib::ExMetadataSpmDataInput::Url { url } => url.len(),
|
|
kb_lib::ExMetadataSpmDataInput::External { .. } => {
|
|
kb_lib::DC_METADATA_SPM_EXTERNAL_DATA_BYTES
|
|
},
|
|
};
|
|
return match u64::try_from(length) {
|
|
std::result::Result::Ok(value) => std::result::Result::Ok(value),
|
|
std::result::Result::Err(error) => std::result::Result::Err(kb_core::Error::new(
|
|
"metadata_solana_program_preflight_data_length_overflow",
|
|
error.to_string(),
|
|
)),
|
|
};
|
|
}
|
|
|
|
fn add_metadata_header(
|
|
header: u64,
|
|
data_length: kb_core::Result<u64>,
|
|
) -> kb_core::Result<std::option::Option<u64>> {
|
|
let data_length = match data_length {
|
|
std::result::Result::Ok(value) => value,
|
|
std::result::Result::Err(error) => return std::result::Result::Err(error),
|
|
};
|
|
return match header.checked_add(data_length) {
|
|
std::option::Option::Some(value) => {
|
|
std::result::Result::Ok(std::option::Option::Some(value))
|
|
},
|
|
std::option::Option::None => std::result::Result::Err(kb_core::Error::new(
|
|
"metadata_solana_program_preflight_target_space_overflow",
|
|
"Program Metadata target space overflowed u64",
|
|
)),
|
|
};
|
|
}
|
|
|
|
fn safety_violation_codes(violations: &[kb_lib::ExSafetyViolation]) -> std::string::String {
|
|
if violations.is_empty() {
|
|
return "Program Metadata execution safety denied without a diagnostic".to_string();
|
|
}
|
|
return violations
|
|
.iter()
|
|
.map(|value| return value.code.as_str())
|
|
.collect::<std::vec::Vec<_>>()
|
|
.join(",");
|
|
}
|
|
|
|
#[cfg(test)]
|
|
mod tests {
|
|
fn pubkey(value: u8) -> kb_lib::MdPubkey {
|
|
return kb_lib::MdPubkey(solana_pubkey::Pubkey::new_from_array([value; 32]).to_string());
|
|
}
|
|
|
|
fn policy(authority: &kb_lib::MdPubkey) -> kb_lib::ExApiExecutionPolicy {
|
|
return kb_lib::ExApiExecutionPolicy {
|
|
cost_limit: kb_lib::ExApiExecutionCostLimit {
|
|
max_spend_lamports: std::option::Option::Some(0),
|
|
max_fee_lamports: std::option::Option::Some(20_000),
|
|
max_compute_unit_price_micro_lamports: std::option::Option::None,
|
|
},
|
|
authorized_signers: vec![authority.clone()],
|
|
post_execution_validation: kb_lib::ExApiPostExecutionValidationPolicy {
|
|
canonical_insert_required: true,
|
|
core_extraction_required: true,
|
|
decode_replay_required: true,
|
|
materialization_required: true,
|
|
},
|
|
..kb_lib::ExApiExecutionPolicy::default()
|
|
};
|
|
}
|
|
|
|
fn buffer_read(
|
|
account: kb_lib::MdPubkey,
|
|
authority: kb_lib::MdPubkey,
|
|
) -> crate::SolanaProgramMetadataStatefulReadResult {
|
|
return crate::SolanaProgramMetadataStatefulReadResult {
|
|
commitment: "confirmed".to_string(),
|
|
context_slot: 42,
|
|
account: account.clone(),
|
|
lamports: std::option::Option::Some(1_000),
|
|
owner: std::option::Option::Some(kb_lib::MdProgramId(
|
|
kb_program_ids::METADATA_SOLANA_PROGRAM_METADATA_PROGRAM_ID.to_string(),
|
|
)),
|
|
space: std::option::Option::Some(100),
|
|
state: crate::SolanaProgramMetadataObservedAccountState::Buffer(
|
|
kb_lib::DcMetadataSpmBufferAccountSnapshot {
|
|
account: account.0,
|
|
program: std::option::Option::None,
|
|
authority: std::option::Option::Some(authority.0),
|
|
canonical: false,
|
|
seed: kb_lib::DcMetadataSpmSeed::default(),
|
|
data: vec![0_u8; 4],
|
|
allocated_data_bytes: 4,
|
|
},
|
|
),
|
|
materialized_output: std::option::Option::None,
|
|
};
|
|
}
|
|
|
|
#[test]
|
|
fn write_preflight_requires_buffer_capacity_and_common_safety() {
|
|
let authority = pubkey(2);
|
|
let buffer = pubkey(3);
|
|
let intent = kb_lib::ExMetadataSpmExecutionIntent {
|
|
intent_id: "spm-preflight-write".to_string(),
|
|
fee_payer: authority.clone(),
|
|
policy: policy(&authority),
|
|
allow_destructive_operation: true,
|
|
operation: kb_lib::ExMetadataSpmOperation::Write {
|
|
buffer: buffer.clone(),
|
|
authority: authority.clone(),
|
|
offset: 2,
|
|
source: kb_lib::ExMetadataSpmWriteSource::Inline { data: vec![8, 9] },
|
|
},
|
|
};
|
|
let plan = kb_lib::ExApiTypedInstructionExecutor::build_prepared_plan(
|
|
&kb_lib::ExMetadataSolanaProgramMetadataExecutor,
|
|
&intent,
|
|
);
|
|
assert!(plan.is_ok());
|
|
let plan = match plan {
|
|
std::result::Result::Ok(value) => value,
|
|
std::result::Result::Err(_) => return,
|
|
};
|
|
let report = crate::inspect_solana_program_metadata_preflight(
|
|
&crate::SolanaProgramMetadataPreflightRequest {
|
|
intent,
|
|
plan,
|
|
before: vec![buffer_read(buffer, authority)],
|
|
rent_observations: vec![],
|
|
},
|
|
);
|
|
assert!(report.is_ok());
|
|
if let std::result::Result::Ok(value) = report {
|
|
assert_eq!(value.operation_code, kb_lib::EX_METADATA_SPM_WRITE_OPERATION);
|
|
assert_eq!(value.safety.decision, kb_lib::ExSafetyDecision::Allow);
|
|
}
|
|
}
|
|
|
|
#[test]
|
|
fn allocate_requires_vacancy_and_explicit_rent_evidence() {
|
|
let authority = pubkey(4);
|
|
let buffer = authority.clone();
|
|
let intent = kb_lib::ExMetadataSpmExecutionIntent {
|
|
intent_id: "spm-preflight-allocate".to_string(),
|
|
fee_payer: authority.clone(),
|
|
policy: policy(&authority),
|
|
allow_destructive_operation: false,
|
|
operation: kb_lib::ExMetadataSpmOperation::Allocate {
|
|
buffer: buffer.clone(),
|
|
authority: authority.clone(),
|
|
seed: std::option::Option::None,
|
|
program_context: std::option::Option::None,
|
|
canonical: false,
|
|
allocate_account: true,
|
|
},
|
|
};
|
|
let plan = kb_lib::ExApiTypedInstructionExecutor::build_prepared_plan(
|
|
&kb_lib::ExMetadataSolanaProgramMetadataExecutor,
|
|
&intent,
|
|
);
|
|
assert!(plan.is_ok());
|
|
let plan = match plan {
|
|
std::result::Result::Ok(value) => value,
|
|
std::result::Result::Err(_) => return,
|
|
};
|
|
let before = crate::SolanaProgramMetadataStatefulReadResult {
|
|
commitment: "confirmed".to_string(),
|
|
context_slot: 9,
|
|
account: buffer.clone(),
|
|
lamports: std::option::Option::Some(1_000),
|
|
owner: std::option::Option::Some(kb_lib::MdProgramId(
|
|
kb_program_ids::SYSTEM_PROGRAM_ID.to_string(),
|
|
)),
|
|
space: std::option::Option::Some(0),
|
|
state: crate::SolanaProgramMetadataObservedAccountState::Uninitialized {
|
|
owner: kb_lib::MdProgramId(kb_program_ids::SYSTEM_PROGRAM_ID.to_string()),
|
|
lamports: 1_000,
|
|
space: 0,
|
|
},
|
|
materialized_output: std::option::Option::None,
|
|
};
|
|
let missing_rent = crate::inspect_solana_program_metadata_preflight(
|
|
&crate::SolanaProgramMetadataPreflightRequest {
|
|
intent: intent.clone(),
|
|
plan: plan.clone(),
|
|
before: vec![before.clone()],
|
|
rent_observations: vec![],
|
|
},
|
|
);
|
|
assert!(missing_rent.is_err());
|
|
let target_space = match u64::try_from(kb_lib::DC_METADATA_SPM_BUFFER_HEADER_BYTES) {
|
|
std::result::Result::Ok(value) => value,
|
|
std::result::Result::Err(_) => return,
|
|
};
|
|
let report = crate::inspect_solana_program_metadata_preflight(
|
|
&crate::SolanaProgramMetadataPreflightRequest {
|
|
intent,
|
|
plan,
|
|
before: vec![before],
|
|
rent_observations: vec![crate::SolanaProgramMetadataRentObservation {
|
|
account: buffer,
|
|
target_space,
|
|
observed_lamports: 1_000,
|
|
required_lamports: 900,
|
|
}],
|
|
},
|
|
);
|
|
assert!(report.is_ok());
|
|
}
|
|
}
|