sui_core/accumulators/
coin_reservations.rs1use 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 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 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 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}