1use std::rc::Rc;
2use std::sync::Arc;
3
4use num_bigint::{BigInt, Sign};
5use sha2::Digest;
6use tycho_types::error::Error;
7use tycho_types::models::{
8 BlockchainConfigParams, CurrencyCollection, IntAddr, IntMsgInfo, MsgType, StateInit,
9};
10use tycho_types::num::Tokens;
11use tycho_types::prelude::*;
12
13use crate::error::VmResult;
14use crate::saferc::{SafeDelete, SafeRc};
15use crate::stack::{RcStackValue, Stack, Tuple};
16use crate::util::OwnedCellSlice;
17
18#[derive(Debug, Copy, Clone, Eq, PartialEq)]
20pub enum VmVersion {
21 Everscale(u32),
22 Ton(u32),
23}
24
25impl VmVersion {
26 pub const LATEST_TON: Self = Self::Ton(12);
27
28 pub fn is_ton<R: std::ops::RangeBounds<u32>>(&self, range: R) -> bool {
29 matches!(self, Self::Ton(version) if range.contains(version))
30 }
31
32 pub fn require_ton<R: std::ops::RangeBounds<u32>>(&self, range: R) -> VmResult<()> {
33 vm_ensure!(self.is_ton(range), InvalidOpcode);
34 Ok(())
35 }
36}
37
38pub trait SmcInfo {
40 fn version(&self) -> VmVersion;
41
42 fn build_c7(&self) -> SafeRc<Tuple>;
43}
44
45impl<T: SmcInfo + ?Sized> SmcInfo for &'_ T {
46 #[inline]
47 fn version(&self) -> VmVersion {
48 T::version(self)
49 }
50
51 #[inline]
52 fn build_c7(&self) -> SafeRc<Tuple> {
53 T::build_c7(self)
54 }
55}
56
57impl<T: SmcInfo + ?Sized> SmcInfo for Box<T> {
58 #[inline]
59 fn version(&self) -> VmVersion {
60 T::version(self)
61 }
62
63 #[inline]
64 fn build_c7(&self) -> SafeRc<Tuple> {
65 T::build_c7(self)
66 }
67}
68
69impl<T: SmcInfo + ?Sized> SmcInfo for Rc<T> {
70 #[inline]
71 fn version(&self) -> VmVersion {
72 T::version(self)
73 }
74
75 #[inline]
76 fn build_c7(&self) -> SafeRc<Tuple> {
77 T::build_c7(self)
78 }
79}
80
81impl<T: SmcInfo + ?Sized> SmcInfo for Arc<T> {
82 #[inline]
83 fn version(&self) -> VmVersion {
84 T::version(self)
85 }
86
87 #[inline]
88 fn build_c7(&self) -> SafeRc<Tuple> {
89 T::build_c7(self)
90 }
91}
92
93impl<T: SmcInfo + SafeDelete + ?Sized> SmcInfo for SafeRc<T> {
94 #[inline]
95 fn version(&self) -> VmVersion {
96 T::version(self)
97 }
98
99 #[inline]
100 fn build_c7(&self) -> SafeRc<Tuple> {
101 T::build_c7(self)
102 }
103}
104
105#[derive(Default, Debug, Clone)]
107pub struct SmcInfoBase {
108 pub now: u32,
110 pub block_lt: u64,
112 pub tx_lt: u64,
114 pub rand_seed: HashBytes,
116 pub account_balance: CurrencyCollection,
118 pub addr: IntAddr,
120 pub config: Option<BlockchainConfigParams>,
122}
123
124impl SmcInfoBase {
125 pub const MAGIC: u32 = 0x076ef1ea;
126
127 pub const ACTIONS_IDX: usize = 1;
128 pub const MSGS_SENT_IDX: usize = 2;
129 pub const UNIX_TIME_IDX: usize = 3;
130 pub const BLOCK_LT_IDX: usize = 4;
131 pub const TX_LT_IDX: usize = 5;
132 pub const RANDSEED_IDX: usize = 6;
133 pub const BALANCE_IDX: usize = 7;
134 pub const MYADDR_IDX: usize = 8;
135 pub const CONFIG_IDX: usize = 9;
136
137 const C7_ITEM_COUNT: usize = 10;
138
139 pub fn new() -> Self {
140 Self::default()
141 }
142
143 pub fn with_now(mut self, now: u32) -> Self {
144 self.now = now;
145 self
146 }
147
148 pub fn with_block_lt(mut self, block_lt: u64) -> Self {
149 self.block_lt = block_lt;
150 self
151 }
152
153 pub fn with_tx_lt(mut self, tx_lt: u64) -> Self {
154 self.tx_lt = tx_lt;
155 self
156 }
157
158 pub fn with_raw_rand_seed(mut self, raw_rand_seed: HashBytes) -> Self {
159 self.rand_seed = raw_rand_seed;
160 self
161 }
162
163 pub fn with_mixed_rand_seed(mut self, block_seed: &HashBytes, account: &HashBytes) -> Self {
164 if *block_seed == HashBytes::ZERO {
165 self.rand_seed = HashBytes::ZERO;
166 } else {
167 let mut hasher = sha2::Sha256::new();
168 hasher.update(block_seed.as_array());
169 hasher.update(account.as_array());
170 self.rand_seed = HashBytes(hasher.finalize().into());
171 }
172 self
173 }
174
175 pub fn with_account_balance(mut self, balance: CurrencyCollection) -> Self {
176 self.account_balance = balance;
177 self
178 }
179
180 pub fn with_account_addr(mut self, addr: IntAddr) -> Self {
181 self.addr = addr;
182 self
183 }
184
185 pub fn with_config(mut self, params: BlockchainConfigParams) -> Self {
186 self.config = Some(params);
187 self
188 }
189
190 pub fn require_ton_v4(self) -> SmcInfoTonV4 {
191 SmcInfoTonV4 {
192 base: self,
193 code: None,
194 message_balance: CurrencyCollection::ZERO,
195 storage_fees: Tokens::ZERO,
196 prev_blocks_info: None,
197 }
198 }
199
200 fn write_items(&self, items: &mut Tuple) {
201 items.push(SafeRc::new_dyn_value(BigInt::from(Self::MAGIC)));
203 items.push(Stack::make_zero());
205 items.push(Stack::make_zero());
207 items.push(SafeRc::new_dyn_value(BigInt::from(self.now)));
209 items.push(SafeRc::new_dyn_value(BigInt::from(self.block_lt)));
211 items.push(SafeRc::new_dyn_value(BigInt::from(self.tx_lt)));
213 items.push(SafeRc::new_dyn_value(BigInt::from_bytes_be(
215 Sign::Plus,
216 self.rand_seed.as_slice(),
217 )));
218 items.push(balance_as_tuple(&self.account_balance).into_dyn_value());
220 items.push(SafeRc::new_dyn_value(OwnedCellSlice::new_allow_exotic(
222 CellBuilder::build_from(&self.addr).unwrap(),
223 )));
224 items.push(
226 match self
227 .config
228 .as_ref()
229 .and_then(|c| c.as_dict().root().as_ref())
230 {
231 None => Stack::make_null(),
232 Some(config_root) => SafeRc::new_dyn_value(config_root.clone()),
233 },
234 );
235 }
236}
237
238impl SmcInfo for SmcInfoBase {
239 fn version(&self) -> VmVersion {
240 VmVersion::Ton(1)
241 }
242
243 fn build_c7(&self) -> SafeRc<Tuple> {
244 let mut t1 = Vec::with_capacity(Self::C7_ITEM_COUNT);
245 self.write_items(&mut t1);
246 SafeRc::new(vec![SafeRc::new_dyn_value(t1)])
247 }
248}
249
250#[derive(Default, Debug, Clone)]
252pub struct SmcInfoTonV4 {
253 pub base: SmcInfoBase,
255 pub code: Option<Cell>,
257 pub message_balance: CurrencyCollection,
259 pub storage_fees: Tokens,
261 pub prev_blocks_info: Option<SafeRc<Tuple>>,
263}
264
265impl SmcInfoTonV4 {
266 pub const MYCODE_IDX: usize = 10;
267 pub const IN_MSG_VALUE_IDX: usize = 11;
268 pub const STORAGE_FEE_IDX: usize = 12;
269 pub const PREV_BLOCKS_IDX: usize = 13;
270
271 const C7_ITEM_COUNT: usize = SmcInfoBase::C7_ITEM_COUNT + 4;
272
273 pub fn with_code(mut self, code: Cell) -> Self {
274 self.code = Some(code);
275 self
276 }
277
278 pub fn with_message_balance(mut self, balance: CurrencyCollection) -> Self {
279 self.message_balance = balance;
280 self
281 }
282
283 pub fn with_storage_fees(mut self, storage_fees: Tokens) -> Self {
284 self.storage_fees = storage_fees;
285 self
286 }
287
288 pub fn with_prev_blocks_info(mut self, prev_blocks_info: SafeRc<Tuple>) -> Self {
289 self.prev_blocks_info = Some(prev_blocks_info);
290 self
291 }
292
293 pub fn require_ton_v6(self) -> SmcInfoTonV6 {
294 SmcInfoTonV6 {
295 base: self,
296 unpacked_config: None,
297 due_payment: Tokens::ZERO,
298 }
299 }
300
301 fn write_items(&self, items: &mut Tuple) {
302 self.base.write_items(items);
304 items.push(match self.code.clone() {
306 None => Stack::make_null(),
307 Some(code) => SafeRc::new_dyn_value(code),
308 });
309 items.push(balance_as_tuple(&self.message_balance).into_dyn_value());
311 items.push(SafeRc::new_dyn_value(BigInt::from(
313 self.storage_fees.into_inner(),
314 )));
315 match self.prev_blocks_info.clone() {
319 None => items.push(Stack::make_null()),
320 Some(info) => items.push(info.into_dyn_value()),
321 }
322 }
323}
324
325impl SmcInfo for SmcInfoTonV4 {
326 fn version(&self) -> VmVersion {
327 VmVersion::Ton(4)
328 }
329
330 fn build_c7(&self) -> SafeRc<Tuple> {
331 let mut t1 = Vec::with_capacity(Self::C7_ITEM_COUNT);
332 self.write_items(&mut t1);
333 SafeRc::new(vec![SafeRc::new_dyn_value(t1)])
334 }
335}
336
337#[derive(Default, Debug, Clone)]
339pub struct SmcInfoTonV6 {
340 pub base: SmcInfoTonV4,
342 pub unpacked_config: Option<SafeRc<Tuple>>,
344 pub due_payment: Tokens,
346 }
348
349impl SmcInfoTonV6 {
350 pub const PARSED_CONFIG_IDX: usize = 14;
351 pub const STORAGE_DEBT_IDX: usize = 15;
352 pub const PRECOMPILED_GAS_IDX: usize = 16;
353
354 const C7_ITEM_COUNT: usize = SmcInfoTonV4::C7_ITEM_COUNT + 3;
355
356 pub fn unpack_config(
357 params: &BlockchainConfigParams,
358 now: u32,
359 ) -> Result<SafeRc<Tuple>, Error> {
360 let get_param = |id| {
361 let Some(value) = params.as_dict().get(id)? else {
362 return Ok(Stack::make_null());
363 };
364 Ok(SafeRc::new_dyn_value(OwnedCellSlice::new_allow_exotic(
365 value,
366 )))
367 };
368
369 Ok(SafeRc::new(vec![
370 match Self::find_storage_prices(params, now)? {
371 None => Stack::make_null(),
372 Some(prices) => SafeRc::new_dyn_value(OwnedCellSlice::from(prices)),
373 }, get_param(19)?, get_param(20)?, get_param(21)?, get_param(24)?, get_param(25)?, get_param(43)?, ]))
381 }
382
383 pub fn unpack_config_partial(
384 params: &BlockchainConfigParams,
385 now: u32,
386 ) -> Result<UnpackedConfig, Error> {
387 let get_param = |id| params.as_dict().get(id);
388
389 Ok(UnpackedConfig {
390 latest_storage_prices: Self::find_storage_prices(params, now)?,
391 global_id: get_param(19)?,
392 mc_gas_prices: get_param(20)?,
393 gas_prices: get_param(21)?,
394 mc_fwd_prices: get_param(24)?,
395 fwd_prices: get_param(25)?,
396 size_limits_config: get_param(43)?,
397 })
398 }
399
400 fn find_storage_prices(
401 params: &BlockchainConfigParams,
402 now: u32,
403 ) -> Result<Option<CellSliceParts>, Error> {
404 let prices = RawDict::<32>::from(params.get_storage_prices()?.into_root());
405 for value in prices.values_owned().reversed() {
406 let value = value?;
407
408 let utime_since = value.0.apply_allow_exotic(&value.1).load_u32()?;
410 if now < utime_since {
411 continue;
412 }
413 return Ok(Some(value));
414 }
415 Ok(None)
416 }
417
418 pub fn with_unpacked_config(mut self, config: SafeRc<Tuple>) -> Self {
419 self.unpacked_config = Some(config);
420 self
421 }
422
423 pub fn fill_unpacked_config(mut self) -> Result<Self, Error> {
424 let Some(params) = &self.base.base.config else {
425 return Err(Error::CellUnderflow);
426 };
427 self.unpacked_config = Some(Self::unpack_config(params, self.base.base.now)?);
428 Ok(self)
429 }
430
431 pub fn with_due_payment(mut self, due_payment: Tokens) -> Self {
432 self.due_payment = due_payment;
433 self
434 }
435
436 pub fn require_ton_v11(self) -> SmcInfoTonV11 {
437 SmcInfoTonV11 {
438 base: self,
439 in_msg: None,
440 }
441 }
442
443 fn write_items(&self, items: &mut Tuple) {
444 self.base.write_items(items);
446 items.push(match &self.unpacked_config {
448 None => Stack::make_null(),
449 Some(config) => config.clone().into_dyn_value(),
450 });
451 items.push(SafeRc::new_dyn_value(BigInt::from(
453 self.due_payment.into_inner(),
454 )));
455 items.push(Stack::make_null());
457 }
458}
459
460impl SmcInfo for SmcInfoTonV6 {
461 fn version(&self) -> VmVersion {
462 VmVersion::Ton(6)
463 }
464
465 fn build_c7(&self) -> SafeRc<Tuple> {
466 let mut t1 = Vec::with_capacity(Self::C7_ITEM_COUNT);
467 self.write_items(&mut t1);
468 SafeRc::new(vec![SafeRc::new_dyn_value(t1)])
469 }
470}
471
472#[derive(Default, Debug, Clone)]
473pub struct SmcInfoTonV11 {
474 pub base: SmcInfoTonV6,
475 pub in_msg: Option<SafeRc<Tuple>>,
476}
477
478impl SmcInfoTonV11 {
479 pub const IN_MSG_PARAMS_IDX: usize = 17;
480
481 const C7_ITEM_COUNT: usize = SmcInfoTonV6::C7_ITEM_COUNT + 1;
482
483 pub fn unpack_in_msg_partial(
484 msg_root: Cell,
485 remaining_value: Option<CurrencyCollection>,
486 ) -> Result<Option<UnpackedInMsgSmcInfo>, Error> {
487 fn src_addr_slice_range(mut cs: CellSlice<'_>) -> Result<CellSliceRange, Error> {
488 cs.skip_first(3, 0)?;
490
491 let mut addr_slice = cs;
493 IntAddr::load_from(&mut cs)?;
494
495 addr_slice.skip_last(cs.size_bits(), cs.size_refs())?;
497
498 Ok(addr_slice.range())
499 }
500
501 let mut cs = msg_root.as_slice()?;
502 let src_addr_slice;
503 let info = match MsgType::load_from(&mut cs)? {
504 MsgType::Int => {
505 src_addr_slice = src_addr_slice_range(cs)?;
506 IntMsgInfo::load_from(&mut cs)?
507 }
508 MsgType::ExtIn | MsgType::ExtOut => return Ok(None),
509 };
510
511 let state_init = if cs.load_bit()? {
512 Some(if cs.load_bit()? {
513 cs.load_reference_cloned()?
514 } else {
515 let mut state_init_cs = cs;
516 StateInit::load_from(&mut cs)?;
517 state_init_cs.skip_last(cs.size_bits(), cs.size_refs())?;
518 CellBuilder::build_from(state_init_cs)?
519 })
520 } else {
521 None
522 };
523
524 Ok(Some(UnpackedInMsgSmcInfo {
525 bounce: info.bounce,
526 bounced: info.bounced,
527 src_addr: (src_addr_slice, msg_root).into(),
528 fwd_fee: info.fwd_fee,
529 created_lt: info.created_lt,
530 created_at: info.created_at,
531 original_value: info.value.tokens,
532 remaining_value: remaining_value.unwrap_or(info.value),
533 state_init,
534 }))
535 }
536
537 pub fn with_unpacked_in_msg(mut self, in_msg: Option<SafeRc<Tuple>>) -> Self {
538 self.in_msg = in_msg;
539 self
540 }
541
542 #[inline]
543 fn write_items(&self, items: &mut Tuple) {
544 self.base.write_items(items);
546 items.push(
548 match &self.in_msg {
549 Some(message) => message.clone(),
550 None => UnpackedInMsgSmcInfo::empty_msg_tuple(),
551 }
552 .into_dyn_value(),
553 );
554 }
555}
556
557impl SmcInfo for SmcInfoTonV11 {
558 fn version(&self) -> VmVersion {
559 VmVersion::Ton(11)
560 }
561
562 fn build_c7(&self) -> SafeRc<Tuple> {
563 let mut t1 = Vec::with_capacity(Self::C7_ITEM_COUNT);
564 self.write_items(&mut t1);
565 SafeRc::new(vec![SafeRc::new_dyn_value(t1)])
566 }
567}
568
569#[derive(Clone)]
574pub struct UnpackedConfig {
575 pub latest_storage_prices: Option<CellSliceParts>,
576 pub global_id: Option<Cell>,
577 pub mc_gas_prices: Option<Cell>,
578 pub gas_prices: Option<Cell>,
579 pub mc_fwd_prices: Option<Cell>,
580 pub fwd_prices: Option<Cell>,
581 pub size_limits_config: Option<Cell>,
582}
583
584impl UnpackedConfig {
585 pub fn into_tuple(self) -> SafeRc<Tuple> {
586 SafeRc::new(vec![
587 Self::slice_or_null(self.latest_storage_prices),
588 Self::slice_or_null(self.global_id),
589 Self::slice_or_null(self.mc_gas_prices),
590 Self::slice_or_null(self.gas_prices),
591 Self::slice_or_null(self.mc_fwd_prices),
592 Self::slice_or_null(self.fwd_prices),
593 Self::slice_or_null(self.size_limits_config),
594 ])
595 }
596
597 pub fn as_tuple(&self) -> SafeRc<Tuple> {
598 self.clone().into_tuple()
599 }
600
601 fn slice_or_null<T>(slice: Option<T>) -> RcStackValue
602 where
603 T: IntoSliceUnchecked,
604 {
605 match slice {
606 None => Stack::make_null(),
607 Some(slice) => SafeRc::new_dyn_value(slice.into_slice_unchecked()),
608 }
609 }
610}
611
612trait IntoSliceUnchecked {
613 fn into_slice_unchecked(self) -> OwnedCellSlice;
614}
615
616impl IntoSliceUnchecked for Cell {
617 #[inline]
618 fn into_slice_unchecked(self) -> OwnedCellSlice {
619 OwnedCellSlice::new_allow_exotic(self)
620 }
621}
622
623impl IntoSliceUnchecked for CellSliceParts {
624 #[inline]
625 fn into_slice_unchecked(self) -> OwnedCellSlice {
626 OwnedCellSlice::from(self)
627 }
628}
629
630pub struct UnpackedInMsgSmcInfo {
632 pub bounce: bool,
633 pub bounced: bool,
634 pub src_addr: OwnedCellSlice,
635 pub fwd_fee: Tokens,
636 pub created_lt: u64,
637 pub created_at: u32,
638 pub original_value: Tokens,
639 pub remaining_value: CurrencyCollection,
640 pub state_init: Option<Cell>,
641}
642
643impl Default for UnpackedInMsgSmcInfo {
644 fn default() -> Self {
645 Self {
646 bounce: false,
647 bounced: false,
648 src_addr: addr_none_slice(),
649 fwd_fee: Tokens::ZERO,
650 created_lt: 0,
651 created_at: 0,
652 original_value: Tokens::ZERO,
653 remaining_value: CurrencyCollection::ZERO,
654 state_init: None,
655 }
656 }
657}
658
659impl UnpackedInMsgSmcInfo {
660 pub fn empty_msg_tuple() -> SafeRc<Tuple> {
661 thread_local! {
662 static TUPLE: SafeRc<Tuple> = {
663 SafeRc::new(tuple![
664 int 0, int 0, raw UnpackedInMsgSmcInfo::addr_none_slice(),
667 int 0, int 0, int 0, int 0, int 0, null, null, ])
675 }
676 }
677
678 TUPLE.with(Clone::clone)
679 }
680
681 pub fn into_tuple(self) -> SafeRc<Tuple> {
682 SafeRc::new(tuple![
683 raw Stack::make_bool(self.bounce),
684 raw Stack::make_bool(self.bounced),
685 slice self.src_addr,
686 int self.fwd_fee,
687 int self.created_lt,
688 int self.created_at,
689 int self.original_value,
690 int self.remaining_value.tokens,
691 raw match self.remaining_value.other.into_dict().into_root() {
692 Some(root) => SafeRc::new_dyn_value(root),
693 None => Stack::make_null(),
694 },
695 raw match self.state_init {
696 Some(root) => SafeRc::new_dyn_value(root),
697 None => Stack::make_null(),
698 },
699 ])
700 }
701
702 pub fn addr_none_slice() -> RcStackValue {
703 thread_local! {
704 static ADDR_NONE: RcStackValue = SafeRc::new_dyn_value(addr_none_slice());
705 }
706
707 ADDR_NONE.with(SafeRc::clone)
708 }
709}
710
711fn addr_none_slice() -> OwnedCellSlice {
712 let mut addr_none = CellBuilder::new();
713 addr_none.store_zeros(2).unwrap();
714 OwnedCellSlice::from(CellSliceParts::from(addr_none.build().unwrap()))
715}
716
717pub struct CustomSmcInfo {
719 pub version: VmVersion,
720 pub c7: SafeRc<Tuple>,
721}
722
723impl SmcInfo for CustomSmcInfo {
724 fn version(&self) -> VmVersion {
725 self.version
726 }
727
728 fn build_c7(&self) -> SafeRc<Tuple> {
729 self.c7.clone()
730 }
731}
732
733fn balance_as_tuple(balance: &CurrencyCollection) -> SafeRc<Tuple> {
734 SafeRc::new(vec![
735 SafeRc::new_dyn_value(BigInt::from(balance.tokens.into_inner())),
736 match balance.other.as_dict().root() {
737 None => Stack::make_null(),
738 Some(cell) => SafeRc::new_dyn_value(cell.clone()),
739 },
740 ])
741}