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 half_digits_rounding(n: u64) -> u64 {
91 if n < 1000 {
92 return 1000;
93 }
94 let digits = n.ilog10();
95 let drop = digits / 2;
96 let base = 10u64.pow(drop);
97 n.div_ceil(base).saturating_mul(base)
98}
99
100#[derive(Debug, Clone, Serialize, Deserialize)]
101pub struct PerObjectStorage {
102 pub storage_cost: u64,
106 pub storage_rebate: u64,
110 pub new_size: u64,
112}
113
114#[derive(Debug)]
117pub struct StorageGas {
118 per_object_storage: Vec<(ObjectID, PerObjectStorage)>,
120 total_storage_cost: u64,
122 total_storage_rebate: u64,
124 unmetered_storage_rebate: u64,
128 pub storage_gas_price: u64,
130 storage_per_byte_cost: u64,
132}
133
134impl StorageGas {
135 pub fn new(storage_gas_price: u64, storage_per_byte_cost: u64) -> Self {
136 Self {
137 per_object_storage: Vec::new(),
138 total_storage_cost: 0,
139 total_storage_rebate: 0,
140 unmetered_storage_rebate: 0,
141 storage_gas_price,
142 storage_per_byte_cost,
143 }
144 }
145
146 pub fn storage_gas_units(&self) -> u64 {
147 self.total_storage_cost
148 }
149
150 pub fn storage_rebate(&self) -> u64 {
151 self.total_storage_rebate
152 }
153
154 pub fn unmetered_storage_rebate(&self) -> u64 {
155 self.unmetered_storage_rebate
156 }
157
158 pub fn per_object_storage(&self) -> &Vec<(ObjectID, PerObjectStorage)> {
159 &self.per_object_storage
160 }
161
162 pub fn reset(&mut self) {
163 self.per_object_storage = Vec::new();
164 self.total_storage_cost = 0;
165 self.total_storage_rebate = 0;
166 self.unmetered_storage_rebate = 0;
167 }
168
169 pub fn track_mutation(
172 &mut self,
173 object_id: ObjectID,
174 new_size: usize,
175 storage_rebate: u64,
176 unmetered: bool,
177 ) -> Option<u64> {
178 if unmetered {
179 let total = self.unmetered_storage_rebate.checked_add(storage_rebate)?;
180 self.unmetered_storage_rebate = total;
181 return Some(0);
182 }
183
184 let new_size = new_size as u64;
185 let storage_cost = new_size
186 .checked_mul(self.storage_per_byte_cost)?
187 .checked_mul(self.storage_gas_price)?;
188 self.total_storage_cost = self.total_storage_cost.checked_add(storage_cost)?;
189 self.total_storage_rebate = self.total_storage_rebate.checked_add(storage_rebate)?;
190 self.per_object_storage.push((
191 object_id,
192 PerObjectStorage {
193 storage_cost,
194 storage_rebate,
195 new_size,
196 },
197 ));
198 Some(storage_cost)
199 }
200}
201
202#[test]
203fn test_half_digits_rounding() {
204 assert_eq!(half_digits_rounding(0), 1000);
205 assert_eq!(half_digits_rounding(1), 1000);
206 assert_eq!(half_digits_rounding(999), 1000);
207 assert_eq!(half_digits_rounding(1000), 1000);
208 assert_eq!(half_digits_rounding(1001), 1010);
209 assert_eq!(half_digits_rounding(1050), 1050);
210 assert_eq!(half_digits_rounding(1999), 2000);
211 assert_eq!(half_digits_rounding(20_000), 20_000);
212 assert_eq!(half_digits_rounding(20_001), 20_100);
213 assert_eq!(half_digits_rounding(20_500), 20_500);
214 assert_eq!(half_digits_rounding(29_999), 30_000);
215 assert_eq!(half_digits_rounding(300_000), 300_000);
216 assert_eq!(half_digits_rounding(300_001), 300_100);
217 assert_eq!(half_digits_rounding(305_500), 305_500);
218 assert_eq!(half_digits_rounding(305_501), 305_600);
219 assert_eq!(half_digits_rounding(999_999), 1_000_000);
220 assert_eq!(half_digits_rounding(1_000_000), 1_000_000);
221 assert_eq!(half_digits_rounding(1_000_001), 1_001_000);
222 assert_eq!(half_digits_rounding(1_005_000), 1_005_000);
223 assert_eq!(half_digits_rounding(1_005_001), 1_006_000);
224 assert_eq!(half_digits_rounding(1_999_999), 2_000_000);
225 assert_eq!(half_digits_rounding(10_000_001), 10_001_000);
226 assert_eq!(half_digits_rounding(100_000_001), 100_010_000);
227}