use serde::{Deserialize, Serialize};
#[derive(Debug, Serialize, Deserialize)]
pub struct SimulationOutput {
pub metadata: SimulationMetadata,
pub transactions: Vec<Transaction>,
pub summary: SimulationSummary,
}
#[derive(Debug, Serialize, Deserialize)]
pub struct SimulationMetadata {
pub start_slot: u64,
pub end_slot: u64,
#[serde(default, skip_serializing_if = "Vec::is_empty")]
pub program_ids: Vec<String>,
#[serde(default, skip_serializing_if = "Vec::is_empty")]
pub program_so: Vec<String>,
pub ran_at_unix_secs: u64,
pub session_ids: Vec<String>,
}
#[derive(Debug, Clone, Serialize, Deserialize)]
pub struct Transaction {
pub slot: u64,
#[serde(default, skip_serializing_if = "Option::is_none")]
pub timestamp: Option<u64>,
pub signature: String,
pub success: bool,
#[serde(skip_serializing_if = "Option::is_none")]
pub error: Option<String>,
pub logs: Vec<String>,
pub sol_changes: Vec<SolChange>,
pub token_changes: Vec<TokenChange>,
#[serde(skip_serializing_if = "Vec::is_empty", default)]
pub account_diffs: Vec<AccountDiff>,
}
#[derive(Debug, Clone, Serialize, Deserialize)]
pub struct AccountDiff {
pub account: String,
#[serde(skip_serializing_if = "Option::is_none")]
pub pre_state: Option<serde_json::Value>,
#[serde(skip_serializing_if = "Option::is_none")]
pub post_state: Option<serde_json::Value>,
}
#[derive(Debug, Clone, Serialize, Deserialize)]
pub struct SolChange {
pub pubkey: String,
pub pre_lamports: u64,
pub post_lamports: u64,
}
impl SolChange {
pub fn delta(&self) -> i64 {
self.post_lamports as i64 - self.pre_lamports as i64
}
}
#[derive(Debug, Clone, Serialize, Deserialize)]
pub struct TokenChange {
pub pubkey: String,
pub mint: String,
pub owner: String,
pub pre_amount: u64,
pub post_amount: u64,
pub decimals: u8,
}
impl TokenChange {
pub fn delta(&self) -> i64 {
self.post_amount as i64 - self.pre_amount as i64
}
}
#[derive(Debug, Default, Serialize, Deserialize)]
pub struct SimulationSummary {
pub total_transactions: usize,
pub successes: usize,
pub failures: usize,
}
impl SimulationOutput {
pub fn build(metadata: SimulationMetadata, transactions: Vec<Transaction>) -> Self {
let summary = SimulationSummary {
total_transactions: transactions.len(),
successes: transactions.iter().filter(|t| t.success).count(),
failures: transactions.iter().filter(|t| !t.success).count(),
};
Self {
metadata,
transactions,
summary,
}
}
}
#[derive(Debug, Clone, Serialize, Deserialize)]
pub struct TokenInfo {
pub mint: String,
pub owner: String,
pub amount: u64,
pub decimals: u8,
}
#[derive(Debug, Clone, Serialize, Deserialize)]
pub struct AccountDiffRow {
pub slot: u64,
pub tx_hash: String,
pub account: String,
#[serde(skip_serializing_if = "Option::is_none")]
pub pre_lamports: Option<u64>,
#[serde(skip_serializing_if = "Option::is_none")]
pub post_lamports: Option<u64>,
#[serde(skip_serializing_if = "Option::is_none")]
pub pre_state: Option<serde_json::Value>,
#[serde(skip_serializing_if = "Option::is_none")]
pub post_state: Option<serde_json::Value>,
#[serde(skip_serializing_if = "Option::is_none")]
pub pre_tokens: Option<TokenInfo>,
#[serde(skip_serializing_if = "Option::is_none")]
pub post_tokens: Option<TokenInfo>,
}
impl AccountDiffRow {
pub fn from_diff(
slot: u64,
tx_hash: &str,
diff: &AccountDiff,
sol_changes: &[SolChange],
token_changes: &[TokenChange],
) -> Self {
let sol = sol_changes.iter().find(|s| s.pubkey == diff.account);
let token = token_changes.iter().find(|t| t.pubkey == diff.account);
Self {
slot,
tx_hash: tx_hash.to_string(),
account: diff.account.clone(),
pre_lamports: sol.map(|s| s.pre_lamports),
post_lamports: sol.map(|s| s.post_lamports),
pre_state: diff.pre_state.clone(),
post_state: diff.post_state.clone(),
pre_tokens: token.map(|t| TokenInfo {
mint: t.mint.clone(),
owner: t.owner.clone(),
amount: t.pre_amount,
decimals: t.decimals,
}),
post_tokens: token.map(|t| TokenInfo {
mint: t.mint.clone(),
owner: t.owner.clone(),
amount: t.post_amount,
decimals: t.decimals,
}),
}
}
}
pub(crate) fn format_slot(n: u64) -> String {
let s = n.to_string();
let mut out = String::new();
for (i, c) in s.chars().rev().enumerate() {
if i > 0 && i % 3 == 0 {
out.push(',');
}
out.push(c);
}
out.chars().rev().collect()
}