tycho_executor/
util.rs

1use std::mem::ManuallyDrop;
2
3use ahash::HashMap;
4use tycho_types::cell::CellTreeStats;
5use tycho_types::models::{
6    IntAddr, ShardIdent, SimpleLib, SizeLimitsConfig, StateInit, StdAddr, WorkchainDescription,
7    WorkchainFormat,
8};
9use tycho_types::num::{VarUint24, VarUint56};
10use tycho_types::prelude::*;
11
12/// Brings [unlikely](core::intrinsics::unlikely) to stable rust.
13#[inline(always)]
14pub(crate) const fn unlikely(b: bool) -> bool {
15    #[allow(clippy::needless_bool, clippy::bool_to_int_with_if)]
16    if (1i32).checked_div(if b { 0 } else { 1 }).is_none() {
17        true
18    } else {
19        false
20    }
21}
22
23#[derive(Default)]
24pub struct StorageStatLimits {
25    pub bit_count: u32,
26    pub cell_count: u32,
27}
28
29impl StorageStatLimits {
30    pub const UNLIMITED: Self = Self {
31        bit_count: u32::MAX,
32        cell_count: u32::MAX,
33    };
34}
35
36pub struct OwnedExtStorageStat {
37    cells: Vec<Cell>,
38    inner: ManuallyDrop<ExtStorageStat<'static>>,
39}
40
41impl OwnedExtStorageStat {
42    pub fn unlimited() -> Self {
43        Self::with_limits(StorageStatLimits::UNLIMITED)
44    }
45
46    pub fn with_limits(limits: StorageStatLimits) -> Self {
47        Self {
48            cells: Vec::new(),
49            inner: ManuallyDrop::new(ExtStorageStat::with_limits(limits)),
50        }
51    }
52
53    pub fn set_unlimited(&mut self) {
54        self.inner.limits = StorageStatLimits::UNLIMITED;
55    }
56
57    pub fn stats(&self) -> CellTreeStats {
58        self.inner.stats()
59    }
60
61    pub fn add_cell(&mut self, cell: Cell) -> bool {
62        if self.inner.visited.contains_key(cell.repr_hash()) {
63            return true;
64        }
65
66        // SAFETY: We will store the cell afterwards so the lifetime of its hash
67        //         will outlive the `inner` object.
68        let cell_ref = unsafe { std::mem::transmute::<&DynCell, &'static DynCell>(cell.as_ref()) };
69        let res = self.inner.add_cell(cell_ref);
70        self.cells.push(cell);
71        res
72    }
73
74    pub fn clear(&mut self) {
75        self.inner.visited.clear();
76        self.inner.cells = 0;
77        self.inner.bits = 0;
78
79        // NOTE: We can clear the cells vector only after clearing the `visited` map.
80        self.cells.clear();
81    }
82}
83
84impl Drop for OwnedExtStorageStat {
85    fn drop(&mut self) {
86        // We must ensure that `inner` is dropped before `cells`.
87        // SAFETY: This is the only place where `inner` is dropped.
88        unsafe { ManuallyDrop::drop(&mut self.inner) };
89    }
90}
91
92#[derive(Default)]
93pub struct ExtStorageStat<'a> {
94    visited: ahash::HashMap<&'a HashBytes, u8>,
95    limits: StorageStatLimits,
96    pub cells: u32,
97    pub bits: u32,
98}
99
100impl<'a> ExtStorageStat<'a> {
101    pub const MAX_ALLOWED_MERKLE_DEPTH: u8 = 2;
102
103    pub fn with_limits(limits: StorageStatLimits) -> Self {
104        Self {
105            visited: ahash::HashMap::default(),
106            limits,
107            cells: 0,
108            bits: 0,
109        }
110    }
111
112    pub fn compute_for_slice(
113        cs: &CellSlice<'a>,
114        limits: StorageStatLimits,
115    ) -> Option<CellTreeStats> {
116        let mut state = Self {
117            visited: ahash::HashMap::default(),
118            limits,
119            cells: 1,
120            bits: cs.size_bits() as u32,
121        };
122
123        for cell in cs.references() {
124            state.add_cell_impl(cell)?;
125        }
126
127        Some(CellTreeStats {
128            bit_count: state.bits as u64,
129            cell_count: state.cells as u64,
130        })
131    }
132
133    pub fn stats(&self) -> CellTreeStats {
134        CellTreeStats {
135            bit_count: self.bits as u64,
136            cell_count: self.cells as u64,
137        }
138    }
139
140    pub fn add_cell(&mut self, cell: &'a DynCell) -> bool {
141        self.add_cell_impl(cell).is_some()
142    }
143
144    fn add_cell_impl(&mut self, cell: &'a DynCell) -> Option<u8> {
145        if let Some(merkle_depth) = self.visited.get(cell.repr_hash()).copied() {
146            return Some(merkle_depth);
147        }
148
149        self.cells = self.cells.checked_add(1)?;
150        self.bits = self.bits.checked_add(cell.bit_len() as u32)?;
151
152        if self.cells > self.limits.cell_count || self.bits > self.limits.bit_count {
153            return None;
154        }
155
156        let mut max_merkle_depth = 0u8;
157        for cell in cell.references() {
158            max_merkle_depth = std::cmp::max(self.add_cell_impl(cell)?, max_merkle_depth);
159        }
160        max_merkle_depth = max_merkle_depth.saturating_add(cell.cell_type().is_merkle() as u8);
161
162        self.visited.insert(cell.repr_hash(), max_merkle_depth);
163        (max_merkle_depth <= Self::MAX_ALLOWED_MERKLE_DEPTH).then_some(max_merkle_depth)
164    }
165}
166
167pub fn new_varuint24_truncate(value: u64) -> VarUint24 {
168    VarUint24::new(std::cmp::min(value, VarUint24::MAX.into_inner() as u64) as _)
169}
170
171pub fn new_varuint56_truncate(value: u64) -> VarUint56 {
172    VarUint56::new(std::cmp::min(value, VarUint56::MAX.into_inner()))
173}
174
175/// Rewrites message source address.
176pub fn check_rewrite_src_addr(my_addr: &StdAddr, addr: &mut Option<IntAddr>) -> bool {
177    match addr {
178        // Replace `addr_none` with the address of the account.
179        None => {
180            *addr = Some(my_addr.clone().into());
181            true
182        }
183        // Only allow `addr_std` that must be equal to the account address.
184        Some(IntAddr::Std(addr)) if addr == my_addr => true,
185        // All other addresses are considered invalid.
186        Some(_) => false,
187    }
188}
189
190/// Rewrite message destination address.
191pub fn check_rewrite_dst_addr(
192    workchains: &HashMap<i32, WorkchainDescription>,
193    addr: &mut IntAddr,
194) -> bool {
195    const STD_WORKCHAINS: std::ops::Range<i32> = -128..128;
196    const STD_ADDR_LEN: u16 = 256;
197
198    // Check destination workchain.
199    let mut can_rewrite = false;
200    let workchain = match addr {
201        IntAddr::Std(addr) => {
202            if addr.anycast.is_some() {
203                // Anycasts are not supported for now.
204                return false;
205            }
206
207            addr.workchain as i32
208        }
209        IntAddr::Var(addr) => {
210            if addr.anycast.is_some() {
211                // Anycasts are not supported for now.
212                return false;
213            }
214
215            // `addr_var` of len 256 in a valid workchains range
216            // can be rewritten to `addr_std` if needed.
217            can_rewrite = addr.address_len.into_inner() == STD_ADDR_LEN
218                && STD_WORKCHAINS.contains(&addr.workchain);
219
220            addr.workchain
221        }
222    };
223
224    if workchain != ShardIdent::MASTERCHAIN.workchain() {
225        let Some(workchain) = workchains.get(&workchain) else {
226            // Cannot send message to an unknown workchain.
227            return false;
228        };
229
230        if !workchain.accept_msgs {
231            // Cannot send messages to disabled workchains.
232            return false;
233        }
234
235        match (&workchain.format, &*addr) {
236            // `addr_std` is the default address format for basic workchains.
237            (WorkchainFormat::Basic(_), IntAddr::Std(_)) => {}
238            // `addr_var` can be rewritten to `addr_std` for basic workchains.
239            (WorkchainFormat::Basic(_), IntAddr::Var(_)) if can_rewrite => {}
240            // `addr_std` can be used for extended workchains if the length is ok.
241            (WorkchainFormat::Extended(f), IntAddr::Std(_)) if f.check_addr_len(STD_ADDR_LEN) => {}
242            // `addr_var` can be used for extended workchains if the length is ok.
243            (WorkchainFormat::Extended(f), IntAddr::Var(a))
244                if f.check_addr_len(a.address_len.into_inner()) => {}
245            // All other combinations are invalid.
246            _ => return false,
247        }
248    }
249
250    // Rewrite if needed.
251    if can_rewrite {
252        if let IntAddr::Var(var) = addr {
253            debug_assert!(STD_WORKCHAINS.contains(&var.workchain));
254            debug_assert_eq!(var.address_len.into_inner(), STD_ADDR_LEN);
255
256            // Copy high address bytes into the target address.
257            let len = std::cmp::min(var.address.len(), 32);
258            let mut address = [0; 32];
259            address[..len].copy_from_slice(&var.address[..len]);
260
261            // Set type as `addr_std`.
262            *addr = IntAddr::Std(StdAddr::new(var.workchain as i8, HashBytes(address)));
263        }
264    }
265
266    // Done
267    true
268}
269
270pub enum StateLimitsResult {
271    Unchanged,
272    Exceeds,
273    Fits,
274}
275
276/// NOTE: `stats_cache` is updated only when `StateLimitsResult::Fits` is returned.
277pub fn check_state_limits_diff(
278    old_state: &StateInit,
279    new_state: &StateInit,
280    limits: &SizeLimitsConfig,
281    is_masterchain: bool,
282    stats_cache: &mut Option<OwnedExtStorageStat>,
283) -> StateLimitsResult {
284    /// Returns (code, data, libs)
285    fn unpack_state(state: &StateInit) -> (Option<&'_ Cell>, Option<&'_ Cell>, &'_ StateLibs) {
286        (state.code.as_ref(), state.data.as_ref(), &state.libraries)
287    }
288
289    let (old_code, old_data, old_libs) = unpack_state(old_state);
290    let (new_code, new_data, new_libs) = unpack_state(new_state);
291
292    // Early exit if nothing changed.
293    let libs_changed = old_libs != new_libs;
294    if old_code == new_code && old_data == new_data && !libs_changed {
295        return StateLimitsResult::Unchanged;
296    }
297
298    // Check public libraries (only for masterchain, because in other workchains all
299    // public libraries are ignored and not tracked).
300    let check_public_libs = is_masterchain && libs_changed;
301
302    check_state_limits(
303        new_code,
304        new_data,
305        new_libs,
306        limits,
307        check_public_libs,
308        stats_cache,
309    )
310}
311
312pub fn check_state_limits(
313    code: Option<&Cell>,
314    data: Option<&Cell>,
315    libs: &StateLibs,
316    limits: &SizeLimitsConfig,
317    check_public_libs: bool,
318    stats_cache: &mut Option<OwnedExtStorageStat>,
319) -> StateLimitsResult {
320    // Compute storage stats.
321    let mut stats = OwnedExtStorageStat::with_limits(StorageStatLimits {
322        bit_count: limits.max_acc_state_bits,
323        cell_count: limits.max_acc_state_cells,
324    });
325
326    if let Some(code) = code {
327        if !stats.add_cell(code.clone()) {
328            return StateLimitsResult::Exceeds;
329        }
330    }
331
332    if let Some(data) = data {
333        if !stats.add_cell(data.clone()) {
334            return StateLimitsResult::Exceeds;
335        }
336    }
337
338    if let Some(libs) = libs.root() {
339        if !stats.add_cell(libs.clone()) {
340            return StateLimitsResult::Exceeds;
341        }
342    }
343
344    // Check public libraries (only for masterchain, because in other workchains all
345    // public libraries are ignored and not tracked).
346    if check_public_libs {
347        let mut public_libs_count = 0;
348        for lib in libs.values() {
349            let Ok(lib) = lib else {
350                return StateLimitsResult::Exceeds;
351            };
352
353            public_libs_count += lib.public as usize;
354            if public_libs_count > limits.max_acc_public_libraries as usize {
355                return StateLimitsResult::Exceeds;
356            }
357        }
358    }
359
360    // Ok
361    *stats_cache = Some(stats);
362    StateLimitsResult::Fits
363}
364
365type StateLibs = Dict<HashBytes, SimpleLib>;
366
367pub const fn shift_ceil_price(value: u128) -> u128 {
368    let r = value & 0xffff != 0;
369    (value >> 16) + r as u128
370}
371
372#[cfg(test)]
373mod tests {
374    use super::*;
375
376    #[test]
377    fn miri_check() {
378        // Drop is ok.
379        let mut owned = OwnedExtStorageStat::with_limits(StorageStatLimits {
380            bit_count: 1000,
381            cell_count: 1000,
382        });
383
384        fn fill(owned: &mut OwnedExtStorageStat) {
385            let res = owned.add_cell(Cell::empty_cell());
386            assert!(res);
387            assert_eq!(owned.inner.bits, 0);
388            assert_eq!(owned.inner.cells, 1);
389
390            let res = owned.add_cell({
391                let mut b = CellBuilder::new();
392                b.store_u32(123).unwrap();
393                b.store_reference(Cell::empty_cell()).unwrap();
394                b.build().unwrap()
395            });
396            assert!(res);
397            assert_eq!(owned.inner.bits, 32);
398            assert_eq!(owned.inner.cells, 2);
399
400            // Create the same cell as above to possibly trigger a dangling pointer access.
401            let res = owned.add_cell({
402                let mut b = CellBuilder::new();
403                b.store_u32(123).unwrap();
404                b.store_reference(Cell::empty_cell()).unwrap();
405                b.build().unwrap()
406            });
407            assert!(res);
408            assert_eq!(owned.inner.bits, 32);
409            assert_eq!(owned.inner.cells, 2);
410        }
411
412        fill(&mut owned);
413        drop(owned);
414
415        // Clear is ok.
416        let mut owned = OwnedExtStorageStat::with_limits(StorageStatLimits {
417            bit_count: 1000,
418            cell_count: 1000,
419        });
420
421        fill(&mut owned);
422        owned.clear();
423        fill(&mut owned);
424    }
425}