616 lines
25 KiB
Rust
616 lines
25 KiB
Rust
// file: kb-pipeline-demo-scenarios/src/solana_metaplex_token_metadata_validation.rs
|
|
// version: 4
|
|
|
|
//! 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>,
|
|
}
|
|
|
|
/// Transaction location covered by one cross-validation case.
|
|
#[derive(
|
|
Clone, Copy, Debug, Eq, Ord, PartialEq, PartialOrd, serde::Deserialize, serde::Serialize,
|
|
)]
|
|
#[serde(rename_all = "snake_case")]
|
|
pub enum MetaplexTokenMetadataTransactionPath {
|
|
/// Top-level instruction.
|
|
Outer,
|
|
/// Cross-program invocation.
|
|
Cpi,
|
|
}
|
|
|
|
/// Expected transaction outcome for one cross-validation case.
|
|
#[derive(Clone, Copy, Debug, Eq, PartialEq, serde::Deserialize, serde::Serialize)]
|
|
#[serde(rename_all = "snake_case")]
|
|
pub enum MetaplexTokenMetadataTransactionOutcome {
|
|
/// Successful transaction path.
|
|
Success,
|
|
/// Failed or rejected transaction path.
|
|
Failed,
|
|
}
|
|
|
|
/// Stable failure category exercised by one negative case.
|
|
#[derive(
|
|
Clone, Copy, Debug, Eq, Ord, PartialEq, PartialOrd, serde::Deserialize, serde::Serialize,
|
|
)]
|
|
#[serde(rename_all = "snake_case")]
|
|
pub enum MetaplexTokenMetadataFailureClass {
|
|
/// Transaction failed before committed observations could be emitted.
|
|
TransactionFailed,
|
|
/// Program ID mismatch.
|
|
ProgramId,
|
|
/// Account owner mismatch.
|
|
Owner,
|
|
/// Program-derived address mismatch.
|
|
Pda,
|
|
/// Positional account or flag mismatch.
|
|
Accounts,
|
|
/// Truncated instruction payload.
|
|
PayloadTruncated,
|
|
/// Forbidden trailing instruction bytes.
|
|
PayloadSuffix,
|
|
/// Unknown instruction discriminant.
|
|
UnknownDiscriminant,
|
|
/// Conflicting Metaplex and Token-2022 metadata sources.
|
|
MetadataSourceConflict,
|
|
}
|
|
|
|
/// One case in the closed Metaplex cross-validation corpus.
|
|
#[derive(Clone, Debug, Eq, PartialEq, serde::Deserialize, serde::Serialize)]
|
|
#[serde(rename_all = "camelCase")]
|
|
pub struct MetaplexTokenMetadataCrossValidationCase {
|
|
/// Stable case identifier.
|
|
pub id: std::string::String,
|
|
/// Outer or CPI transaction path.
|
|
pub path: crate::MetaplexTokenMetadataTransactionPath,
|
|
/// Expected transaction outcome.
|
|
pub outcome: crate::MetaplexTokenMetadataTransactionOutcome,
|
|
/// Optional asset family for successful corpus cases.
|
|
pub asset_family: std::option::Option<crate::MetaplexTokenMetadataAssetFamily>,
|
|
/// Optional stable negative-case category.
|
|
pub failure_class: std::option::Option<crate::MetaplexTokenMetadataFailureClass>,
|
|
/// Whether status changes require actual RPC evidence.
|
|
pub requires_network_evidence: bool,
|
|
/// Exact observed validation status.
|
|
pub status: crate::MetaplexTokenMetadataValidationStatus,
|
|
/// Evidence kinds required before the declared status is accepted.
|
|
pub required_evidence: std::vec::Vec<std::string::String>,
|
|
/// Bounded observed evidence.
|
|
#[serde(default)]
|
|
pub evidence: std::vec::Vec<crate::MetaplexTokenMetadataValidationEvidence>,
|
|
}
|
|
|
|
/// Closed Metaplex cross-validation corpus for `0.4.7-pre.010`.
|
|
#[derive(Clone, Debug, Eq, PartialEq, serde::Deserialize, serde::Serialize)]
|
|
#[serde(rename_all = "camelCase")]
|
|
pub struct MetaplexTokenMetadataCrossValidationMatrix {
|
|
/// Matrix schema version.
|
|
pub matrix_version: u32,
|
|
/// Exact owning prerelease.
|
|
pub milestone: std::string::String,
|
|
/// Ordered validation cases.
|
|
pub cases: std::vec::Vec<crate::MetaplexTokenMetadataCrossValidationCase>,
|
|
}
|
|
|
|
/// Loads and validates the closed Metaplex cross-validation corpus.
|
|
pub fn load_metaplex_token_metadata_cross_validation_matrix()
|
|
-> kb_core::Result<crate::MetaplexTokenMetadataCrossValidationMatrix> {
|
|
let parsed = match serde_json::from_str::<crate::MetaplexTokenMetadataCrossValidationMatrix>(
|
|
include_str!(
|
|
"../../test-fixtures/contract-matrices/METAPLEX_TOKEN_METADATA_CROSS_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_cross_validation_matrix_invalid_json",
|
|
format!("Metaplex cross-validation matrix JSON is invalid: {error}"),
|
|
));
|
|
},
|
|
};
|
|
if let std::result::Result::Err(error) =
|
|
validate_metaplex_token_metadata_cross_validation_matrix(&parsed)
|
|
{
|
|
return std::result::Result::Err(error);
|
|
}
|
|
return std::result::Result::Ok(parsed);
|
|
}
|
|
|
|
/// Validates the closed Metaplex cross-validation corpus and evidence claims.
|
|
pub fn validate_metaplex_token_metadata_cross_validation_matrix(
|
|
matrix: &crate::MetaplexTokenMetadataCrossValidationMatrix,
|
|
) -> kb_core::Result<()> {
|
|
if matrix.matrix_version != 1 || matrix.milestone != "0.4.7-pre.010" {
|
|
return std::result::Result::Err(kb_core::Error::new(
|
|
"metaplex_token_metadata_cross_validation_matrix_contract_mismatch",
|
|
"Metaplex cross-validation matrix must use version 1 for 0.4.7-pre.010",
|
|
));
|
|
}
|
|
let mut ids = std::collections::BTreeSet::<&str>::new();
|
|
for case in &matrix.cases {
|
|
if !ids.insert(case.id.as_str()) || case.required_evidence.is_empty() {
|
|
return std::result::Result::Err(kb_core::Error::new(
|
|
"metaplex_token_metadata_cross_validation_case_invalid",
|
|
"Metaplex cross-validation cases must be unique and declare evidence",
|
|
));
|
|
}
|
|
if case.evidence.len() > crate::MAX_METAPLEX_TOKEN_METADATA_VALIDATION_EVIDENCE {
|
|
return std::result::Result::Err(kb_core::Error::new(
|
|
"metaplex_token_metadata_cross_validation_evidence_limit_exceeded",
|
|
"Metaplex cross-validation evidence exceeds the compiled bound",
|
|
));
|
|
}
|
|
if case.outcome == crate::MetaplexTokenMetadataTransactionOutcome::Success
|
|
&& case.asset_family.is_none()
|
|
{
|
|
return std::result::Result::Err(kb_core::Error::new(
|
|
"metaplex_token_metadata_cross_validation_success_without_asset_family",
|
|
"Successful Metaplex corpus cases must identify an asset family",
|
|
));
|
|
}
|
|
if case.outcome == crate::MetaplexTokenMetadataTransactionOutcome::Failed
|
|
&& case.failure_class.is_none()
|
|
{
|
|
return std::result::Result::Err(kb_core::Error::new(
|
|
"metaplex_token_metadata_cross_validation_failure_without_class",
|
|
"Failed Metaplex corpus cases must identify a failure class",
|
|
));
|
|
}
|
|
if matches!(
|
|
case.status,
|
|
crate::MetaplexTokenMetadataValidationStatus::NotRun
|
|
| crate::MetaplexTokenMetadataValidationStatus::Unavailable
|
|
) && !case.evidence.is_empty()
|
|
{
|
|
return std::result::Result::Err(kb_core::Error::new(
|
|
"metaplex_token_metadata_cross_validation_unobserved_with_evidence",
|
|
"Not-run or unavailable cross-validation cases must not contain evidence",
|
|
));
|
|
}
|
|
if case.requires_network_evidence
|
|
&& matches!(
|
|
case.status,
|
|
crate::MetaplexTokenMetadataValidationStatus::SyntheticValidated
|
|
)
|
|
{
|
|
return std::result::Result::Err(kb_core::Error::new(
|
|
"metaplex_token_metadata_cross_validation_network_overdeclared",
|
|
"Network cases cannot be declared validated from synthetic evidence",
|
|
));
|
|
}
|
|
if matches!(
|
|
case.status,
|
|
crate::MetaplexTokenMetadataValidationStatus::Simulated
|
|
| crate::MetaplexTokenMetadataValidationStatus::Submitted
|
|
| crate::MetaplexTokenMetadataValidationStatus::Confirmed
|
|
) {
|
|
let observed = case
|
|
.evidence
|
|
.iter()
|
|
.map(|evidence| return evidence.kind.as_str())
|
|
.collect::<std::collections::BTreeSet<&str>>();
|
|
if !case
|
|
.required_evidence
|
|
.iter()
|
|
.all(|kind| return observed.contains(kind.as_str()))
|
|
{
|
|
return std::result::Result::Err(kb_core::Error::new(
|
|
"metaplex_token_metadata_cross_validation_status_without_required_evidence",
|
|
"Observed Metaplex network status lacks required evidence",
|
|
));
|
|
}
|
|
}
|
|
}
|
|
return std::result::Result::Ok(());
|
|
}
|
|
|
|
/// 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());
|
|
}
|
|
}
|
|
|
|
#[test]
|
|
fn cross_validation_corpus_is_closed_and_covers_every_required_dimension() {
|
|
let result = crate::load_metaplex_token_metadata_cross_validation_matrix();
|
|
assert!(result.is_ok());
|
|
if let std::result::Result::Ok(matrix) = result {
|
|
assert_eq!(matrix.cases.len(), 19);
|
|
let paths = matrix
|
|
.cases
|
|
.iter()
|
|
.map(|case| return case.path)
|
|
.collect::<std::collections::BTreeSet<crate::MetaplexTokenMetadataTransactionPath>>(
|
|
);
|
|
assert_eq!(paths.len(), 2);
|
|
let families = matrix
|
|
.cases
|
|
.iter()
|
|
.filter_map(|case| return case.asset_family)
|
|
.collect::<std::collections::BTreeSet<crate::MetaplexTokenMetadataAssetFamily>>(
|
|
);
|
|
assert_eq!(families.len(), 5);
|
|
let failures = matrix
|
|
.cases
|
|
.iter()
|
|
.filter_map(|case| return case.failure_class)
|
|
.collect::<std::collections::BTreeSet<crate::MetaplexTokenMetadataFailureClass>>(
|
|
);
|
|
assert_eq!(failures.len(), 9);
|
|
assert_eq!(
|
|
matrix.cases.iter().filter(|case| return case.requires_network_evidence).count(),
|
|
3
|
|
);
|
|
}
|
|
}
|
|
|
|
#[test]
|
|
fn network_cases_cannot_be_promoted_from_synthetic_or_incomplete_evidence() {
|
|
let result = crate::load_metaplex_token_metadata_cross_validation_matrix();
|
|
assert!(result.is_ok());
|
|
if let std::result::Result::Ok(mut matrix) = result {
|
|
let network =
|
|
matrix.cases.iter_mut().find(|case| return case.requires_network_evidence);
|
|
assert!(network.is_some());
|
|
if let std::option::Option::Some(case) = network {
|
|
case.status = crate::MetaplexTokenMetadataValidationStatus::SyntheticValidated;
|
|
}
|
|
assert!(
|
|
crate::validate_metaplex_token_metadata_cross_validation_matrix(&matrix).is_err()
|
|
);
|
|
}
|
|
}
|
|
}
|
|
|
|
/// One current Metaplex operation in the closed Devnet execution matrix.
|
|
#[derive(Clone, Debug, Eq, PartialEq, serde::Deserialize, serde::Serialize)]
|
|
#[serde(rename_all = "camelCase")]
|
|
pub struct MetaplexTokenMetadataDevnetExecutionOperation {
|
|
/// Stable instruction discriminator.
|
|
pub discriminator: u8,
|
|
/// Official instruction name.
|
|
pub name: std::string::String,
|
|
/// Stable executor operation code.
|
|
pub operation_code: std::string::String,
|
|
/// Whether the operation is deprecated.
|
|
pub deprecated: bool,
|
|
/// Prerelease responsible for the campaign.
|
|
pub campaign_prerelease: std::string::String,
|
|
/// Implementation status of the reusable runner.
|
|
pub runner_status: std::string::String,
|
|
/// Exact observed network status.
|
|
pub network_status: std::string::String,
|
|
/// Evidence required after simulation.
|
|
pub required_evidence: std::vec::Vec<std::string::String>,
|
|
/// Additional evidence required after submission.
|
|
pub submission_evidence: std::vec::Vec<std::string::String>,
|
|
}
|
|
|
|
/// Closed inventory of every current Metaplex operation requiring Devnet coverage.
|
|
#[derive(Clone, Debug, Eq, PartialEq, serde::Deserialize, serde::Serialize)]
|
|
#[serde(rename_all = "camelCase")]
|
|
pub struct MetaplexTokenMetadataDevnetExecutionMatrix {
|
|
/// Matrix schema version.
|
|
pub matrix_version: u32,
|
|
/// Owning prerelease.
|
|
pub milestone: std::string::String,
|
|
/// Canonical Token Metadata program ID.
|
|
pub program_id: std::string::String,
|
|
/// Ordered current operations.
|
|
pub operations: std::vec::Vec<crate::MetaplexTokenMetadataDevnetExecutionOperation>,
|
|
}
|
|
|
|
/// Loads and validates the closed Devnet execution matrix.
|
|
pub fn load_metaplex_token_metadata_devnet_execution_matrix()
|
|
-> kb_core::Result<crate::MetaplexTokenMetadataDevnetExecutionMatrix> {
|
|
let matrix = match serde_json::from_str::<crate::MetaplexTokenMetadataDevnetExecutionMatrix>(
|
|
include_str!(
|
|
"../../test-fixtures/contract-matrices/METAPLEX_TOKEN_METADATA_DEVNET_EXECUTION_MATRIX.json"
|
|
),
|
|
) {
|
|
std::result::Result::Ok(value) => value,
|
|
std::result::Result::Err(error) => {
|
|
return std::result::Result::Err(kb_core::Error::new(
|
|
"metaplex_devnet_execution_matrix_invalid_json",
|
|
format!("Metaplex Devnet execution matrix JSON is invalid: {error}"),
|
|
));
|
|
},
|
|
};
|
|
if let std::result::Result::Err(error) =
|
|
validate_metaplex_token_metadata_devnet_execution_matrix(&matrix)
|
|
{
|
|
return std::result::Result::Err(error);
|
|
}
|
|
return std::result::Result::Ok(matrix);
|
|
}
|
|
|
|
/// Validates exact current-operation coverage and conservative network statuses.
|
|
pub fn validate_metaplex_token_metadata_devnet_execution_matrix(
|
|
matrix: &crate::MetaplexTokenMetadataDevnetExecutionMatrix,
|
|
) -> kb_core::Result<()> {
|
|
if matrix.matrix_version != 1
|
|
|| matrix.milestone != "0.4.7-pre.012"
|
|
|| matrix.program_id != kb_program_ids::METADATA_METAPLEX_TOKEN_METADATA_PROGRAM_ID
|
|
|| matrix.operations.len() != 20
|
|
{
|
|
return std::result::Result::Err(kb_core::Error::new(
|
|
"metaplex_devnet_execution_matrix_contract_mismatch",
|
|
"Metaplex Devnet matrix must declare exactly 20 current operations for pre.012",
|
|
));
|
|
}
|
|
let expected = [
|
|
"metadata.metaplex_token_metadata.create_escrow_account",
|
|
"metadata.metaplex_token_metadata.close_escrow_account",
|
|
"metadata.metaplex_token_metadata.transfer_out_of_escrow",
|
|
"metadata.metaplex_token_metadata.burn",
|
|
"metadata.metaplex_token_metadata.create",
|
|
"metadata.metaplex_token_metadata.mint",
|
|
"metadata.metaplex_token_metadata.delegate",
|
|
"metadata.metaplex_token_metadata.revoke",
|
|
"metadata.metaplex_token_metadata.lock",
|
|
"metadata.metaplex_token_metadata.unlock",
|
|
"metadata.metaplex_token_metadata.migrate",
|
|
"metadata.metaplex_token_metadata.transfer",
|
|
"metadata.metaplex_token_metadata.update",
|
|
"metadata.metaplex_token_metadata.use",
|
|
"metadata.metaplex_token_metadata.verify",
|
|
"metadata.metaplex_token_metadata.unverify",
|
|
"metadata.metaplex_token_metadata.collect",
|
|
"metadata.metaplex_token_metadata.print",
|
|
"metadata.metaplex_token_metadata.resize",
|
|
"metadata.metaplex_token_metadata.close_accounts",
|
|
]
|
|
.into_iter()
|
|
.collect::<std::collections::BTreeSet<&str>>();
|
|
let observed = matrix
|
|
.operations
|
|
.iter()
|
|
.map(|operation| return operation.operation_code.as_str())
|
|
.collect::<std::collections::BTreeSet<&str>>();
|
|
if observed != expected {
|
|
return std::result::Result::Err(kb_core::Error::new(
|
|
"metaplex_devnet_execution_matrix_operation_mismatch",
|
|
"Metaplex Devnet matrix must cover the exact 20 current operation codes",
|
|
));
|
|
}
|
|
for operation in &matrix.operations {
|
|
if operation.deprecated
|
|
|| operation.runner_status != "implemented"
|
|
|| !matches!(operation.campaign_prerelease.as_str(), "0.4.7-pre.011" | "0.4.7-pre.012")
|
|
|| operation.required_evidence.is_empty()
|
|
|| operation.submission_evidence.is_empty()
|
|
|| !matches!(
|
|
operation.network_status.as_str(),
|
|
"not_run" | "simulated" | "confirmed" | "unavailable"
|
|
)
|
|
{
|
|
return std::result::Result::Err(kb_core::Error::new(
|
|
"metaplex_devnet_execution_matrix_entry_invalid",
|
|
"Metaplex Devnet entries must be current, implemented, evidenced and conservatively classified",
|
|
));
|
|
}
|
|
}
|
|
return std::result::Result::Ok(());
|
|
}
|
|
|
|
#[cfg(test)]
|
|
mod devnet_execution_matrix_tests {
|
|
#[test]
|
|
fn devnet_execution_matrix_is_closed_current_and_conservative() {
|
|
let result = crate::load_metaplex_token_metadata_devnet_execution_matrix();
|
|
assert!(result.is_ok());
|
|
let matrix = if let std::result::Result::Ok(value) = result {
|
|
value
|
|
} else {
|
|
return;
|
|
};
|
|
assert_eq!(matrix.operations.len(), 20);
|
|
assert_eq!(
|
|
matrix
|
|
.operations
|
|
.iter()
|
|
.filter(|operation| return operation.campaign_prerelease == "0.4.7-pre.011")
|
|
.count(),
|
|
4,
|
|
);
|
|
assert_eq!(
|
|
matrix
|
|
.operations
|
|
.iter()
|
|
.filter(|operation| return operation.campaign_prerelease == "0.4.7-pre.012")
|
|
.count(),
|
|
16,
|
|
);
|
|
assert!(matrix.operations.iter().all(|operation| return !operation.deprecated));
|
|
assert!(
|
|
matrix
|
|
.operations
|
|
.iter()
|
|
.all(|operation| return operation.runner_status == "implemented")
|
|
);
|
|
assert!(
|
|
matrix
|
|
.operations
|
|
.iter()
|
|
.all(|operation| return operation.network_status == "not_run")
|
|
);
|
|
}
|
|
}
|