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::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, ])
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); 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}