1pub use checked::*;
7
8#[iota_macros::with_checked_arithmetic]
9pub mod checked {
10
11 use std::collections::HashMap;
12
13 use enum_dispatch::enum_dispatch;
14 use iota_protocol_config::ProtocolConfig;
15 use iota_sdk_types::{GasPayment, ObjectReference, Transaction, gas::GasCostSummary};
16
17 use crate::{
18 ObjectId,
19 error::{ExecutionError, IotaResult, UserInputError, UserInputResult},
20 gas_model::{gas_v1::IotaGasStatus as IotaGasStatusV1, tables::GasStatus},
21 object::{MoveStructExt, Object},
22 transaction::{InputObjects, ObjectReadResult, TransactionAPI},
23 };
24
25 #[enum_dispatch]
26 pub trait IotaGasStatusAPI {
27 fn is_unmetered(&self) -> bool;
28 fn move_gas_status(&self) -> &GasStatus;
29 fn move_gas_status_mut(&mut self) -> &mut GasStatus;
30 fn bucketize_computation(&mut self) -> Result<(), ExecutionError>;
31 fn summary(&self) -> GasCostSummary;
32 fn gas_budget(&self) -> u64;
33 fn gas_price(&self) -> u64;
34 fn reference_gas_price(&self) -> u64;
35 fn storage_gas_units(&self) -> u64;
36 fn storage_rebate(&self) -> u64;
37 fn unmetered_storage_rebate(&self) -> u64;
38 fn gas_used(&self) -> u64;
39 fn reset_storage_cost_and_rebate(&mut self);
40 fn charge_storage_read(&mut self, size: usize) -> Result<(), ExecutionError>;
41 fn charge_publish_package(&mut self, size: usize) -> Result<(), ExecutionError>;
42 fn track_storage_mutation(
43 &mut self,
44 object_id: ObjectId,
45 new_size: usize,
46 storage_rebate: u64,
47 ) -> u64;
48 fn charge_storage_and_rebate(&mut self) -> Result<(), ExecutionError>;
49 fn adjust_computation_on_out_of_gas(&mut self);
50 }
51
52 #[enum_dispatch(IotaGasStatusAPI)]
54 #[derive(Debug)]
55 pub enum IotaGasStatus {
56 V1(IotaGasStatusV1),
57 }
58
59 impl IotaGasStatus {
60 pub fn new(
61 gas_budget: u64,
62 gas_price: u64,
63 reference_gas_price: u64,
64 config: &ProtocolConfig,
65 ) -> IotaResult<Self> {
66 Self::check_gas_preconditions(gas_price, reference_gas_price, config)?;
67
68 Ok(Self::V1(IotaGasStatusV1::new_with_budget(
69 gas_budget,
70 gas_price,
71 reference_gas_price,
72 config,
73 )))
74 }
75
76 pub fn new_unmetered() -> Self {
77 Self::V1(IotaGasStatusV1::new_unmetered())
80 }
81
82 pub fn check_gas_balance(
85 &self,
86 gas_objs: &[&ObjectReadResult],
87 gas_budget: u64,
88 ) -> UserInputResult {
89 match self {
90 Self::V1(status) => status.check_gas_balance(gas_objs, gas_budget),
91 }
92 }
93
94 fn check_gas_preconditions(
95 gas_price: u64,
96 reference_gas_price: u64,
97 config: &ProtocolConfig,
98 ) -> IotaResult<()> {
99 if gas_price < reference_gas_price {
104 return Err(UserInputError::GasPriceUnderRGP {
105 gas_price,
106 reference_gas_price,
107 }
108 .into());
109 }
110 if gas_price > config.max_gas_price() {
111 return Err(UserInputError::GasPriceTooHigh {
112 max_gas_price: config.max_gas_price(),
113 }
114 .into());
115 }
116
117 Ok(())
118 }
119 }
120
121 pub fn deduct_gas(gas_object: &mut Object, charge_or_rebate: i64) {
124 let gas_coin = gas_object.data.as_opt_mut_struct().unwrap();
126 let balance = gas_coin.get_coin_value_unchecked();
127 let new_balance = if charge_or_rebate < 0 {
128 balance + (-charge_or_rebate as u64)
129 } else {
130 assert!(balance >= charge_or_rebate as u64);
131 balance - charge_or_rebate as u64
132 };
133 gas_coin.set_coin_value_unchecked(new_balance)
134 }
135
136 pub fn get_gas_balance(gas_object: &Object) -> UserInputResult<u64> {
137 if let Some(move_obj) = gas_object.data.as_opt_struct() {
138 if !move_obj.struct_tag().is_gas_coin() {
139 return Err(UserInputError::InvalidGasObject {
140 object_id: gas_object.id(),
141 });
142 }
143 Ok(move_obj.get_coin_value_unchecked())
144 } else {
145 Err(UserInputError::InvalidGasObject {
146 object_id: gas_object.id(),
147 })
148 }
149 }
150
151 pub fn fill_in_unset_simulation_gas(
155 transaction: &mut Transaction,
156 input_objects: &InputObjects,
157 reference_gas_price: u64,
158 protocol_config: &ProtocolConfig,
159 ) {
160 if transaction.gas_price() == 0 {
161 transaction.gas_data_mut().price = reference_gas_price;
162 }
163 if transaction.gas_budget() == 0 {
164 let min_gas_budget = protocol_config
165 .base_tx_cost_fixed()
166 .saturating_mul(transaction.gas_price());
167
168 let gas_balance = gas_coins_balance(input_objects, transaction.gas());
173
174 let budget = std::cmp::min(protocol_config.max_tx_gas() as u128, gas_balance)
179 .max(min_gas_budget as u128);
180
181 transaction.gas_data_mut().budget = budget as u64;
182 }
183 }
184
185 fn gas_coins_balance(input_objects: &InputObjects, gas: &[ObjectReference]) -> u128 {
188 let objects: HashMap<_, _> = input_objects
189 .iter()
190 .map(|object| (object.id(), object))
191 .collect();
192
193 gas.iter()
194 .filter_map(|gas_ref| objects.get(&gas_ref.object_id)?.as_object())
195 .filter_map(|object| get_gas_balance(object).ok())
196 .map(u128::from)
197 .sum()
198 }
199
200 pub fn report_simulation_gas(reported: &mut GasPayment, simulated: &GasPayment, gas_used: u64) {
212 let estimating = reported.budget == 0;
213 *reported = simulated.clone();
214 if estimating {
215 reported.budget = gas_used;
216 }
217 }
218
219 pub fn check_gas_coins_cover_budget_in_simulation(
223 input_objects: &InputObjects,
224 gas: &[ObjectReference],
225 gas_budget: u64,
226 ) -> UserInputResult {
227 let objects: HashMap<_, _> = input_objects
228 .iter()
229 .map(|object| (object.id(), object))
230 .collect();
231
232 let mut gas_balance = 0u128;
233 for gas_ref in gas {
234 let read = objects
235 .get(&gas_ref.object_id)
236 .ok_or(UserInputError::ObjectNotFound {
237 object_id: gas_ref.object_id,
238 version: Some(gas_ref.version),
239 })?;
240 let object = read.as_object().ok_or(UserInputError::MissingGasPayment)?;
243 if !object.is_address_owned() {
244 return Err(UserInputError::GasObjectNotOwnedObject {
245 owner: object.owner,
246 });
247 }
248 gas_balance += get_gas_balance(object)? as u128;
249 }
250
251 if gas_balance < gas_budget as u128 {
252 return Err(UserInputError::GasBalanceTooLow {
253 gas_balance,
254 needed_gas_amount: gas_budget as u128,
255 });
256 }
257
258 Ok(())
259 }
260}