sui_core/accumulators/
funds_read.rs

1// Copyright (c) Mysten Labs, Inc.
2// SPDX-License-Identifier: Apache-2.0
3
4use 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,
11    error::{SuiErrorKind, SuiResult, UserInputError},
12};
13
14pub trait AccountFundsRead: Send + Sync {
15    /// Gets the latest amount in an account. If the account does not exist, returns 0.
16    /// This does an unsequenced read and does not guarantee consistency with the root accumulator
17    /// version.
18    fn get_latest_account_amount(&self, account_id: &AccumulatorObjId) -> u128;
19
20    /// Gets the account amount at a version consistent with a stable accumulator root version.
21    /// If the account object has advanced ahead of that root version, this returns the amount
22    /// at or before the root version instead of the latest account object amount. If the account
23    /// does not exist at that root version, returns 0 for the amount.
24    /// It guarantees no data race between the read of the account object and the root accumulator version.
25    fn get_consistent_latest_account_amount_and_version(
26        &self,
27        account_id: &AccumulatorObjId,
28    ) -> (u128, SequenceNumber);
29
30    /// Read the amount at a precise version. Care must be taken to only call this function if we
31    /// can guarantee that objects behind this version have not yet been pruned.
32    fn get_account_amount_at_version(
33        &self,
34        account_id: &AccumulatorObjId,
35        version: SequenceNumber,
36    ) -> u128;
37
38    /// Checks if given amounts are available in the latest versions of the referenced acccumulator
39    /// objects. This does un-sequenced reads and can only be used on the signing/voting path
40    /// where deterministic results are not required.
41    fn check_amounts_available(
42        &self,
43        requested_amounts: &BTreeMap<AccumulatorObjId, (u64, TypeTag)>,
44    ) -> SuiResult {
45        for (object_id, (requested_amount, _type_tag)) in requested_amounts {
46            let actual_amount = self.get_latest_account_amount(object_id);
47
48            if actual_amount < *requested_amount as u128 {
49                return Err(SuiErrorKind::UserInputError {
50                    error: UserInputError::InvalidWithdrawReservation {
51                        error: format!(
52                            "Available amount in account for object id {} is less than requested: {} < {}",
53                            object_id, actual_amount, requested_amount
54                        ),
55                    },
56                }
57                .into());
58            }
59        }
60
61        Ok(())
62    }
63
64    /// For gasless transactions, checks that withdrawing the requested amounts does not leave
65    /// a balance below the minimum in the sender's account. For each withdrawal, the remaining balance
66    /// (actual - requested) must be either 0 or >= the minimum transfer amount for that
67    /// token type.
68    fn check_remaining_amounts_after_withdrawal(
69        &self,
70        requested_amounts: &BTreeMap<AccumulatorObjId, (u64, TypeTag)>,
71        min_amounts: &BTreeMap<TypeTag, u64>,
72    ) -> SuiResult {
73        for (object_id, (requested_amount, type_tag)) in requested_amounts {
74            let actual_amount = self.get_latest_account_amount(object_id);
75            let remaining = actual_amount.saturating_sub(*requested_amount as u128);
76            if remaining == 0 {
77                continue;
78            }
79            let coin_type =
80                Balance::maybe_get_balance_type_param(type_tag).unwrap_or_else(|| type_tag.clone());
81            if let Some(&min_amount) = min_amounts.get(&coin_type)
82                && min_amount > 0
83                && remaining < min_amount as u128
84            {
85                return Err(SuiErrorKind::UserInputError {
86                    error: UserInputError::InvalidWithdrawReservation {
87                        error: format!(
88                            "Invalid gasless withdrawal from {object_id}. \
89                             Gasless transactions must either use the entire balance, \
90                             or leave at least {min_amount} for token type {coin_type}. \
91                             Remaining amount is {remaining}",
92                        ),
93                    },
94                }
95                .into());
96            }
97        }
98
99        Ok(())
100    }
101}