Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
188 changes: 175 additions & 13 deletions crates/core/src/xdr/diff.rs
Original file line number Diff line number Diff line change
Expand Up @@ -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<Signer>,
pub removed_signers: Vec<Signer>,
}

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.
///
Expand Down Expand Up @@ -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)),
}
}

Expand Down Expand Up @@ -214,7 +310,73 @@ fn json_string<T: Debug + Serialize>(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,
Expand Down Expand Up @@ -376,7 +538,7 @@ mod tests {
}

fn trustline(
asset: Asset,
asset: stellar_xdr::curr::TrustLineAsset,
balance: i64,
limit: i64,
flags: u32,
Expand All @@ -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]
Expand Down Expand Up @@ -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);

Expand All @@ -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);

Expand Down
2 changes: 1 addition & 1 deletion crates/core/src/xdr/mod.rs
Original file line number Diff line number Diff line change
@@ -1,4 +1,4 @@
pub mod cmp;
pub mod codec;
#[id(feature = "decode")]
#[cfg(feature = "decode")]
pub mod diff;
8 changes: 8 additions & 0 deletions crates/core/src/xdr/scratch.rs
Original file line number Diff line number Diff line change
@@ -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;
}