Skip to main content

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    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/// Version of a VM context.
20#[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
39/// Smart Contract Info context.
40pub 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/// Common Smart Contract Info.
107#[derive(Default, Debug, Clone)]
108pub struct SmcInfoBase {
109    /// Unix timestamp in seconds.
110    pub now: u32,
111    /// Block logical time.
112    pub block_lt: u64,
113    /// Transaction logical time.
114    pub tx_lt: u64,
115    /// Random seed.
116    pub rand_seed: HashBytes,
117    /// Account balance.
118    pub account_balance: CurrencyCollection,
119    /// Account address.
120    pub addr: IntAddr,
121    /// Blockchain config.
122    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        let mut hasher = sha2::Sha256::new();
166        hasher.update(block_seed.as_array());
167        hasher.update(account.as_array());
168        self.rand_seed = HashBytes(hasher.finalize().into());
169        self
170    }
171
172    pub fn with_account_balance(mut self, balance: CurrencyCollection) -> Self {
173        self.account_balance = balance;
174        self
175    }
176
177    pub fn with_account_addr(mut self, addr: IntAddr) -> Self {
178        self.addr = addr;
179        self
180    }
181
182    pub fn with_config(mut self, params: BlockchainConfigParams) -> Self {
183        self.config = Some(params);
184        self
185    }
186
187    pub fn require_ton_v4(self) -> SmcInfoTonV4 {
188        SmcInfoTonV4 {
189            base: self,
190            code: None,
191            message_balance: CurrencyCollection::ZERO,
192            storage_fees: Tokens::ZERO,
193            prev_blocks_info: None,
194        }
195    }
196
197    fn write_items(&self, items: &mut Tuple) {
198        // magic:0x076ef1ea
199        items.push(SafeRc::new_dyn_value(BigInt::from(Self::MAGIC)));
200        // actions:Integer
201        items.push(Stack::make_zero());
202        // msgs_sent:Integer
203        items.push(Stack::make_zero());
204        // unixtime:Integer
205        items.push(SafeRc::new_dyn_value(BigInt::from(self.now)));
206        // block_lt:Integer
207        items.push(SafeRc::new_dyn_value(BigInt::from(self.block_lt)));
208        // trans_lt:Integer
209        items.push(SafeRc::new_dyn_value(BigInt::from(self.tx_lt)));
210        // rand_seed:Integer
211        items.push(SafeRc::new_dyn_value(BigInt::from_bytes_be(
212            Sign::Plus,
213            self.rand_seed.as_slice(),
214        )));
215        // balance_remaining:[Integer (Maybe Cell)]
216        items.push(balance_as_tuple(&self.account_balance).into_dyn_value());
217        // myself:MsgAddressInt
218        items.push(SafeRc::new_dyn_value(OwnedCellSlice::new_allow_exotic(
219            CellBuilder::build_from(&self.addr).unwrap(),
220        )));
221        // global_config:(Maybe Cell) ] = SmartContractInfo;
222        items.push(
223            match self
224                .config
225                .as_ref()
226                .and_then(|c| c.as_dict().root().as_ref())
227            {
228                None => Stack::make_null(),
229                Some(config_root) => SafeRc::new_dyn_value(config_root.clone()),
230            },
231        );
232    }
233}
234
235impl SmcInfo for SmcInfoBase {
236    fn version(&self) -> VmVersion {
237        VmVersion::Ton(1)
238    }
239
240    fn build_c7(&self) -> SafeRc<Tuple> {
241        let mut t1 = Vec::with_capacity(Self::C7_ITEM_COUNT);
242        self.write_items(&mut t1);
243        SafeRc::new(vec![SafeRc::new_dyn_value(t1)])
244    }
245}
246
247/// Extended smart contract info for TVM since version 4.
248#[derive(Default, Debug, Clone)]
249pub struct SmcInfoTonV4 {
250    /// Base values.
251    pub base: SmcInfoBase,
252    /// Smart contract code.
253    pub code: Option<Cell>,
254    /// Incoming message balance (zero for external messages).
255    pub message_balance: CurrencyCollection,
256    /// Storage fees collected on the storage phase.
257    pub storage_fees: Tokens,
258    /// Previous blocks info (raw for now).
259    pub prev_blocks_info: Option<SafeRc<Tuple>>,
260}
261
262impl SmcInfoTonV4 {
263    pub const MYCODE_IDX: usize = 10;
264    pub const IN_MSG_VALUE_IDX: usize = 11;
265    pub const STORAGE_FEE_IDX: usize = 12;
266    pub const PREV_BLOCKS_IDX: usize = 13;
267
268    const C7_ITEM_COUNT: usize = SmcInfoBase::C7_ITEM_COUNT + 4;
269
270    pub fn with_code(mut self, code: Cell) -> Self {
271        self.code = Some(code);
272        self
273    }
274
275    pub fn with_message_balance(mut self, balance: CurrencyCollection) -> Self {
276        self.message_balance = balance;
277        self
278    }
279
280    pub fn with_storage_fees(mut self, storage_fees: Tokens) -> Self {
281        self.storage_fees = storage_fees;
282        self
283    }
284
285    pub fn with_prev_blocks_info(mut self, prev_blocks_info: SafeRc<Tuple>) -> Self {
286        self.prev_blocks_info = Some(prev_blocks_info);
287        self
288    }
289
290    pub fn require_ton_v6(self) -> SmcInfoTonV6 {
291        SmcInfoTonV6 {
292            base: self,
293            unpacked_config: None,
294            due_payment: Tokens::ZERO,
295        }
296    }
297
298    fn write_items(&self, items: &mut Tuple) {
299        // ..base:SmartContractInfo
300        self.base.write_items(items);
301        // code:Cell
302        items.push(match self.code.clone() {
303            None => Stack::make_null(),
304            Some(code) => SafeRc::new_dyn_value(code),
305        });
306        // in_msg_value:[Integer (Maybe Cell)]
307        items.push(balance_as_tuple(&self.message_balance).into_dyn_value());
308        // storage_fees:Integer
309        items.push(SafeRc::new_dyn_value(BigInt::from(
310            self.storage_fees.into_inner(),
311        )));
312        // [ wc:Integer shard:Integer seqno:Integer root_hash:Integer file_hash:Integer] = BlockId;
313        // [ last_mc_blocks:[BlockId...]
314        //   prev_key_block:BlockId ] : PrevBlocksInfo
315        match self.prev_blocks_info.clone() {
316            None => items.push(Stack::make_null()),
317            Some(info) => items.push(info.into_dyn_value()),
318        }
319    }
320}
321
322impl SmcInfo for SmcInfoTonV4 {
323    fn version(&self) -> VmVersion {
324        VmVersion::Ton(4)
325    }
326
327    fn build_c7(&self) -> SafeRc<Tuple> {
328        let mut t1 = Vec::with_capacity(Self::C7_ITEM_COUNT);
329        self.write_items(&mut t1);
330        SafeRc::new(vec![SafeRc::new_dyn_value(t1)])
331    }
332}
333
334/// Extended smart contract info for TVM since version 6.
335#[derive(Default, Debug, Clone)]
336pub struct SmcInfoTonV6 {
337    /// Base values.
338    pub base: SmcInfoTonV4,
339    /// Unpacked blockchain config.
340    pub unpacked_config: Option<SafeRc<Tuple>>,
341    /// Storage phase debt.
342    pub due_payment: Tokens,
343    // TODO: Add `precompiled_gas_usage`.
344}
345
346impl SmcInfoTonV6 {
347    pub const PARSED_CONFIG_IDX: usize = 14;
348    pub const STORAGE_DEBT_IDX: usize = 15;
349    pub const PRECOMPILED_GAS_IDX: usize = 16;
350
351    const C7_ITEM_COUNT: usize = SmcInfoTonV4::C7_ITEM_COUNT + 3;
352
353    pub fn unpack_config(
354        params: &BlockchainConfigParams,
355        now: u32,
356    ) -> Result<SafeRc<Tuple>, Error> {
357        let get_param = |id| {
358            let Some(value) = params.as_dict().get(id)? else {
359                return Ok(Stack::make_null());
360            };
361            Ok(SafeRc::new_dyn_value(OwnedCellSlice::new_allow_exotic(
362                value,
363            )))
364        };
365
366        Ok(SafeRc::new(vec![
367            match Self::find_storage_prices(params, now)? {
368                None => Stack::make_null(),
369                Some(prices) => SafeRc::new_dyn_value(OwnedCellSlice::from(prices)),
370            }, // storage_prices
371            get_param(19)?, // global_id
372            get_param(20)?, // config_mc_gas_prices
373            get_param(21)?, // config_gas_prices
374            get_param(24)?, // config_mc_fwd_prices
375            get_param(25)?, // config_fwd_prices
376            get_param(43)?, // size_limits_config
377        ]))
378    }
379
380    pub fn unpack_config_partial(
381        params: &BlockchainConfigParams,
382        now: u32,
383    ) -> Result<UnpackedConfig, Error> {
384        let get_param = |id| params.as_dict().get(id);
385
386        Ok(UnpackedConfig {
387            latest_storage_prices: Self::find_storage_prices(params, now)?,
388            global_id: get_param(19)?,
389            mc_gas_prices: get_param(20)?,
390            gas_prices: get_param(21)?,
391            mc_fwd_prices: get_param(24)?,
392            fwd_prices: get_param(25)?,
393            size_limits_config: get_param(43)?,
394        })
395    }
396
397    fn find_storage_prices(
398        params: &BlockchainConfigParams,
399        now: u32,
400    ) -> Result<Option<CellSliceParts>, Error> {
401        let prices = RawDict::<32>::from(params.get_storage_prices()?.into_root());
402        for value in prices.values_owned().reversed() {
403            let value = value?;
404
405            let parsed = StoragePrices::load_from(&mut value.0.apply_allow_exotic(&value.1))?;
406            if now < parsed.utime_since {
407                continue;
408            }
409            return Ok(Some(value));
410        }
411        Ok(None)
412    }
413
414    pub fn with_unpacked_config(mut self, config: SafeRc<Tuple>) -> Self {
415        self.unpacked_config = Some(config);
416        self
417    }
418
419    pub fn fill_unpacked_config(mut self) -> Result<Self, Error> {
420        let Some(params) = &self.base.base.config else {
421            return Err(Error::CellUnderflow);
422        };
423        self.unpacked_config = Some(Self::unpack_config(params, self.base.base.now)?);
424        Ok(self)
425    }
426
427    pub fn with_due_payment(mut self, due_payment: Tokens) -> Self {
428        self.due_payment = due_payment;
429        self
430    }
431
432    pub fn require_ton_v11(self) -> SmcInfoTonV11 {
433        SmcInfoTonV11 {
434            base: self,
435            in_msg: None,
436        }
437    }
438
439    fn write_items(&self, items: &mut Tuple) {
440        // ..base:SmartContractInfoV4
441        self.base.write_items(items);
442        // unpacked_config_tuple:[...]
443        items.push(match &self.unpacked_config {
444            None => Stack::make_null(),
445            Some(config) => config.clone().into_dyn_value(),
446        });
447        // due_payment:Integer
448        items.push(SafeRc::new_dyn_value(BigInt::from(
449            self.due_payment.into_inner(),
450        )));
451        // precompiled_gas_usage:Integer
452        items.push(Stack::make_null());
453    }
454}
455
456impl SmcInfo for SmcInfoTonV6 {
457    fn version(&self) -> VmVersion {
458        VmVersion::Ton(6)
459    }
460
461    fn build_c7(&self) -> SafeRc<Tuple> {
462        let mut t1 = Vec::with_capacity(Self::C7_ITEM_COUNT);
463        self.write_items(&mut t1);
464        SafeRc::new(vec![SafeRc::new_dyn_value(t1)])
465    }
466}
467
468#[derive(Default, Debug, Clone)]
469pub struct SmcInfoTonV11 {
470    pub base: SmcInfoTonV6,
471    pub in_msg: Option<SafeRc<Tuple>>,
472}
473
474impl SmcInfoTonV11 {
475    pub const IN_MSG_PARAMS_IDX: usize = 17;
476
477    const C7_ITEM_COUNT: usize = SmcInfoTonV6::C7_ITEM_COUNT + 1;
478
479    pub fn unpack_in_msg_partial(
480        msg_root: Cell,
481        remaining_value: Option<CurrencyCollection>,
482    ) -> Result<Option<UnpackedInMsgSmcInfo>, Error> {
483        fn src_addr_slice_range(mut cs: CellSlice<'_>) -> Result<CellSliceRange, Error> {
484            // Skip flags.
485            cs.skip_first(3, 0)?;
486
487            // Read `src`.
488            let mut addr_slice = cs;
489            IntAddr::load_from(&mut cs)?;
490
491            // Leave only a `src` range in the `addr_slice`.
492            addr_slice.skip_last(cs.size_bits(), cs.size_refs())?;
493
494            Ok(addr_slice.range())
495        }
496
497        let mut cs = msg_root.as_slice()?;
498        let src_addr_slice;
499        let info = match MsgType::load_from(&mut cs)? {
500            MsgType::Int => {
501                src_addr_slice = src_addr_slice_range(cs)?;
502                IntMsgInfo::load_from(&mut cs)?
503            }
504            MsgType::ExtIn | MsgType::ExtOut => return Ok(None),
505        };
506
507        let state_init = if cs.load_bit()? {
508            Some(if cs.load_bit()? {
509                cs.load_reference_cloned()?
510            } else {
511                let mut state_init_cs = cs;
512                StateInit::load_from(&mut cs)?;
513                state_init_cs.skip_last(cs.size_bits(), cs.size_refs())?;
514                CellBuilder::build_from(state_init_cs)?
515            })
516        } else {
517            None
518        };
519
520        Ok(Some(UnpackedInMsgSmcInfo {
521            bounce: info.bounce,
522            bounced: info.bounced,
523            src_addr: (src_addr_slice, msg_root).into(),
524            fwd_fee: info.fwd_fee,
525            created_lt: info.created_lt,
526            created_at: info.created_at,
527            original_value: info.value.tokens,
528            remaining_value: remaining_value.unwrap_or(info.value),
529            state_init,
530        }))
531    }
532
533    pub fn with_unpacked_in_msg(mut self, in_msg: Option<SafeRc<Tuple>>) -> Self {
534        self.in_msg = in_msg;
535        self
536    }
537
538    #[inline]
539    fn write_items(&self, items: &mut Tuple) {
540        // ..base:SmcInfoTonV6
541        self.base.write_items(items);
542        // in_msg_params:[...]
543        items.push(
544            match &self.in_msg {
545                Some(message) => message.clone(),
546                None => UnpackedInMsgSmcInfo::empty_msg_tuple(),
547            }
548            .into_dyn_value(),
549        );
550    }
551}
552
553impl SmcInfo for SmcInfoTonV11 {
554    fn version(&self) -> VmVersion {
555        VmVersion::Ton(11)
556    }
557
558    fn build_c7(&self) -> SafeRc<Tuple> {
559        let mut t1 = Vec::with_capacity(Self::C7_ITEM_COUNT);
560        self.write_items(&mut t1);
561        SafeRc::new(vec![SafeRc::new_dyn_value(t1)])
562    }
563}
564
565/// Unpacked config params, ready to be used in [`SmcInfoTonV6::with_unpacked_config`].
566///
567/// A `Send + Sync` alternative of C7 [`SafeRc<Tuple>`] at the cost of vec allocation.
568/// Can be shared between execution groups in multiple threads.
569#[derive(Debug, Clone)]
570pub struct UnpackedConfig {
571    pub latest_storage_prices: Option<CellSliceParts>,
572    pub global_id: Option<Cell>,
573    pub mc_gas_prices: Option<Cell>,
574    pub gas_prices: Option<Cell>,
575    pub mc_fwd_prices: Option<Cell>,
576    pub fwd_prices: Option<Cell>,
577    pub size_limits_config: Option<Cell>,
578}
579
580impl UnpackedConfig {
581    pub fn into_tuple(self) -> SafeRc<Tuple> {
582        SafeRc::new(vec![
583            Self::slice_or_null(self.latest_storage_prices),
584            Self::slice_or_null(self.global_id),
585            Self::slice_or_null(self.mc_gas_prices),
586            Self::slice_or_null(self.gas_prices),
587            Self::slice_or_null(self.mc_fwd_prices),
588            Self::slice_or_null(self.fwd_prices),
589            Self::slice_or_null(self.size_limits_config),
590        ])
591    }
592
593    pub fn as_tuple(&self) -> SafeRc<Tuple> {
594        self.clone().into_tuple()
595    }
596
597    fn slice_or_null<T>(slice: Option<T>) -> RcStackValue
598    where
599        T: IntoSliceUnchecked,
600    {
601        match slice {
602            None => Stack::make_null(),
603            Some(slice) => SafeRc::new_dyn_value(slice.into_slice_unchecked()),
604        }
605    }
606}
607
608trait IntoSliceUnchecked {
609    fn into_slice_unchecked(self) -> OwnedCellSlice;
610}
611
612impl IntoSliceUnchecked for Cell {
613    #[inline]
614    fn into_slice_unchecked(self) -> OwnedCellSlice {
615        OwnedCellSlice::new_allow_exotic(self)
616    }
617}
618
619impl IntoSliceUnchecked for CellSliceParts {
620    #[inline]
621    fn into_slice_unchecked(self) -> OwnedCellSlice {
622        OwnedCellSlice::from(self)
623    }
624}
625
626/// Internal message parts.
627pub struct UnpackedInMsgSmcInfo {
628    pub bounce: bool,
629    pub bounced: bool,
630    pub src_addr: OwnedCellSlice,
631    pub fwd_fee: Tokens,
632    pub created_lt: u64,
633    pub created_at: u32,
634    pub original_value: Tokens,
635    pub remaining_value: CurrencyCollection,
636    pub state_init: Option<Cell>,
637}
638
639impl Default for UnpackedInMsgSmcInfo {
640    fn default() -> Self {
641        Self {
642            bounce: false,
643            bounced: false,
644            src_addr: addr_none_slice(),
645            fwd_fee: Tokens::ZERO,
646            created_lt: 0,
647            created_at: 0,
648            original_value: Tokens::ZERO,
649            remaining_value: CurrencyCollection::ZERO,
650            state_init: None,
651        }
652    }
653}
654
655impl UnpackedInMsgSmcInfo {
656    pub fn empty_msg_tuple() -> SafeRc<Tuple> {
657        thread_local! {
658            static TUPLE: SafeRc<Tuple> = {
659                SafeRc::new(tuple![
660                    int 0, // bounce
661                    int 0, // bounced
662                    raw UnpackedInMsgSmcInfo::addr_none_slice(),
663                    int 0, // fwd fee
664                    int 0, // created_lt
665                    int 0, // created_at
666                    int 0, // original value
667                    int 0, // value
668                    null,  // value extra
669                    null,  // state_init
670                ])
671            }
672        }
673
674        TUPLE.with(Clone::clone)
675    }
676
677    pub fn into_tuple(self) -> SafeRc<Tuple> {
678        SafeRc::new(tuple![
679            raw Stack::make_bool(self.bounce),
680            raw Stack::make_bool(self.bounced),
681            slice self.src_addr,
682            int self.fwd_fee,
683            int self.created_lt,
684            int self.created_at,
685            int self.original_value,
686            int self.remaining_value.tokens,
687            raw match self.remaining_value.other.into_dict().into_root() {
688                Some(root) => SafeRc::new_dyn_value(root),
689                None => Stack::make_null(),
690            },
691            raw match self.state_init {
692                Some(root) => SafeRc::new_dyn_value(root),
693                None => Stack::make_null(),
694            },
695        ])
696    }
697
698    pub fn addr_none_slice() -> RcStackValue {
699        thread_local! {
700            static ADDR_NONE: RcStackValue = SafeRc::new_dyn_value(addr_none_slice());
701        }
702
703        ADDR_NONE.with(SafeRc::clone)
704    }
705}
706
707fn addr_none_slice() -> OwnedCellSlice {
708    let mut addr_none = CellBuilder::new();
709    addr_none.store_zeros(2).unwrap();
710    OwnedCellSlice::from(CellSliceParts::from(addr_none.build().unwrap()))
711}
712
713/// Custom-built Smart Contract Info.
714pub struct CustomSmcInfo {
715    pub version: VmVersion,
716    pub c7: SafeRc<Tuple>,
717}
718
719impl SmcInfo for CustomSmcInfo {
720    fn version(&self) -> VmVersion {
721        self.version
722    }
723
724    fn build_c7(&self) -> SafeRc<Tuple> {
725        self.c7.clone()
726    }
727}
728
729fn balance_as_tuple(balance: &CurrencyCollection) -> SafeRc<Tuple> {
730    SafeRc::new(vec![
731        SafeRc::new_dyn_value(BigInt::from(balance.tokens.into_inner())),
732        match balance.other.as_dict().root() {
733            None => Stack::make_null(),
734            Some(cell) => SafeRc::new_dyn_value(cell.clone()),
735        },
736    ])
737}