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