sui_types/
balance_change.rs

1// Copyright (c) Mysten Labs, Inc.
2// SPDX-License-Identifier: Apache-2.0
3
4use crate::base_types::SuiAddress;
5use crate::coin::Coin;
6use crate::effects::TransactionEffects;
7use crate::effects::TransactionEffectsAPI;
8use crate::full_checkpoint_content::ObjectSet;
9use crate::object::Object;
10use crate::object::Owner;
11use crate::storage::ObjectKey;
12use move_core_types::language_storage::TypeTag;
13
14#[derive(Clone, serde::Serialize, serde::Deserialize, PartialEq, Eq, PartialOrd, Ord)]
15pub struct BalanceChange {
16    /// Owner of the balance change
17    pub address: SuiAddress,
18
19    /// Type of the Coin
20    pub coin_type: TypeTag,
21
22    /// The amount indicate the balance value changes.
23    ///
24    /// A negative amount means spending coin value and positive means receiving coin value.
25    pub amount: i128,
26}
27
28impl std::fmt::Debug for BalanceChange {
29    fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
30        f.debug_struct("BalanceChange")
31            .field("address", &self.address)
32            .field("coin_type", &self.coin_type.to_canonical_string(true))
33            .field("amount", &self.amount)
34            .finish()
35    }
36}
37
38fn coins(objects: &[Object]) -> impl Iterator<Item = (&SuiAddress, TypeTag, u64)> + '_ {
39    objects.iter().filter_map(|object| {
40        let address = match object.owner() {
41            Owner::AddressOwner(sui_address)
42            | Owner::ObjectOwner(sui_address)
43            | Owner::ConsensusAddressOwner {
44                owner: sui_address, ..
45            } => sui_address,
46            Owner::Shared { .. } | Owner::Immutable => return None,
47        };
48        let (coin_type, balance) = Coin::extract_balance_if_coin(object).ok().flatten()?;
49        Some((address, coin_type, balance))
50    })
51}
52
53pub fn derive_balance_changes(
54    _effects: &TransactionEffects,
55    input_objects: &[Object],
56    output_objects: &[Object],
57) -> Vec<BalanceChange> {
58    // 1. subtract all input coins
59    let balances = coins(input_objects).fold(
60        std::collections::BTreeMap::<_, i128>::new(),
61        |mut acc, (address, coin_type, balance)| {
62            *acc.entry((address, coin_type)).or_default() -= balance as i128;
63            acc
64        },
65    );
66
67    // 2. add all mutated/output coins
68    let balances =
69        coins(output_objects).fold(balances, |mut acc, (address, coin_type, balance)| {
70            *acc.entry((address, coin_type)).or_default() += balance as i128;
71            acc
72        });
73
74    balances
75        .into_iter()
76        .filter_map(|((address, coin_type), amount)| {
77            if amount == 0 {
78                return None;
79            }
80
81            Some(BalanceChange {
82                address: *address,
83                coin_type,
84                amount,
85            })
86        })
87        .collect()
88}
89
90pub fn derive_balance_changes_2(
91    effects: &TransactionEffects,
92    objects: &ObjectSet,
93) -> Vec<BalanceChange> {
94    let input_objects = effects
95        .modified_at_versions()
96        .into_iter()
97        .filter_map(|(object_id, version)| objects.get(&ObjectKey(object_id, version)).cloned())
98        .collect::<Vec<_>>();
99    let output_objects = effects
100        .all_changed_objects()
101        .into_iter()
102        .filter_map(|(object_ref, _owner, _kind)| objects.get(&object_ref.into()).cloned())
103        .collect::<Vec<_>>();
104
105    // 1. subtract all input coins
106    let balances = coins(&input_objects).fold(
107        std::collections::BTreeMap::<_, i128>::new(),
108        |mut acc, (address, coin_type, balance)| {
109            *acc.entry((address, coin_type)).or_default() -= balance as i128;
110            acc
111        },
112    );
113
114    // 2. add all mutated/output coins
115    let balances =
116        coins(&output_objects).fold(balances, |mut acc, (address, coin_type, balance)| {
117            *acc.entry((address, coin_type)).or_default() += balance as i128;
118            acc
119        });
120
121    balances
122        .into_iter()
123        .filter_map(|((address, coin_type), amount)| {
124            if amount == 0 {
125                return None;
126            }
127
128            Some(BalanceChange {
129                address: *address,
130                coin_type,
131                amount,
132            })
133        })
134        .collect()
135}