diff --git a/crates/core/src/xdr/diff.rs b/crates/core/src/xdr/diff.rs index a8457571..28d30e93 100644 --- a/crates/core/src/xdr/diff.rs +++ b/crates/core/src/xdr/diff.rs @@ -2,8 +2,103 @@ use crate::types::trace::{DiffChangeType, LedgerEntryDiff, StateDiff}; use serde::Serialize; -use std::fmt::Debug; -use stellar_xdr::curr::{Asset, ContractCodeEntry, ContractDataEntry, ScMap, ScVal, TrustLineEntry}; +use std::fmt::{self, Debug}; +use stellar_xdr::curr::{AccountEntry, Signer, Asset, ContractCodeEntry, ContractDataEntry, ScMap, ScVal, TrustLineEntry}; + +#[derive(Debug, Clone, PartialEq, Eq)] +pub struct AccountEntryDiff { + pub balance_change: i64, + pub seq_num_change: i64, + pub num_sub_entries_change: i32, + pub added_signers: Vec, + pub removed_signers: Vec, +} + +impl fmt::Display for AccountEntryDiff { + fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result { + writeln!(f, "AccountEntry Diff:")?; + + if self.balance_change != 0 { + let direction = if self.balance_change > 0 { "+" } else { "" }; + writeln!( + f, + " 💰 Balance: {}{} XLM (stroops)", + direction, self.balance_change + )?; + } + + if self.seq_num_change != 0 { + let direction = if self.seq_num_change > 0 { "+" } else { "" }; + writeln!(f, " 🔢 Sequence: {}{}", direction, self.seq_num_change)?; + } + + if self.num_sub_entries_change != 0 { + let direction = if self.num_sub_entries_change > 0 { + "+" + } else { + "" + }; + writeln!( + f, + " 📦 Sub-entries: {}{}", + direction, self.num_sub_entries_change + )?; + } + + if !self.added_signers.is_empty() { + writeln!(f, " 🔐 Added Signers: {}", self.added_signers.len())?; + for signer in &self.added_signers { + writeln!(f, " - {signer:?}")?; + } + } + + if !self.removed_signers.is_empty() { + writeln!(f, " 🔓 Removed Signers: {}", self.removed_signers.len())?; + for signer in &self.removed_signers { + writeln!(f, " - {signer:?}")?; + } + } + + Ok(()) + } +} + +pub fn diff_account_entry(old: &AccountEntry, new: &AccountEntry) -> AccountEntryDiff { + let balance_change = new.balance - old.balance; + let seq_num_change = new.seq_num.0 - old.seq_num.0; + + let old_sub = old.num_sub_entries.cast_signed(); + let new_sub = new.num_sub_entries.cast_signed(); + let num_sub_entries_change = new_sub - old_sub; + + let mut added_signers = Vec::new(); + let mut removed_signers = Vec::new(); + + let old_signers = old.signers.as_vec(); + let new_signers = new.signers.as_vec(); + + for signer in new_signers { + if !old_signers.contains(signer) { + added_signers.push(signer.clone()); + } + } + + for signer in old_signers { + if !new_signers.contains(signer) { + removed_signers.push(signer.clone()); + } + } + + AccountEntryDiff { + balance_change, + seq_num_change, + num_sub_entries_change, + added_signers, + removed_signers, + } +} + + /// Report a contract WASM code hash rotation. /// @@ -89,21 +184,22 @@ pub fn diff_trustline_entry(old: &TrustLineEntry, new: &TrustLineEntry) -> State StateDiff { entries } } -fn format_asset(asset: &Asset) -> String { +fn format_asset(asset: &stellar_xdr::curr::TrustLineAsset) -> String { match asset { - Asset::Native => "native".to_owned(), - Asset::CreditAlphanum4(a) => { + stellar_xdr::curr::TrustLineAsset::Native => "native".to_owned(), + stellar_xdr::curr::TrustLineAsset::CreditAlphanum4(a) => { let code = String::from_utf8_lossy(&a.asset_code.0) .trim_end_matches('\0') .to_owned(); format!("{code}:{}", a.issuer.to_string()) } - Asset::CreditAlphanum12(a) => { + stellar_xdr::curr::TrustLineAsset::CreditAlphanum12(a) => { let code = String::from_utf8_lossy(&a.asset_code.0) .trim_end_matches('\0') .to_owned(); format!("{code}:{}", a.issuer.to_string()) } + stellar_xdr::curr::TrustLineAsset::PoolShare(pool) => format!("pool_share:{}", hex::encode(&pool.0)), } } @@ -214,7 +310,73 @@ fn json_string(value: &T) -> String { #[cfg(test)] mod tests { - use super::{diff_contract_code_entry, diff_contract_data, diff_trustline_entry}; +use super::*; + use stellar_xdr::curr::{ + AccountEntryExt, AccountId, PublicKey, SequenceNumber, SignerKey, String32, StringM, Thresholds, + Uint256, VecM, + }; + + fn dummy_account_entry() -> AccountEntry { + AccountEntry { + account_id: AccountId(PublicKey::PublicKeyTypeEd25519(Uint256([0; 32]))), + balance: 1000, + seq_num: SequenceNumber(10), + num_sub_entries: 2, + inflation_dest: None, + flags: 0, + home_domain: String32(StringM::default()), + thresholds: Thresholds([0; 4]), + signers: VecM::default(), + ext: AccountEntryExt::V0, + } + } + + #[test] + fn test_diff_balance_and_seq() { + let old = dummy_account_entry(); + let mut new = old.clone(); + new.balance = 800; // 200 fee deduction + new.seq_num = SequenceNumber(11); // +1 seq + + let diff = diff_account_entry(&old, &new); + assert_eq!(diff.balance_change, -200); + assert_eq!(diff.seq_num_change, 1); + assert_eq!(diff.num_sub_entries_change, 0); + assert!(diff.added_signers.is_empty()); + assert!(diff.removed_signers.is_empty()); + } + + #[test] + fn test_diff_signers() { + let mut old = dummy_account_entry(); + let mut new = dummy_account_entry(); + + let signer1 = Signer { + key: SignerKey::Ed25519(Uint256([1; 32])), + weight: 1, + }; + let signer2 = Signer { + key: SignerKey::Ed25519(Uint256([2; 32])), + weight: 2, + }; + + let mut old_signers = old.signers.to_vec(); + old_signers.push(signer1.clone()); + old.signers = old_signers.try_into().unwrap(); + + let mut new_signers = new.signers.to_vec(); + new_signers.push(signer2.clone()); + new.signers = new_signers.try_into().unwrap(); + + let diff = diff_account_entry(&old, &new); + assert_eq!(diff.added_signers.len(), 1); + assert_eq!(diff.added_signers[0], signer2); + + assert_eq!(diff.removed_signers.len(), 1); + assert_eq!(diff.removed_signers[0], signer1); + } + +use super::{diff_contract_code_entry, diff_contract_data, diff_trustline_entry}; use crate::types::trace::DiffChangeType; use stellar_xdr::curr::{ AccountId, Asset, AssetCode12, AssetCode4, ContractCodeEntry, ContractCodeEntryExt, @@ -376,7 +538,7 @@ mod tests { } fn trustline( - asset: Asset, + asset: stellar_xdr::curr::TrustLineAsset, balance: i64, limit: i64, flags: u32, @@ -391,8 +553,8 @@ mod tests { } } - fn usdc_asset() -> Asset { - Asset::CreditAlphanum4(AssetCode4(*b"USDC")) + fn usdc_asset() -> stellar_xdr::curr::TrustLineAsset { + stellar_xdr::curr::TrustLineAsset::CreditAlphanum4(stellar_xdr::curr::AlphaNum4 { asset_code: AssetCode4(*b"USDC"), issuer: AccountId(PublicKey::PublicKeyTypeEd25519(Uint256([0; 32]))) }) } #[test] @@ -436,7 +598,7 @@ mod tests { #[test] fn trustline_alphanum12_asset_is_rendered() { - let asset = Asset::CreditAlphanum12(AssetCode12(*b"LONGASSET\0\0\0")); + let asset = stellar_xdr::curr::TrustLineAsset::CreditAlphanum12(stellar_xdr::curr::AlphaNum12 { asset_code: AssetCode12(*b"LONGASSET\0\0\0"), issuer: AccountId(PublicKey::PublicKeyTypeEd25519(Uint256([0; 32]))) }); let old = trustline(asset.clone(), 0, 1000, 0); let new = trustline(asset, 5, 1000, 0); @@ -447,8 +609,8 @@ mod tests { #[test] fn trustline_native_asset_is_rendered() { - let old = trustline(Asset::Native, 0, 1000, 0); - let new = trustline(Asset::Native, 10, 1000, 0); + let old = trustline(stellar_xdr::curr::TrustLineAsset::Native, 0, 1000, 0); + let new = trustline(stellar_xdr::curr::TrustLineAsset::Native, 10, 1000, 0); let diff = diff_trustline_entry(&old, &new); diff --git a/crates/core/src/xdr/mod.rs b/crates/core/src/xdr/mod.rs index eb43d176..8f8cbe9e 100644 --- a/crates/core/src/xdr/mod.rs +++ b/crates/core/src/xdr/mod.rs @@ -1,4 +1,4 @@ pub mod cmp; pub mod codec; -#[id(feature = "decode")] +#[cfg(feature = "decode")] pub mod diff; diff --git a/crates/core/src/xdr/scratch.rs b/crates/core/src/xdr/scratch.rs new file mode 100644 index 00000000..bf3b2e72 --- /dev/null +++ b/crates/core/src/xdr/scratch.rs @@ -0,0 +1,8 @@ +use stellar_xdr::next::{AccountEntry, SequenceNumber}; + +pub fn check_fields(entry: &AccountEntry) { + let _bal = entry.balance; + let _seq = entry.seq_num; + let _num = entry.num_sub_entries; + let _sig = &entry.signers; +}