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 StoragePrices,
10};
11use tycho_types::num::Tokens;
12use tycho_types::prelude::*;
13
14use crate::error::VmResult;
15use crate::saferc::{SafeDelete, SafeRc};
16use crate::stack::{RcStackValue, Stack, Tuple};
17use crate::util::OwnedCellSlice;
18
19#[derive(Debug, Copy, Clone, Eq, PartialEq)]
21pub enum VmVersion {
22 Everscale(u32),
23 Ton(u32),
24}
25
26impl VmVersion {
27 pub const LATEST_TON: Self = Self::Ton(12);
28
29 pub fn is_ton<R: std::ops::RangeBounds<u32>>(&self, range: R) -> bool {
30 matches!(self, Self::Ton(version) if range.contains(version))
31 }
32
33 pub fn require_ton<R: std::ops::RangeBounds<u32>>(&self, range: R) -> VmResult<()> {
34 vm_ensure!(self.is_ton(range), InvalidOpcode);
35 Ok(())
36 }
37}
38
39pub trait SmcInfo {
41 fn version(&self) -> VmVersion;
42
43 fn build_c7(&self) -> SafeRc<Tuple>;
44}
45
46impl<T: SmcInfo + ?Sized> SmcInfo for &'_ T {
47 #[inline]
48 fn version(&self) -> VmVersion {
49 T::version(self)
50 }
51
52 #[inline]
53 fn build_c7(&self) -> SafeRc<Tuple> {
54 T::build_c7(self)
55 }
56}
57
58impl<T: SmcInfo + ?Sized> SmcInfo for Box<T> {
59 #[inline]
60 fn version(&self) -> VmVersion {
61 T::version(self)
62 }
63
64 #[inline]
65 fn build_c7(&self) -> SafeRc<Tuple> {
66 T::build_c7(self)
67 }
68}
69
70impl<T: SmcInfo + ?Sized> SmcInfo for Rc<T> {
71 #[inline]
72 fn version(&self) -> VmVersion {
73 T::version(self)
74 }
75
76 #[inline]
77 fn build_c7(&self) -> SafeRc<Tuple> {
78 T::build_c7(self)
79 }
80}
81
82impl<T: SmcInfo + ?Sized> SmcInfo for Arc<T> {
83 #[inline]
84 fn version(&self) -> VmVersion {
85 T::version(self)
86 }
87
88 #[inline]
89 fn build_c7(&self) -> SafeRc<Tuple> {
90 T::build_c7(self)
91 }
92}
93
94impl<T: SmcInfo + SafeDelete + ?Sized> SmcInfo for SafeRc<T> {
95 #[inline]
96 fn version(&self) -> VmVersion {
97 T::version(self)
98 }
99
100 #[inline]
101 fn build_c7(&self) -> SafeRc<Tuple> {
102 T::build_c7(self)
103 }
104}
105
106#[derive(Default, Debug, Clone)]
108pub struct SmcInfoBase {
109 pub now: u32,
111 pub block_lt: u64,
113 pub tx_lt: u64,
115 pub rand_seed: HashBytes,
117 pub account_balance: CurrencyCollection,
119 pub addr: IntAddr,
121 pub config: Option<BlockchainConfigParams>,
123}
124
125impl SmcInfoBase {
126 pub const MAGIC: u32 = 0x076ef1ea;
127
128 pub const ACTIONS_IDX: usize = 1;
129 pub const MSGS_SENT_IDX: usize = 2;
130 pub const UNIX_TIME_IDX: usize = 3;
131 pub const BLOCK_LT_IDX: usize = 4;
132 pub const TX_LT_IDX: usize = 5;
133 pub const RANDSEED_IDX: usize = 6;
134 pub const BALANCE_IDX: usize = 7;
135 pub const MYADDR_IDX: usize = 8;
136 pub const CONFIG_IDX: usize = 9;
137
138 const C7_ITEM_COUNT: usize = 10;
139
140 pub fn new() -> Self {
141 Self::default()
142 }
143
144 pub fn with_now(mut self, now: u32) -> Self {
145 self.now = now;
146 self
147 }
148
149 pub fn with_block_lt(mut self, block_lt: u64) -> Self {
150 self.block_lt = block_lt;
151 self
152 }
153
154 pub fn with_tx_lt(mut self, tx_lt: u64) -> Self {
155 self.tx_lt = tx_lt;
156 self
157 }
158
159 pub fn with_raw_rand_seed(mut self, raw_rand_seed: HashBytes) -> Self {
160 self.rand_seed = raw_rand_seed;
161 self
162 }
163
164 pub fn with_mixed_rand_seed(mut self, block_seed: &HashBytes, account: &HashBytes) -> Self {
165 if *block_seed == HashBytes::ZERO {
166 self.rand_seed = HashBytes::ZERO;
167 } else {
168 let mut hasher = sha2::Sha256::new();
169 hasher.update(block_seed.as_array());
170 hasher.update(account.as_array());
171 self.rand_seed = HashBytes(hasher.finalize().into());
172 }
173 self
174 }
175
176 pub fn with_account_balance(mut self, balance: CurrencyCollection) -> Self {
177 self.account_balance = balance;
178 self
179 }
180
181 pub fn with_account_addr(mut self, addr: IntAddr) -> Self {
182 self.addr = addr;
183 self
184 }
185
186 pub fn with_config(mut self, params: BlockchainConfigParams) -> Self {
187 self.config = Some(params);
188 self
189 }
190
191 pub fn require_ton_v4(self) -> SmcInfoTonV4 {
192 SmcInfoTonV4 {
193 base: self,
194 code: None,
195 message_balance: CurrencyCollection::ZERO,
196 storage_fees: Tokens::ZERO,
197 prev_blocks_info: None,
198 }
199 }
200
201 fn write_items(&self, items: &mut Tuple) {
202 items.push(SafeRc::new_dyn_value(BigInt::from(Self::MAGIC)));
204 items.push(Stack::make_zero());
206 items.push(Stack::make_zero());
208 items.push(SafeRc::new_dyn_value(BigInt::from(self.now)));
210 items.push(SafeRc::new_dyn_value(BigInt::from(self.block_lt)));
212 items.push(SafeRc::new_dyn_value(BigInt::from(self.tx_lt)));
214 items.push(SafeRc::new_dyn_value(BigInt::from_bytes_be(
216 Sign::Plus,
217 self.rand_seed.as_slice(),
218 )));
219 items.push(balance_as_tuple(&self.account_balance).into_dyn_value());
221 items.push(SafeRc::new_dyn_value(OwnedCellSlice::new_allow_exotic(
223 CellBuilder::build_from(&self.addr).unwrap(),
224 )));
225 items.push(
227 match self
228 .config
229 .as_ref()
230 .and_then(|c| c.as_dict().root().as_ref())
231 {
232 None => Stack::make_null(),
233 Some(config_root) => SafeRc::new_dyn_value(config_root.clone()),
234 },
235 );
236 }
237}
238
239impl SmcInfo for SmcInfoBase {
240 fn version(&self) -> VmVersion {
241 VmVersion::Ton(1)
242 }
243
244 fn build_c7(&self) -> SafeRc<Tuple> {
245 let mut t1 = Vec::with_capacity(Self::C7_ITEM_COUNT);
246 self.write_items(&mut t1);
247 SafeRc::new(vec![SafeRc::new_dyn_value(t1)])
248 }
249}
250
251#[derive(Default, Debug, Clone)]
253pub struct SmcInfoTonV4 {
254 pub base: SmcInfoBase,
256 pub code: Option<Cell>,
258 pub message_balance: CurrencyCollection,
260 pub storage_fees: Tokens,
262 pub prev_blocks_info: Option<SafeRc<Tuple>>,
264}
265
266impl SmcInfoTonV4 {
267 pub const MYCODE_IDX: usize = 10;
268 pub const IN_MSG_VALUE_IDX: usize = 11;
269 pub const STORAGE_FEE_IDX: usize = 12;
270 pub const PREV_BLOCKS_IDX: usize = 13;
271
272 const C7_ITEM_COUNT: usize = SmcInfoBase::C7_ITEM_COUNT + 4;
273
274 pub fn with_code(mut self, code: Cell) -> Self {
275 self.code = Some(code);
276 self
277 }
278
279 pub fn with_message_balance(mut self, balance: CurrencyCollection) -> Self {
280 self.message_balance = balance;
281 self
282 }
283
284 pub fn with_storage_fees(mut self, storage_fees: Tokens) -> Self {
285 self.storage_fees = storage_fees;
286 self
287 }
288
289 pub fn with_prev_blocks_info(mut self, prev_blocks_info: SafeRc<Tuple>) -> Self {
290 self.prev_blocks_info = Some(prev_blocks_info);
291 self
292 }
293
294 pub fn require_ton_v6(self) -> SmcInfoTonV6 {
295 SmcInfoTonV6 {
296 base: self,
297 unpacked_config: None,
298 due_payment: Tokens::ZERO,
299 }
300 }
301
302 fn write_items(&self, items: &mut Tuple) {
303 self.base.write_items(items);
305 items.push(match self.code.clone() {
307 None => Stack::make_null(),
308 Some(code) => SafeRc::new_dyn_value(code),
309 });
310 items.push(balance_as_tuple(&self.message_balance).into_dyn_value());
312 items.push(SafeRc::new_dyn_value(BigInt::from(
314 self.storage_fees.into_inner(),
315 )));
316 match self.prev_blocks_info.clone() {
320 None => items.push(Stack::make_null()),
321 Some(info) => items.push(info.into_dyn_value()),
322 }
323 }
324}
325
326impl SmcInfo for SmcInfoTonV4 {
327 fn version(&self) -> VmVersion {
328 VmVersion::Ton(4)
329 }
330
331 fn build_c7(&self) -> SafeRc<Tuple> {
332 let mut t1 = Vec::with_capacity(Self::C7_ITEM_COUNT);
333 self.write_items(&mut t1);
334 SafeRc::new(vec![SafeRc::new_dyn_value(t1)])
335 }
336}
337
338#[derive(Default, Debug, Clone)]
340pub struct SmcInfoTonV6 {
341 pub base: SmcInfoTonV4,
343 pub unpacked_config: Option<SafeRc<Tuple>>,
345 pub due_payment: Tokens,
347 }
349
350impl SmcInfoTonV6 {
351 pub const PARSED_CONFIG_IDX: usize = 14;
352 pub const STORAGE_DEBT_IDX: usize = 15;
353 pub const PRECOMPILED_GAS_IDX: usize = 16;
354
355 const C7_ITEM_COUNT: usize = SmcInfoTonV4::C7_ITEM_COUNT + 3;
356
357 pub fn unpack_config(
358 params: &BlockchainConfigParams,
359 now: u32,
360 ) -> Result<SafeRc<Tuple>, Error> {
361 let get_param = |id| {
362 let Some(value) = params.as_dict().get(id)? else {
363 return Ok(Stack::make_null());
364 };
365 Ok(SafeRc::new_dyn_value(OwnedCellSlice::new_allow_exotic(
366 value,
367 )))
368 };
369
370 Ok(SafeRc::new(vec![
371 match Self::find_storage_prices(params, now)? {
372 None => Stack::make_null(),
373 Some(prices) => SafeRc::new_dyn_value(OwnedCellSlice::from(prices)),
374 }, get_param(19)?, get_param(20)?, get_param(21)?, get_param(24)?, get_param(25)?, get_param(43)?, ]))
382 }
383
384 pub fn unpack_config_partial(
385 params: &BlockchainConfigParams,
386 now: u32,
387 ) -> Result<UnpackedConfig, Error> {
388 let get_param = |id| params.as_dict().get(id);
389
390 Ok(UnpackedConfig {
391 latest_storage_prices: Self::find_storage_prices(params, now)?,
392 global_id: get_param(19)?,
393 mc_gas_prices: get_param(20)?,
394 gas_prices: get_param(21)?,
395 mc_fwd_prices: get_param(24)?,
396 fwd_prices: get_param(25)?,
397 size_limits_config: get_param(43)?,
398 })
399 }
400
401 fn find_storage_prices(
402 params: &BlockchainConfigParams,
403 now: u32,
404 ) -> Result<Option<CellSliceParts>, Error> {
405 let prices = RawDict::<32>::from(params.get_storage_prices()?.into_root());
406 for value in prices.values_owned().reversed() {
407 let value = value?;
408
409 let parsed = StoragePrices::load_from(&mut value.0.apply_allow_exotic(&value.1))?;
410 if now < parsed.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}