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