0.4.7-pre.008
This commit is contained in:
@@ -1,5 +1,5 @@
|
||||
// file: kb-pipeline-demo-scenarios/src/lib.rs
|
||||
// version: 5
|
||||
// version: 6
|
||||
|
||||
#![forbid(unsafe_code)]
|
||||
#![deny(unreachable_pub)]
|
||||
@@ -12,6 +12,8 @@ mod environment;
|
||||
mod solana_ata_execution;
|
||||
mod solana_execution;
|
||||
mod solana_memo_execution;
|
||||
mod solana_metaplex_token_metadata_scenarios;
|
||||
mod solana_metaplex_token_metadata_validation;
|
||||
mod solana_token_2022_devnet_execution;
|
||||
mod solana_token_2022_devnet_scenarios;
|
||||
mod solana_token_2022_validation;
|
||||
@@ -65,6 +67,28 @@ pub use self::solana_memo_execution::DevnetMemoExecutionRequest;
|
||||
pub use self::solana_memo_execution::DevnetMemoExecutionSummary;
|
||||
/// Executes one SPL Memo v4 Devnet simulation or explicitly authorized submission.
|
||||
pub use self::solana_memo_execution::execute_devnet_memo;
|
||||
/// Stable asset family covered by one Metaplex scenario.
|
||||
pub use self::solana_metaplex_token_metadata_scenarios::MetaplexTokenMetadataAssetFamily;
|
||||
/// One stable Metaplex scenario reusable by automated tests and demo adapters.
|
||||
pub use self::solana_metaplex_token_metadata_scenarios::MetaplexTokenMetadataScenario;
|
||||
/// Execution mode accepted by one Metaplex scenario.
|
||||
pub use self::solana_metaplex_token_metadata_scenarios::MetaplexTokenMetadataScenarioMode;
|
||||
/// Returns the complete ordered synthetic Metaplex scenario inventory.
|
||||
pub use self::solana_metaplex_token_metadata_scenarios::metaplex_token_metadata_synthetic_scenarios;
|
||||
/// Maximum evidence entries accepted by one Metaplex validation scenario.
|
||||
pub use self::solana_metaplex_token_metadata_validation::MAX_METAPLEX_TOKEN_METADATA_VALIDATION_EVIDENCE;
|
||||
/// One bounded Metaplex validation evidence entry.
|
||||
pub use self::solana_metaplex_token_metadata_validation::MetaplexTokenMetadataValidationEvidence;
|
||||
/// Canonical Metaplex validation matrix.
|
||||
pub use self::solana_metaplex_token_metadata_validation::MetaplexTokenMetadataValidationMatrix;
|
||||
/// One scenario declared by the canonical Metaplex validation matrix.
|
||||
pub use self::solana_metaplex_token_metadata_validation::MetaplexTokenMetadataValidationMatrixScenario;
|
||||
/// Exact validation status for one Metaplex scenario.
|
||||
pub use self::solana_metaplex_token_metadata_validation::MetaplexTokenMetadataValidationStatus;
|
||||
/// Loads and validates the canonical Metaplex validation matrix.
|
||||
pub use self::solana_metaplex_token_metadata_validation::load_metaplex_token_metadata_validation_matrix;
|
||||
/// Validates the canonical Metaplex validation matrix.
|
||||
pub use self::solana_metaplex_token_metadata_validation::validate_metaplex_token_metadata_validation_matrix;
|
||||
/// Complete request for one Devnet Token-2022 execution.
|
||||
pub use self::solana_token_2022_devnet_execution::DevnetSplToken2022ExecutionRequest;
|
||||
/// Complete result of one Devnet Token-2022 execution.
|
||||
|
||||
@@ -0,0 +1,170 @@
|
||||
// file: kb-pipeline-demo-scenarios/src/solana_metaplex_token_metadata_scenarios.rs
|
||||
// version: 1
|
||||
|
||||
//! Deterministic Metaplex Token Metadata scenario inventory for tests and Devnet campaigns.
|
||||
|
||||
/// Stable asset family covered by one Metaplex scenario.
|
||||
#[derive(
|
||||
Clone, Copy, Debug, Eq, Ord, PartialEq, PartialOrd, serde::Deserialize, serde::Serialize,
|
||||
)]
|
||||
#[serde(rename_all = "snake_case")]
|
||||
pub enum MetaplexTokenMetadataAssetFamily {
|
||||
/// Non-fungible token.
|
||||
Nft,
|
||||
/// Semi-fungible token.
|
||||
Sft,
|
||||
/// Fungible token with Metaplex metadata.
|
||||
Fungible,
|
||||
/// Collection metadata and verification flow.
|
||||
Collection,
|
||||
/// Programmable non-fungible token.
|
||||
ProgrammableNft,
|
||||
}
|
||||
|
||||
/// Execution mode accepted by one scenario.
|
||||
#[derive(Clone, Copy, Debug, Eq, PartialEq, serde::Deserialize, serde::Serialize)]
|
||||
#[serde(rename_all = "snake_case")]
|
||||
pub enum MetaplexTokenMetadataScenarioMode {
|
||||
/// Purely deterministic scenario using synthetic accounts and plans.
|
||||
Synthetic,
|
||||
/// Devnet or Testnet simulation without submission.
|
||||
NetworkSimulation,
|
||||
/// Explicitly authorized Devnet or Testnet submission.
|
||||
NetworkSubmission,
|
||||
}
|
||||
|
||||
/// One stable Metaplex scenario reusable by automated tests and demo adapters.
|
||||
#[derive(Clone, Debug, Eq, PartialEq, serde::Deserialize, serde::Serialize)]
|
||||
pub struct MetaplexTokenMetadataScenario {
|
||||
/// Stable scenario identifier.
|
||||
pub id: std::string::String,
|
||||
/// Asset family exercised by the scenario.
|
||||
pub asset_family: crate::MetaplexTokenMetadataAssetFamily,
|
||||
/// Execution mode.
|
||||
pub mode: crate::MetaplexTokenMetadataScenarioMode,
|
||||
/// Stable operation codes executed in order.
|
||||
pub operation_codes: std::vec::Vec<std::string::String>,
|
||||
/// Whether the scenario requires a collection account.
|
||||
pub requires_collection: bool,
|
||||
/// Whether the scenario requires programmable authorization rules.
|
||||
pub requires_programmable_rules: bool,
|
||||
/// Whether a confirmed postcondition is mandatory.
|
||||
pub requires_postcondition: bool,
|
||||
}
|
||||
|
||||
/// Returns the complete ordered synthetic Metaplex scenario inventory.
|
||||
pub fn metaplex_token_metadata_synthetic_scenarios()
|
||||
-> std::vec::Vec<crate::MetaplexTokenMetadataScenario> {
|
||||
return vec![
|
||||
crate::MetaplexTokenMetadataScenario {
|
||||
id: "metaplex_nft_create_update".to_string(),
|
||||
asset_family: crate::MetaplexTokenMetadataAssetFamily::Nft,
|
||||
mode: crate::MetaplexTokenMetadataScenarioMode::Synthetic,
|
||||
operation_codes: vec![
|
||||
kb_lib::EX_METAPLEX_TOKEN_METADATA_CREATE_OPERATION.to_string(),
|
||||
kb_lib::EX_METAPLEX_TOKEN_METADATA_UPDATE_OPERATION.to_string(),
|
||||
],
|
||||
requires_collection: false,
|
||||
requires_programmable_rules: false,
|
||||
requires_postcondition: true,
|
||||
},
|
||||
crate::MetaplexTokenMetadataScenario {
|
||||
id: "metaplex_sft_create_mint".to_string(),
|
||||
asset_family: crate::MetaplexTokenMetadataAssetFamily::Sft,
|
||||
mode: crate::MetaplexTokenMetadataScenarioMode::Synthetic,
|
||||
operation_codes: vec![
|
||||
kb_lib::EX_METAPLEX_TOKEN_METADATA_CREATE_OPERATION.to_string(),
|
||||
kb_lib::EX_METAPLEX_TOKEN_METADATA_MINT_OPERATION.to_string(),
|
||||
],
|
||||
requires_collection: false,
|
||||
requires_programmable_rules: false,
|
||||
requires_postcondition: true,
|
||||
},
|
||||
crate::MetaplexTokenMetadataScenario {
|
||||
id: "metaplex_fungible_create_update".to_string(),
|
||||
asset_family: crate::MetaplexTokenMetadataAssetFamily::Fungible,
|
||||
mode: crate::MetaplexTokenMetadataScenarioMode::Synthetic,
|
||||
operation_codes: vec![
|
||||
kb_lib::EX_METAPLEX_TOKEN_METADATA_CREATE_OPERATION.to_string(),
|
||||
kb_lib::EX_METAPLEX_TOKEN_METADATA_UPDATE_OPERATION.to_string(),
|
||||
],
|
||||
requires_collection: false,
|
||||
requires_programmable_rules: false,
|
||||
requires_postcondition: true,
|
||||
},
|
||||
crate::MetaplexTokenMetadataScenario {
|
||||
id: "metaplex_collection_verify_unverify".to_string(),
|
||||
asset_family: crate::MetaplexTokenMetadataAssetFamily::Collection,
|
||||
mode: crate::MetaplexTokenMetadataScenarioMode::Synthetic,
|
||||
operation_codes: vec![
|
||||
kb_lib::EX_METAPLEX_TOKEN_METADATA_VERIFY_OPERATION.to_string(),
|
||||
kb_lib::EX_METAPLEX_TOKEN_METADATA_UNVERIFY_OPERATION.to_string(),
|
||||
],
|
||||
requires_collection: true,
|
||||
requires_programmable_rules: false,
|
||||
requires_postcondition: true,
|
||||
},
|
||||
crate::MetaplexTokenMetadataScenario {
|
||||
id: "metaplex_pnft_delegate_transfer_revoke".to_string(),
|
||||
asset_family: crate::MetaplexTokenMetadataAssetFamily::ProgrammableNft,
|
||||
mode: crate::MetaplexTokenMetadataScenarioMode::Synthetic,
|
||||
operation_codes: vec![
|
||||
kb_lib::EX_METAPLEX_TOKEN_METADATA_DELEGATE_OPERATION.to_string(),
|
||||
kb_lib::EX_METAPLEX_TOKEN_METADATA_TRANSFER_OPERATION.to_string(),
|
||||
kb_lib::EX_METAPLEX_TOKEN_METADATA_REVOKE_OPERATION.to_string(),
|
||||
],
|
||||
requires_collection: false,
|
||||
requires_programmable_rules: true,
|
||||
requires_postcondition: true,
|
||||
},
|
||||
];
|
||||
}
|
||||
|
||||
#[cfg(test)]
|
||||
mod tests {
|
||||
#[test]
|
||||
fn synthetic_inventory_is_exact_unique_and_covers_every_required_asset_family() {
|
||||
let scenarios = crate::metaplex_token_metadata_synthetic_scenarios();
|
||||
assert_eq!(scenarios.len(), 5);
|
||||
let ids = scenarios
|
||||
.iter()
|
||||
.map(|scenario| return scenario.id.as_str())
|
||||
.collect::<std::collections::BTreeSet<&str>>();
|
||||
assert_eq!(ids.len(), scenarios.len());
|
||||
let families = scenarios
|
||||
.iter()
|
||||
.map(|scenario| return scenario.asset_family)
|
||||
.collect::<std::collections::BTreeSet<crate::MetaplexTokenMetadataAssetFamily>>(
|
||||
);
|
||||
assert_eq!(families.len(), 5);
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn synthetic_scenarios_are_simulation_safe_and_require_postconditions() {
|
||||
for scenario in crate::metaplex_token_metadata_synthetic_scenarios() {
|
||||
assert_eq!(scenario.mode, crate::MetaplexTokenMetadataScenarioMode::Synthetic);
|
||||
assert!(!scenario.operation_codes.is_empty());
|
||||
assert!(scenario.requires_postcondition);
|
||||
}
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn programmable_and_collection_requirements_are_declared_only_where_needed() {
|
||||
let scenarios = crate::metaplex_token_metadata_synthetic_scenarios();
|
||||
let programmable = scenarios
|
||||
.iter()
|
||||
.filter(|scenario| return scenario.requires_programmable_rules)
|
||||
.collect::<std::vec::Vec<&crate::MetaplexTokenMetadataScenario>>();
|
||||
assert_eq!(programmable.len(), 1);
|
||||
assert_eq!(
|
||||
programmable[0].asset_family,
|
||||
crate::MetaplexTokenMetadataAssetFamily::ProgrammableNft
|
||||
);
|
||||
let collection = scenarios
|
||||
.iter()
|
||||
.filter(|scenario| return scenario.requires_collection)
|
||||
.collect::<std::vec::Vec<&crate::MetaplexTokenMetadataScenario>>();
|
||||
assert_eq!(collection.len(), 1);
|
||||
assert_eq!(collection[0].asset_family, crate::MetaplexTokenMetadataAssetFamily::Collection);
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,192 @@
|
||||
// file: kb-pipeline-demo-scenarios/src/solana_metaplex_token_metadata_validation.rs
|
||||
// version: 1
|
||||
|
||||
//! Machine-readable Metaplex Token Metadata validation contract.
|
||||
|
||||
/// Maximum evidence entries accepted by one Metaplex validation scenario.
|
||||
pub const MAX_METAPLEX_TOKEN_METADATA_VALIDATION_EVIDENCE: usize = 64;
|
||||
|
||||
/// Exact validation status for one Metaplex scenario.
|
||||
#[derive(Clone, Copy, Debug, Eq, PartialEq, serde::Deserialize, serde::Serialize)]
|
||||
#[serde(rename_all = "snake_case")]
|
||||
pub enum MetaplexTokenMetadataValidationStatus {
|
||||
/// Scenario has not been executed.
|
||||
NotRun,
|
||||
/// Scenario passed deterministic synthetic validation.
|
||||
SyntheticValidated,
|
||||
/// Scenario was simulated on Devnet or Testnet.
|
||||
Simulated,
|
||||
/// Scenario was submitted but confirmation evidence is incomplete.
|
||||
Submitted,
|
||||
/// Scenario was confirmed and all postconditions passed.
|
||||
Confirmed,
|
||||
/// Scenario is unavailable in the selected environment.
|
||||
Unavailable,
|
||||
/// Scenario ran and failed.
|
||||
Failed,
|
||||
}
|
||||
|
||||
/// One bounded validation evidence entry.
|
||||
#[derive(Clone, Debug, Eq, PartialEq, serde::Deserialize, serde::Serialize)]
|
||||
pub struct MetaplexTokenMetadataValidationEvidence {
|
||||
/// Stable evidence kind.
|
||||
pub kind: std::string::String,
|
||||
/// Bounded evidence value.
|
||||
pub value: std::string::String,
|
||||
}
|
||||
|
||||
/// One scenario declared by the canonical Metaplex validation matrix.
|
||||
#[derive(Clone, Debug, Eq, PartialEq, serde::Deserialize, serde::Serialize)]
|
||||
#[serde(rename_all = "camelCase")]
|
||||
pub struct MetaplexTokenMetadataValidationMatrixScenario {
|
||||
/// Stable scenario identifier.
|
||||
pub id: std::string::String,
|
||||
/// Required scenario mode.
|
||||
pub mode: crate::MetaplexTokenMetadataScenarioMode,
|
||||
/// Exact validation status.
|
||||
pub status: crate::MetaplexTokenMetadataValidationStatus,
|
||||
/// Evidence kinds required before confirmation.
|
||||
pub required_evidence: std::vec::Vec<std::string::String>,
|
||||
/// Observed evidence.
|
||||
#[serde(default)]
|
||||
pub evidence: std::vec::Vec<crate::MetaplexTokenMetadataValidationEvidence>,
|
||||
}
|
||||
|
||||
/// Canonical Metaplex validation matrix.
|
||||
#[derive(Clone, Debug, Eq, PartialEq, serde::Deserialize, serde::Serialize)]
|
||||
#[serde(rename_all = "camelCase")]
|
||||
pub struct MetaplexTokenMetadataValidationMatrix {
|
||||
/// Matrix schema version.
|
||||
pub matrix_version: u32,
|
||||
/// Owning milestone.
|
||||
pub milestone: std::string::String,
|
||||
/// Ordered validation scenarios.
|
||||
pub scenarios: std::vec::Vec<crate::MetaplexTokenMetadataValidationMatrixScenario>,
|
||||
}
|
||||
|
||||
/// Loads and validates the canonical Metaplex validation matrix.
|
||||
pub fn load_metaplex_token_metadata_validation_matrix()
|
||||
-> kb_core::Result<crate::MetaplexTokenMetadataValidationMatrix> {
|
||||
let parsed =
|
||||
match serde_json::from_str::<crate::MetaplexTokenMetadataValidationMatrix>(include_str!(
|
||||
"../../test-fixtures/contract-matrices/METAPLEX_TOKEN_METADATA_VALIDATION_MATRIX.json"
|
||||
)) {
|
||||
std::result::Result::Ok(value) => value,
|
||||
std::result::Result::Err(error) => {
|
||||
return std::result::Result::Err(kb_core::Error::new(
|
||||
"metaplex_token_metadata_validation_matrix_invalid_json",
|
||||
format!("Metaplex validation matrix JSON is invalid: {error}"),
|
||||
));
|
||||
},
|
||||
};
|
||||
if let std::result::Result::Err(error) =
|
||||
validate_metaplex_token_metadata_validation_matrix(&parsed)
|
||||
{
|
||||
return std::result::Result::Err(error);
|
||||
}
|
||||
return std::result::Result::Ok(parsed);
|
||||
}
|
||||
|
||||
/// Validates the canonical Metaplex validation matrix.
|
||||
pub fn validate_metaplex_token_metadata_validation_matrix(
|
||||
matrix: &crate::MetaplexTokenMetadataValidationMatrix,
|
||||
) -> kb_core::Result<()> {
|
||||
if matrix.matrix_version != 1 || matrix.milestone != "0.4.7" {
|
||||
return std::result::Result::Err(kb_core::Error::new(
|
||||
"metaplex_token_metadata_validation_matrix_contract_mismatch",
|
||||
"Metaplex validation matrix must use version 1 for milestone 0.4.7",
|
||||
));
|
||||
}
|
||||
let expected = crate::metaplex_token_metadata_synthetic_scenarios()
|
||||
.iter()
|
||||
.map(|scenario| return scenario.id.clone())
|
||||
.collect::<std::vec::Vec<std::string::String>>();
|
||||
let actual = matrix
|
||||
.scenarios
|
||||
.iter()
|
||||
.map(|scenario| return scenario.id.clone())
|
||||
.collect::<std::vec::Vec<std::string::String>>();
|
||||
if actual != expected {
|
||||
return std::result::Result::Err(kb_core::Error::new(
|
||||
"metaplex_token_metadata_validation_matrix_inventory_mismatch",
|
||||
"Metaplex validation matrix inventory differs from the compiled scenario inventory",
|
||||
));
|
||||
}
|
||||
let mut ids = std::collections::BTreeSet::<&str>::new();
|
||||
for scenario in &matrix.scenarios {
|
||||
if !ids.insert(scenario.id.as_str()) || scenario.required_evidence.is_empty() {
|
||||
return std::result::Result::Err(kb_core::Error::new(
|
||||
"metaplex_token_metadata_validation_matrix_scenario_invalid",
|
||||
"Metaplex validation scenarios must be unique and declare required evidence",
|
||||
));
|
||||
}
|
||||
if scenario.evidence.len() > crate::MAX_METAPLEX_TOKEN_METADATA_VALIDATION_EVIDENCE {
|
||||
return std::result::Result::Err(kb_core::Error::new(
|
||||
"metaplex_token_metadata_validation_evidence_limit_exceeded",
|
||||
"Metaplex validation evidence exceeds the compiled bound",
|
||||
));
|
||||
}
|
||||
if matches!(
|
||||
scenario.status,
|
||||
crate::MetaplexTokenMetadataValidationStatus::NotRun
|
||||
| crate::MetaplexTokenMetadataValidationStatus::Unavailable
|
||||
) && !scenario.evidence.is_empty()
|
||||
{
|
||||
return std::result::Result::Err(kb_core::Error::new(
|
||||
"metaplex_token_metadata_validation_unobserved_with_evidence",
|
||||
"Not-run or unavailable Metaplex scenarios must not contain evidence",
|
||||
));
|
||||
}
|
||||
if scenario.status == crate::MetaplexTokenMetadataValidationStatus::Confirmed {
|
||||
let observed = scenario
|
||||
.evidence
|
||||
.iter()
|
||||
.map(|evidence| return evidence.kind.as_str())
|
||||
.collect::<std::collections::BTreeSet<&str>>();
|
||||
if !scenario
|
||||
.required_evidence
|
||||
.iter()
|
||||
.all(|kind| return observed.contains(kind.as_str()))
|
||||
{
|
||||
return std::result::Result::Err(kb_core::Error::new(
|
||||
"metaplex_token_metadata_validation_confirmed_without_required_evidence",
|
||||
"Confirmed Metaplex scenario lacks required evidence",
|
||||
));
|
||||
}
|
||||
}
|
||||
}
|
||||
return std::result::Result::Ok(());
|
||||
}
|
||||
|
||||
#[cfg(test)]
|
||||
mod tests {
|
||||
#[test]
|
||||
fn canonical_matrix_is_machine_readable_and_matches_synthetic_inventory() {
|
||||
let result = crate::load_metaplex_token_metadata_validation_matrix();
|
||||
assert!(result.is_ok());
|
||||
if let std::result::Result::Ok(matrix) = result {
|
||||
assert_eq!(matrix.scenarios.len(), 5);
|
||||
}
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn confirmed_scenario_requires_every_declared_evidence_kind() {
|
||||
let result = crate::load_metaplex_token_metadata_validation_matrix();
|
||||
assert!(result.is_ok());
|
||||
if let std::result::Result::Ok(mut matrix) = result {
|
||||
matrix.scenarios[0].status = crate::MetaplexTokenMetadataValidationStatus::Confirmed;
|
||||
assert!(crate::validate_metaplex_token_metadata_validation_matrix(&matrix).is_err());
|
||||
matrix.scenarios[0].evidence = matrix.scenarios[0]
|
||||
.required_evidence
|
||||
.iter()
|
||||
.map(|kind| {
|
||||
return crate::MetaplexTokenMetadataValidationEvidence {
|
||||
kind: kind.clone(),
|
||||
value: "synthetic-proof".to_string(),
|
||||
};
|
||||
})
|
||||
.collect();
|
||||
assert!(crate::validate_metaplex_token_metadata_validation_matrix(&matrix).is_ok());
|
||||
}
|
||||
}
|
||||
}
|
||||
Reference in New Issue
Block a user