tycho_vm/
smc_info.rs

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/// Version of a VM context.
19#[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
38/// Smart Contract Info context.
39pub 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/// Common Smart Contract Info.
106#[derive(Default, Debug, Clone)]
107pub struct SmcInfoBase {
108    /// Unix timestamp in seconds.
109    pub now: u32,
110    /// Block logical time.
111    pub block_lt: u64,
112    /// Transaction logical time.
113    pub tx_lt: u64,
114    /// Random seed.
115    pub rand_seed: HashBytes,
116    /// Account balance.
117    pub account_balance: CurrencyCollection,
118    /// Account address.
119    pub addr: IntAddr,
120    /// Blockchain config.
121    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        // magic:0x076ef1ea
202        items.push(SafeRc::new_dyn_value(BigInt::from(Self::MAGIC)));
203        // actions:Integer
204        items.push(Stack::make_zero());
205        // msgs_sent:Integer
206        items.push(Stack::make_zero());
207        // unixtime:Integer
208        items.push(SafeRc::new_dyn_value(BigInt::from(self.now)));
209        // block_lt:Integer
210        items.push(SafeRc::new_dyn_value(BigInt::from(self.block_lt)));
211        // trans_lt:Integer
212        items.push(SafeRc::new_dyn_value(BigInt::from(self.tx_lt)));
213        // rand_seed:Integer
214        items.push(SafeRc::new_dyn_value(BigInt::from_bytes_be(
215            Sign::Plus,
216            self.rand_seed.as_slice(),
217        )));
218        // balance_remaining:[Integer (Maybe Cell)]
219        items.push(balance_as_tuple(&self.account_balance).into_dyn_value());
220        // myself:MsgAddressInt
221        items.push(SafeRc::new_dyn_value(OwnedCellSlice::new_allow_exotic(
222            CellBuilder::build_from(&self.addr).unwrap(),
223        )));
224        // global_config:(Maybe Cell) ] = SmartContractInfo;
225        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/// Extended smart contract info for TVM since version 4.
251#[derive(Default, Debug, Clone)]
252pub struct SmcInfoTonV4 {
253    /// Base values.
254    pub base: SmcInfoBase,
255    /// Smart contract code.
256    pub code: Option<Cell>,
257    /// Incoming message balance (zero for external messages).
258    pub message_balance: CurrencyCollection,
259    /// Storage fees collected on the storage phase.
260    pub storage_fees: Tokens,
261    /// Previous blocks info (raw for now).
262    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        // ..base:SmartContractInfo
303        self.base.write_items(items);
304        // code:Cell
305        items.push(match self.code.clone() {
306            None => Stack::make_null(),
307            Some(code) => SafeRc::new_dyn_value(code),
308        });
309        // in_msg_value:[Integer (Maybe Cell)]
310        items.push(balance_as_tuple(&self.message_balance).into_dyn_value());
311        // storage_fees:Integer
312        items.push(SafeRc::new_dyn_value(BigInt::from(
313            self.storage_fees.into_inner(),
314        )));
315        // [ wc:Integer shard:Integer seqno:Integer root_hash:Integer file_hash:Integer] = BlockId;
316        // [ last_mc_blocks:[BlockId...]
317        //   prev_key_block:BlockId ] : PrevBlocksInfo
318        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/// Extended smart contract info for TVM since version 6.
338#[derive(Default, Debug, Clone)]
339pub struct SmcInfoTonV6 {
340    /// Base values.
341    pub base: SmcInfoTonV4,
342    /// Unpacked blockchain config.
343    pub unpacked_config: Option<SafeRc<Tuple>>,
344    /// Storage phase debt.
345    pub due_payment: Tokens,
346    // TODO: Add `precompiled_gas_usage`.
347}
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            }, // storage_prices
374            get_param(19)?, // global_id
375            get_param(20)?, // config_mc_gas_prices
376            get_param(21)?, // config_gas_prices
377            get_param(24)?, // config_mc_fwd_prices
378            get_param(25)?, // config_fwd_prices
379            get_param(43)?, // size_limits_config
380        ]))
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            // First 32 bits of value is unix timestamp.
409            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        // ..base:SmartContractInfoV4
445        self.base.write_items(items);
446        // unpacked_config_tuple:[...]
447        items.push(match &self.unpacked_config {
448            None => Stack::make_null(),
449            Some(config) => config.clone().into_dyn_value(),
450        });
451        // due_payment:Integer
452        items.push(SafeRc::new_dyn_value(BigInt::from(
453            self.due_payment.into_inner(),
454        )));
455        // precompiled_gas_usage:Integer
456        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            // Skip flags.
489            cs.skip_first(3, 0)?;
490
491            // Read `src`.
492            let mut addr_slice = cs;
493            IntAddr::load_from(&mut cs)?;
494
495            // Leave only a `src` range in the `addr_slice`.
496            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        // ..base:SmcInfoTonV6
545        self.base.write_items(items);
546        // in_msg_params:[...]
547        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/// Unpacked config params, ready to be used in [`SmcInfoTonV6::with_unpacked_config`].
570///
571/// A `Send + Sync` alternative of C7 [`SafeRc<Tuple>`] at the cost of vec allocation.
572/// Can be shared between execution groups in multiple threads.
573#[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
630/// Internal message parts.
631pub 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, // bounce
665                    int 0, // bounced
666                    raw UnpackedInMsgSmcInfo::addr_none_slice(),
667                    int 0, // fwd fee
668                    int 0, // created_lt
669                    int 0, // created_at
670                    int 0, // original value
671                    int 0, // value
672                    null,  // value extra
673                    null,  // state_init
674                ])
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
717/// Custom-built Smart Contract Info.
718pub 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}