1use std::collections::VecDeque;
5
6use move_binary_format::{
7 errors::{PartialVMError, PartialVMResult},
8 safe_assert_eq, safe_unwrap,
9};
10use move_core_types::{
11 account_address::AccountAddress, gas_algebra::InternalGas, u256::U256, vm_status::StatusCode,
12};
13use move_vm_runtime::{
14 execution::{
15 Type,
16 values::{Struct, Value},
17 },
18 native_charge_gas_early_exit,
19 natives::functions::{NativeContext, NativeResult},
20};
21use smallvec::smallvec;
22use sui_types::{
23 accumulator_root::check_accumulator_type_bounds,
24 base_types::{ObjectID, SuiAddress},
25 funds_accumulator::E_OBJECT_FUNDS_INSUFFICIENT,
26 storage::ObjectFundsSufficiency,
27};
28
29use crate::{
30 NativesCostTable,
31 object_runtime::{MoveAccumulatorAction, MoveAccumulatorValue, ObjectRuntime},
32};
33
34const E_OVERFLOW: u64 = 0;
35const E_ADDRESS_BALANCE_NOT_ENABLED: u64 = 1;
36const E_ACCUMULATOR_TYPE_TOO_LARGE: u64 = 4;
37
38#[derive(Clone)]
39pub struct ReserveObjectFundsForWithdrawalCostParams {
40 pub base_cost: Option<InternalGas>,
41 pub cold_read_cost: Option<InternalGas>,
42}
43
44pub fn add_to_accumulator_address(
45 context: &mut NativeContext,
46 mut ty_args: Vec<Type>,
47 mut args: VecDeque<Value>,
48) -> PartialVMResult<NativeResult> {
49 debug_assert!(ty_args.len() == 1);
50 debug_assert!(args.len() == 3);
51
52 let event_emit_cost_params = context
54 .extensions_mut()
55 .get::<NativesCostTable>()?
56 .event_emit_cost_params
57 .clone();
58 native_charge_gas_early_exit!(context, event_emit_cost_params.event_emit_cost_base);
59
60 let ty_tag = context.type_to_type_tag(&safe_unwrap!(ty_args.pop()))?;
61
62 let Some(value) = safe_unwrap!(args.pop_back()).value_as::<Struct>().ok() else {
63 debug_assert!(false);
65 return Err(
66 PartialVMError::new(StatusCode::UNKNOWN_INVARIANT_VIOLATION_ERROR).with_message(
67 "Balance should be guaranteed under current implementation".to_owned(),
68 ),
69 );
70 };
71 let recipient = safe_unwrap!(safe_unwrap!(args.pop_back()).value_as::<AccountAddress>());
72 let accumulator: ObjectID =
73 safe_unwrap!(safe_unwrap!(args.pop_back()).value_as::<AccountAddress>()).into();
74
75 let Some([amount]): Option<[Value; 1]> = value.unpack().collect::<Vec<_>>().try_into().ok()
77 else {
78 debug_assert!(false);
79 return Err(
80 PartialVMError::new(StatusCode::UNKNOWN_INVARIANT_VIOLATION_ERROR).with_message(
81 "Balance should be guaranteed under current implementation".to_owned(),
82 ),
83 );
84 };
85 let Some(amount) = amount.value_as::<u64>().ok() else {
86 debug_assert!(false);
87 return Err(
88 PartialVMError::new(StatusCode::UNKNOWN_INVARIANT_VIOLATION_ERROR).with_message(
89 "Balance should be guaranteed under current implementation".to_owned(),
90 ),
91 );
92 };
93
94 let cost = context.gas_used();
95
96 let obj_runtime: &mut ObjectRuntime = context.extensions_mut().get_mut()?;
97
98 if !obj_runtime.protocol_config.enable_accumulators() {
99 return Ok(NativeResult::err(cost, E_ADDRESS_BALANCE_NOT_ENABLED));
100 }
101
102 if !check_accumulator_type_bounds(obj_runtime.protocol_config, &ty_tag) {
103 return Ok(NativeResult::err(cost, E_ACCUMULATOR_TYPE_TOO_LARGE));
104 }
105
106 obj_runtime.emit_accumulator_event(
107 accumulator,
108 MoveAccumulatorAction::Merge,
109 recipient,
110 ty_tag,
111 MoveAccumulatorValue::U64(amount),
112 )?;
113 Ok(NativeResult::ok(context.gas_used(), smallvec![]))
114}
115
116pub fn withdraw_from_accumulator_address(
117 context: &mut NativeContext,
118 mut ty_args: Vec<Type>,
119 mut args: VecDeque<Value>,
120) -> PartialVMResult<NativeResult> {
121 debug_assert!(ty_args.len() == 1);
122 debug_assert!(args.len() == 3);
123
124 let event_emit_cost_params = context
127 .extensions_mut()
128 .get::<NativesCostTable>()?
129 .event_emit_cost_params
130 .clone();
131 native_charge_gas_early_exit!(context, event_emit_cost_params.event_emit_cost_base);
132
133 let ty_tag = context.type_to_type_tag(&safe_unwrap!(ty_args.pop()))?;
134
135 let value = safe_unwrap!(safe_unwrap!(args.pop_back()).value_as::<U256>());
136 let recipient = safe_unwrap!(safe_unwrap!(args.pop_back()).value_as::<AccountAddress>());
137 let accumulator: ObjectID =
138 safe_unwrap!(safe_unwrap!(args.pop_back()).value_as::<AccountAddress>()).into();
139
140 let Ok(amount): Result<u64, _> = value.try_into() else {
142 return Ok(NativeResult::err(context.gas_used(), E_OVERFLOW));
143 };
144
145 let cost = context.gas_used();
146
147 let obj_runtime: &mut ObjectRuntime = context.extensions_mut().get_mut()?;
148
149 if !check_accumulator_type_bounds(obj_runtime.protocol_config, &ty_tag) {
150 return Ok(NativeResult::err(cost, E_ACCUMULATOR_TYPE_TOO_LARGE));
151 }
152
153 obj_runtime.emit_accumulator_event(
154 accumulator,
155 MoveAccumulatorAction::Split,
156 recipient,
157 ty_tag,
158 MoveAccumulatorValue::U64(amount),
159 )?;
160 let withdrawn = Value::struct_(Struct::pack(vec![Value::u64(amount)]));
162 Ok(NativeResult::ok(context.gas_used(), smallvec![withdrawn]))
163}
164
165pub fn reserve_object_funds_for_withdrawal(
166 context: &mut NativeContext,
167 mut ty_args: Vec<Type>,
168 mut args: VecDeque<Value>,
169) -> PartialVMResult<NativeResult> {
170 safe_assert_eq!(ty_args.len(), 1);
171 safe_assert_eq!(args.len(), 2);
172
173 let (ty_tag, limit, owner) = {
174 let obj_runtime: &ObjectRuntime = context.extensions().get()?;
175 if !obj_runtime
176 .protocol_config
177 .check_object_funds_withdraw_in_execution()
178 {
179 return Ok(NativeResult::ok(context.gas_used(), smallvec![]));
182 }
183
184 let cost_params = context
185 .extensions()
186 .get::<NativesCostTable>()?
187 .reserve_object_funds_for_withdrawal_cost_params
188 .clone();
189 let base_cost = cost_params.base_cost.ok_or_else(|| {
190 PartialVMError::new(StatusCode::UNKNOWN_INVARIANT_VIOLATION_ERROR).with_message(
191 "reserve_object_funds_for_withdrawal base gas cost is not set".to_string(),
192 )
193 })?;
194 native_charge_gas_early_exit!(context, base_cost);
195
196 let ty_tag = context.type_to_type_tag(&safe_unwrap!(ty_args.pop()))?;
197 let limit = safe_unwrap!(safe_unwrap!(args.pop_back()).value_as::<U256>());
198 let owner: SuiAddress =
199 safe_unwrap!(safe_unwrap!(args.pop_back()).value_as::<AccountAddress>()).into();
200
201 if obj_runtime.object_funds_sufficiency_needs_store_read(owner, &ty_tag, limit) {
205 let cold_read_cost = cost_params.cold_read_cost.ok_or_else(|| {
206 PartialVMError::new(StatusCode::UNKNOWN_INVARIANT_VIOLATION_ERROR).with_message(
207 "reserve_object_funds_for_withdrawal cold read gas cost is not set".to_string(),
208 )
209 })?;
210 native_charge_gas_early_exit!(context, cold_read_cost);
211 }
212 (ty_tag, limit, owner)
213 };
214
215 let obj_runtime: &mut ObjectRuntime = context.extensions_mut().get_mut()?;
216 match obj_runtime.check_object_funds_sufficiency(owner, &ty_tag, limit) {
217 ObjectFundsSufficiency::Sufficient => Ok(NativeResult::ok(context.gas_used(), smallvec![])),
218 ObjectFundsSufficiency::Insufficient => Ok(NativeResult::err(
219 context.gas_used(),
220 E_OBJECT_FUNDS_INSUFFICIENT,
221 )),
222 ObjectFundsSufficiency::Overflow => Ok(NativeResult::err(context.gas_used(), E_OVERFLOW)),
223 ObjectFundsSufficiency::LoadError(msg) => Err(PartialVMError::new(
224 StatusCode::UNKNOWN_INVARIANT_VIOLATION_ERROR,
225 )
226 .with_message(msg)),
227 }
228}