1#[cfg(test)]
2#[macro_use]
3extern crate tycho_asm_macros;
4extern crate self as tycho_vm;
5
6macro_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#[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, ])
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); 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}