tycho_vm/
lib.rs

1#[cfg(test)]
2#[macro_use]
3extern crate tycho_asm_macros;
4extern crate self as tycho_vm;
5
6/// Prevents using `From::from` for plain error conversion.
7macro_rules! ok {
8    ($e:expr $(,)?) => {
9        match $e {
10            core::result::Result::Ok(val) => val,
11            core::result::Result::Err(err) => return core::result::Result::Err(err),
12        }
13    };
14}
15
16macro_rules! vm_ensure {
17    ($cond:expr, $($tt:tt)+) => {
18        if $crate::__private::not($cond) {
19            return Err(Box::new($crate::error::VmError::$($tt)+));
20        }
21    };
22}
23
24macro_rules! vm_bail {
25    ($($tt:tt)*) => {
26        return Err(Box::new($crate::error::VmError::$($tt)*))
27    };
28}
29
30/// Tuple builder.
31#[macro_export]
32macro_rules! tuple {
33    ($($tt:tt)*) => {
34        $crate::tuple_impl!(@v [] $($tt)*)
35    };
36}
37
38#[doc(hidden)]
39#[macro_export]
40macro_rules! tuple_impl {
41    (@v [$($values:tt)*] null $(, $($tt:tt)* )?) => {
42        $crate::tuple_impl!(@v [$($values)* $crate::Stack::make_null(), ] $($($tt)*)?)
43    };
44
45    (@v [$($values:tt)*] nan $(, $($tt:tt)* )?) => {
46        $crate::tuple_impl!(@v [$($values)* $crate::Stack::make_nan(), ] $($($tt)*)?)
47    };
48
49    (@v [$($values:tt)*] int $value:expr $(, $($tt:tt)* )?) => {
50        $crate::tuple_impl!(@v [
51            $($values)* $crate::RcStackValue::new_dyn_value(
52                $crate::__export::num_bigint::BigInt::from($value)
53            ),
54        ] $($($tt)*)?)
55    };
56
57    (@v [$($values:tt)*] cell $value:expr $(, $($tt:tt)* )?) => {
58        $crate::tuple_impl!(@v [
59            $($values)* $crate::SafeRc::into_dyn_value(
60                $crate::SafeRc::<::tycho_types::cell::Cell>::new($value)
61            ),
62        ] $($($tt)*)?)
63    };
64
65    (@v [$($values:tt)*] slice $value:expr $(, $($tt:tt)* )?) => {
66        $crate::tuple_impl!(@v [
67            $($values)* $crate::RcStackValue::new_dyn_value(
68                $crate::OwnedCellSlice::from($value)
69            ),
70        ] $($($tt)*)?)
71    };
72
73    (@v [$($values:tt)*] builder $value:expr $(, $($tt:tt)* )?) => {
74        $crate::tuple_impl!(@v [
75            $($values)* $crate::SafeRc::into_dyn_value(
76                $crate::SafeRc::<::tycho_types::cell::CellBuilder>::new($value)
77            ),
78        ] $($($tt)*)?)
79    };
80
81    (@v [$($values:tt)*] [ $($inner:tt)* ] $(, $($tt:tt)* )?) => {
82        $crate::tuple_impl!(@v [
83            $($values)* $crate::RcStackValue::new_dyn_value(
84                $crate::tuple!($($inner)*)
85            ),
86        ] $($($tt)*)?)
87    };
88
89    (@v [$($values:tt)*] raw $value:expr $(, $($tt:tt)* )?) => {
90        $crate::tuple_impl!(@v [
91            $($values)* $crate::SafeRc::into_dyn_value($value),
92        ] $($($tt)*)?)
93    };
94
95    (@v [$($values:tt)*] $(,)?) => {
96        vec![$($values)*]
97    };
98}
99
100#[cfg(test)]
101#[macro_export]
102macro_rules! assert_run_vm {
103    (
104        $($code:literal),+,
105        $(c7: $c7_params:expr,)?
106        $(gas: $gas_limit:expr,)?
107        $(libs: $libs:expr,)?
108        $(state: |$state:ident| $state_expr:expr,)?
109        [$($origin_stack:tt)*] => [$($expected_stack:tt)*]
110        $(, exit_code: $exit_code:literal)?
111        $(,)?
112    ) => {{
113        let libs = $crate::assert_run_vm!(@libs $($libs)?);
114        let mut output = $crate::tests::TracingOutput::default();
115        let (exit_code, vm) = $crate::tests::run_vm_with_stack(
116            tvmasm!($($code),+),
117            $crate::assert_run_vm!(@c7 $($c7_params)?),
118            $crate::tuple![$($origin_stack)*],
119            $crate::assert_run_vm!(@gas $($gas_limit)?),
120            &libs,
121            $crate::assert_run_vm!(@state $($state $state_expr)?),
122            &mut output,
123        );
124
125        vm_log_trace!(
126            "test vm finished: res={exit_code}, steps={}, gas={}",
127            vm.steps,
128            vm.gas.consumed(),
129        );
130
131        $crate::assert_run_vm!(@check_exit_code exit_code $($exit_code)?);
132
133        let expected_stack = $crate::tuple![$($expected_stack)*];
134
135        let expected = format!("{}", (&expected_stack as &dyn $crate::stack::StackValue).display_list());
136        let actual = format!("{}", (&vm.stack.items as &dyn $crate::stack::StackValue).display_list());
137        assert_eq!(actual, expected);
138
139        $crate::tests::compare_stack(&vm.stack.items, &expected_stack);
140    }};
141    (@check_exit_code $ident:ident) => {
142        assert_eq!($ident, 0, "non-zero exit code")
143    };
144    (@check_exit_code $ident:ident $exit_code:literal) => {
145        assert_eq!($ident, $exit_code, "exit code mismatch")
146    };
147    (@c7) => {
148        $crate::tuple![]
149    };
150    (@c7 $c7_params:expr) => {
151        $c7_params
152    };
153    (@gas) => {
154        1000000
155    };
156    (@gas $gas_limit:expr) => {
157        $gas_limit
158    };
159    (@libs) => {
160        $crate::NoLibraries
161    };
162    (@libs $libs:expr) => {
163        $libs
164    };
165    (@state) => {
166        |_| {}
167    };
168    (@state $state:ident $state_expr:expr) => {
169        |$state| $state_expr
170    };
171}
172
173pub use self::cont::{
174    AgainCont, ArgContExt, Cont, ControlData, ControlRegs, ExcQuitCont, OrdCont, PushIntCont,
175    QuitCont, RcCont, RepeatCont, UntilCont, WhileCont,
176};
177pub use self::dispatch::{
178    DispatchTable, FnExecInstrArg, FnExecInstrFull, FnExecInstrSimple, OpcodeBase, OpcodeExec,
179    Opcodes,
180};
181#[cfg(feature = "dump")]
182pub use self::dispatch::{
183    DumpOutput, FnDumpInstrArg, FnDumpInstrFull, FnDumpInstrSimple, OpcodeDump,
184};
185#[cfg(feature = "dump")]
186pub use self::error::{DumpError, DumpResult};
187pub use self::error::{VmError, VmException, VmResult};
188pub use self::gas::{
189    GasConsumer, GasConsumerDeriveParams, GasParams, LibraryProvider, LimitedGasConsumer,
190    NoLibraries, ParentGasConsumer, RestoredGasConsumer,
191};
192pub use self::instr::{codepage, codepage0};
193#[cfg(feature = "tracing")]
194pub use self::log::{VM_LOG_TARGET, VmLogRows, VmLogRowsGuard, VmLogSubscriber};
195pub use self::saferc::{SafeDelete, SafeRc, SafeRcMakeMut};
196pub use self::smc_info::{
197    CustomSmcInfo, SmcInfo, SmcInfoBase, SmcInfoTonV4, SmcInfoTonV6, SmcInfoTonV11, UnpackedConfig,
198    UnpackedInMsgSmcInfo, VmVersion,
199};
200pub use self::stack::{
201    NaN, RcStackValue, Stack, StackValue, StackValueType, StaticStackValue, Tuple, TupleExt,
202};
203#[cfg(feature = "tracing")]
204pub use self::state::VmLogMask;
205pub use self::state::{
206    BehaviourModifiers, CommittedState, InitSelectorParams, IntoCode, ParentVmState, SaveCr,
207    VmState, VmStateBuilder,
208};
209pub use self::util::OwnedCellSlice;
210
211#[macro_use]
212mod log;
213
214mod cont;
215mod dispatch;
216mod error;
217mod gas;
218mod instr;
219mod saferc;
220mod smc_info;
221mod stack;
222mod state;
223mod util;
224
225#[doc(hidden)]
226pub mod __export {
227    pub use {num_bigint, tycho_types};
228}
229
230#[doc(hidden)]
231mod __private {
232    use self::not::Bool;
233
234    #[doc(hidden)]
235    #[inline]
236    pub fn not(cond: impl Bool) -> bool {
237        cond.not()
238    }
239
240    mod not {
241        #[doc(hidden)]
242        pub trait Bool {
243            fn not(self) -> bool;
244        }
245
246        impl Bool for bool {
247            #[inline]
248            fn not(self) -> bool {
249                !self
250            }
251        }
252
253        impl Bool for &bool {
254            #[inline]
255            fn not(self) -> bool {
256                !*self
257            }
258        }
259    }
260}
261
262#[cfg(test)]
263mod tests {
264    use std::collections::HashMap;
265
266    use tracing_test::traced_test;
267    use tycho_types::models::{CurrencyCollection, SimpleLib, StdAddr};
268    use tycho_types::prelude::*;
269
270    use super::*;
271    use crate::stack::{RcStackValue, Tuple};
272
273    pub fn run_vm_with_stack<'a, I>(
274        code: &[u8],
275        c7_params: Tuple,
276        original_stack: I,
277        gas_limit: u64,
278        libs: &'a impl LibraryProvider,
279        modify_state: impl FnOnce(&mut VmState),
280        output: &'a mut impl std::fmt::Write,
281    ) -> (i32, VmState<'a>)
282    where
283        I: IntoIterator<Item = RcStackValue>,
284    {
285        let code = Boc::decode(code).unwrap();
286
287        let mut vm = VmState::builder()
288            .with_code(code)
289            .with_libraries(libs)
290            .with_smc_info(CustomSmcInfo {
291                version: VmState::DEFAULT_VERSION,
292                c7: SafeRc::new(c7_params),
293            })
294            .with_debug(output)
295            .with_stack(original_stack)
296            .with_gas(GasParams {
297                max: gas_limit,
298                limit: gas_limit,
299                credit: 0,
300                ..GasParams::getter()
301            })
302            .build();
303
304        modify_state(&mut vm);
305
306        let exit_code = !vm.run();
307
308        (exit_code, vm)
309    }
310
311    #[track_caller]
312    pub fn compare_stack(actual: &Tuple, expected: &Tuple) {
313        let cx = Cell::empty_context();
314
315        let actual_stack = {
316            let mut b = CellBuilder::new();
317            actual.store_as_stack_value(&mut b, cx).unwrap();
318            b.build_ext(cx).unwrap()
319        };
320
321        let expected_stack = {
322            let mut b = CellBuilder::new();
323            expected.store_as_stack_value(&mut b, cx).unwrap();
324            b.build_ext(cx).unwrap()
325        };
326
327        assert_eq!(actual_stack, expected_stack, "stack mismatch");
328    }
329
330    #[test]
331    #[traced_test]
332    fn dispatch_works() {
333        let code = Boc::decode(tvmasm!(
334            "PUSHINT 0",
335            "PUSHINT 1",
336            "PUSHINT 2",
337            "NOP",
338            "PUSHNAN",
339            "DEBUG 0",
340            "XCHG s0, s3",
341            "XCHG s1, s3",
342            "PUXC s1, s2",
343            "DUP",
344            "OVER",
345            "PUSH s3",
346            "DROP",
347            "NIP",
348            "POP s3",
349            "XCHG3 s1, s2, s3",
350            "XCHG2 s1, s2",
351            "XCPU s1, s2",
352            "PUXC s1, s0",
353            "PUSH2 s3, s4",
354            "XCHG3 s3, s4, s0",
355            "PUXC2 s3, s1, s0",
356            "PUSH3 s1, s2, s3",
357            "PU2XC s1, s2, s(-2)",
358            "BLKSWAP 1, 2",
359            "DEBUG 0",
360            "DEBUGSTR x{48454c50313233}",
361        ))
362        .unwrap();
363
364        let mut output = TracingOutput::default();
365        let mut vm = VmState::builder()
366            .with_code(code)
367            .with_debug(&mut output)
368            .build();
369        let exit_code = !vm.run();
370        println!("Exit code: {exit_code}");
371    }
372
373    #[test]
374    #[traced_test]
375    fn library_cells_works() -> anyhow::Result<()> {
376        let library = Boc::decode_base64(
377            "te6ccuECDwEAA9EAABoAJAEkAS4CJgL+A5wEVAVSBkoGrgcsB1EHfAeiART/APSkE/S88sgLAQIBYgIDAvjQAdDTAwFxsI5IE18DgCDXIe1E0NMD+gD6QPpA0QTTHwGEDyGCEBeNRRm6AoIQe92X3roSsfL0gEDXIfoAMBKgQBMDyMsDWPoCAc8WAc8Wye1U4PpA+kAx+gAx9AH6ADH6AAExcPg6AtMfASCCEA+KfqW6joUwNFnbPOAzBAUCASANDgHyA9M/AQH6APpAIfpEMMAA8uFN7UTQ0wP6APpA+kDRUwnHBSRxsMAAIbHyrVIrxwVQCrHy4ElRFaEgwv/yr/gqVCWQcFRgBBMVA8jLA1j6AgHPFgHPFskhyMsBE/QAEvQAywDJIPkAcHTIywLKB8v/ydAE+kD0AfoAIAYC0CKCEBeNRRm6joQyWts84DQhghBZXwe8uo6EMQHbPOAyIIIQ7tI207qOLzABgEDXIdMD0e1E0NMD+gD6QPpA0TNRQscF8uBKQDMDyMsDWPoCAc8WAc8Wye1U4GwhghDTchWMutyED/LwCAkBmCDXCwCa10vAAQHAAbDysZEw4siCEBeNRRkByx9QCgHLP1AI+gIjzxYBzxYm+gJQB88WyciAGAHLBVAEzxZw+gJAY3dQA8trzMzJRTcHALQhkXKRceL4OSBuk4EkJ5Eg4iFulDGBKHORAeJQI6gToHOBA6Nw+DygAnD4NhKgAXD4NqBzgQQJghAJZgGAcPg3oLzysASAUPsAWAPIywNY+gIBzxYBzxbJ7VQD9O1E0NMD+gD6QPpA0SNysMAC8m0H0z8BAfoAUUGgBPpA+kBTuscF+CpUZOBwVGAEExUDyMsDWPoCAc8WAc8WySHIywET9AAS9ADLAMn5AHB0yMsCygfL/8nQUAzHBRux8uBKCfoAIZJfBOMNJtcLAcAAs5MwbDPjDVUCCgsMAfLtRNDTA/oA+kD6QNEG0z8BAfoA+kD0AdFRQaFSiMcF8uBJJsL/8q/IghB73ZfeAcsfWAHLPwH6AiHPFljPFsnIgBgBywUmzxZw+gIBcVjLaszJA/g5IG6UMIEWn95xgQLycPg4AXD4NqCBGndw+DagvPKwAoBQ+wADDABgyIIQc2LQnAHLHyUByz9QBPoCWM8WWM8WyciAEAHLBSTPFlj6AgFxWMtqzMmAEfsAAHpQVKH4L6BzgQQJghAJZgGAcPg3tgly+wLIgBABywVQBc8WcPoCcAHLaoIQ1TJ22wHLH1gByz/JgQCC+wBZACADyMsDWPoCAc8WAc8Wye1UACe/2BdqJoaYH9AH0gfSBomfwVIJhAAhvFCPaiaGmB/QB9IH0gaK+Bz+s3AU",
378        )?;
379        let libraries = HashMap::from([(
380            "8f452d7a4dfd74066b682365177259ed05734435be76b5fd4bd5d8af2b7c3d68"
381                .parse::<HashBytes>()?,
382            SimpleLib {
383                public: true,
384                root: library,
385            },
386        )]);
387
388        let addr = "0:2626CF30B702BDDED845EFC883EFA45029FF59DEFDACC4CE7B8B0A5966D75002"
389            .parse::<StdAddr>()?;
390
391        let mut code =
392            Boc::decode_base64("te6ccgEBAQEAIwAIQgKPRS16Tf10BmtoI2UXclntBXNENb52tf1L1divK3w9aA==")?;
393
394        let data = Boc::decode_base64(
395            "te6ccgEBAQEATAAAkwYKW203ZzmABH9S8yMeP84FtyIBfwh9D44CvZmnNI5D0211guF4CZxwAsROplLUCShZxn2kTkyjrdZWWw4ol9ZAosUb+zcNiHf6",
396        )?;
397
398        let smc_info = SmcInfoBase::new()
399            .with_now(1733142533)
400            .with_block_lt(52499545000000)
401            .with_tx_lt(52499545000005)
402            .with_account_balance(CurrencyCollection::new(5981380))
403            .with_account_addr(addr.clone().into())
404            .require_ton_v4();
405
406        if code.is_exotic() {
407            code = CellBuilder::build_from(code)?;
408        }
409
410        let mut output = TracingOutput::default();
411        let mut vm_state = VmState::builder()
412            .with_smc_info(smc_info)
413            .with_stack(tuple![
414                int 97026, // get_wallet_data
415            ])
416            .with_code(code)
417            .with_data(data)
418            .with_gas(GasParams::getter())
419            .with_debug(&mut output)
420            .with_libraries(&libraries)
421            .build();
422
423        assert_eq!(vm_state.run(), -1);
424        Ok(())
425    }
426
427    #[test]
428    #[traced_test]
429    fn recursive_libraries() -> anyhow::Result<()> {
430        fn make_lib(code: &DynCell) -> Cell {
431            let mut b = CellBuilder::new();
432            b.set_exotic(true);
433            b.store_u8(CellType::LibraryReference.to_byte()).unwrap();
434            b.store_u256(code.repr_hash()).unwrap();
435            b.build().unwrap()
436        }
437
438        let leaf_lib = Boc::decode(tvmasm!("NOP"))?;
439        let lib1 = make_lib(leaf_lib.as_ref());
440        let lib2 = make_lib(lib1.as_ref());
441
442        let libraries = HashMap::from([
443            (*leaf_lib.repr_hash(), SimpleLib {
444                public: true,
445                root: leaf_lib,
446            }),
447            (*lib1.repr_hash(), SimpleLib {
448                public: true,
449                root: lib1,
450            }),
451            (*lib2.repr_hash(), SimpleLib {
452                public: true,
453                root: lib2.clone(),
454            }),
455        ]);
456
457        let code = CellBuilder::build_from(lib2)?;
458
459        let smc_info = SmcInfoBase::new()
460            .with_now(1733142533)
461            .with_block_lt(52499545000000)
462            .with_tx_lt(52499545000005)
463            .with_account_balance(CurrencyCollection::new(5981380))
464            .with_account_addr(Default::default())
465            .require_ton_v4();
466
467        let mut output = TracingOutput::default();
468        let mut vm_state = VmState::builder()
469            .with_smc_info(smc_info)
470            .with_code(code)
471            .with_gas(GasParams::getter())
472            .with_debug(&mut output)
473            .with_libraries(&libraries)
474            .build();
475
476        assert_eq!(vm_state.run(), -10); // cell underflow
477        Ok(())
478    }
479
480    #[derive(Default)]
481    pub struct TracingOutput {
482        buffer: String,
483    }
484
485    impl std::fmt::Write for TracingOutput {
486        fn write_str(&mut self, mut s: &str) -> std::fmt::Result {
487            while !s.is_empty() {
488                match s.split_once('\n') {
489                    None => {
490                        self.buffer.push_str(s);
491                        return Ok(());
492                    }
493                    Some((prefix, rest)) => {
494                        tracing::debug!("{}{prefix}", self.buffer);
495                        self.buffer.clear();
496                        s = rest;
497                    }
498                }
499            }
500            Ok(())
501        }
502    }
503}