sui_types/gas_model/
gas_common.rs1#![deny(clippy::arithmetic_side_effects)]
5#![deny(clippy::cast_possible_truncation)]
6#![deny(clippy::indexing_slicing)]
7#![deny(clippy::cast_possible_wrap)]
8#![deny(clippy::cast_sign_loss)]
9
10use crate::error::{UserInputError, UserInputResult};
11use crate::transaction::ObjectReadResult;
12use crate::{ObjectID, gas};
13use serde::{Deserialize, Serialize};
14
15pub fn check_gas_objects(gas_objs: &[&ObjectReadResult]) -> UserInputResult {
16 for gas_object in gas_objs {
19 if let Some(obj) = gas_object.as_object() {
22 if !obj.is_address_owned() {
23 return Err(UserInputError::GasObjectNotOwnedObject {
24 owner: obj.owner.clone(),
25 });
26 }
27 } else {
28 return Err(UserInputError::MissingGasPayment);
31 }
32 }
33 Ok(())
34}
35
36pub fn check_gas_data(
37 gas_objs: &[&ObjectReadResult],
38 gas_budget: u64,
39 available_address_balance_gas: u64,
40 min_transaction_cost: u64,
41 max_gas_budget: u64,
42) -> UserInputResult {
43 if gas_budget > max_gas_budget {
45 return Err(UserInputError::GasBudgetTooHigh {
46 gas_budget,
47 max_budget: max_gas_budget,
48 });
49 }
50 if gas_budget < min_transaction_cost {
51 return Err(UserInputError::GasBudgetTooLow {
52 gas_budget,
53 min_budget: min_transaction_cost,
54 });
55 }
56
57 let mut gas_balance = available_address_balance_gas as u128;
59 for gas_obj in gas_objs {
60 gas_balance = gas_balance.saturating_add(gas::get_gas_balance(gas_obj.as_object().ok_or(
62 UserInputError::InvalidGasObject {
63 object_id: gas_obj.id(),
64 },
65 )?)? as u128);
66 }
67 if gas_balance < gas_budget as u128 {
68 Err(UserInputError::GasBalanceTooLow {
69 gas_balance,
70 needed_gas_amount: gas_budget as u128,
71 })
72 } else {
73 Ok(())
74 }
75}
76
77pub fn sender_rebate(storage_rebate: u64, storage_rebate_rate: u64) -> u64 {
80 const BASIS_POINTS: u128 = 10000;
83 let rebate = (storage_rebate as u128)
84 .saturating_mul(storage_rebate_rate as u128)
85 .saturating_add(BASIS_POINTS / 2) / BASIS_POINTS;
87 u64::try_from(rebate).unwrap_or(u64::MAX)
88}
89
90pub fn package_read_internal_gas(size: usize, cost_per_kb: u64) -> u64 {
93 let cost = (size as u128)
95 .saturating_mul(u128::from(cost_per_kb))
96 .div_ceil(1024);
97 u64::try_from(cost).unwrap_or(u64::MAX)
98}
99
100pub fn half_digits_rounding(n: u64) -> u64 {
101 if n < 1000 {
102 return 1000;
103 }
104 let digits = n.ilog10();
105 let drop = digits / 2;
106 let base = 10u64.pow(drop);
107 n.div_ceil(base).saturating_mul(base)
108}
109
110#[derive(Debug, Clone, Serialize, Deserialize)]
111pub struct PerObjectStorage {
112 pub storage_cost: u64,
116 pub storage_rebate: u64,
120 pub new_size: u64,
122}
123
124#[derive(Debug)]
127pub struct StorageGas {
128 per_object_storage: Vec<(ObjectID, PerObjectStorage)>,
130 total_storage_cost: u64,
132 total_storage_rebate: u64,
134 unmetered_storage_rebate: u64,
138 pub storage_gas_price: u64,
140 storage_per_byte_cost: u64,
142}
143
144impl StorageGas {
145 pub fn new(storage_gas_price: u64, storage_per_byte_cost: u64) -> Self {
146 Self {
147 per_object_storage: Vec::new(),
148 total_storage_cost: 0,
149 total_storage_rebate: 0,
150 unmetered_storage_rebate: 0,
151 storage_gas_price,
152 storage_per_byte_cost,
153 }
154 }
155
156 pub fn storage_gas_units(&self) -> u64 {
157 self.total_storage_cost
158 }
159
160 pub fn storage_rebate(&self) -> u64 {
161 self.total_storage_rebate
162 }
163
164 pub fn unmetered_storage_rebate(&self) -> u64 {
165 self.unmetered_storage_rebate
166 }
167
168 pub fn per_object_storage(&self) -> &Vec<(ObjectID, PerObjectStorage)> {
169 &self.per_object_storage
170 }
171
172 pub fn reset(&mut self) {
173 self.per_object_storage = Vec::new();
174 self.total_storage_cost = 0;
175 self.total_storage_rebate = 0;
176 self.unmetered_storage_rebate = 0;
177 }
178
179 pub fn track_mutation(
182 &mut self,
183 object_id: ObjectID,
184 new_size: usize,
185 storage_rebate: u64,
186 unmetered: bool,
187 ) -> Option<u64> {
188 if unmetered {
189 let total = self.unmetered_storage_rebate.checked_add(storage_rebate)?;
190 self.unmetered_storage_rebate = total;
191 return Some(0);
192 }
193
194 let new_size = new_size as u64;
195 let storage_cost = new_size
196 .checked_mul(self.storage_per_byte_cost)?
197 .checked_mul(self.storage_gas_price)?;
198 self.total_storage_cost = self.total_storage_cost.checked_add(storage_cost)?;
199 self.total_storage_rebate = self.total_storage_rebate.checked_add(storage_rebate)?;
200 self.per_object_storage.push((
201 object_id,
202 PerObjectStorage {
203 storage_cost,
204 storage_rebate,
205 new_size,
206 },
207 ));
208 Some(storage_cost)
209 }
210}
211
212#[test]
213fn test_half_digits_rounding() {
214 assert_eq!(half_digits_rounding(0), 1000);
215 assert_eq!(half_digits_rounding(1), 1000);
216 assert_eq!(half_digits_rounding(999), 1000);
217 assert_eq!(half_digits_rounding(1000), 1000);
218 assert_eq!(half_digits_rounding(1001), 1010);
219 assert_eq!(half_digits_rounding(1050), 1050);
220 assert_eq!(half_digits_rounding(1999), 2000);
221 assert_eq!(half_digits_rounding(20_000), 20_000);
222 assert_eq!(half_digits_rounding(20_001), 20_100);
223 assert_eq!(half_digits_rounding(20_500), 20_500);
224 assert_eq!(half_digits_rounding(29_999), 30_000);
225 assert_eq!(half_digits_rounding(300_000), 300_000);
226 assert_eq!(half_digits_rounding(300_001), 300_100);
227 assert_eq!(half_digits_rounding(305_500), 305_500);
228 assert_eq!(half_digits_rounding(305_501), 305_600);
229 assert_eq!(half_digits_rounding(999_999), 1_000_000);
230 assert_eq!(half_digits_rounding(1_000_000), 1_000_000);
231 assert_eq!(half_digits_rounding(1_000_001), 1_001_000);
232 assert_eq!(half_digits_rounding(1_005_000), 1_005_000);
233 assert_eq!(half_digits_rounding(1_005_001), 1_006_000);
234 assert_eq!(half_digits_rounding(1_999_999), 2_000_000);
235 assert_eq!(half_digits_rounding(10_000_001), 10_001_000);
236 assert_eq!(half_digits_rounding(100_000_001), 100_010_000);
237}