sui_core/accumulators/
coin_reservations.rs

1// Copyright (c) Mysten Labs, Inc.
2// SPDX-License-Identifier: Apache-2.0
3
4use std::sync::Arc;
5
6use moka::sync::Cache as MokaCache;
7use sui_types::{
8    TypeTag,
9    accumulator_root::{AccumulatorKey, AccumulatorValue},
10    base_types::{ObjectID, SuiAddress},
11    coin_reservation::{CoinReservationResolverTrait, ParsedObjectRefWithdrawal},
12    error::{UserInputError, UserInputResult},
13    storage::ChildObjectResolver,
14    transaction::FundsWithdrawalArg,
15};
16
17macro_rules! invalid_res_error {
18    ($($args:tt)*) => {
19        UserInputError::InvalidWithdrawReservation {
20            error: format!($($args)*),
21        }
22    };
23}
24
25pub struct CoinReservationResolver {
26    child_object_resolver: Arc<dyn ChildObjectResolver + Send + Sync>,
27    object_id_to_type_cache: MokaCache<ObjectID, (SuiAddress, TypeTag)>,
28}
29
30impl CoinReservationResolver {
31    pub fn new(child_object_resolver: Arc<dyn ChildObjectResolver + Send + Sync>) -> Self {
32        Self {
33            child_object_resolver,
34            object_id_to_type_cache: MokaCache::builder().max_capacity(1000).build(),
35        }
36    }
37
38    fn get_type_tag_for_object(
39        &self,
40        sender: SuiAddress,
41        object_id: ObjectID,
42    ) -> UserInputResult<TypeTag> {
43        let (owner, type_input) = self
44            .object_id_to_type_cache
45            .try_get_with(object_id, || -> UserInputResult<(SuiAddress, TypeTag)> {
46                // Load accumulator field object
47                let object = AccumulatorValue::load_object_by_id(
48                    self.child_object_resolver.as_ref(),
49                    None,
50                    object_id,
51                )
52                .map_err(|e| invalid_res_error!("could not load coin reservation object id {}", e))?
53                .ok_or_else(|| {
54                    invalid_res_error!("coin reservation object id {} not found", object_id)
55                })?;
56
57                let move_object = object.data.try_as_move().unwrap();
58
59                // Get the balance type
60                let type_input: TypeTag = move_object
61                    .type_()
62                    .balance_accumulator_field_type_maybe()
63                    .ok_or_else(|| {
64                        invalid_res_error!(
65                            "coin reservation object id {} is not a balance accumulator field",
66                            object_id
67                        )
68                    })?;
69
70                // get the owner
71                let (key, _): (AccumulatorKey, AccumulatorValue) =
72                    move_object.try_into().map_err(|e| {
73                        invalid_res_error!("could not load coin reservation object id {}", e)
74                    })?;
75                Ok((key.owner, type_input))
76            })
77            .map_err(|e| (*e).clone())?;
78
79        if sender != owner {
80            return Err(invalid_res_error!(
81                "coin reservation object id {} is owned by {}, not sender {}",
82                object_id,
83                owner,
84                sender
85            ));
86        }
87
88        Ok(type_input)
89    }
90
91    pub fn resolve_funds_withdrawal(
92        &self,
93        sender: SuiAddress,
94        coin_reservation: ParsedObjectRefWithdrawal,
95    ) -> UserInputResult<FundsWithdrawalArg> {
96        let type_tag = self.get_type_tag_for_object(sender, coin_reservation.unmasked_object_id)?;
97
98        Ok(FundsWithdrawalArg::balance_from_sender(
99            coin_reservation.reservation_amount(),
100            type_tag,
101        ))
102    }
103}
104
105impl CoinReservationResolverTrait for CoinReservationResolver {
106    fn resolve_funds_withdrawal(
107        &self,
108        sender: SuiAddress,
109        coin_reservation: ParsedObjectRefWithdrawal,
110    ) -> UserInputResult<FundsWithdrawalArg> {
111        self.resolve_funds_withdrawal(sender, coin_reservation)
112    }
113}
114
115impl CoinReservationResolverTrait for &'_ CoinReservationResolver {
116    fn resolve_funds_withdrawal(
117        &self,
118        sender: SuiAddress,
119        coin_reservation: ParsedObjectRefWithdrawal,
120    ) -> UserInputResult<FundsWithdrawalArg> {
121        CoinReservationResolver::resolve_funds_withdrawal(self, sender, coin_reservation)
122    }
123}