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