sui_core/accumulators/
funds_read.rs1use std::collections::BTreeMap;
5
6use move_core_types::language_storage::TypeTag;
7use sui_types::{
8 accumulator_root::AccumulatorObjId,
9 balance::Balance,
10 base_types::{SequenceNumber, SuiAddress},
11 error::{SuiErrorKind, SuiResult, UserInputError},
12};
13
14pub trait AccountFundsRead: Send + Sync {
15 fn get_latest_account_amount(&self, account_id: &AccumulatorObjId) -> u128;
19
20 fn get_consistent_latest_account_amount_and_version(
26 &self,
27 account_id: &AccumulatorObjId,
28 ) -> (u128, SequenceNumber);
29
30 fn get_account_amount_at_version(
33 &self,
34 account_id: &AccumulatorObjId,
35 version: SequenceNumber,
36 ) -> u128;
37
38 fn check_amounts_available(
42 &self,
43 requested_amounts: &BTreeMap<AccumulatorObjId, (u64, TypeTag, SuiAddress)>,
44 ) -> SuiResult {
45 for (object_id, (requested_amount, type_tag, owner)) in requested_amounts {
46 let actual_amount = self.get_latest_account_amount(object_id);
47
48 if actual_amount < *requested_amount as u128 {
49 let coin_type = Balance::maybe_get_balance_type_param(type_tag)
50 .unwrap_or_else(|| type_tag.clone());
51 return Err(SuiErrorKind::UserInputError {
52 error: UserInputError::InvalidWithdrawReservation {
53 error: format!(
54 "Insufficient address balance of coin type {coin_type} \
55 for address {owner}: the transaction requires \
56 {requested_amount} but only {actual_amount} is available. \
57 Note that the address balance does not include funds held \
58 in Coin objects owned by the address; to spend those funds, \
59 use the Coin objects directly as transaction inputs.",
60 ),
61 },
62 }
63 .into());
64 }
65 }
66
67 Ok(())
68 }
69
70 fn check_remaining_amounts_after_withdrawal(
75 &self,
76 requested_amounts: &BTreeMap<AccumulatorObjId, (u64, TypeTag, SuiAddress)>,
77 min_amounts: &BTreeMap<TypeTag, u64>,
78 ) -> SuiResult {
79 for (object_id, (requested_amount, type_tag, owner)) in requested_amounts {
80 let actual_amount = self.get_latest_account_amount(object_id);
81 let remaining = actual_amount.saturating_sub(*requested_amount as u128);
82 if remaining == 0 {
83 continue;
84 }
85 let coin_type =
86 Balance::maybe_get_balance_type_param(type_tag).unwrap_or_else(|| type_tag.clone());
87 if let Some(&min_amount) = min_amounts.get(&coin_type)
88 && min_amount > 0
89 && remaining < min_amount as u128
90 {
91 return Err(SuiErrorKind::UserInputError {
92 error: UserInputError::InvalidWithdrawReservation {
93 error: format!(
94 "Invalid gasless withdrawal of coin type {coin_type} \
95 from address {owner}. \
96 Gasless transactions must either use the entire address \
97 balance, or leave at least {min_amount}. \
98 Remaining amount would be {remaining}",
99 ),
100 },
101 }
102 .into());
103 }
104 }
105
106 Ok(())
107 }
108}