0.5.1-pre.002
This commit is contained in:
@@ -0,0 +1,661 @@
|
||||
// file: ks-pipeline-demo-scenarios/src/spl/token_2022/metadata/fixture.rs
|
||||
// version: 4
|
||||
|
||||
//! Native Token-2022 mint preparation for the embedded Token Metadata Devnet campaign.
|
||||
|
||||
/// Stable metadata name used by the `0.4.8-pre.012` campaign.
|
||||
pub const TOKEN_2022_METADATA_CAMPAIGN_NAME: &str = "Khadhroony Token-2022 Metadata";
|
||||
/// Stable metadata symbol used by the `0.4.8-pre.012` campaign.
|
||||
pub const TOKEN_2022_METADATA_CAMPAIGN_SYMBOL: &str = "KBTM";
|
||||
/// Opaque URI stored on-chain by the `0.4.8-pre.012` campaign.
|
||||
pub const TOKEN_2022_METADATA_CAMPAIGN_URI: &str =
|
||||
"https://example.invalid/khadhroony/token-2022-metadata.json";
|
||||
/// Additional metadata key created and removed by the campaign.
|
||||
pub const TOKEN_2022_METADATA_CAMPAIGN_KEY: &str = "campaign";
|
||||
/// Additional metadata value created and removed by the campaign.
|
||||
pub const TOKEN_2022_METADATA_CAMPAIGN_VALUE: &str = "0.4.8-pre.012";
|
||||
|
||||
const TOKEN_2022_TLV_HEADER_BYTES: usize = 4;
|
||||
|
||||
/// Options used to prepare one fresh Token-2022 mint for the metadata campaign.
|
||||
#[derive(Clone, Debug, Eq, PartialEq)]
|
||||
pub struct Token2022MetadataFixturePreparationOptions {
|
||||
/// Endpoint role used for state, rent, balance, blockhash and fee calls.
|
||||
pub query_role: std::string::String,
|
||||
/// Endpoint role used for simulation, submission and confirmation polling.
|
||||
pub transaction_role: std::string::String,
|
||||
/// Directory receiving persistent fixture keypairs.
|
||||
pub wallet_dir: std::path::PathBuf,
|
||||
/// Explicit operator authorization for the lamport-spending fixture transaction.
|
||||
pub operator_confirmed: bool,
|
||||
}
|
||||
|
||||
impl crate::Token2022MetadataFixturePreparationOptions {
|
||||
/// Creates conservative defaults matching Devnet execution profiles.
|
||||
pub fn new(wallet_dir: std::path::PathBuf) -> Self {
|
||||
return Self {
|
||||
query_role: "http_queries".to_string(),
|
||||
transaction_role: "http_transactions".to_string(),
|
||||
wallet_dir,
|
||||
operator_confirmed: false,
|
||||
};
|
||||
}
|
||||
}
|
||||
|
||||
/// Fresh Token-2022 mint and authority identities used by the metadata campaign.
|
||||
#[derive(Clone, Debug, PartialEq, serde::Serialize)]
|
||||
pub struct Token2022MetadataFixturePreparationSummary {
|
||||
/// Persistent fixture mint keypair path.
|
||||
pub mint_keypair_path: std::path::PathBuf,
|
||||
/// Persistent final metadata-authority keypair path.
|
||||
pub final_authority_keypair_path: std::path::PathBuf,
|
||||
/// Fresh Token-2022 mint address. Embedded metadata uses this same account.
|
||||
pub mint: ks_lib::MdPubkey,
|
||||
/// Profile wallet used as mint and initial metadata update authority.
|
||||
pub initial_authority: ks_lib::MdPubkey,
|
||||
/// Fresh public key installed by the final `UpdateAuthority` campaign step.
|
||||
pub final_authority: ks_lib::MdPubkey,
|
||||
/// Initial mint account bytes containing only the fixed Metadata Pointer extension.
|
||||
pub mint_space: usize,
|
||||
/// Account-size budget used to pre-fund rent for the largest campaign metadata state.
|
||||
pub rent_budget_space: usize,
|
||||
/// Rent reserve deposited when the mint is created.
|
||||
pub rent_reserve_lamports: u64,
|
||||
/// Confirmed signature that created the Token-2022 mint fixture.
|
||||
pub preparation_signature: std::string::String,
|
||||
/// Authoritative snapshot after mint creation and before Token Metadata initialization.
|
||||
pub initial_snapshot: ks_pipeline::Token2022StatefulReadResult,
|
||||
}
|
||||
|
||||
/// Creates a fresh Token-2022 mint with Metadata Pointer initialized to the mint itself.
|
||||
///
|
||||
/// The mint account is created with the fixed Metadata Pointer size but is pre-funded
|
||||
/// for the largest state reached by this campaign so Token Metadata reallocations
|
||||
/// remain rent-exempt without introducing an additional payer instruction.
|
||||
pub async fn prepare_token_2022_metadata_fixture(
|
||||
http_pool: &ks_onchain_transport::HttpEndpointPool,
|
||||
profile: &ks_config::ProfileConfig,
|
||||
workspace_root: &std::path::Path,
|
||||
options: &crate::Token2022MetadataFixturePreparationOptions,
|
||||
) -> ks_core::Result<crate::Token2022MetadataFixturePreparationSummary> {
|
||||
if options.query_role.trim().is_empty() || options.transaction_role.trim().is_empty() {
|
||||
return std::result::Result::Err(ks_core::Error::config(
|
||||
"Token-2022 metadata fixture endpoint roles must not be empty",
|
||||
));
|
||||
}
|
||||
if profile.wallet.cluster != "devnet"
|
||||
|| !profile.wallet.temporary_wallet_enabled
|
||||
|| !profile.wallet.temporary_wallet_persist
|
||||
{
|
||||
return std::result::Result::Err(ks_core::Error::config(
|
||||
"Token-2022 metadata fixture requires a persistent Devnet wallet profile",
|
||||
));
|
||||
}
|
||||
if !profile.wallet.devnet_send_enabled || !options.operator_confirmed {
|
||||
return std::result::Result::Err(ks_core::Error::config(
|
||||
"Token-2022 metadata fixture requires enabled Devnet submission and explicit operator confirmation",
|
||||
));
|
||||
}
|
||||
let operator = match crate::load_profile_wallet(profile, workspace_root).await {
|
||||
std::result::Result::Ok(value) => value,
|
||||
std::result::Result::Err(error) => return std::result::Result::Err(error),
|
||||
};
|
||||
let fixture_store = match ks_wallet::TemporaryWalletStore::new(
|
||||
options.wallet_dir.join("token_2022_metadata_validation"),
|
||||
) {
|
||||
std::result::Result::Ok(value) => value,
|
||||
std::result::Result::Err(error) => return std::result::Result::Err(error),
|
||||
};
|
||||
let fixture_epoch = match std::time::SystemTime::now().duration_since(std::time::UNIX_EPOCH) {
|
||||
std::result::Result::Ok(value) => value.as_millis(),
|
||||
std::result::Result::Err(error) => {
|
||||
return std::result::Result::Err(ks_core::Error::config(format!(
|
||||
"unable to create a unique Token-2022 metadata fixture identifier: {error}"
|
||||
)));
|
||||
},
|
||||
};
|
||||
let mint_alias = match ks_wallet::WalletAlias::parse(
|
||||
format!("token-2022-metadata-mint-{fixture_epoch}").as_str(),
|
||||
) {
|
||||
std::result::Result::Ok(value) => value,
|
||||
std::result::Result::Err(error) => return std::result::Result::Err(error),
|
||||
};
|
||||
let final_authority_alias = match ks_wallet::WalletAlias::parse(
|
||||
format!("token-2022-metadata-authority-{fixture_epoch}").as_str(),
|
||||
) {
|
||||
std::result::Result::Ok(value) => value,
|
||||
std::result::Result::Err(error) => return std::result::Result::Err(error),
|
||||
};
|
||||
let mint_wallet = match fixture_store.load_or_create(mint_alias.clone()).await {
|
||||
std::result::Result::Ok(value) => value,
|
||||
std::result::Result::Err(error) => return std::result::Result::Err(error),
|
||||
};
|
||||
let final_authority_wallet =
|
||||
match fixture_store.load_or_create(final_authority_alias.clone()).await {
|
||||
std::result::Result::Ok(value) => value,
|
||||
std::result::Result::Err(error) => return std::result::Result::Err(error),
|
||||
};
|
||||
let mint = ks_lib::MdPubkey(mint_wallet.public_key());
|
||||
let initial_authority = ks_lib::MdPubkey(operator.public_key());
|
||||
let final_authority = ks_lib::MdPubkey(final_authority_wallet.public_key());
|
||||
let mint_space = match token_2022_metadata_pointer_mint_space() {
|
||||
std::result::Result::Ok(value) => value,
|
||||
std::result::Result::Err(error) => return std::result::Result::Err(error),
|
||||
};
|
||||
let rent_budget_space = metadata_campaign_rent_budget_space(mint_space);
|
||||
let preparation_signature = match create_native_token_2022_metadata_mint(
|
||||
http_pool,
|
||||
profile,
|
||||
options,
|
||||
&operator,
|
||||
&mint_wallet,
|
||||
mint_space,
|
||||
rent_budget_space,
|
||||
)
|
||||
.await
|
||||
{
|
||||
std::result::Result::Ok(value) => value,
|
||||
std::result::Result::Err(error) => return std::result::Result::Err(error),
|
||||
};
|
||||
let initial_snapshot = match ks_pipeline::read_token_2022_stateful_snapshot(
|
||||
http_pool,
|
||||
&ks_pipeline::Token2022StatefulReadRequest {
|
||||
query_role: options.query_role.clone(),
|
||||
account: mint.clone(),
|
||||
kind: ks_lib::DcToken2022StateKind::Mint,
|
||||
min_context_slot: std::option::Option::None,
|
||||
max_data_bytes: ks_pipeline::MAX_TOKEN_2022_STATEFUL_ACCOUNT_BYTES,
|
||||
context: ks_pipeline::Token2022StatefulContext::default(),
|
||||
},
|
||||
)
|
||||
.await
|
||||
{
|
||||
std::result::Result::Ok(value) => value,
|
||||
std::result::Result::Err(error) => return std::result::Result::Err(error),
|
||||
};
|
||||
if !initial_snapshot
|
||||
.snapshot
|
||||
.extension_names
|
||||
.iter()
|
||||
.any(|value| return value == "metadata_pointer")
|
||||
|| initial_snapshot
|
||||
.snapshot
|
||||
.extension_names
|
||||
.iter()
|
||||
.any(|value| return value == "token_metadata")
|
||||
|| !metadata_pointer_snapshot_matches(&initial_snapshot.snapshot, &mint, &initial_authority)
|
||||
{
|
||||
return std::result::Result::Err(ks_core::Error::new(
|
||||
"token_2022_metadata_fixture_state_invalid",
|
||||
"fresh metadata fixture must contain the expected self-referential Metadata Pointer and no Token Metadata extension",
|
||||
));
|
||||
}
|
||||
let rent = match http_pool
|
||||
.get_minimum_balance_for_rent_exemption_for_role(
|
||||
options.query_role.as_str(),
|
||||
match u64::try_from(rent_budget_space) {
|
||||
std::result::Result::Ok(value) => value,
|
||||
std::result::Result::Err(error) => {
|
||||
return std::result::Result::Err(ks_core::Error::config(format!(
|
||||
"Token-2022 metadata rent budget does not fit u64: {error}"
|
||||
)));
|
||||
},
|
||||
},
|
||||
&ks_onchain_transport::GetMinimumBalanceForRentExemptionConfig::confirmed(),
|
||||
)
|
||||
.await
|
||||
{
|
||||
std::result::Result::Ok(value) => value,
|
||||
std::result::Result::Err(error) => return std::result::Result::Err(error),
|
||||
};
|
||||
return std::result::Result::Ok(crate::Token2022MetadataFixturePreparationSummary {
|
||||
mint_keypair_path: fixture_store.wallet_path(&mint_alias),
|
||||
final_authority_keypair_path: fixture_store.wallet_path(&final_authority_alias),
|
||||
mint,
|
||||
initial_authority,
|
||||
final_authority,
|
||||
mint_space,
|
||||
rent_budget_space,
|
||||
rent_reserve_lamports: rent.minimum_balance_lamports,
|
||||
preparation_signature,
|
||||
initial_snapshot,
|
||||
});
|
||||
}
|
||||
|
||||
fn metadata_pointer_snapshot_matches(
|
||||
snapshot: &ks_pipeline::Token2022StatefulSnapshotBundle,
|
||||
mint: &ks_lib::MdPubkey,
|
||||
authority: &ks_lib::MdPubkey,
|
||||
) -> bool {
|
||||
return snapshot.outputs.iter().any(|output| {
|
||||
return output.family == ks_lib::MdMaterializedEventFamily::Admin
|
||||
&& output.payload_json.get("extensionName").and_then(serde_json::Value::as_str)
|
||||
== std::option::Option::Some("metadata_pointer")
|
||||
&& output
|
||||
.payload_json
|
||||
.get("valueFields")
|
||||
.and_then(|value| return value.get("address"))
|
||||
.and_then(serde_json::Value::as_str)
|
||||
== std::option::Option::Some(mint.0.as_str())
|
||||
&& output
|
||||
.payload_json
|
||||
.get("valueFields")
|
||||
.and_then(|value| return value.get("authority"))
|
||||
.and_then(serde_json::Value::as_str)
|
||||
== std::option::Option::Some(authority.0.as_str());
|
||||
});
|
||||
}
|
||||
|
||||
fn token_2022_metadata_pointer_mint_space() -> ks_core::Result<usize> {
|
||||
return match spl_token_2022_interface::extension::ExtensionType::try_calculate_account_len::<
|
||||
spl_token_2022_interface::pod::PodMint,
|
||||
>(&[spl_token_2022_interface::extension::ExtensionType::MetadataPointer])
|
||||
{
|
||||
std::result::Result::Ok(value) => std::result::Result::Ok(value),
|
||||
std::result::Result::Err(error) => std::result::Result::Err(ks_core::Error::new(
|
||||
"token_2022_metadata_fixture_space_failed",
|
||||
format!("unable to calculate Metadata Pointer mint space: {error}"),
|
||||
)),
|
||||
};
|
||||
}
|
||||
|
||||
fn metadata_campaign_rent_budget_space(mint_space: usize) -> usize {
|
||||
return mint_space
|
||||
.saturating_add(TOKEN_2022_TLV_HEADER_BYTES)
|
||||
.saturating_add(metadata_campaign_max_value_bytes());
|
||||
}
|
||||
|
||||
fn metadata_campaign_max_value_bytes() -> usize {
|
||||
return 64_usize
|
||||
.saturating_add(4)
|
||||
.saturating_add(TOKEN_2022_METADATA_CAMPAIGN_NAME.len())
|
||||
.saturating_add(4)
|
||||
.saturating_add(TOKEN_2022_METADATA_CAMPAIGN_SYMBOL.len())
|
||||
.saturating_add(4)
|
||||
.saturating_add(TOKEN_2022_METADATA_CAMPAIGN_URI.len())
|
||||
.saturating_add(4)
|
||||
.saturating_add(4)
|
||||
.saturating_add(TOKEN_2022_METADATA_CAMPAIGN_KEY.len())
|
||||
.saturating_add(4)
|
||||
.saturating_add(TOKEN_2022_METADATA_CAMPAIGN_VALUE.len());
|
||||
}
|
||||
|
||||
#[allow(clippy::too_many_arguments)]
|
||||
async fn create_native_token_2022_metadata_mint(
|
||||
http_pool: &ks_onchain_transport::HttpEndpointPool,
|
||||
profile: &ks_config::ProfileConfig,
|
||||
options: &crate::Token2022MetadataFixturePreparationOptions,
|
||||
operator: &ks_wallet::TemporaryWallet,
|
||||
mint_wallet: &ks_wallet::TemporaryWallet,
|
||||
mint_space: usize,
|
||||
rent_budget_space: usize,
|
||||
) -> ks_core::Result<std::string::String> {
|
||||
let rent_space = match u64::try_from(rent_budget_space) {
|
||||
std::result::Result::Ok(value) => value,
|
||||
std::result::Result::Err(error) => {
|
||||
return std::result::Result::Err(ks_core::Error::config(format!(
|
||||
"Token-2022 metadata rent budget does not fit u64: {error}"
|
||||
)));
|
||||
},
|
||||
};
|
||||
let account_space = match u64::try_from(mint_space) {
|
||||
std::result::Result::Ok(value) => value,
|
||||
std::result::Result::Err(error) => {
|
||||
return std::result::Result::Err(ks_core::Error::config(format!(
|
||||
"Token-2022 Metadata Pointer mint space does not fit u64: {error}"
|
||||
)));
|
||||
},
|
||||
};
|
||||
let rent = match http_pool
|
||||
.get_minimum_balance_for_rent_exemption_for_role(
|
||||
options.query_role.as_str(),
|
||||
rent_space,
|
||||
&ks_onchain_transport::GetMinimumBalanceForRentExemptionConfig::confirmed(),
|
||||
)
|
||||
.await
|
||||
{
|
||||
std::result::Result::Ok(value) => value,
|
||||
std::result::Result::Err(error) => return std::result::Result::Err(error),
|
||||
};
|
||||
let payer = ks_lib::MdPubkey(operator.public_key());
|
||||
let mint = ks_lib::MdPubkey(mint_wallet.public_key());
|
||||
let policy =
|
||||
fixture_policy(profile, payer.clone(), mint.clone(), rent.minimum_balance_lamports);
|
||||
let system_intent = ks_lib::ExSolanaCoreExecutionIntent {
|
||||
intent_id: format!("token-2022-metadata-fixture-create-account-{}", mint.0),
|
||||
fee_payer: payer.clone(),
|
||||
policy: policy.clone(),
|
||||
operation: ks_lib::ExSolanaCoreOperation::SystemCreateAccount {
|
||||
from: payer.clone(),
|
||||
new_account: mint.clone(),
|
||||
lamports: rent.minimum_balance_lamports,
|
||||
space: account_space,
|
||||
owner: ks_lib::MdPubkey(ks_program_ids::SPL_TOKEN_2022_PROGRAM_ID.to_string()),
|
||||
},
|
||||
};
|
||||
let system_plan = match ks_lib::ExApiTypedInstructionExecutor::build_prepared_plan(
|
||||
&ks_lib::ExSolanaCoreExecutor,
|
||||
&system_intent,
|
||||
) {
|
||||
std::result::Result::Ok(value) => value,
|
||||
std::result::Result::Err(error) => return std::result::Result::Err(error),
|
||||
};
|
||||
let token_program = match <solana_pubkey::Pubkey as std::str::FromStr>::from_str(
|
||||
ks_program_ids::SPL_TOKEN_2022_PROGRAM_ID,
|
||||
) {
|
||||
std::result::Result::Ok(value) => value,
|
||||
std::result::Result::Err(error) => {
|
||||
return std::result::Result::Err(ks_core::Error::config(format!(
|
||||
"invalid Token-2022 Program ID: {error}"
|
||||
)));
|
||||
},
|
||||
};
|
||||
let mint_pubkey = match <solana_pubkey::Pubkey as std::str::FromStr>::from_str(mint.0.as_str())
|
||||
{
|
||||
std::result::Result::Ok(value) => value,
|
||||
std::result::Result::Err(error) => {
|
||||
return std::result::Result::Err(ks_core::Error::config(format!(
|
||||
"invalid Token-2022 metadata fixture mint: {error}"
|
||||
)));
|
||||
},
|
||||
};
|
||||
let authority_pubkey =
|
||||
match <solana_pubkey::Pubkey as std::str::FromStr>::from_str(payer.0.as_str()) {
|
||||
std::result::Result::Ok(value) => value,
|
||||
std::result::Result::Err(error) => {
|
||||
return std::result::Result::Err(ks_core::Error::config(format!(
|
||||
"invalid Token-2022 metadata fixture authority: {error}"
|
||||
)));
|
||||
},
|
||||
};
|
||||
let pointer_instruction =
|
||||
match spl_token_2022_interface::extension::metadata_pointer::instruction::initialize(
|
||||
&token_program,
|
||||
&mint_pubkey,
|
||||
std::option::Option::Some(authority_pubkey),
|
||||
std::option::Option::Some(mint_pubkey),
|
||||
) {
|
||||
std::result::Result::Ok(value) => value,
|
||||
std::result::Result::Err(error) => {
|
||||
return std::result::Result::Err(ks_core::Error::new(
|
||||
"token_2022_metadata_fixture_pointer_build_failed",
|
||||
error.to_string(),
|
||||
));
|
||||
},
|
||||
};
|
||||
let initialize_mint = match spl_token_2022_interface::instruction::initialize_mint2(
|
||||
&token_program,
|
||||
&mint_pubkey,
|
||||
&authority_pubkey,
|
||||
std::option::Option::Some(&authority_pubkey),
|
||||
0,
|
||||
) {
|
||||
std::result::Result::Ok(value) => value,
|
||||
std::result::Result::Err(error) => {
|
||||
return std::result::Result::Err(ks_core::Error::new(
|
||||
"token_2022_metadata_fixture_initialize_mint_build_failed",
|
||||
error.to_string(),
|
||||
));
|
||||
},
|
||||
};
|
||||
let plan = merge_fixture_instructions(
|
||||
system_plan,
|
||||
std::vec![
|
||||
planned_instruction("spl.token_2022.initialize_metadata_pointer", &pointer_instruction,),
|
||||
planned_instruction("spl.token_2022.initialize_mint2", &initialize_mint),
|
||||
],
|
||||
policy,
|
||||
rent.minimum_balance_lamports,
|
||||
);
|
||||
let plan_evaluation = match ks_lib::ExSafetyChecker.evaluate_prepared_plan(&plan) {
|
||||
std::result::Result::Ok(value) => value,
|
||||
std::result::Result::Err(error) => return std::result::Result::Err(error),
|
||||
};
|
||||
if plan_evaluation.decision == ks_lib::ExSafetyDecision::Deny {
|
||||
return std::result::Result::Err(ks_core::Error::new(
|
||||
"token_2022_metadata_fixture_plan_denied",
|
||||
crate::violation_message(plan_evaluation.violations.as_slice()),
|
||||
));
|
||||
}
|
||||
let latest_blockhash = match http_pool
|
||||
.get_latest_blockhash_for_role(
|
||||
options.query_role.as_str(),
|
||||
&ks_onchain_transport::GetLatestBlockhashConfig::confirmed(),
|
||||
)
|
||||
.await
|
||||
{
|
||||
std::result::Result::Ok(value) => value,
|
||||
std::result::Result::Err(error) => return std::result::Result::Err(error),
|
||||
};
|
||||
let unsigned = match ks_lib::executor_solana_build_legacy_transaction(
|
||||
&plan,
|
||||
latest_blockhash.blockhash.as_str(),
|
||||
) {
|
||||
std::result::Result::Ok(value) => value,
|
||||
std::result::Result::Err(error) => return std::result::Result::Err(error),
|
||||
};
|
||||
let fee = match http_pool
|
||||
.get_fee_for_message_for_role(
|
||||
options.query_role.as_str(),
|
||||
unsigned.message_base64().as_str(),
|
||||
&ks_onchain_transport::GetFeeForMessageConfig::new(
|
||||
ks_onchain_transport::RpcCommitmentLevel::Confirmed,
|
||||
std::option::Option::Some(latest_blockhash.context.slot),
|
||||
),
|
||||
)
|
||||
.await
|
||||
{
|
||||
std::result::Result::Ok(value) => value,
|
||||
std::result::Result::Err(error) => return std::result::Result::Err(error),
|
||||
};
|
||||
let unsigned_base64 = match unsigned.transaction_base64() {
|
||||
std::result::Result::Ok(value) => value,
|
||||
std::result::Result::Err(error) => return std::result::Result::Err(error),
|
||||
};
|
||||
let simulation_config = match ks_onchain_transport::SimulateTransactionConfig::new(
|
||||
ks_onchain_transport::RpcCommitmentLevel::Confirmed,
|
||||
false,
|
||||
false,
|
||||
std::option::Option::Some(latest_blockhash.context.slot),
|
||||
true,
|
||||
std::option::Option::None,
|
||||
) {
|
||||
std::result::Result::Ok(value) => value,
|
||||
std::result::Result::Err(error) => return std::result::Result::Err(error),
|
||||
};
|
||||
let simulation_rpc = match http_pool
|
||||
.simulate_transaction_for_role(
|
||||
options.transaction_role.as_str(),
|
||||
unsigned_base64.as_str(),
|
||||
&simulation_config,
|
||||
)
|
||||
.await
|
||||
{
|
||||
std::result::Result::Ok(value) => value,
|
||||
std::result::Result::Err(error) => return std::result::Result::Err(error),
|
||||
};
|
||||
let simulation = simulation_rpc.to_execution_result(
|
||||
ks_lib::ExApiExecutionCluster::Devnet,
|
||||
ks_lib::ExApiExecutionBlockhashKind::Latest,
|
||||
std::option::Option::Some(
|
||||
simulation_rpc.context.slot.saturating_sub(latest_blockhash.context.slot),
|
||||
),
|
||||
std::option::Option::None,
|
||||
std::option::Option::None,
|
||||
std::option::Option::Some(&fee),
|
||||
);
|
||||
if !simulation.success {
|
||||
return std::result::Result::Err(ks_core::Error::new(
|
||||
"token_2022_metadata_fixture_simulation_failed",
|
||||
crate::simulation_failure_message(&simulation),
|
||||
));
|
||||
}
|
||||
let send_evaluation = match ks_lib::ExSafetyChecker.evaluate_send(&plan, &simulation) {
|
||||
std::result::Result::Ok(value) => value,
|
||||
std::result::Result::Err(error) => return std::result::Result::Err(error),
|
||||
};
|
||||
if send_evaluation.decision == ks_lib::ExSafetyDecision::Deny {
|
||||
return std::result::Result::Err(ks_core::Error::new(
|
||||
"token_2022_metadata_fixture_send_denied",
|
||||
crate::violation_message(send_evaluation.violations.as_slice()),
|
||||
));
|
||||
}
|
||||
let evidence = unsigned.bind_simulation(simulation);
|
||||
let signed = match unsigned
|
||||
.sign_after_simulation(&evidence, &[operator.as_signer(), mint_wallet.as_signer()])
|
||||
{
|
||||
std::result::Result::Ok(value) => value,
|
||||
std::result::Result::Err(error) => return std::result::Result::Err(error),
|
||||
};
|
||||
if let std::result::Result::Err(error) = signed.verify_signatures() {
|
||||
return std::result::Result::Err(error);
|
||||
}
|
||||
let signature = signed.primary_signature().clone();
|
||||
let send_config = match ks_onchain_transport::SendTransactionConfig::from_execution_config(
|
||||
&profile.execution,
|
||||
std::option::Option::Some(latest_blockhash.context.slot),
|
||||
) {
|
||||
std::result::Result::Ok(value) => value,
|
||||
std::result::Result::Err(error) => return std::result::Result::Err(error),
|
||||
};
|
||||
if let std::result::Result::Err(error) = http_pool
|
||||
.send_transaction_for_role(
|
||||
options.transaction_role.as_str(),
|
||||
signed.transaction_base64().as_str(),
|
||||
&signature,
|
||||
&send_config,
|
||||
)
|
||||
.await
|
||||
{
|
||||
return std::result::Result::Err(error);
|
||||
}
|
||||
let confirmation_config =
|
||||
match ks_onchain_transport::ConfirmTransactionConfig::from_execution_config(
|
||||
&profile.execution,
|
||||
std::option::Option::Some(latest_blockhash.last_valid_block_height),
|
||||
std::option::Option::Some(latest_blockhash.context.slot),
|
||||
) {
|
||||
std::result::Result::Ok(value) => value,
|
||||
std::result::Result::Err(error) => return std::result::Result::Err(error),
|
||||
};
|
||||
let confirmation = match http_pool
|
||||
.confirm_transaction_for_roles(
|
||||
options.transaction_role.as_str(),
|
||||
options.query_role.as_str(),
|
||||
ks_lib::ExApiExecutionCluster::Devnet,
|
||||
&signature,
|
||||
&confirmation_config,
|
||||
)
|
||||
.await
|
||||
{
|
||||
std::result::Result::Ok(value) => value,
|
||||
std::result::Result::Err(error) => return std::result::Result::Err(error),
|
||||
};
|
||||
if !matches!(
|
||||
confirmation.status,
|
||||
ks_lib::ExApiExecutionConfirmationStatus::Confirmed
|
||||
| ks_lib::ExApiExecutionConfirmationStatus::Finalized
|
||||
) {
|
||||
return std::result::Result::Err(ks_core::Error::new(
|
||||
"token_2022_metadata_fixture_confirmation_failed",
|
||||
format!("Token-2022 metadata mint preparation stopped at {:?}", confirmation.status),
|
||||
));
|
||||
}
|
||||
return std::result::Result::Ok(signature.0);
|
||||
}
|
||||
|
||||
fn planned_instruction(
|
||||
operation_code: &str,
|
||||
instruction: &solana_instruction::Instruction,
|
||||
) -> ks_lib::ExApiPlannedInstruction {
|
||||
return ks_lib::ExApiPlannedInstruction {
|
||||
program_id: ks_lib::MdProgramId(instruction.program_id.to_string()),
|
||||
operation_code: operation_code.to_string(),
|
||||
accounts: instruction
|
||||
.accounts
|
||||
.iter()
|
||||
.map(|account| {
|
||||
return ks_lib::ExApiPlannedAccount {
|
||||
pubkey: ks_lib::MdPubkey(account.pubkey.to_string()),
|
||||
is_signer: account.is_signer,
|
||||
is_writable: account.is_writable,
|
||||
};
|
||||
})
|
||||
.collect(),
|
||||
data: instruction.data.clone(),
|
||||
};
|
||||
}
|
||||
|
||||
fn fixture_policy(
|
||||
profile: &ks_config::ProfileConfig,
|
||||
payer: ks_lib::MdPubkey,
|
||||
mint: ks_lib::MdPubkey,
|
||||
_rent_lamports: u64,
|
||||
) -> ks_lib::ExApiExecutionPolicy {
|
||||
return ks_lib::ExApiExecutionPolicy {
|
||||
cluster: ks_lib::ExApiExecutionClusterPolicy {
|
||||
expected_cluster: ks_lib::ExApiExecutionCluster::Devnet,
|
||||
allow_mainnet: false,
|
||||
mainnet_confirmation: false,
|
||||
},
|
||||
simulation: ks_lib::ExApiExecutionSimulationPolicy::Required,
|
||||
blockhash: ks_lib::ExApiExecutionBlockhashPolicy {
|
||||
kind: ks_lib::ExApiExecutionBlockhashKind::Latest,
|
||||
max_age_slots: std::option::Option::Some(
|
||||
profile.execution.recent_blockhash_max_age_slots,
|
||||
),
|
||||
nonce_account: std::option::Option::None,
|
||||
nonce_authority: std::option::Option::None,
|
||||
},
|
||||
cost_limit: ks_lib::ExApiExecutionCostLimit {
|
||||
max_spend_lamports: std::option::Option::Some(
|
||||
profile.execution.devnet_max_spend_lamports,
|
||||
),
|
||||
max_fee_lamports: std::option::Option::Some(profile.execution.max_fee_lamports),
|
||||
max_compute_unit_price_micro_lamports: std::option::Option::Some(
|
||||
profile.execution.max_compute_unit_price_micro_lamports,
|
||||
),
|
||||
},
|
||||
authorized_signers: std::vec![payer, mint],
|
||||
dry_run: false,
|
||||
post_execution_validation: ks_lib::ExApiPostExecutionValidationPolicy {
|
||||
canonical_insert_required: false,
|
||||
core_extraction_required: false,
|
||||
decode_replay_required: false,
|
||||
materialization_required: false,
|
||||
},
|
||||
};
|
||||
}
|
||||
|
||||
fn merge_fixture_instructions(
|
||||
mut system_plan: ks_lib::ExApiPreparedExecutionPlan,
|
||||
token_instructions: std::vec::Vec<ks_lib::ExApiPlannedInstruction>,
|
||||
policy: ks_lib::ExApiExecutionPolicy,
|
||||
rent_lamports: u64,
|
||||
) -> ks_lib::ExApiPreparedExecutionPlan {
|
||||
system_plan.instructions.extend(token_instructions);
|
||||
system_plan.executor_name =
|
||||
"ks-pipeline-demo-scenarios.token_2022_metadata_fixture".to_string();
|
||||
system_plan.operation_code = "spl.token_2022.metadata.prepare_mint".to_string();
|
||||
system_plan.policy = policy;
|
||||
system_plan.requested_spend_lamports = rent_lamports;
|
||||
return system_plan;
|
||||
}
|
||||
|
||||
#[cfg(test)]
|
||||
mod tests {
|
||||
#[test]
|
||||
fn metadata_rent_budget_covers_pointer_and_largest_campaign_value() {
|
||||
let mint_space = super::token_2022_metadata_pointer_mint_space()
|
||||
.unwrap_or_else(|error| panic!("metadata pointer mint size failed: {error}"));
|
||||
assert!(mint_space > 82);
|
||||
let budget = super::metadata_campaign_rent_budget_space(mint_space);
|
||||
assert_eq!(
|
||||
budget,
|
||||
mint_space
|
||||
+ super::TOKEN_2022_TLV_HEADER_BYTES
|
||||
+ super::metadata_campaign_max_value_bytes()
|
||||
);
|
||||
assert!(budget > mint_space);
|
||||
}
|
||||
}
|
||||
Reference in New Issue
Block a user