Skip to main content

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::getter::{
193    VmCaller, VmCallerTxInput, VmGetterArgs, VmGetterError, VmGetterMethodId, VmGetterOutput,
194    VmMessageArgs, VmMessageError, VmMessageOutput,
195};
196pub use self::instr::{codepage, codepage0};
197#[cfg(feature = "tracing")]
198pub use self::log::{VM_LOG_TARGET, VmLogRows, VmLogRowsGuard, VmLogSubscriber};
199pub use self::saferc::{SafeDelete, SafeRc, SafeRcMakeMut};
200pub use self::smc_info::{
201    CustomSmcInfo, SmcInfo, SmcInfoBase, SmcInfoTonV4, SmcInfoTonV6, SmcInfoTonV11, UnpackedConfig,
202    UnpackedInMsgSmcInfo, VmVersion,
203};
204pub use self::stack::{
205    NaN, RcStackValue, Stack, StackValue, StackValueType, StaticStackValue, Tuple, TupleExt,
206};
207#[cfg(feature = "tracing")]
208pub use self::state::VmLogMask;
209pub use self::state::{
210    BehaviourModifiers, CommittedState, InitSelectorParams, IntoCode, ParentVmState, SaveCr,
211    VmState, VmStateBuilder,
212};
213pub use self::util::OwnedCellSlice;
214
215#[macro_use]
216mod log;
217
218mod cont;
219mod dispatch;
220mod error;
221mod gas;
222mod getter;
223mod instr;
224mod saferc;
225mod smc_info;
226mod stack;
227mod state;
228mod util;
229
230#[doc(hidden)]
231pub mod __export {
232    pub use num_bigint;
233    pub use tycho_types;
234}
235
236#[doc(hidden)]
237mod __private {
238    use self::not::Bool;
239
240    #[doc(hidden)]
241    #[inline]
242    pub fn not(cond: impl Bool) -> bool {
243        cond.not()
244    }
245
246    mod not {
247        #[doc(hidden)]
248        pub trait Bool {
249            fn not(self) -> bool;
250        }
251
252        impl Bool for bool {
253            #[inline]
254            fn not(self) -> bool {
255                !self
256            }
257        }
258
259        impl Bool for &bool {
260            #[inline]
261            fn not(self) -> bool {
262                !*self
263            }
264        }
265    }
266}
267
268#[cfg(test)]
269mod tests {
270    use std::collections::HashMap;
271
272    use tracing_test::traced_test;
273    use tycho_types::models::{CurrencyCollection, SimpleLib, StdAddr};
274    use tycho_types::prelude::*;
275
276    use super::*;
277    use crate::stack::{RcStackValue, Tuple};
278
279    pub fn run_vm_with_stack<'a, I>(
280        code: &[u8],
281        c7_params: Tuple,
282        original_stack: I,
283        gas_limit: u64,
284        libs: &'a impl LibraryProvider,
285        modify_state: impl FnOnce(&mut VmState),
286        output: &'a mut impl std::fmt::Write,
287    ) -> (i32, VmState<'a>)
288    where
289        I: IntoIterator<Item = RcStackValue>,
290    {
291        let code = Boc::decode(code).unwrap();
292
293        let mut vm = VmState::builder()
294            .with_code(code)
295            .with_libraries(libs)
296            .with_smc_info(CustomSmcInfo {
297                version: VmState::DEFAULT_VERSION,
298                c7: SafeRc::new(c7_params),
299            })
300            .with_debug(output)
301            .with_stack(original_stack)
302            .with_gas(GasParams {
303                max: gas_limit,
304                limit: gas_limit,
305                credit: 0,
306                ..GasParams::getter()
307            })
308            .build();
309
310        modify_state(&mut vm);
311
312        let exit_code = !vm.run();
313
314        (exit_code, vm)
315    }
316
317    #[track_caller]
318    pub fn compare_stack(actual: &Tuple, expected: &Tuple) {
319        let cx = Cell::empty_context();
320
321        let actual_stack = {
322            let mut b = CellBuilder::new();
323            actual.store_as_stack_value(&mut b, cx).unwrap();
324            b.build_ext(cx).unwrap()
325        };
326
327        let expected_stack = {
328            let mut b = CellBuilder::new();
329            expected.store_as_stack_value(&mut b, cx).unwrap();
330            b.build_ext(cx).unwrap()
331        };
332
333        assert_eq!(actual_stack, expected_stack, "stack mismatch");
334    }
335
336    #[test]
337    #[traced_test]
338    fn dispatch_works() {
339        let code = Boc::decode(tvmasm!(
340            "PUSHINT 0",
341            "PUSHINT 1",
342            "PUSHINT 2",
343            "NOP",
344            "PUSHNAN",
345            "DEBUG 0",
346            "XCHG s0, s3",
347            "XCHG s1, s3",
348            "PUXC s1, s2",
349            "DUP",
350            "OVER",
351            "PUSH s3",
352            "DROP",
353            "NIP",
354            "POP s3",
355            "XCHG3 s1, s2, s3",
356            "XCHG2 s1, s2",
357            "XCPU s1, s2",
358            "PUXC s1, s0",
359            "PUSH2 s3, s4",
360            "XCHG3 s3, s4, s0",
361            "PUXC2 s3, s1, s0",
362            "PUSH3 s1, s2, s3",
363            "PU2XC s1, s2, s(-2)",
364            "BLKSWAP 1, 2",
365            "DEBUG 0",
366            "DEBUGSTR x{48454c50313233}",
367        ))
368        .unwrap();
369
370        let mut output = TracingOutput::default();
371        let mut vm = VmState::builder()
372            .with_code(code)
373            .with_debug(&mut output)
374            .build();
375        let exit_code = !vm.run();
376        println!("Exit code: {exit_code}");
377    }
378
379    #[test]
380    #[traced_test]
381    fn library_cells_works() -> anyhow::Result<()> {
382        let library = Boc::decode_base64(
383            "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",
384        )?;
385        let libraries = HashMap::from([(
386            "8f452d7a4dfd74066b682365177259ed05734435be76b5fd4bd5d8af2b7c3d68"
387                .parse::<HashBytes>()?,
388            SimpleLib {
389                public: true,
390                root: library,
391            },
392        )]);
393
394        let addr = "0:2626CF30B702BDDED845EFC883EFA45029FF59DEFDACC4CE7B8B0A5966D75002"
395            .parse::<StdAddr>()?;
396
397        let mut code =
398            Boc::decode_base64("te6ccgEBAQEAIwAIQgKPRS16Tf10BmtoI2UXclntBXNENb52tf1L1divK3w9aA==")?;
399
400        let data = Boc::decode_base64(
401            "te6ccgEBAQEATAAAkwYKW203ZzmABH9S8yMeP84FtyIBfwh9D44CvZmnNI5D0211guF4CZxwAsROplLUCShZxn2kTkyjrdZWWw4ol9ZAosUb+zcNiHf6",
402        )?;
403
404        let smc_info = SmcInfoBase::new()
405            .with_now(1733142533)
406            .with_block_lt(52499545000000)
407            .with_tx_lt(52499545000005)
408            .with_account_balance(CurrencyCollection::new(5981380))
409            .with_account_addr(addr.clone().into())
410            .require_ton_v4();
411
412        if code.is_exotic() {
413            code = CellBuilder::build_from(code)?;
414        }
415
416        let mut output = TracingOutput::default();
417        let mut vm_state = VmState::builder()
418            .with_smc_info(smc_info)
419            .with_stack(tuple![
420                int 97026, // get_wallet_data
421            ])
422            .with_code(code)
423            .with_data(data)
424            .with_gas(GasParams::getter())
425            .with_debug(&mut output)
426            .with_libraries(&libraries)
427            .build();
428
429        assert_eq!(vm_state.run(), -1);
430        Ok(())
431    }
432
433    #[test]
434    #[traced_test]
435    fn recursive_libraries() -> anyhow::Result<()> {
436        fn make_lib(code: &DynCell) -> Cell {
437            let mut b = CellBuilder::new();
438            b.set_exotic(true);
439            b.store_u8(CellType::LibraryReference.to_byte()).unwrap();
440            b.store_u256(code.repr_hash()).unwrap();
441            b.build().unwrap()
442        }
443
444        let leaf_lib = Boc::decode(tvmasm!("NOP"))?;
445        let lib1 = make_lib(leaf_lib.as_ref());
446        let lib2 = make_lib(lib1.as_ref());
447
448        let libraries = HashMap::from([
449            (*leaf_lib.repr_hash(), SimpleLib {
450                public: true,
451                root: leaf_lib,
452            }),
453            (*lib1.repr_hash(), SimpleLib {
454                public: true,
455                root: lib1,
456            }),
457            (*lib2.repr_hash(), SimpleLib {
458                public: true,
459                root: lib2.clone(),
460            }),
461        ]);
462
463        let code = CellBuilder::build_from(lib2)?;
464
465        let smc_info = SmcInfoBase::new()
466            .with_now(1733142533)
467            .with_block_lt(52499545000000)
468            .with_tx_lt(52499545000005)
469            .with_account_balance(CurrencyCollection::new(5981380))
470            .with_account_addr(Default::default())
471            .require_ton_v4();
472
473        let mut output = TracingOutput::default();
474        let mut vm_state = VmState::builder()
475            .with_smc_info(smc_info)
476            .with_code(code)
477            .with_gas(GasParams::getter())
478            .with_debug(&mut output)
479            .with_libraries(&libraries)
480            .build();
481
482        assert_eq!(vm_state.run(), -10); // cell underflow
483        Ok(())
484    }
485
486    #[derive(Default)]
487    pub struct TracingOutput {
488        buffer: String,
489    }
490
491    impl std::fmt::Write for TracingOutput {
492        fn write_str(&mut self, mut s: &str) -> std::fmt::Result {
493            while !s.is_empty() {
494                match s.split_once('\n') {
495                    None => {
496                        self.buffer.push_str(s);
497                        return Ok(());
498                    }
499                    Some((prefix, rest)) => {
500                        tracing::debug!("{}{prefix}", self.buffer);
501                        self.buffer.clear();
502                        s = rest;
503                    }
504                }
505            }
506            Ok(())
507        }
508    }
509}