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    accumulator_root::{AccumulatorKey, AccumulatorValue},
9    base_types::{ObjectID, SuiAddress},
10    coin_reservation::{CoinReservationResolverTrait, ParsedObjectRefWithdrawal},
11    error::{UserInputError, UserInputResult},
12    storage::ChildObjectResolver,
13    transaction::FundsWithdrawalArg,
14    type_input::TypeInput,
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, TypeInput)>,
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_input_for_object(
39        &self,
40        sender: SuiAddress,
41        object_id: ObjectID,
42    ) -> UserInputResult<TypeInput> {
43        let (owner, type_input) = self
44            .object_id_to_type_cache
45            .try_get_with(object_id, || -> UserInputResult<(SuiAddress, TypeInput)> {
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: TypeInput = 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                    .into();
70
71                // get the owner
72                let (key, _): (AccumulatorKey, AccumulatorValue) =
73                    move_object.try_into().map_err(|e| {
74                        invalid_res_error!("could not load coin reservation object id {}", e)
75                    })?;
76                Ok((key.owner, type_input))
77            })
78            .map_err(|e| (*e).clone())?;
79
80        if sender != owner {
81            return Err(invalid_res_error!(
82                "coin reservation object id {} is owned by {}, not sender {}",
83                object_id,
84                owner,
85                sender
86            ));
87        }
88
89        Ok(type_input)
90    }
91
92    pub fn resolve_funds_withdrawal(
93        &self,
94        sender: SuiAddress,
95        coin_reservation: ParsedObjectRefWithdrawal,
96    ) -> UserInputResult<FundsWithdrawalArg> {
97        let type_input =
98            self.get_type_input_for_object(sender, coin_reservation.unmasked_object_id)?;
99
100        Ok(FundsWithdrawalArg::balance_from_sender(
101            coin_reservation.reservation_amount(),
102            type_input,
103        ))
104    }
105}
106
107impl CoinReservationResolverTrait for CoinReservationResolver {
108    fn resolve_funds_withdrawal(
109        &self,
110        sender: SuiAddress,
111        coin_reservation: ParsedObjectRefWithdrawal,
112    ) -> UserInputResult<FundsWithdrawalArg> {
113        self.resolve_funds_withdrawal(sender, coin_reservation)
114    }
115}
116
117impl CoinReservationResolverTrait for &'_ CoinReservationResolver {
118    fn resolve_funds_withdrawal(
119        &self,
120        sender: SuiAddress,
121        coin_reservation: ParsedObjectRefWithdrawal,
122    ) -> UserInputResult<FundsWithdrawalArg> {
123        CoinReservationResolver::resolve_funds_withdrawal(self, sender, coin_reservation)
124    }
125}