1use ahash::{HashMap, HashSet};
2use anyhow::Result;
3use tycho_types::error::Error;
4use tycho_types::models::{
5 BlockchainConfig, BlockchainConfigParams, BurningConfig, GasLimitsPrices, GlobalVersion,
6 MsgForwardPrices, SizeLimitsConfig, StdAddr, StorageInfo, StoragePrices, WorkchainDescription,
7};
8use tycho_types::num::Tokens;
9use tycho_types::prelude::*;
10use tycho_vm::{GasParams, UnpackedConfig};
11
12use crate::util::shift_ceil_price;
13
14pub struct ParsedConfig {
16 pub blackhole_addr: Option<HashBytes>,
17 pub mc_gas_prices: GasLimitsPrices,
18 pub gas_prices: GasLimitsPrices,
19 pub mc_fwd_prices: MsgForwardPrices,
20 pub fwd_prices: MsgForwardPrices,
21 pub size_limits: SizeLimitsConfig,
22 pub storage_prices: Vec<StoragePrices>,
23 pub global_id: i32,
24 pub global: GlobalVersion,
25 pub workchains: HashMap<i32, WorkchainDescription>,
26 pub special_accounts: HashSet<HashBytes>,
27 pub raw: BlockchainConfig,
28 pub unpacked: UnpackedConfig,
29}
30
31impl ParsedConfig {
32 pub const DEFAULT_SIZE_LIMITS_CONFIG: SizeLimitsConfig = SizeLimitsConfig {
33 max_msg_bits: 1 << 21,
34 max_msg_cells: 1 << 13,
35 max_library_cells: 1000,
36 max_vm_data_depth: 512,
37 max_ext_msg_size: 65535,
38 max_ext_msg_depth: 512,
39 max_acc_state_cells: 1 << 16,
40 max_acc_state_bits: (1 << 16) * 1023,
41 max_acc_public_libraries: 256,
42 defer_out_queue_size_limit: 256,
43 };
44
45 pub fn parse(config: BlockchainConfig, now: u32) -> Result<Self, Error> {
49 thread_local! {
50 static SIZE_LIMITS: Cell =
51 CellBuilder::build_from(ParsedConfig::DEFAULT_SIZE_LIMITS_CONFIG).unwrap();
52 }
53
54 let dict = config.params.as_dict();
55
56 let burning = dict.get(5).and_then(|cell| match cell {
57 Some(cell) => cell.parse::<BurningConfig>(),
58 None => Ok(BurningConfig::default()),
59 })?;
60
61 let Some(mc_gas_prices_raw) = dict.get(20)? else {
62 return Err(Error::CellUnderflow);
63 };
64 let Some(gas_prices_raw) = dict.get(21)? else {
65 return Err(Error::CellUnderflow);
66 };
67
68 let Some(mc_fwd_prices_raw) = dict.get(24)? else {
69 return Err(Error::CellUnderflow);
70 };
71 let Some(fwd_prices_raw) = dict.get(25)? else {
72 return Err(Error::CellUnderflow);
73 };
74
75 let ParsedStoragePrices {
76 latest_storage_prices,
77 storage_prices,
78 } = parse_storage_prices(&config.params, now)?;
79
80 let workchains_dict = config.params.get_workchains()?;
81 let mut workchains = HashMap::<i32, WorkchainDescription>::default();
82 for entry in workchains_dict.iter() {
83 let (workchain, desc) = entry?;
84 workchains.insert(workchain, desc);
85 }
86
87 let global_id_raw = dict.get(19)?;
88 let global = config.params.get_global_version()?;
89
90 let size_limits_raw = dict
91 .get(43)?
92 .unwrap_or_else(|| SIZE_LIMITS.with(Cell::clone));
93
94 let mut special_accounts = HashSet::default();
95 for addr in config.params.get_fundamental_addresses()?.keys() {
96 special_accounts.insert(addr?);
97 }
98
99 Ok(Self {
100 blackhole_addr: burning.blackhole_addr,
101 mc_gas_prices: mc_gas_prices_raw.parse::<GasLimitsPrices>()?,
102 gas_prices: gas_prices_raw.parse::<GasLimitsPrices>()?,
103 mc_fwd_prices: mc_fwd_prices_raw.parse::<MsgForwardPrices>()?,
104 fwd_prices: fwd_prices_raw.parse::<MsgForwardPrices>()?,
105 size_limits: size_limits_raw.parse::<SizeLimitsConfig>()?,
106 storage_prices,
107 global_id: match &global_id_raw {
108 None => 0, Some(param) => param.parse::<i32>()?,
110 },
111 global,
112 workchains,
113 special_accounts,
114 raw: config,
115 unpacked: UnpackedConfig {
116 latest_storage_prices,
117 global_id: global_id_raw,
118 mc_gas_prices: Some(mc_gas_prices_raw),
119 gas_prices: Some(gas_prices_raw),
120 mc_fwd_prices: Some(mc_fwd_prices_raw),
121 fwd_prices: Some(fwd_prices_raw),
122 size_limits_config: Some(size_limits_raw),
123 },
124 })
125 }
126
127 pub fn update_storage_prices(&mut self, now: u32) -> Result<(), Error> {
128 let ParsedStoragePrices {
129 latest_storage_prices,
130 storage_prices,
131 } = parse_storage_prices(&self.raw.params, now)?;
132
133 self.storage_prices = storage_prices;
134 self.unpacked.latest_storage_prices = latest_storage_prices;
135 Ok(())
136 }
137
138 pub fn is_blackhole(&self, addr: &StdAddr) -> bool {
139 match &self.blackhole_addr {
140 Some(blackhole_addr) => addr.is_masterchain() && addr.address == *blackhole_addr,
141 None => false,
142 }
143 }
144
145 pub fn is_special(&self, addr: &StdAddr) -> bool {
146 addr.is_masterchain()
147 && (self.special_accounts.contains(&addr.address) || addr.address == self.raw.address)
148 }
149
150 pub fn fwd_prices(&self, is_masterchain: bool) -> &MsgForwardPrices {
151 if is_masterchain {
152 &self.mc_fwd_prices
153 } else {
154 &self.fwd_prices
155 }
156 }
157
158 pub fn gas_prices(&self, is_masterchain: bool) -> &GasLimitsPrices {
159 if is_masterchain {
160 &self.mc_gas_prices
161 } else {
162 &self.gas_prices
163 }
164 }
165
166 pub fn compute_storage_fees(
171 &self,
172 storage_stat: &StorageInfo,
173 now: u32,
174 is_special: bool,
175 is_masterchain: bool,
176 ) -> Tokens {
177 if now <= storage_stat.last_paid || storage_stat.last_paid == 0 || is_special {
183 return Tokens::ZERO;
184 }
185
186 let Some(oldest_prices) = self.storage_prices.first() else {
187 return Tokens::ZERO;
189 };
190 if now <= oldest_prices.utime_since {
191 return Tokens::ZERO;
193 }
194
195 let get_prices = if is_masterchain {
196 |prices: &StoragePrices| (prices.mc_bit_price_ps, prices.mc_cell_price_ps)
197 } else {
198 |prices: &StoragePrices| (prices.bit_price_ps, prices.cell_price_ps)
199 };
200
201 let mut total = 0u128;
202
203 let mut upto = now;
205 for prices in self.storage_prices.iter().rev() {
206 if prices.utime_since > upto {
207 continue;
208 }
209
210 let delta = upto - std::cmp::max(prices.utime_since, storage_stat.last_paid);
212
213 let (bit_price, cell_price) = get_prices(prices);
215 let fee = (bit_price as u128 * storage_stat.used.bits.into_inner() as u128)
216 .saturating_add(cell_price as u128 * storage_stat.used.cells.into_inner() as u128)
217 .saturating_mul(delta as u128);
218 total = total.saturating_add(fee);
219
220 upto = prices.utime_since;
222 if upto <= storage_stat.last_paid {
223 break;
224 }
225 }
226
227 Tokens::new(shift_ceil_price(total))
229 }
230
231 pub fn compute_gas_params(
233 &self,
234 account_balance: &Tokens,
235 msg_balance_remaining: &Tokens,
236 is_special: bool,
237 is_masterchain: bool,
238 is_tx_ordinary: bool,
239 is_in_msg_external: bool,
240 ) -> GasParams {
241 let prices = self.gas_prices(is_masterchain);
242
243 let gas_max = if is_special {
244 prices.special_gas_limit
245 } else {
246 gas_bought_for(prices, account_balance)
247 };
248
249 let gas_limit = if !is_tx_ordinary || is_special {
250 gas_max
252 } else {
253 std::cmp::min(gas_bought_for(prices, msg_balance_remaining), gas_max)
257 };
258
259 let gas_credit = if is_tx_ordinary && is_in_msg_external {
260 std::cmp::min(prices.gas_credit, gas_max)
263 } else {
264 0
265 };
266
267 GasParams {
268 max: gas_max,
269 limit: gas_limit,
270 credit: gas_credit,
271 price: prices.gas_price,
272 }
273 }
274}
275
276fn parse_storage_prices(
277 config: &BlockchainConfigParams,
278 now: u32,
279) -> Result<ParsedStoragePrices, Error> {
280 let storage_prices_dict = RawDict::<32>::from(config.as_dict().get(18)?);
281 let mut storage_prices = Vec::new();
282 let mut latest_storage_prices = None;
283 for value in storage_prices_dict.values_owned() {
284 let value = value?;
285 let prices = StoragePrices::load_from(&mut value.0.apply_allow_exotic(&value.1))?;
286 if prices.utime_since <= now {
287 latest_storage_prices = Some(value);
288 }
289
290 storage_prices.push(prices);
291 }
292
293 Ok(ParsedStoragePrices {
294 latest_storage_prices,
295 storage_prices,
296 })
297}
298
299struct ParsedStoragePrices {
300 latest_storage_prices: Option<CellSliceParts>,
301 storage_prices: Vec<StoragePrices>,
302}
303
304fn gas_bought_for(prices: &GasLimitsPrices, balance: &Tokens) -> u64 {
305 let balance = balance.into_inner();
306 if balance == 0 || balance < prices.flat_gas_price as u128 {
307 return 0;
308 }
309
310 let max_gas_threshold = if prices.gas_limit > prices.flat_gas_limit {
311 shift_ceil_price(
312 (prices.gas_price as u128) * (prices.gas_limit - prices.flat_gas_limit) as u128,
313 )
314 .saturating_add(prices.flat_gas_price as u128)
315 } else {
316 prices.flat_gas_price as u128
317 };
318
319 if balance >= max_gas_threshold || prices.gas_price == 0 {
320 return prices.gas_limit;
321 }
322
323 let mut res = ((balance - prices.flat_gas_price as u128) << 16) / (prices.gas_price as u128);
324 res = res.saturating_add(prices.flat_gas_limit as u128);
325
326 match res.try_into() {
327 Ok(limit) => limit,
328 Err(_) => u64::MAX,
329 }
330}