587 lines
25 KiB
Rust
587 lines
25 KiB
Rust
// file: kb-pipeline-demo-scenarios/src/solana_token_2022_metadata_campaign.rs
|
|
// version: 3
|
|
|
|
//! Ordered confirmed Devnet campaign for the five Token-2022 Token Metadata instructions.
|
|
|
|
/// One confirmed Token Metadata campaign step and its authoritative postcondition.
|
|
#[derive(Clone, Debug, PartialEq)]
|
|
pub struct DevnetToken2022MetadataCampaignStepSummary {
|
|
/// Stable step identifier.
|
|
pub step_id: std::string::String,
|
|
/// Canonical operation code built by `kb-lib`.
|
|
pub operation_code: std::string::String,
|
|
/// Complete simulation, submission, replay and instruction-materialization evidence.
|
|
pub execution: crate::DevnetSplToken2022ExecutionSummary,
|
|
/// Authoritative mint snapshot read at or after the confirmed transaction slot.
|
|
pub stateful_snapshot: kb_pipeline::Token2022StatefulReadResult,
|
|
/// Validated return-data evidence for the `Emit` step only.
|
|
pub emit_evidence: std::option::Option<kb_pipeline::Token2022MetadataEmitEvidence>,
|
|
/// Explicit stateful or return-data postcondition.
|
|
pub postcondition: kb_pipeline::Token2022ExecutionPostcondition,
|
|
}
|
|
|
|
/// Complete evidence retained by the five-step Token Metadata campaign.
|
|
#[derive(Clone, Debug, PartialEq)]
|
|
pub struct DevnetToken2022MetadataCampaignSummary {
|
|
/// Fresh Token-2022 mint prepared for this campaign.
|
|
pub fixture: crate::Token2022MetadataFixturePreparationSummary,
|
|
/// Five confirmed steps in interface order.
|
|
pub steps: std::vec::Vec<crate::DevnetToken2022MetadataCampaignStepSummary>,
|
|
}
|
|
|
|
/// Returns the exact ordered operation names exercised by the Devnet campaign.
|
|
pub fn token_2022_metadata_campaign_operation_names() -> &'static [&'static str; 5] {
|
|
return &[
|
|
"initialize_token_metadata",
|
|
"update_token_metadata_field",
|
|
"emit_token_metadata",
|
|
"remove_token_metadata_key",
|
|
"update_token_metadata_authority",
|
|
];
|
|
}
|
|
|
|
/// Prepares a fresh mint and executes all five Token Metadata interface instructions on Devnet.
|
|
#[allow(clippy::too_many_arguments)]
|
|
pub async fn execute_devnet_token_2022_metadata_campaign<S, O>(
|
|
http_pool: &kb_onchain_transport::HttpEndpointPool,
|
|
store: &S,
|
|
profile: &kb_config::ProfileConfig,
|
|
workspace_root: &std::path::Path,
|
|
options: &crate::Token2022MetadataFixturePreparationOptions,
|
|
observer: &O,
|
|
) -> kb_core::Result<crate::DevnetToken2022MetadataCampaignSummary>
|
|
where
|
|
S: kb_store::RawTransactionStore
|
|
+ kb_store::CoreExtractionStore
|
|
+ kb_store::DecodePipelineStore
|
|
+ Sync,
|
|
O: crate::SolanaExecutionObserver,
|
|
{
|
|
if !options.operator_confirmed {
|
|
return std::result::Result::Err(kb_core::Error::config(
|
|
"Token-2022 Token Metadata Devnet campaign requires explicit operator confirmation",
|
|
));
|
|
}
|
|
let fixture = match crate::prepare_token_2022_metadata_fixture(
|
|
http_pool,
|
|
profile,
|
|
workspace_root,
|
|
options,
|
|
)
|
|
.await
|
|
{
|
|
std::result::Result::Ok(value) => value,
|
|
std::result::Result::Err(error) => return std::result::Result::Err(error),
|
|
};
|
|
let operations = campaign_operations(&fixture);
|
|
let decoders: std::vec::Vec<std::sync::Arc<dyn kb_lib::DcApiInstructionDecoder>> =
|
|
std::vec![std::sync::Arc::new(kb_lib::DcSplToken2022Decoder)];
|
|
let materializers: std::vec::Vec<std::sync::Arc<dyn kb_lib::MtApiEventMaterializer>> =
|
|
std::vec![std::sync::Arc::new(kb_lib::MtMetadataToken2022Materializer)];
|
|
let mut steps = std::vec::Vec::with_capacity(operations.len());
|
|
for (index, (step_id, operation)) in operations.into_iter().enumerate() {
|
|
let operation_code = operation.operation_code().to_string();
|
|
let mut request = crate::DevnetSplToken2022ExecutionRequest::new(
|
|
format!("token-2022-metadata-devnet-{step_id}-{}", uuid::Uuid::new_v4()),
|
|
operation.clone(),
|
|
);
|
|
request.query_role = options.query_role.clone();
|
|
request.transaction_role = options.transaction_role.clone();
|
|
request.submit = true;
|
|
request.operator_confirmed = true;
|
|
request.post_validation_max_retries = 20;
|
|
let execution = match crate::execute_devnet_spl_token_2022(
|
|
http_pool,
|
|
store,
|
|
profile,
|
|
workspace_root,
|
|
&request,
|
|
decoders.as_slice(),
|
|
materializers.as_slice(),
|
|
observer,
|
|
)
|
|
.await
|
|
{
|
|
std::result::Result::Ok(value) => value,
|
|
std::result::Result::Err(error) => return std::result::Result::Err(error),
|
|
};
|
|
let confirmation = match execution.confirmation.as_ref() {
|
|
std::option::Option::Some(value)
|
|
if matches!(
|
|
value.status,
|
|
kb_lib::ExApiExecutionConfirmationStatus::Confirmed
|
|
| kb_lib::ExApiExecutionConfirmationStatus::Finalized
|
|
) =>
|
|
{
|
|
value
|
|
},
|
|
std::option::Option::Some(value) => {
|
|
return std::result::Result::Err(kb_core::Error::new(
|
|
"token_2022_metadata_campaign_step_not_confirmed",
|
|
format!("campaign step {step_id} stopped at {:?}", value.status),
|
|
));
|
|
},
|
|
std::option::Option::None => {
|
|
return std::result::Result::Err(kb_core::Error::new(
|
|
"token_2022_metadata_campaign_confirmation_missing",
|
|
format!("campaign step {step_id} has no confirmation evidence"),
|
|
));
|
|
},
|
|
};
|
|
let diagnostic = match execution.post_execution.as_ref() {
|
|
std::option::Option::Some(value) => value,
|
|
std::option::Option::None => {
|
|
return std::result::Result::Err(kb_core::Error::new(
|
|
"token_2022_metadata_campaign_post_execution_missing",
|
|
format!("campaign step {step_id} has no post-execution diagnostic"),
|
|
));
|
|
},
|
|
};
|
|
if !execution.simulation.success
|
|
|| !diagnostic.canonical_inserted
|
|
|| !diagnostic.core_extracted
|
|
|| !diagnostic.decode_replayed
|
|
{
|
|
return std::result::Result::Err(kb_core::Error::new(
|
|
"token_2022_metadata_campaign_replay_incomplete",
|
|
format!(
|
|
"campaign step {step_id} did not complete simulation and canonical decode replay"
|
|
),
|
|
));
|
|
}
|
|
let idempotence_replay = match execution.idempotence_replay.as_ref() {
|
|
std::option::Option::Some(value) => value,
|
|
std::option::Option::None => {
|
|
return std::result::Result::Err(kb_core::Error::new(
|
|
"token_2022_metadata_campaign_idempotence_missing",
|
|
format!("campaign step {step_id} has no idempotence replay evidence"),
|
|
));
|
|
},
|
|
};
|
|
if idempotence_replay.failed_inputs != 0
|
|
|| idempotence_replay.processing_error_inputs != 0
|
|
|| idempotence_replay.processors.iter().any(|processor| {
|
|
return processor.failed != 0
|
|
|| processor.processing_errors != 0
|
|
|| processor.materialized_outputs != 0
|
|
|| processor.materialization_refused != 0;
|
|
})
|
|
{
|
|
return std::result::Result::Err(kb_core::Error::new(
|
|
"token_2022_metadata_campaign_idempotence_failed",
|
|
format!("campaign step {step_id} did not produce a clean second replay"),
|
|
));
|
|
}
|
|
if !execution.plan.policy.post_execution_validation.materialization_required
|
|
&& operation_code != kb_lib::EX_SPL_TOKEN_2022_EMIT_TOKEN_METADATA_OPERATION
|
|
{
|
|
return std::result::Result::Err(kb_core::Error::new(
|
|
"token_2022_metadata_campaign_materialization_policy_invalid",
|
|
format!("campaign step {step_id} unexpectedly disabled materialization"),
|
|
));
|
|
}
|
|
if execution.plan.policy.post_execution_validation.materialization_required
|
|
&& execution.materializations.is_empty()
|
|
{
|
|
return std::result::Result::Err(kb_core::Error::new(
|
|
"token_2022_metadata_campaign_materialization_missing",
|
|
format!("campaign step {step_id} produced no metadata materialization"),
|
|
));
|
|
}
|
|
let stateful_snapshot = match kb_pipeline::read_token_2022_stateful_snapshot(
|
|
http_pool,
|
|
&kb_pipeline::Token2022StatefulReadRequest {
|
|
query_role: options.query_role.clone(),
|
|
account: fixture.mint.clone(),
|
|
kind: kb_lib::DcToken2022StateKind::Mint,
|
|
min_context_slot: confirmation.slot,
|
|
max_data_bytes: kb_pipeline::MAX_TOKEN_2022_STATEFUL_ACCOUNT_BYTES,
|
|
context: kb_pipeline::Token2022StatefulContext::default(),
|
|
},
|
|
)
|
|
.await
|
|
{
|
|
std::result::Result::Ok(value) => value,
|
|
std::result::Result::Err(error) => return std::result::Result::Err(error),
|
|
};
|
|
let emit_evidence = match &operation {
|
|
kb_lib::ExSplToken2022Operation::Instruction { value } => match value.as_ref() {
|
|
kb_lib::ExSplTokenSingleOperation::EmitTokenMetadata { start, end, .. } => {
|
|
match kb_pipeline::inspect_token_2022_metadata_emit_simulation(
|
|
&execution.simulation,
|
|
*start,
|
|
*end,
|
|
) {
|
|
std::result::Result::Ok(value) => std::option::Option::Some(value),
|
|
std::result::Result::Err(error) => return std::result::Result::Err(error),
|
|
}
|
|
},
|
|
_ => std::option::Option::None,
|
|
},
|
|
kb_lib::ExSplToken2022Operation::Batch { instructions: _ } => std::option::Option::None,
|
|
};
|
|
let postcondition = campaign_postcondition(
|
|
index,
|
|
&fixture,
|
|
&stateful_snapshot.snapshot,
|
|
emit_evidence.as_ref(),
|
|
);
|
|
if postcondition.status != kb_pipeline::Token2022ExecutionPostconditionStatus::Confirmed {
|
|
return std::result::Result::Err(kb_core::Error::new(
|
|
"token_2022_metadata_campaign_postcondition_failed",
|
|
format!("campaign step {step_id}: {}", postcondition.diagnostic),
|
|
));
|
|
}
|
|
steps.push(crate::DevnetToken2022MetadataCampaignStepSummary {
|
|
step_id: step_id.to_string(),
|
|
operation_code,
|
|
execution,
|
|
stateful_snapshot,
|
|
emit_evidence,
|
|
postcondition,
|
|
});
|
|
}
|
|
return std::result::Result::Ok(crate::DevnetToken2022MetadataCampaignSummary {
|
|
fixture,
|
|
steps,
|
|
});
|
|
}
|
|
|
|
fn campaign_operations(
|
|
fixture: &crate::Token2022MetadataFixturePreparationSummary,
|
|
) -> std::vec::Vec<(&'static str, kb_lib::ExSplToken2022Operation)> {
|
|
let metadata = fixture.mint.clone();
|
|
let authority = fixture.initial_authority.clone();
|
|
return std::vec![
|
|
(
|
|
"token_2022_metadata_initialize",
|
|
instruction(kb_lib::ExSplTokenSingleOperation::InitializeTokenMetadata {
|
|
metadata: metadata.clone(),
|
|
update_authority: authority.clone(),
|
|
mint: metadata.clone(),
|
|
mint_authority: authority.clone(),
|
|
name: crate::TOKEN_2022_METADATA_CAMPAIGN_NAME.to_string(),
|
|
symbol: crate::TOKEN_2022_METADATA_CAMPAIGN_SYMBOL.to_string(),
|
|
uri: crate::TOKEN_2022_METADATA_CAMPAIGN_URI.to_string(),
|
|
}),
|
|
),
|
|
(
|
|
"token_2022_metadata_update_field",
|
|
instruction(kb_lib::ExSplTokenSingleOperation::UpdateTokenMetadataField {
|
|
metadata: metadata.clone(),
|
|
update_authority: authority.clone(),
|
|
field: kb_lib::ExSplTokenMetadataField::Key(
|
|
crate::TOKEN_2022_METADATA_CAMPAIGN_KEY.to_string(),
|
|
),
|
|
value: crate::TOKEN_2022_METADATA_CAMPAIGN_VALUE.to_string(),
|
|
}),
|
|
),
|
|
(
|
|
"token_2022_metadata_emit",
|
|
instruction(kb_lib::ExSplTokenSingleOperation::EmitTokenMetadata {
|
|
metadata: metadata.clone(),
|
|
start: std::option::Option::None,
|
|
end: std::option::Option::None,
|
|
}),
|
|
),
|
|
(
|
|
"token_2022_metadata_remove_key",
|
|
instruction(kb_lib::ExSplTokenSingleOperation::RemoveTokenMetadataKey {
|
|
metadata: metadata.clone(),
|
|
update_authority: authority.clone(),
|
|
key: crate::TOKEN_2022_METADATA_CAMPAIGN_KEY.to_string(),
|
|
idempotent: false,
|
|
}),
|
|
),
|
|
(
|
|
"token_2022_metadata_update_authority",
|
|
instruction(kb_lib::ExSplTokenSingleOperation::UpdateTokenMetadataAuthority {
|
|
metadata,
|
|
current_authority: authority,
|
|
new_authority: std::option::Option::Some(fixture.final_authority.clone()),
|
|
}),
|
|
),
|
|
];
|
|
}
|
|
|
|
fn instruction(value: kb_lib::ExSplTokenSingleOperation) -> kb_lib::ExSplToken2022Operation {
|
|
return kb_lib::ExSplToken2022Operation::Instruction { value: std::boxed::Box::new(value) };
|
|
}
|
|
|
|
fn campaign_postcondition(
|
|
step_index: usize,
|
|
fixture: &crate::Token2022MetadataFixturePreparationSummary,
|
|
snapshot: &kb_pipeline::Token2022StatefulSnapshotBundle,
|
|
emit_evidence: std::option::Option<&kb_pipeline::Token2022MetadataEmitEvidence>,
|
|
) -> kb_pipeline::Token2022ExecutionPostcondition {
|
|
if step_index == 4 {
|
|
return kb_pipeline::inspect_token_2022_metadata_authority_postcondition(
|
|
&fixture.mint,
|
|
std::option::Option::Some(&fixture.final_authority),
|
|
snapshot,
|
|
);
|
|
}
|
|
let value_fields = metadata_value_fields(snapshot);
|
|
let confirmed = match step_index {
|
|
0 => value_fields.is_some_and(|value| {
|
|
return required_fields_match(value, fixture.initial_authority.0.as_str())
|
|
&& !contains_campaign_pair(value);
|
|
}),
|
|
1 => value_fields.is_some_and(|value| {
|
|
return required_fields_match(value, fixture.initial_authority.0.as_str())
|
|
&& contains_campaign_pair(value);
|
|
}),
|
|
2 => {
|
|
let snapshot_matches = value_fields.is_some_and(|value| {
|
|
return required_fields_match(value, fixture.initial_authority.0.as_str())
|
|
&& contains_campaign_pair(value);
|
|
});
|
|
let emit_matches = emit_evidence
|
|
.and_then(|evidence| return evidence.decoded_metadata.as_ref())
|
|
.is_some_and(|value| {
|
|
return required_fields_match(value, fixture.initial_authority.0.as_str())
|
|
&& contains_campaign_pair(value);
|
|
});
|
|
snapshot_matches && emit_matches
|
|
},
|
|
3 => value_fields.is_some_and(|value| {
|
|
return required_fields_match(value, fixture.initial_authority.0.as_str())
|
|
&& !contains_campaign_pair(value);
|
|
}),
|
|
_ => false,
|
|
};
|
|
return kb_pipeline::Token2022ExecutionPostcondition {
|
|
role: "metadata".to_string(),
|
|
account: fixture.mint.clone(),
|
|
status: if confirmed {
|
|
kb_pipeline::Token2022ExecutionPostconditionStatus::Confirmed
|
|
} else {
|
|
kb_pipeline::Token2022ExecutionPostconditionStatus::Contradicted
|
|
},
|
|
diagnostic: if confirmed {
|
|
format!("Token-2022 metadata campaign postcondition {step_index} is confirmed")
|
|
} else {
|
|
format!("Token-2022 metadata campaign postcondition {step_index} is contradicted")
|
|
},
|
|
};
|
|
}
|
|
|
|
fn metadata_value_fields(
|
|
snapshot: &kb_pipeline::Token2022StatefulSnapshotBundle,
|
|
) -> std::option::Option<&serde_json::Value> {
|
|
return snapshot.outputs.iter().find_map(|output| {
|
|
if output.family != kb_lib::MdMaterializedEventFamily::Metadata
|
|
|| output.payload_json.get("projectionKind").and_then(serde_json::Value::as_str)
|
|
!= std::option::Option::Some("token_metadata")
|
|
{
|
|
return std::option::Option::None;
|
|
}
|
|
return output.payload_json.get("valueFields");
|
|
});
|
|
}
|
|
|
|
fn required_fields_match(value: &serde_json::Value, authority: &str) -> bool {
|
|
return value.get("updateAuthority").and_then(serde_json::Value::as_str)
|
|
== std::option::Option::Some(authority)
|
|
&& value.get("name").and_then(serde_json::Value::as_str)
|
|
== std::option::Option::Some(crate::TOKEN_2022_METADATA_CAMPAIGN_NAME)
|
|
&& value.get("symbol").and_then(serde_json::Value::as_str)
|
|
== std::option::Option::Some(crate::TOKEN_2022_METADATA_CAMPAIGN_SYMBOL)
|
|
&& value.get("uri").and_then(serde_json::Value::as_str)
|
|
== std::option::Option::Some(crate::TOKEN_2022_METADATA_CAMPAIGN_URI);
|
|
}
|
|
|
|
fn contains_campaign_pair(value: &serde_json::Value) -> bool {
|
|
return value
|
|
.get("additionalMetadata")
|
|
.and_then(serde_json::Value::as_array)
|
|
.is_some_and(|pairs| {
|
|
return pairs.iter().any(|pair| {
|
|
return pair.get("key").and_then(serde_json::Value::as_str)
|
|
== std::option::Option::Some(crate::TOKEN_2022_METADATA_CAMPAIGN_KEY)
|
|
&& pair.get("value").and_then(serde_json::Value::as_str)
|
|
== std::option::Option::Some(crate::TOKEN_2022_METADATA_CAMPAIGN_VALUE);
|
|
});
|
|
});
|
|
}
|
|
|
|
#[cfg(test)]
|
|
mod tests {
|
|
fn pubkey(byte: u8) -> kb_lib::MdPubkey {
|
|
return kb_lib::MdPubkey(bs58::encode([byte; 32]).into_string());
|
|
}
|
|
|
|
fn fixture() -> crate::Token2022MetadataFixturePreparationSummary {
|
|
return crate::Token2022MetadataFixturePreparationSummary {
|
|
mint_keypair_path: std::path::PathBuf::from("mint.json"),
|
|
final_authority_keypair_path: std::path::PathBuf::from("authority.json"),
|
|
mint: pubkey(1),
|
|
initial_authority: pubkey(2),
|
|
final_authority: pubkey(3),
|
|
mint_space: 234,
|
|
rent_budget_space: 512,
|
|
rent_reserve_lamports: 1,
|
|
preparation_signature: "signature".to_string(),
|
|
initial_snapshot: kb_pipeline::Token2022StatefulReadResult {
|
|
commitment: "confirmed".to_string(),
|
|
context_slot: 1,
|
|
snapshot: kb_pipeline::Token2022StatefulSnapshotBundle {
|
|
account_key: pubkey(1).0,
|
|
slot: 1,
|
|
state_kind: "mint".to_string(),
|
|
extension_names: vec!["metadata_pointer".to_string()],
|
|
outputs: std::vec::Vec::new(),
|
|
},
|
|
},
|
|
};
|
|
}
|
|
|
|
#[test]
|
|
fn campaign_contract_covers_five_interface_operations_once_in_order() {
|
|
let operations = super::campaign_operations(&fixture());
|
|
assert_eq!(
|
|
operations
|
|
.iter()
|
|
.map(|(_, operation)| return operation.operation_code())
|
|
.collect::<std::vec::Vec<_>>(),
|
|
vec![
|
|
kb_lib::EX_SPL_TOKEN_2022_INITIALIZE_TOKEN_METADATA_OPERATION,
|
|
kb_lib::EX_SPL_TOKEN_2022_UPDATE_TOKEN_METADATA_FIELD_OPERATION,
|
|
kb_lib::EX_SPL_TOKEN_2022_EMIT_TOKEN_METADATA_OPERATION,
|
|
kb_lib::EX_SPL_TOKEN_2022_REMOVE_TOKEN_METADATA_KEY_OPERATION,
|
|
kb_lib::EX_SPL_TOKEN_2022_UPDATE_TOKEN_METADATA_AUTHORITY_OPERATION,
|
|
]
|
|
);
|
|
assert_eq!(
|
|
crate::token_2022_metadata_campaign_operation_names(),
|
|
&[
|
|
"initialize_token_metadata",
|
|
"update_token_metadata_field",
|
|
"emit_token_metadata",
|
|
"remove_token_metadata_key",
|
|
"update_token_metadata_authority",
|
|
]
|
|
);
|
|
}
|
|
|
|
#[test]
|
|
fn campaign_pair_detection_is_exact_and_removal_is_observable() {
|
|
let with_pair = serde_json::json!({
|
|
"additionalMetadata": [{
|
|
"key": crate::TOKEN_2022_METADATA_CAMPAIGN_KEY,
|
|
"value": crate::TOKEN_2022_METADATA_CAMPAIGN_VALUE
|
|
}]
|
|
});
|
|
let without_pair = serde_json::json!({"additionalMetadata": []});
|
|
assert!(super::contains_campaign_pair(&with_pair));
|
|
assert!(!super::contains_campaign_pair(&without_pair));
|
|
}
|
|
|
|
#[tokio::test]
|
|
async fn optional_devnet_token_2022_metadata_campaign_from_env() {
|
|
if std::env::var("KB_DEVNET_TOKEN_2022_METADATA_CAMPAIGN_TEST").ok().as_deref()
|
|
!= std::option::Option::Some("1")
|
|
{
|
|
return;
|
|
}
|
|
if std::env::var("KB_DEVNET_TOKEN_2022_METADATA_OPERATOR_CONFIRMED")
|
|
.ok()
|
|
.as_deref()
|
|
!= std::option::Option::Some("1")
|
|
{
|
|
panic!(
|
|
"KB_DEVNET_TOKEN_2022_METADATA_OPERATOR_CONFIRMED=1 is required for the spending campaign"
|
|
);
|
|
}
|
|
let workspace_root = match std::path::Path::new(env!("CARGO_MANIFEST_DIR")).parent() {
|
|
std::option::Option::Some(value) => value,
|
|
std::option::Option::None => panic!("workspace root cannot be resolved"),
|
|
};
|
|
let config_path = match std::env::var("KB_DEVNET_CONFIG_PATH") {
|
|
std::result::Result::Ok(value) => {
|
|
let path = std::path::PathBuf::from(value);
|
|
if path.is_absolute() { path } else { workspace_root.join(path) }
|
|
},
|
|
std::result::Result::Err(_) => workspace_root.join("config/example.config.json"),
|
|
};
|
|
let config = kb_config::read_config_json_file_with_environment(
|
|
config_path.as_path(),
|
|
workspace_root,
|
|
)
|
|
.unwrap_or_else(|error| panic!("Devnet configuration loading failed: {error}"));
|
|
let profile_name = std::env::var("KB_DEVNET_PROFILE").ok();
|
|
let mut profile = crate::resolve_demo_devnet_profile(&config, profile_name.as_deref())
|
|
.unwrap_or_else(|error| panic!("Devnet profile resolution failed: {error}"));
|
|
let database_url = std::env::var("KB_POSTGRES_TEST_URL")
|
|
.unwrap_or_else(|error| panic!("KB_POSTGRES_TEST_URL is required: {error}"));
|
|
profile.database.backend = "postgres".to_string();
|
|
profile.database.postgres.url = database_url;
|
|
let pool = kb_onchain_transport::HttpEndpointPool::from_profile(&profile)
|
|
.unwrap_or_else(|error| panic!("HTTP pool creation failed: {error}"));
|
|
let store_options = kb_store::PostgresStoreOptions::new(
|
|
profile.database.postgres.url.clone(),
|
|
profile.database.postgres.max_connections,
|
|
profile.database.postgres.connect_timeout_ms,
|
|
false,
|
|
)
|
|
.unwrap_or_else(|error| panic!("PostgreSQL options failed: {error}"));
|
|
let store = kb_store::PostgresStore::connect(store_options)
|
|
.await
|
|
.unwrap_or_else(|error| panic!("PostgreSQL connection failed: {error}"));
|
|
if let std::result::Result::Err(error) = store.initialize_store_schema().await {
|
|
panic!("PostgreSQL schema initialization failed: {error}");
|
|
}
|
|
let configured_wallet_dir = std::path::PathBuf::from(profile.wallet.wallet_dir.as_str());
|
|
let wallet_dir = if configured_wallet_dir.is_absolute() {
|
|
configured_wallet_dir
|
|
} else {
|
|
workspace_root.join(configured_wallet_dir)
|
|
};
|
|
let mut options = crate::Token2022MetadataFixturePreparationOptions::new(wallet_dir);
|
|
options.operator_confirmed = true;
|
|
let summary = crate::execute_devnet_token_2022_metadata_campaign(
|
|
&pool,
|
|
&store,
|
|
&profile,
|
|
workspace_root,
|
|
&options,
|
|
&crate::NoopSolanaExecutionObserver,
|
|
)
|
|
.await
|
|
.unwrap_or_else(|error| {
|
|
panic!("Token-2022 Token Metadata Devnet campaign failed: {error}")
|
|
});
|
|
assert_eq!(summary.steps.len(), 5);
|
|
assert!(summary.steps.iter().all(|step| {
|
|
return step.postcondition.status
|
|
== kb_pipeline::Token2022ExecutionPostconditionStatus::Confirmed;
|
|
}));
|
|
println!(
|
|
"TOKEN_2022_METADATA_FIXTURE mint={} preparation_signature={} initial_authority={} final_authority={}",
|
|
summary.fixture.mint.0,
|
|
summary.fixture.preparation_signature,
|
|
summary.fixture.initial_authority.0,
|
|
summary.fixture.final_authority.0,
|
|
);
|
|
for step in &summary.steps {
|
|
let signature = step
|
|
.execution
|
|
.confirmation
|
|
.as_ref()
|
|
.map(|value| return value.signature.0.as_str())
|
|
.unwrap_or("missing");
|
|
let slot = step.execution.confirmation.as_ref().and_then(|value| return value.slot);
|
|
println!(
|
|
"TOKEN_2022_METADATA_STEP id={} operation={} signature={} slot={:?} postcondition={:?} materializations={} emit_bytes={}",
|
|
step.step_id,
|
|
step.operation_code,
|
|
signature,
|
|
slot,
|
|
step.postcondition.status,
|
|
step.execution.materializations.len(),
|
|
step.emit_evidence.as_ref().map(|value| return value.data.len()).unwrap_or(0),
|
|
);
|
|
}
|
|
}
|
|
}
|