use std::borrow::Cow;
use num_bigint::{BigInt, Sign};
use num_traits::ToPrimitive;
use tycho_types::cell::LoadMode;
use tycho_types::error::Error;
use tycho_types::prelude::*;
use tycho_vm_proc::vm_module;
use crate::cont::OrdCont;
#[cfg(feature = "dump")]
use crate::dispatch::DumpOutput;
#[cfg(feature = "dump")]
use crate::error::{DumpError, DumpResult};
use crate::error::{VmError, VmResult};
use crate::saferc::SafeRc;
use crate::stack::{Stack, StackValue};
use crate::state::VmState;
#[cfg(any(feature = "dump", feature = "tracing"))]
use crate::util::CellSliceExt;
use crate::util::{OwnedCellSlice, remove_trailing};
pub struct CellOps;
#[vm_module]
impl CellOps {
#[op_ext(code = 0x88, code_bits = 8, arg_bits = 0, dump_with = dump_push_ref)]
fn exec_push_ref(st: &mut VmState, _: u32, bits: u16) -> VmResult<i32> {
exec_push_ref_common(st, bits, "PUSHREF", PushRefMode::Cell)
}
#[cfg(feature = "dump")]
fn dump_push_ref(
code: &mut CellSlice<'_>,
_: u32,
bits: u16,
f: &mut dyn DumpOutput,
) -> DumpResult {
dump_push_ref_common(code, bits, "PUSHREF", false, f)
}
#[op_ext(code = 0x89, code_bits = 8, arg_bits = 0, dump_with = dump_push_ref_slice)]
fn exec_push_ref_slice(st: &mut VmState, _: u32, bits: u16) -> VmResult<i32> {
exec_push_ref_common(st, bits, "PUSHREFSLICE", PushRefMode::Slice)
}
#[cfg(feature = "dump")]
fn dump_push_ref_slice(
code: &mut CellSlice<'_>,
_: u32,
bits: u16,
f: &mut dyn DumpOutput,
) -> DumpResult {
dump_push_ref_common(code, bits, "PUSHREFSLICE", false, f)
}
#[op_ext(code = 0x8a, code_bits = 8, arg_bits = 0, dump_with = dump_push_ref_cont)]
fn exec_push_ref_cont(st: &mut VmState, _: u32, bits: u16) -> VmResult<i32> {
exec_push_ref_common(st, bits, "PUSHREFCONT", PushRefMode::Cont)
}
#[cfg(feature = "dump")]
fn dump_push_ref_cont(
code: &mut CellSlice<'_>,
_: u32,
bits: u16,
f: &mut dyn DumpOutput,
) -> DumpResult {
dump_push_ref_common(code, bits, "PUSHREFCONT", true, f)
}
#[op_ext(code = 0x8b, code_bits = 8, arg_bits = 4, dump_with = dump_push_slice)]
fn exec_push_slice(st: &mut VmState, args: u32, bits: u16) -> VmResult<i32> {
let data_bits = ((args & 0xf) * 8 + 4) as u16;
exec_push_slice_common(st, bits, data_bits, 0)
}
#[cfg(feature = "dump")]
fn dump_push_slice(
code: &mut CellSlice<'_>,
args: u32,
bits: u16,
f: &mut dyn DumpOutput,
) -> DumpResult {
let data_bits = ((args & 0xf) * 8 + 4) as u16;
dump_push_slice_common(code, bits, data_bits, 0, f)
}
#[op_ext(code = 0x8c, code_bits = 8, arg_bits = 7, dump_with = dump_push_slice_r)]
fn exec_push_slice_r(st: &mut VmState, args: u32, bits: u16) -> VmResult<i32> {
let data_bits = ((args & 0x1f) * 8 + 1) as u16;
let refs = (((args >> 5) & 0b11) + 1) as u8;
exec_push_slice_common(st, bits, data_bits, refs)
}
#[cfg(feature = "dump")]
fn dump_push_slice_r(
code: &mut CellSlice<'_>,
args: u32,
bits: u16,
f: &mut dyn DumpOutput,
) -> DumpResult {
let data_bits = ((args & 0x1f) * 8 + 1) as u16;
let refs = (((args >> 5) & 0b11) + 1) as u8;
dump_push_slice_common(code, bits, data_bits, refs, f)
}
#[op_ext_range(
code_min = 0x8d << 10,
code_max = ((0x8d << 3) + 5) << 7,
total_bits = 18,
dump_with = dump_push_slice_r2,
)]
fn exec_push_slice_r2(st: &mut VmState, args: u32, bits: u16) -> VmResult<i32> {
let data_bits = ((args & 0x7f) * 8 + 6) as u16;
let refs = ((args >> 7) & 0b111) as u8;
exec_push_slice_common(st, bits, data_bits, refs)
}
#[cfg(feature = "dump")]
fn dump_push_slice_r2(
code: &mut CellSlice<'_>,
args: u32,
bits: u16,
f: &mut dyn DumpOutput,
) -> DumpResult {
let data_bits = ((args & 0x7f) * 8 + 6) as u16;
let refs = ((args >> 7) & 0b111) as u8;
dump_push_slice_common(code, bits, data_bits, refs, f)
}
#[op_ext(code = 0x8e >> 1, code_bits = 7, arg_bits = 9, dump_with = dump_push_cont)]
fn exec_push_cont(st: &mut VmState, args: u32, bits: u16) -> VmResult<i32> {
let data_bits = ((args & 0x7f) * 8) as u16;
let refs = ((args >> 7) & 0b11) as u8;
let code_range = st.code.range_mut();
vm_ensure!(
code_range.has_remaining(bits + data_bits, refs),
InvalidOpcode
);
code_range.skip_first(bits, 0).ok();
let mut slice_range = *code_range;
slice_range.only_first(data_bits, refs).ok();
code_range.skip_first(data_bits, refs).ok();
let code = OwnedCellSlice::from((slice_range, st.code.cell().clone()));
vm_log_op!("PUSHCONT {}", code);
let cont = SafeRc::new(OrdCont::simple(code, st.cp.id()));
ok!(SafeRc::make_mut(&mut st.stack).push_raw(cont));
Ok(0)
}
#[cfg(feature = "dump")]
fn dump_push_cont(
code: &mut CellSlice<'_>,
args: u32,
bits: u16,
f: &mut dyn DumpOutput,
) -> DumpResult {
let data_bits = ((args & 0x7f) * 8) as u16;
let refs = ((args >> 7) & 0b11) as u8;
if !code.has_remaining(bits + data_bits, refs) {
return Err(DumpError::InvalidOpcode);
}
code.skip_first(bits, 0)?;
let mut slice = *code;
slice.only_first(data_bits, refs)?;
code.skip_first(data_bits, refs)?;
f.record_cont_slice(slice)?;
f.record_opcode(&format_args!("PUSHCONT {}", slice.display_as_stack_value()))
}
#[op_ext(code = 0x9, code_bits = 4, arg_bits = 4, dump_with = dump_push_cont_simple)]
fn exec_push_cont_simple(st: &mut VmState, args: u32, bits: u16) -> VmResult<i32> {
let data_bits = ((args & 0xf) * 8) as u16;
let code_range = st.code.range_mut();
vm_ensure!(code_range.has_remaining(bits + data_bits, 0), InvalidOpcode);
code_range.skip_first(bits, 0).ok();
let mut slice_range = *code_range;
slice_range.only_first(data_bits, 0).ok();
code_range.skip_first(data_bits, 0).ok();
let code = OwnedCellSlice::from((slice_range, st.code.cell().clone()));
vm_log_op!("PUSHCONT {}", code);
let cont = SafeRc::new(OrdCont::simple(code, st.cp.id()));
ok!(SafeRc::make_mut(&mut st.stack).push_raw(cont));
Ok(0)
}
#[cfg(feature = "dump")]
fn dump_push_cont_simple(
code: &mut CellSlice<'_>,
args: u32,
bits: u16,
f: &mut dyn DumpOutput,
) -> DumpResult {
let data_bits = ((args & 0xf) * 8) as u16;
if !code.has_remaining(bits + data_bits, 0) {
return Err(DumpError::InvalidOpcode);
}
code.skip_first(bits, 0)?;
let mut slice = *code;
slice.only_first(data_bits, 0)?;
code.skip_first(data_bits, 0)?;
f.record_cont_slice(slice)?;
f.record_opcode(&format_args!("PUSHCONT {}", slice.display_as_stack_value()))
}
#[op(code = "c700", fmt = "SEMPTY", args(op = SliceBoolUnaryOp::IsEmpty))]
#[op(code = "c701", fmt = "SDEMPTY", args(op = SliceBoolUnaryOp::NoBits))]
#[op(code = "c702", fmt = "SREMPTY", args(op = SliceBoolUnaryOp::NoRefs))]
#[op(code = "c703", fmt = "SDFIRST", args(op = SliceBoolUnaryOp::FirstBit))]
fn exec_slice_bool_unary_op(st: &mut VmState, op: SliceBoolUnaryOp) -> VmResult<i32> {
let stack = SafeRc::make_mut(&mut st.stack);
let cs = ok!(stack.pop_cs());
let range = cs.range();
let res = match op {
SliceBoolUnaryOp::IsEmpty => range.is_data_empty() && range.is_refs_empty(),
SliceBoolUnaryOp::NoBits => range.is_data_empty(),
SliceBoolUnaryOp::NoRefs => range.is_refs_empty(),
SliceBoolUnaryOp::FirstBit => {
let slice = cs.apply();
slice.get_bit(0).unwrap_or_default()
}
};
ok!(stack.push_bool(res));
Ok(0)
}
#[op(code = "c704", fmt = "SDLEXCMP")]
fn exec_slice_lex_cmp(st: &mut VmState) -> VmResult<i32> {
let stack = SafeRc::make_mut(&mut st.stack);
let cs2 = ok!(stack.pop_cs());
let cs1 = ok!(stack.pop_cs());
let slice1 = cs1.apply();
let slice2 = cs2.apply();
let res = slice1.lex_cmp(&slice2)? as i8;
ok!(stack.push_int(res));
Ok(0)
}
#[op(code = "c705", fmt = "SDEQ", args(op = SliceBinaryOp::DataEq))]
#[op(code = "c708", fmt = "SDPFX", args(op = SliceBinaryOp::IsPrefix))]
#[op(code = "c709", fmt = "SDPFXREV", args(op = SliceBinaryOp::IsPrefixRev))]
#[op(code = "c70a", fmt = "SDPPFX", args(op = SliceBinaryOp::IsProperPrefix))]
#[op(code = "c70b", fmt = "SDPPFXREV", args(op = SliceBinaryOp::IsProperPrefixRev))]
#[op(code = "c70c", fmt = "SDSFX", args(op = SliceBinaryOp::IsSuffix))]
#[op(code = "c70d", fmt = "SDSFXREV", args(op = SliceBinaryOp::IsSuffixRev))]
#[op(code = "c70e", fmt = "SDPSFX", args(op = SliceBinaryOp::IsProperSuffix))]
#[op(code = "c70f", fmt = "SDPSFXREV", args(op = SliceBinaryOp::IsProperSuffixRev))]
fn exec_slice_bin_op(st: &mut VmState, op: SliceBinaryOp) -> VmResult<i32> {
fn is_proper(left: &CellSlice<'_>, right: &CellSlice<'_>) -> bool {
left.size_bits() < right.size_bits()
}
let stack = SafeRc::make_mut(&mut st.stack);
let cs2 = ok!(stack.pop_cs());
let cs1 = ok!(stack.pop_cs());
let slice1 = cs1.apply();
let slice2 = cs2.apply();
let res = match op {
SliceBinaryOp::DataEq => slice1.lex_cmp(&slice2)?.is_eq(),
SliceBinaryOp::IsPrefix => slice1.is_data_prefix_of(&slice2)?,
SliceBinaryOp::IsPrefixRev => slice2.is_data_prefix_of(&slice1)?,
SliceBinaryOp::IsProperPrefix => {
is_proper(&slice1, &slice2) && slice1.is_data_prefix_of(&slice2)?
}
SliceBinaryOp::IsProperPrefixRev => {
is_proper(&slice2, &slice1) && slice2.is_data_prefix_of(&slice1)?
}
SliceBinaryOp::IsSuffix => slice1.is_data_suffix_of(&slice2)?,
SliceBinaryOp::IsSuffixRev => slice2.is_data_suffix_of(&slice1)?,
SliceBinaryOp::IsProperSuffix => {
is_proper(&slice1, &slice2) && slice1.is_data_suffix_of(&slice2)?
}
SliceBinaryOp::IsProperSuffixRev => {
is_proper(&slice2, &slice1) && slice2.is_data_suffix_of(&slice1)?
}
};
ok!(stack.push_bool(res));
Ok(0)
}
#[op(code = "c710", fmt = "SDCNTLEAD0", args(op = SliceIntUnaryOp::Leading0))]
#[op(code = "c711", fmt = "SDCNTLEAD1", args(op = SliceIntUnaryOp::Leading1))]
#[op(code = "c712", fmt = "SDCNTTRAIL0", args(op = SliceIntUnaryOp::Trailing0))]
#[op(code = "c713", fmt = "SDCNTTRAIL1", args(op = SliceIntUnaryOp::Trailing1))]
fn exec_slice_int_unary_op(st: &mut VmState, op: SliceIntUnaryOp) -> VmResult<i32> {
let stack = SafeRc::make_mut(&mut st.stack);
let cs = ok!(stack.pop_cs());
let slice = cs.apply();
let res = match op {
SliceIntUnaryOp::Leading0 => slice.count_leading(false),
SliceIntUnaryOp::Leading1 => slice.count_leading(true),
SliceIntUnaryOp::Trailing0 => slice.count_trailing(false),
SliceIntUnaryOp::Trailing1 => slice.count_trailing(true),
}?;
ok!(stack.push_int(res));
Ok(0)
}
#[op(code = "c8", fmt = "NEWC")]
fn exec_new_builder(st: &mut VmState) -> VmResult<i32> {
ok!(SafeRc::make_mut(&mut st.stack).push(CellBuilder::new()));
Ok(0)
}
#[op(code = "c9", fmt = "ENDC")]
fn exec_builder_to_cell(st: &mut VmState) -> VmResult<i32> {
let stack = SafeRc::make_mut(&mut st.stack);
let builder = stack.pop_builder()?;
let cell = SafeRc::unwrap_or_clone(builder).build_ext(&st.gas)?;
ok!(stack.push(cell));
Ok(0)
}
#[op(code = "caxx", fmt = "STI {x}", args(x = (args & 0xff) + 1, signed = true))]
#[op(code = "cbxx", fmt = "STU {x}", args(x = (args & 0xff) + 1, signed = false))]
fn exec_store_int(st: &mut VmState, x: u32, signed: bool) -> VmResult<i32> {
let stack = SafeRc::make_mut(&mut st.stack);
exec_store_int_common(stack, x as u16, StoreIntArgs::from_sign(signed))
}
#[op(code = "cc", fmt = "STREF", args(quiet = false))]
#[op(code = "cf10", fmt = "STREF", args(quiet = false))]
#[op(code = "cf18", fmt = "STREFQ", args(quiet = true))]
fn exec_store_ref(st: &mut VmState, quiet: bool) -> VmResult<i32> {
let stack = SafeRc::make_mut(&mut st.stack);
let mut builder = ok!(stack.pop_builder());
let cell = ok!(stack.pop_cell());
if !builder.has_capacity(0, 1) {
return finish_store_overflow(stack, cell, builder, quiet);
}
SafeRc::make_mut(&mut builder).store_reference(SafeRc::unwrap_or_clone(cell))?;
finish_store_ok(stack, builder, quiet)
}
#[op(code = "cd", fmt = "ENDCST", args(quiet = false))]
#[op(code = "cf15", fmt = "STBREFR", args(quiet = false))]
#[op(code = "cf1d", fmt = "STBREFRQ", args(quiet = true))]
fn exec_store_builder_as_ref_rev(st: &mut VmState, quiet: bool) -> VmResult<i32> {
let stack = SafeRc::make_mut(&mut st.stack);
let child_builder = ok!(stack.pop_builder());
let mut builder = ok!(stack.pop_builder());
if !builder.has_capacity(0, 1) {
return finish_store_overflow(stack, builder, child_builder, quiet);
}
let cell = SafeRc::unwrap_or_clone(child_builder).build_ext(&st.gas)?;
SafeRc::make_mut(&mut builder).store_reference(cell)?;
finish_store_ok(stack, builder, quiet)
}
#[op(code = "ce", fmt = "STSLICE", args(quiet = false))]
#[op(code = "cf12", fmt = "STSLICE", args(quiet = false))]
#[op(code = "cf1a", fmt = "STSLICEQ", args(quiet = true))]
fn exec_store_slice(st: &mut VmState, quiet: bool) -> VmResult<i32> {
let stack = SafeRc::make_mut(&mut st.stack);
let mut builder = ok!(stack.pop_builder());
let cs = ok!(stack.pop_cs());
if !cs.fits_into(&builder) {
return finish_store_overflow(stack, cs, builder, quiet);
}
let slice = cs.apply();
SafeRc::make_mut(&mut builder).store_slice(slice)?;
finish_store_ok(stack, builder, quiet)
}
#[op(code = "cf0$0sss", fmt = s.display_x(), args(s = StoreIntArgs(args)))]
fn exec_store_int_var(st: &mut VmState, s: StoreIntArgs) -> VmResult<i32> {
let stack = SafeRc::make_mut(&mut st.stack);
let bits = ok!(stack.pop_smallint_range(0, 256 + s.is_signed() as u32));
exec_store_int_common(stack, bits as u16, s)
}
#[op(
code = "cf0$1sss#nn",
fmt = ("{} {n}", s.display()),
args(s = StoreIntArgs(args >> 8), n = (args & 0xff) + 1),
)]
fn exec_store_int_fixed(st: &mut VmState, s: StoreIntArgs, n: u32) -> VmResult<i32> {
exec_store_int_common(SafeRc::make_mut(&mut st.stack), n as _, s)
}
#[op(code = "cf11", fmt = "STBREF", args(quiet = false))]
#[op(code = "cf19", fmt = "STBREFQ", args(quiet = true))]
fn exec_store_builder_as_ref(st: &mut VmState, quiet: bool) -> VmResult<i32> {
let stack = SafeRc::make_mut(&mut st.stack);
let mut builder = ok!(stack.pop_builder());
let child_builder = ok!(stack.pop_builder());
if !builder.has_capacity(0, 1) {
return finish_store_overflow(stack, child_builder, builder, quiet);
}
let cell = SafeRc::unwrap_or_clone(child_builder).build_ext(&st.gas)?;
SafeRc::make_mut(&mut builder).store_reference(cell)?;
finish_store_ok(stack, builder, quiet)
}
#[op(code = "cf13", fmt = "STB", args(quiet = false))]
#[op(code = "cf1b", fmt = "STBQ", args(quiet = true))]
fn exec_store_builder(st: &mut VmState, quiet: bool) -> VmResult<i32> {
let stack = SafeRc::make_mut(&mut st.stack);
let mut builder = ok!(stack.pop_builder());
let other_builder = ok!(stack.pop_builder());
if !builder.has_capacity(other_builder.size_bits(), other_builder.size_refs()) {
return finish_store_overflow(stack, other_builder, builder, quiet);
}
SafeRc::make_mut(&mut builder).store_builder(&other_builder)?;
finish_store_ok(stack, builder, quiet)
}
#[op(code = "cf14", fmt = "STREFR", args(quiet = false))]
#[op(code = "cf1c", fmt = "STREFRQ", args(quiet = true))]
fn exec_store_ref_rev(st: &mut VmState, quiet: bool) -> VmResult<i32> {
let stack = SafeRc::make_mut(&mut st.stack);
let cell = ok!(stack.pop_cell());
let mut builder = ok!(stack.pop_builder());
if !builder.has_capacity(0, 1) {
return finish_store_overflow(stack, builder, cell, quiet);
}
SafeRc::make_mut(&mut builder).store_reference(SafeRc::unwrap_or_clone(cell))?;
finish_store_ok(stack, builder, quiet)
}
#[op(code = "cf16", fmt = "STSLICER", args(quiet = false))]
#[op(code = "cf1e", fmt = "STSLICERQ", args(quiet = true))]
fn exec_store_slice_rev(st: &mut VmState, quiet: bool) -> VmResult<i32> {
let stack = SafeRc::make_mut(&mut st.stack);
let cs = ok!(stack.pop_cs());
let mut builder = ok!(stack.pop_builder());
if !cs.fits_into(&builder) {
return finish_store_overflow(stack, builder, cs, quiet);
}
let slice = cs.apply();
SafeRc::make_mut(&mut builder).store_slice(slice)?;
finish_store_ok(stack, builder, quiet)
}
#[op(code = "cf17", fmt = "STBR", args(quiet = false))]
#[op(code = "cf1f", fmt = "STBRQ", args(quiet = true))]
fn exec_store_builder_rev(st: &mut VmState, quiet: bool) -> VmResult<i32> {
let stack = SafeRc::make_mut(&mut st.stack);
let other_builder = ok!(stack.pop_builder());
let mut builder = ok!(stack.pop_builder());
if !builder.has_capacity(other_builder.size_bits(), other_builder.size_refs()) {
return finish_store_overflow(stack, builder, other_builder, quiet);
}
SafeRc::make_mut(&mut builder).store_builder(&other_builder)?;
finish_store_ok(stack, builder, quiet)
}
#[op_ext_range(code_min = 0xcf20, code_max = 0xcf22, total_bits = 16, dump_with = dump_store_const_ref)]
fn exec_store_const_ref(st: &mut VmState, args: u32, bits: u16) -> VmResult<i32> {
let refs = ((args & 1) + 1) as u8;
let code_range = st.code.range_mut();
vm_ensure!(code_range.has_remaining(bits, refs), InvalidOpcode);
code_range.skip_first(bits, 0).ok();
vm_log_op!("STREF{refs}CONST");
let stack = SafeRc::make_mut(&mut st.stack);
let mut builder = ok!(stack.pop_builder());
vm_ensure!(
builder.has_capacity(0, refs),
CellError(Error::CellOverflow)
);
{
let builder = SafeRc::make_mut(&mut builder);
let mut code = st.code.apply();
for _ in 0..refs {
let cell = code.load_reference_cloned()?;
builder.store_reference(cell)?;
}
st.code.set_range(code.range());
}
ok!(stack.push_raw(builder));
Ok(0)
}
#[cfg(feature = "dump")]
fn dump_store_const_ref(
code: &mut CellSlice<'_>,
args: u32,
bits: u16,
f: &mut dyn DumpOutput,
) -> DumpResult {
let refs = ((args & 1) + 1) as u8;
if !code.has_remaining(bits, refs) {
return Err(DumpError::InvalidOpcode);
}
code.skip_first(bits, 0)?;
for _ in 0..refs {
let cell = code.load_reference_cloned()?;
f.record_cell(cell)?;
}
f.record_opcode(&format_args!("STREF{refs}CONST"))
}
#[op(code = "cf2$10ss", fmt = "{s}", args(s = StoreLeIntArgs(args)))]
fn exec_store_le_int(st: &mut VmState, s: StoreLeIntArgs) -> VmResult<i32> {
let stack = SafeRc::make_mut(&mut st.stack);
let mut builder = ok!(stack.pop_builder());
let bits = if s.is_long() { 64 } else { 32 };
vm_ensure!(
builder.has_capacity(bits, 0),
CellError(Error::CellOverflow)
);
let x = ok!(stack.pop_int());
enum Int {
U32(u32),
U64(u64),
}
let Some(x) = (match (s.is_unsigned(), s.is_long()) {
(false, false) => x.to_i32().map(|x| Int::U32(x as _)),
(false, true) => x.to_i64().map(|x| Int::U64(x as _)),
(true, false) => x.to_u32().map(Int::U32),
(true, true) => x.to_u64().map(Int::U64),
}) else {
vm_bail!(IntegerOutOfRange {
min: 0,
max: bits as _,
actual: String::new(),
});
};
{
let builder = SafeRc::make_mut(&mut builder);
match x {
Int::U32(x) => builder.store_u32(x.swap_bytes()),
Int::U64(x) => builder.store_u64(x.swap_bytes()),
}?;
}
ok!(stack.push_raw(builder));
Ok(0)
}
#[op(code = "cf23", fmt = "ENDXC")]
fn exec_builder_to_special_cell(st: &mut VmState) -> VmResult<i32> {
let stack = SafeRc::make_mut(&mut st.stack);
let special = ok!(stack.pop_bool());
let mut builder = SafeRc::unwrap_or_clone(ok!(stack.pop_builder()));
builder.set_exotic(special);
let cell = builder.build_ext(&st.gas)?;
ok!(stack.push(cell));
Ok(0)
}
#[op(code = "cf30", fmt = "BDEPTH")]
fn exec_builder_depth(st: &mut VmState) -> VmResult<i32> {
let stack = SafeRc::make_mut(&mut st.stack);
let builder = ok!(stack.pop_builder());
let depth = compute_depth(builder.references());
ok!(stack.push_int(depth));
Ok(0)
}
#[op(code = "cf31", fmt = "BBITS")]
fn exec_builder_bits(st: &mut VmState) -> VmResult<i32> {
let stack = SafeRc::make_mut(&mut st.stack);
let builder = ok!(stack.pop_builder());
ok!(stack.push_int(builder.size_bits()));
Ok(0)
}
#[op(code = "cf32", fmt = "BREFS")]
fn exec_builder_refs(st: &mut VmState) -> VmResult<i32> {
let stack = SafeRc::make_mut(&mut st.stack);
let builder = ok!(stack.pop_builder());
ok!(stack.push_int(builder.size_refs()));
Ok(0)
}
#[op(code = "cf33", fmt = "BBITREFS")]
fn exec_builder_bits_refs(st: &mut VmState) -> VmResult<i32> {
let stack = SafeRc::make_mut(&mut st.stack);
let builder = ok!(stack.pop_builder());
ok!(stack.push_int(builder.size_bits()));
ok!(stack.push_int(builder.size_refs()));
Ok(0)
}
#[op(code = "cf35", fmt = "BREMBITS")]
fn exec_builder_rem_bits(st: &mut VmState) -> VmResult<i32> {
let stack = SafeRc::make_mut(&mut st.stack);
let builder = ok!(stack.pop_builder());
ok!(stack.push_int(builder.spare_capacity_bits()));
Ok(0)
}
#[op(code = "cf36", fmt = "BREMREFS")]
fn exec_builder_rem_refs(st: &mut VmState) -> VmResult<i32> {
let stack = SafeRc::make_mut(&mut st.stack);
let builder = ok!(stack.pop_builder());
ok!(stack.push_int(builder.spare_capacity_refs()));
Ok(0)
}
#[op(code = "cf37", fmt = "BREMBITREFS")]
fn exec_builder_rem_bits_refs(st: &mut VmState) -> VmResult<i32> {
let stack = SafeRc::make_mut(&mut st.stack);
let builder = ok!(stack.pop_builder());
ok!(stack.push_int(builder.spare_capacity_bits()));
ok!(stack.push_int(builder.spare_capacity_refs()));
Ok(0)
}
#[op(code = "cf38xx", fmt = "BCHKBITS {x}", args(x = (args & 0xff) + 1, quiet = false))]
#[op(code = "cf3cxx", fmt = "BCHKBITSQ {x}", args(x = (args & 0xff) + 1, quiet = true))]
fn exec_builder_chk_bits(st: &mut VmState, x: u32, quiet: bool) -> VmResult<i32> {
let stack = SafeRc::make_mut(&mut st.stack);
let builder = ok!(stack.pop_builder());
let fits = builder.has_capacity(x as u16, 0);
if quiet {
ok!(stack.push_bool(fits));
} else if !fits {
vm_bail!(CellError(Error::CellOverflow));
}
Ok(0)
}
#[op(code = "cf39", fmt = "BCHKBITS", args(mode = CheckMode::Bits, quiet = false))]
#[op(code = "cf3a", fmt = "BCHKREFS", args(mode = CheckMode::Refs, quiet = false))]
#[op(code = "cf3b", fmt = "BCHKBITREFS", args(mode = CheckMode::BitRefs, quiet = false))]
#[op(code = "cf3d", fmt = "BCHKBITSQ", args(mode = CheckMode::Bits, quiet = true))]
#[op(code = "cf3e", fmt = "BCHKREFSQ", args(mode = CheckMode::Refs, quiet = true))]
#[op(code = "cf3f", fmt = "BCHKBITREFSQ", args(mode = CheckMode::BitRefs, quiet = true))]
fn exec_builder_chk_bits_refs(st: &mut VmState, mode: CheckMode, quiet: bool) -> VmResult<i32> {
let stack = SafeRc::make_mut(&mut st.stack);
let (bits, refs) = match mode {
CheckMode::Bits => (ok!(stack.pop_smallint_range(0, 1023)), 0),
CheckMode::Refs => (0, ok!(stack.pop_smallint_range(0, 7))),
CheckMode::BitRefs => {
let refs = ok!(stack.pop_smallint_range(0, 7));
let bits = ok!(stack.pop_smallint_range(0, 1023));
(bits, refs)
}
};
let builder = ok!(stack.pop_builder());
let fits = builder.has_capacity(bits as u16, refs as u8);
if quiet {
ok!(stack.push_bool(fits));
} else if !fits {
vm_bail!(CellError(Error::CellOverflow));
}
Ok(0)
}
#[op(code = "cf40", fmt = "STZEROES", args(value = Some(false)))]
#[op(code = "cf41", fmt = "STONES", args(value = Some(true)))]
#[op(code = "cf42", fmt = "STSAME", args(value = None))]
fn exec_store_same(st: &mut VmState, value: Option<bool>) -> VmResult<i32> {
let stack = SafeRc::make_mut(&mut st.stack);
let value = match value {
Some(value) => value,
None => ok!(stack.pop_smallint_range(0, 1)) != 0,
};
let bits = ok!(stack.pop_smallint_range(0, 1023));
let mut builder = ok!(stack.pop_builder());
vm_ensure!(
builder.has_capacity(bits as _, 0),
CellError(Error::CellOverflow)
);
{
let builder = SafeRc::make_mut(&mut builder);
match value {
true => builder.store_ones(bits as _)?,
false => builder.store_zeros(bits as _)?,
}
}
ok!(stack.push_raw(builder));
Ok(0)
}
#[op(code = "cf50", fmt = "BTOS")]
fn exec_builder_to_slice(st: &mut VmState) -> VmResult<i32> {
ok!(st.version.require_ton(12..));
let stack = SafeRc::make_mut(&mut st.stack);
let builder = ok!(stack.pop_builder());
let cell = SafeRc::unwrap_or_clone(builder).build()?;
let slice = OwnedCellSlice::new_allow_exotic(cell);
ok!(stack.push(slice));
Ok(0)
}
#[op_ext(code = 0xcf80 >> 7, code_bits = 9, arg_bits = 5, dump_with = dump_store_const_slice)]
fn exec_store_const_slice(st: &mut VmState, args: u32, bits: u16) -> VmResult<i32> {
let data_bits = ((args & 0b111) * 8 + 2) as u16;
let refs = ((args >> 3) & 0b11) as u8;
let code_range = st.code.range_mut();
vm_ensure!(
code_range.has_remaining(bits + data_bits, refs),
InvalidOpcode
);
code_range.skip_first(bits, 0).ok();
let mut slice_range = *code_range;
slice_range.only_first(data_bits, refs).ok();
code_range.skip_first(data_bits, refs).ok();
let mut slice = slice_range.apply(st.code.cell())?;
remove_trailing(&mut slice)?;
vm_log_op!("STSLICECONST {}", slice.display_as_stack_value());
let stack = SafeRc::make_mut(&mut st.stack);
let mut builder = ok!(stack.pop_builder());
SafeRc::make_mut(&mut builder).store_slice(slice)?;
ok!(stack.push_raw(builder));
Ok(0)
}
#[cfg(feature = "dump")]
fn dump_store_const_slice(
code: &mut CellSlice<'_>,
args: u32,
bits: u16,
f: &mut dyn DumpOutput,
) -> DumpResult {
let data_bits = ((args & 0b111) * 8 + 2) as u16;
let refs = ((args >> 3) & 0b11) as u8;
if !code.has_remaining(bits + data_bits, refs) {
return Err(DumpError::InvalidOpcode);
}
code.skip_first(bits, 0)?;
let mut slice = *code;
slice.only_first(data_bits, refs)?;
code.skip_first(data_bits, refs)?;
remove_trailing(&mut slice)?;
f.record_slice(slice)?;
f.record_opcode(&format_args!(
"STSLICECONST {}",
slice.display_as_stack_value()
))
}
#[op(code = "d0", fmt = "CTOS")]
fn exec_cell_to_slice(st: &mut VmState) -> VmResult<i32> {
let stack = SafeRc::make_mut(&mut st.stack);
let cell = ok!(stack.pop_cell());
let cs = st
.gas
.load_cell_as_slice(SafeRc::unwrap_or_clone(cell), LoadMode::Full)?;
ok!(stack.push(cs));
Ok(0)
}
#[op(code = "d1", fmt = "ENDS")]
fn exec_slice_chk_empty(st: &mut VmState) -> VmResult<i32> {
let cs = ok!(SafeRc::make_mut(&mut st.stack).pop_cs());
let range = cs.range();
vm_ensure!(
range.size_bits() == 0 && range.size_refs() == 0,
CellError(Error::CellUnderflow)
);
Ok(0)
}
#[op(code = "d2xx", fmt = "LDI {x}", args(x = (args & 0xff) + 1, signed = true))]
#[op(code = "d3xx", fmt = "LDU {x}", args(x = (args & 0xff) + 1, signed = false))]
fn exec_load_int_fixed(st: &mut VmState, x: u32, signed: bool) -> VmResult<i32> {
let stack = SafeRc::make_mut(&mut st.stack);
exec_load_int_common(stack, x as _, LoadIntArgs::from_sign(signed))
}
#[op(code = "d4", fmt = "LDREF")]
fn exec_load_ref(st: &mut VmState) -> VmResult<i32> {
let stack = SafeRc::make_mut(&mut st.stack);
let mut cs = ok!(stack.pop_cs());
vm_ensure!(cs.range().size_refs() > 0, CellError(Error::CellUnderflow));
let mut slice = cs.apply();
let cell = slice.load_reference_cloned()?;
ok!(stack.push(cell));
let range = slice.range();
SafeRc::make_mut(&mut cs).set_range(range);
ok!(stack.push_raw(cs));
Ok(0)
}
#[op(code = "d5", fmt = "LDREFRTOS")]
fn exec_load_ref_rev_to_slice(st: &mut VmState) -> VmResult<i32> {
let stack = SafeRc::make_mut(&mut st.stack);
let mut cs = ok!(stack.pop_cs());
vm_ensure!(cs.range().size_refs() > 0, CellError(Error::CellUnderflow));
let mut slice = cs.apply();
let cell = slice.load_reference_cloned()?;
let child_cs = st.gas.load_cell_as_slice(cell, LoadMode::Full)?;
let range = slice.range();
SafeRc::make_mut(&mut cs).set_range(range);
ok!(stack.push_raw(cs));
ok!(stack.push(child_cs));
Ok(0)
}
#[op(code = "d6xx", fmt = "LDSLICE {x}", args(x = (args & 0xff) + 1))]
fn exec_load_slice_fixed(st: &mut VmState, x: u32) -> VmResult<i32> {
let stack = SafeRc::make_mut(&mut st.stack);
exec_load_slice_common(stack, x as _, LoadSliceArgs(0))
}
#[op(code = "d70$0sss", fmt = s.display_x(), args(s = LoadIntArgs(args)))]
fn exec_load_int_var(st: &mut VmState, s: LoadIntArgs) -> VmResult<i32> {
let stack = SafeRc::make_mut(&mut st.stack);
let bits = ok!(stack.pop_smallint_range(0, 256 + s.is_signed() as u32));
exec_load_int_common(stack, bits as _, s)
}
#[op(
code = "d70$1sss#nn",
fmt = ("{} {n}", s.display()),
args(s = LoadIntArgs(args >> 8), n = (args & 0xff) + 1)
)]
fn exec_load_int_fixed2(st: &mut VmState, s: LoadIntArgs, n: u32) -> VmResult<i32> {
exec_load_int_common(SafeRc::make_mut(&mut st.stack), n as _, s)
}
#[op(code = "d71$0xxx", fmt = "PLDUZ {x}", args(x = ((args & 7) + 1) << 5))]
fn exec_preload_uint_fixed_0e(st: &mut VmState, x: u32) -> VmResult<i32> {
let stack = SafeRc::make_mut(&mut st.stack);
let mut cs = ok!(stack.pop_cs());
let (int, range) = {
let mut slice = cs.apply();
let ld_bits = std::cmp::min(slice.size_bits(), x as _);
let int = match x {
0..=64 => {
let value = slice
.load_uint(ld_bits)?
.checked_shl(x - ld_bits as u32)
.unwrap_or_default();
Some(BigInt::from(value))
}
65..=256 => {
let rshift = (8 - ld_bits as u32 % 8) % 8;
let lshift = x - ld_bits as u32;
let mut buffer = [0u8; 32];
let mut int = slice
.load_raw(&mut buffer, ld_bits)
.map(|buffer| BigInt::from_bytes_be(Sign::Plus, buffer))?;
match rshift.cmp(&lshift) {
std::cmp::Ordering::Less => {
int <<= lshift - rshift;
}
std::cmp::Ordering::Greater => {
int >>= rshift - lshift;
}
std::cmp::Ordering::Equal => {}
}
Some(int)
}
_ => None,
};
(int, slice.range())
};
SafeRc::make_mut(&mut cs).set_range(range);
ok!(stack.push_raw(cs));
ok!(match int {
Some(int) => stack.push_int(int),
None => stack.push_nan(),
});
Ok(0)
}
#[op(code = "d71$10ss", fmt = s.display_x(), args(s = LoadSliceArgs(args)))]
fn exec_load_slice(st: &mut VmState, s: LoadSliceArgs) -> VmResult<i32> {
let stack = SafeRc::make_mut(&mut st.stack);
let bits = ok!(stack.pop_smallint_range(0, 1023));
exec_load_slice_common(stack, bits as _, s)
}
#[op(
code = "d71$11ss#nn",
fmt = ("{} {n}", s.display()),
args(s = LoadSliceArgs(args >> 8), n = (args & 0xff) + 1)
)]
fn exec_load_slice_fixed2(st: &mut VmState, s: LoadSliceArgs, n: u32) -> VmResult<i32> {
exec_load_slice_common(SafeRc::make_mut(&mut st.stack), n as _, s)
}
#[op(code = "d720", fmt = "SDCUTFIRST", args(op = SliceRangeOp::CutFirst))]
#[op(code = "d721", fmt = "SDSKIPFIRST", args(op = SliceRangeOp::SkipFirst))]
#[op(code = "d722", fmt = "SDCUTLAST", args(op = SliceRangeOp::CutLast))]
#[op(code = "d723", fmt = "SDSKIPLAST", args(op = SliceRangeOp::SkipLast))]
fn exec_slice_range_op(st: &mut VmState, op: SliceRangeOp) -> VmResult<i32> {
let stack = SafeRc::make_mut(&mut st.stack);
let bits = ok!(stack.pop_smallint_range(0, 1023)) as u16;
let mut cs = ok!(stack.pop_cs());
let mut range = cs.range();
vm_ensure!(
range.has_remaining(bits, 0),
CellError(Error::CellUnderflow)
);
let ok = match op {
SliceRangeOp::CutFirst => range.only_first(bits, 0),
SliceRangeOp::SkipFirst => range.skip_first(bits, 0),
SliceRangeOp::CutLast => range.only_last(bits, 0),
SliceRangeOp::SkipLast => range.skip_last(bits, 0),
}
.is_ok();
debug_assert!(ok);
SafeRc::make_mut(&mut cs).set_range(range);
ok!(stack.push_raw(cs));
Ok(0)
}
#[op(code = "d724", fmt = "SDSUBSTR")]
fn exec_slice_substr(st: &mut VmState) -> VmResult<i32> {
let stack = SafeRc::make_mut(&mut st.stack);
let len_bits = ok!(stack.pop_smallint_range(0, 1023)) as u16;
let offset_bits = ok!(stack.pop_smallint_range(0, 1023)) as u16;
let mut cs = ok!(stack.pop_cs());
let mut range = cs.range();
vm_ensure!(
range.has_remaining(offset_bits + len_bits, 0),
CellError(Error::CellUnderflow)
);
let mut ok = range.skip_first(offset_bits, 0).is_ok();
ok &= range.only_first(len_bits, 0).is_ok();
debug_assert!(ok);
SafeRc::make_mut(&mut cs).set_range(range);
ok!(stack.push_raw(cs));
Ok(0)
}
#[op(code = "d726", fmt = "SDBEGINSX", args(quiet = false))]
#[op(code = "d727", fmt = "SDBEGINSXQ", args(quiet = true))]
fn exec_slice_begins_with(st: &mut VmState, quiet: bool) -> VmResult<i32> {
let stack = SafeRc::make_mut(&mut st.stack);
let target = ok!(stack.pop_cs());
let target = target.apply();
exec_slice_begins_with_common(stack, &target, quiet)
}
#[op_ext(code = 0xd728 >> 3, code_bits = 13, arg_bits = 8, dump_with = dump_slice_begins_with_const)]
fn exec_slice_begins_with_const(st: &mut VmState, args: u32, bits: u16) -> VmResult<i32> {
let quiet = (args & 0x80) != 0;
let data_bits = ((args & 0x7f) * 8 + 3) as u16;
let code_range = st.code.range_mut();
vm_ensure!(code_range.has_remaining(bits + data_bits, 0), InvalidOpcode);
code_range.skip_first(bits, 0).ok();
let mut slice_range = *code_range;
slice_range.only_first(data_bits, 0).ok();
code_range.skip_first(data_bits, 0).ok();
let mut slice = slice_range.apply(st.code.cell())?;
remove_trailing(&mut slice)?;
vm_log_op!(
"SDBEGINS{} {}",
if quiet { "Q" } else { "" },
slice.display_as_stack_value()
);
let stack = SafeRc::make_mut(&mut st.stack);
exec_slice_begins_with_common(stack, &slice, quiet)
}
#[cfg(feature = "dump")]
fn dump_slice_begins_with_const(
code: &mut CellSlice<'_>,
args: u32,
bits: u16,
f: &mut dyn DumpOutput,
) -> DumpResult {
let quiet = (args & 0x80) != 0;
let data_bits = ((args & 0x7f) * 8 + 3) as u16;
if !code.has_remaining(bits + data_bits, 0) {
return Err(DumpError::InvalidOpcode);
}
code.skip_first(bits, 0)?;
let mut slice = *code;
slice.only_first(data_bits, 0)?;
code.skip_first(data_bits, 0)?;
remove_trailing(&mut slice)?;
f.record_slice(slice)?;
f.record_opcode(&format_args!(
"SDBEGINS{} {}",
if quiet { "Q" } else { "" },
slice.display_as_stack_value()
))
}
#[op(code = "d730", fmt = "SCUTFIRST", args(op = SliceRangeOp::CutFirst))]
#[op(code = "d731", fmt = "SSKIPFIRST", args(op = SliceRangeOp::SkipFirst))]
#[op(code = "d732", fmt = "SCUTLAST", args(op = SliceRangeOp::CutLast))]
#[op(code = "d733", fmt = "SSKIPLAST", args(op = SliceRangeOp::SkipLast))]
fn exec_slice_full_range_op(st: &mut VmState, op: SliceRangeOp) -> VmResult<i32> {
let stack = SafeRc::make_mut(&mut st.stack);
let refs = ok!(stack.pop_smallint_range(0, 4)) as u8;
let bits = ok!(stack.pop_smallint_range(0, 1023)) as u16;
let mut cs = ok!(stack.pop_cs());
let mut range = cs.range();
vm_ensure!(
range.has_remaining(bits, refs),
CellError(Error::CellUnderflow)
);
let ok = match op {
SliceRangeOp::CutFirst => range.only_first(bits, refs),
SliceRangeOp::SkipFirst => range.skip_first(bits, refs),
SliceRangeOp::CutLast => range.only_last(bits, refs),
SliceRangeOp::SkipLast => range.skip_last(bits, refs),
}
.is_ok();
debug_assert!(ok);
SafeRc::make_mut(&mut cs).set_range(range);
ok!(stack.push_raw(cs));
Ok(0)
}
#[op(code = "d734", fmt = "SUBSLICE")]
fn exec_subslice(st: &mut VmState) -> VmResult<i32> {
let stack = SafeRc::make_mut(&mut st.stack);
let len_refs = ok!(stack.pop_smallint_range(0, 4)) as u8;
let len_bits = ok!(stack.pop_smallint_range(0, 1023)) as u16;
let offset_refs = ok!(stack.pop_smallint_range(0, 4)) as u8;
let offset_bits = ok!(stack.pop_smallint_range(0, 1023)) as u16;
let mut cs = ok!(stack.pop_cs());
let mut range = cs.range();
vm_ensure!(
range.has_remaining(offset_bits + len_bits, offset_refs + len_refs),
CellError(Error::CellUnderflow)
);
let mut ok = range.skip_first(offset_bits, offset_refs).is_ok();
ok &= range.only_first(len_bits, len_refs).is_ok();
debug_assert!(ok);
SafeRc::make_mut(&mut cs).set_range(range);
ok!(stack.push_raw(cs));
Ok(0)
}
#[op(code = "d736", fmt = "SPLIT", args(quiet = false))]
#[op(code = "d737", fmt = "SPLITQ", args(quiet = true))]
fn exec_split(st: &mut VmState, quiet: bool) -> VmResult<i32> {
let stack = SafeRc::make_mut(&mut st.stack);
let refs = ok!(stack.pop_smallint_range(0, 4)) as u8;
let bits = ok!(stack.pop_smallint_range(0, 1023)) as u16;
let mut cs = ok!(stack.pop_cs());
let mut range = cs.range();
if !range.has_remaining(bits, refs) {
if !quiet {
vm_bail!(CellError(Error::CellUnderflow));
}
ok!(stack.push_raw(cs));
ok!(stack.push_bool(false));
return Ok(0);
}
let prefix_range = range.get_prefix(bits, refs);
let ok = range.skip_first(bits, refs).is_ok();
debug_assert!(ok);
ok!(stack.push(OwnedCellSlice::from((prefix_range, cs.cell().clone()))));
SafeRc::make_mut(&mut cs).set_range(range);
ok!(stack.push_raw(cs));
if quiet {
ok!(stack.push_bool(true));
}
Ok(0)
}
#[op(code = "d739", fmt = "XCTOS")]
fn exec_cell_to_slice_maybe_special(st: &mut VmState) -> VmResult<i32> {
let stack = SafeRc::make_mut(&mut st.stack);
let cell = ok!(stack.pop_cell());
let is_special = cell.descriptor().is_exotic();
let cs = st
.gas
.load_cell_as_slice(SafeRc::unwrap_or_clone(cell), LoadMode::UseGas)?;
ok!(stack.push(cs));
ok!(stack.push_bool(is_special));
Ok(0)
}
#[op(code = "d73a", fmt = "XLOAD", args(quiet = false))]
#[op(code = "d73b", fmt = "XLOADQ", args(quiet = true))]
fn exec_load_special_cell(st: &mut VmState, quiet: bool) -> VmResult<i32> {
let handle_error = |stack: &mut Stack, e: Error| {
if quiet {
ok!(stack.push_bool(false));
Ok(0)
} else {
vm_bail!(CellError(e));
}
};
let stack = SafeRc::make_mut(&mut st.stack);
let cell = ok!(stack.pop_cell());
let cell = 'load: {
if st.version.is_ton(..5) {
break 'load cell;
}
st.gas.load_dyn_cell((*cell).as_ref(), LoadMode::UseGas)?;
let descr = cell.descriptor();
if !descr.is_exotic() {
break 'load cell;
}
if !descr.is_library() {
return handle_error(stack, Error::CellUnderflow);
}
debug_assert_eq!(cell.bit_len(), 8 + 256);
let mut library_hash = HashBytes::ZERO;
cell.as_slice_allow_exotic()
.get_raw(8, &mut library_hash.0, 256)?;
match st.gas.libraries().find(&library_hash) {
Ok(Some(lib)) => SafeRc::new(lib),
Ok(None) => {
st.gas.set_missing_library(&library_hash);
return handle_error(stack, Error::CellUnderflow);
}
Err(e) => return handle_error(stack, e),
}
};
ok!(stack.push_raw(cell));
if quiet {
ok!(stack.push_bool(true));
}
Ok(0)
}
#[op(code = "d741", fmt = "SCHKBITS", args(mode = CheckMode::Bits, quiet = false))]
#[op(code = "d742", fmt = "SCHKREFS", args(mode = CheckMode::Refs, quiet = false))]
#[op(code = "d743", fmt = "SCHKBITREFS", args(mode = CheckMode::BitRefs, quiet = false))]
#[op(code = "d745", fmt = "SCHKBITSQ", args(mode = CheckMode::Bits, quiet = true))]
#[op(code = "d746", fmt = "SCHKREFSQ", args(mode = CheckMode::Refs, quiet = true))]
#[op(code = "d747", fmt = "SCHKBITREFSQ", args(mode = CheckMode::BitRefs, quiet = true))]
fn exec_check_slice(st: &mut VmState, mode: CheckMode, quiet: bool) -> VmResult<i32> {
let stack = SafeRc::make_mut(&mut st.stack);
let (bits, refs) = match mode {
CheckMode::Bits => (ok!(stack.pop_smallint_range(0, 1023)), 0),
CheckMode::Refs => (0, ok!(stack.pop_smallint_range(0, 4))),
CheckMode::BitRefs => {
let refs = ok!(stack.pop_smallint_range(0, 4));
let bits = ok!(stack.pop_smallint_range(0, 1023));
(bits, refs)
}
};
let cs = ok!(stack.pop_cs());
let fits = cs.range().has_remaining(bits as _, refs as _);
if quiet {
ok!(stack.push_bool(fits));
} else if !fits {
vm_bail!(CellError(Error::CellOverflow));
}
Ok(0)
}
#[op(code = "d748", fmt = "PLDREFVAR")]
fn exec_preload_ref(st: &mut VmState) -> VmResult<i32> {
let stack = SafeRc::make_mut(&mut st.stack);
let idx = ok!(stack.pop_smallint_range(0, 3)) as u8;
let cs = ok!(stack.pop_cs());
let slice = cs.apply();
let cell = slice.get_reference_cloned(idx)?;
ok!(stack.push(cell));
Ok(0)
}
#[op(code = "d749", fmt = "SBITS", args(mode = CheckMode::Bits))]
#[op(code = "d74a", fmt = "SREFS", args(mode = CheckMode::Refs))]
#[op(code = "d74b", fmt = "SBITREFS", args(mode = CheckMode::BitRefs))]
fn exec_slice_bits_refs(st: &mut VmState, mode: CheckMode) -> VmResult<i32> {
let stack = SafeRc::make_mut(&mut st.stack);
let cs = ok!(stack.pop_cs());
let range = cs.range();
if matches!(mode, CheckMode::Bits | CheckMode::BitRefs) {
ok!(stack.push_int(range.size_bits()));
}
if matches!(mode, CheckMode::Refs | CheckMode::BitRefs) {
ok!(stack.push_int(range.size_refs()));
}
Ok(0)
}
#[op(code = "d74$11xx", fmt = "PLDREFIDX {x}")]
fn exec_preload_ref_fixed(st: &mut VmState, x: u32) -> VmResult<i32> {
let stack = SafeRc::make_mut(&mut st.stack);
let cs = ok!(stack.pop_cs());
let slice = cs.apply();
let cell = slice.get_reference_cloned(x as _)?;
ok!(stack.push(cell));
Ok(0)
}
#[op(code = "d75s", fmt = "{s}", args(s = LoadLeIntArgs(args)))]
fn exec_load_le_int(st: &mut VmState, s: LoadLeIntArgs) -> VmResult<i32> {
let stack = SafeRc::make_mut(&mut st.stack);
let mut cs = ok!(stack.pop_cs());
let bits = if s.is_long() { 64 } else { 32 };
if !cs.range().has_remaining(bits, 0) {
if !s.is_quiet() {
vm_bail!(CellError(Error::CellUnderflow));
}
if !s.is_prefetch() {
ok!(stack.push_raw(cs));
}
ok!(stack.push_bool(false));
return Ok(0);
}
let range = {
let mut slice = cs.apply();
let int = match (s.is_unsigned(), s.is_long()) {
(false, false) => BigInt::from(slice.load_u32()?.swap_bytes() as i32),
(false, true) => BigInt::from(slice.load_u64()?.swap_bytes() as i64),
(true, false) => BigInt::from(slice.load_u32()?.swap_bytes()),
(true, true) => BigInt::from(slice.load_u64()?.swap_bytes()),
};
ok!(stack.push_int(int));
slice.range()
};
if !s.is_prefetch() {
SafeRc::make_mut(&mut cs).set_range(range);
ok!(stack.push_raw(cs));
}
if s.is_quiet() {
ok!(stack.push_bool(true));
}
Ok(0)
}
#[op(code = "d760", fmt = "LDZEROES", args(value = Some(false)))]
#[op(code = "d761", fmt = "LDONES", args(value = Some(true)))]
#[op(code = "d762", fmt = "LDSAME", args(value = None))]
fn exec_load_same(st: &mut VmState, value: Option<bool>) -> VmResult<i32> {
let stack = SafeRc::make_mut(&mut st.stack);
let value = match value {
Some(value) => value,
None => ok!(stack.pop_smallint_range(0, 1)) != 0,
};
let mut cs = ok!(stack.pop_cs());
let range = {
let target = match value {
false => Cell::all_zeros_ref(),
true => Cell::all_ones_ref(),
};
let target = target.as_slice_allow_exotic();
let mut slice = cs.apply();
let prefix = slice.longest_common_data_prefix(&target);
let ok = slice.skip_first(prefix.size_bits(), 0).is_ok();
debug_assert!(ok);
ok!(stack.push_int(prefix.size_bits()));
slice.range()
};
SafeRc::make_mut(&mut cs).set_range(range);
ok!(stack.push_raw(cs));
Ok(0)
}
#[op(code = "d764", fmt = "SDEPTH")]
fn exec_slice_depth(st: &mut VmState) -> VmResult<i32> {
let stack = SafeRc::make_mut(&mut st.stack);
let cs = ok!(stack.pop_cs());
let slice = cs.apply();
let depth = compute_depth(slice.references());
ok!(stack.push_int(depth));
Ok(0)
}
#[op(code = "d765", fmt = "CDEPTH")]
fn exec_cell_depth(st: &mut VmState) -> VmResult<i32> {
let stack = SafeRc::make_mut(&mut st.stack);
let cell = {
let item = ok!(stack.pop());
if item.is_null() {
None
} else {
Some(ok!(item.into_cell()))
}
};
ok!(match cell {
Some(cell) => {
let depth = compute_depth(cell.references());
stack.push_int(depth)
}
None => stack.push_zero(),
});
Ok(0)
}
#[op(code = "d766", fmt = "CLEVEL")]
fn exec_cell_level(st: &mut VmState) -> VmResult<i32> {
let stack = SafeRc::make_mut(&mut st.stack);
let cell = ok!(stack.pop_cell());
let level = cell.descriptor().level_mask().level();
ok!(stack.push_int(level));
Ok(0)
}
#[op(code = "d767", fmt = "CLEVELMASK")]
fn exec_cell_level_mask(st: &mut VmState) -> VmResult<i32> {
let stack = SafeRc::make_mut(&mut st.stack);
let cell = ok!(stack.pop_cell());
let mask = cell.descriptor().level_mask().to_byte();
ok!(stack.push_int(mask));
Ok(0)
}
#[op(code = "d76$10xx", fmt = "CHASHI {x}", args(op = LevelOp::Hash))]
#[op(code = "d76$11xx", fmt = "CDEPTHI {x}", args(op = LevelOp::Depth))]
fn exec_cell_level_op(st: &mut VmState, x: u32, op: LevelOp) -> VmResult<i32> {
exec_cell_level_op_common(SafeRc::make_mut(&mut st.stack), x as _, op)
}
#[op(code = "d770", fmt = "CHASHIX", args(op = LevelOp::Hash))]
#[op(code = "d771", fmt = "CDEPTHIX", args(op = LevelOp::Depth))]
fn exec_cell_level_op_var(st: &mut VmState, op: LevelOp) -> VmResult<i32> {
let stack = SafeRc::make_mut(&mut st.stack);
let x = ok!(stack.pop_smallint_range(0, 3));
exec_cell_level_op_common(stack, x as _, op)
}
}
enum PushRefMode {
Cell,
Slice,
Cont,
}
#[cfg(feature = "dump")]
fn dump_push_ref_common(
code: &mut CellSlice<'_>,
bits: u16,
name: &str,
as_cont: bool,
f: &mut dyn DumpOutput,
) -> DumpResult {
if !code.has_remaining(bits, 1) {
return Err(DumpError::InvalidOpcode);
}
code.skip_first(bits, 0)?;
let cell = code.load_reference_cloned()?;
if as_cont {
f.record_cont(cell.clone())?;
} else {
f.record_cell(cell.clone())?;
}
f.record_opcode(&format_args!("{name} ({})", cell.repr_hash()))
}
fn exec_push_ref_common(
st: &mut VmState,
bits: u16,
name: &str,
mode: PushRefMode,
) -> VmResult<i32> {
let code_range = st.code.range();
vm_ensure!(code_range.has_remaining(bits, 1), InvalidOpcode);
let ok = st.code.range_mut().skip_first(bits, 0).is_ok();
debug_assert!(ok);
let Some(cell) = st.code.cell().reference_cloned(code_range.offset_refs()) else {
vm_bail!(CellError(tycho_types::error::Error::CellUnderflow));
};
let ok = st.code.range_mut().skip_first(0, 1).is_ok();
debug_assert!(ok);
vm_log_op!("{name} ({})", cell.repr_hash());
let stack = SafeRc::make_mut(&mut st.stack);
ok!(match mode {
PushRefMode::Cell => stack.push(cell),
PushRefMode::Slice => {
let cs = st.gas.load_cell_as_slice(cell, LoadMode::Full)?;
stack.push(cs)
}
PushRefMode::Cont => {
let code = st.gas.load_cell_as_slice(cell, LoadMode::Full)?;
let cont = SafeRc::new(OrdCont::simple(code, st.cp.id()));
stack.push_raw(cont)
}
});
Ok(0)
}
fn exec_push_slice_common(st: &mut VmState, bits: u16, data_bits: u16, refs: u8) -> VmResult<i32> {
let code_range = st.code.range_mut();
vm_ensure!(
code_range.has_remaining(bits + data_bits, refs),
InvalidOpcode
);
code_range.skip_first(bits, 0).ok();
let mut slice_range = *code_range;
slice_range.only_first(data_bits, refs).ok();
code_range.skip_first(data_bits, refs).ok();
{
let mut slice = slice_range.apply(st.code.cell())?;
remove_trailing(&mut slice)?;
slice_range = slice.range();
}
let slice = OwnedCellSlice::from((slice_range, st.code.cell().clone()));
vm_log_op!("PUSHSLICE {}", slice);
ok!(SafeRc::make_mut(&mut st.stack).push(slice));
Ok(0)
}
#[cfg(feature = "dump")]
fn dump_push_slice_common(
code: &mut CellSlice<'_>,
bits: u16,
data_bits: u16,
refs: u8,
f: &mut dyn DumpOutput,
) -> DumpResult {
if !code.has_remaining(bits + data_bits, refs) {
return Err(DumpError::InvalidOpcode);
}
code.skip_first(bits, 0)?;
let mut slice = *code;
slice.only_first(data_bits, refs)?;
code.skip_first(data_bits, refs)?;
remove_trailing(&mut slice)?;
f.record_slice(slice)?;
f.record_opcode(&format_args!(
"PUSHSLICE {}",
slice.display_as_stack_value()
))
}
#[derive(Clone, Copy)]
enum SliceBoolUnaryOp {
IsEmpty,
NoBits,
NoRefs,
FirstBit,
}
#[derive(Clone, Copy)]
enum SliceBinaryOp {
DataEq,
IsPrefix,
IsPrefixRev,
IsProperPrefix,
IsProperPrefixRev,
IsSuffix,
IsSuffixRev,
IsProperSuffix,
IsProperSuffixRev,
}
#[derive(Clone, Copy)]
enum SliceIntUnaryOp {
Leading0,
Leading1,
Trailing0,
Trailing1,
}
fn exec_store_int_common(stack: &mut Stack, bits: u16, args: StoreIntArgs) -> VmResult<i32> {
fn finish_store_fail(
stack: &mut Stack,
builder: SafeRc<CellBuilder>,
x: SafeRc<BigInt>,
code: i32,
args: StoreIntArgs,
) -> VmResult<i32> {
if !args.is_reversed() {
ok!(stack.push_raw_int(x, true));
ok!(stack.push_raw(builder));
} else {
ok!(stack.push_raw(builder));
ok!(stack.push_raw_int(x, true));
}
ok!(stack.push_int(code));
Ok(0)
}
let mut builder;
let x;
if !args.is_reversed() {
builder = ok!(stack.pop_builder());
x = ok!(stack.pop_int());
} else {
x = ok!(stack.pop_int());
builder = ok!(stack.pop_builder());
}
if !builder.has_capacity(bits, 0) {
return if args.is_quiet() {
finish_store_fail(stack, builder, x, -1, args)
} else {
Err(Box::new(VmError::CellError(Error::CellOverflow)))
};
}
let int_bits = x.bitsize(args.is_signed());
if bits < int_bits {
return if args.is_quiet() {
finish_store_fail(stack, builder, x, 1, args)
} else {
Err(Box::new(VmError::IntegerOutOfRange {
min: 0,
max: bits as _,
actual: int_bits.to_string(),
}))
};
}
{
let builder = SafeRc::make_mut(&mut builder);
match x.to_u64() {
Some(value) => builder.store_uint(value, bits)?,
None => {
let int = if !bits.is_multiple_of(8) {
let align = 8 - bits % 8;
Cow::Owned((*x).clone() << align)
} else {
Cow::Borrowed(x.as_ref())
};
let minimal_bytes = bits.div_ceil(8) as usize;
let (prefix, mut bytes) = if args.is_signed() {
let bytes = int.to_signed_bytes_le();
(
bytes
.last()
.map(|first| (first >> 7) * 255)
.unwrap_or_default(),
bytes,
)
} else {
(0, int.to_bytes_le().1)
};
bytes.resize(minimal_bytes, prefix);
bytes.reverse();
builder.store_raw(&bytes, bits)?;
}
}
}
finish_store_ok(stack, builder, args.is_quiet())
}
pub(crate) fn finish_store_overflow<T1, T2>(
stack: &mut Stack,
arg1: SafeRc<T1>,
arg2: SafeRc<T2>,
quiet: bool,
) -> VmResult<i32>
where
T1: StackValue + 'static,
T2: StackValue + 'static,
{
if quiet {
ok!(stack.push_raw(arg1));
ok!(stack.push_raw(arg2));
ok!(stack.push_bool(true)); Ok(0)
} else {
Err(Box::new(VmError::CellError(Error::CellOverflow)))
}
}
pub(crate) fn finish_store_ok(
stack: &mut Stack,
builder: SafeRc<CellBuilder>,
quiet: bool,
) -> VmResult<i32> {
ok!(stack.push_raw(builder));
if quiet {
ok!(stack.push_bool(false)); }
Ok(0)
}
#[derive(Clone, Copy)]
struct StoreLeIntArgs(u32);
impl StoreLeIntArgs {
const fn is_unsigned(self) -> bool {
self.0 & 0b1 != 0
}
const fn is_signed(self) -> bool {
!self.is_unsigned()
}
const fn is_long(&self) -> bool {
self.0 & 0b10 != 0
}
}
impl std::fmt::Display for StoreLeIntArgs {
fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
let sign = if self.is_signed() { "I" } else { "U" };
let bytes = if self.is_long() { "8" } else { "4" };
write!(f, "ST{sign}LE{bytes}")
}
}
#[derive(Clone, Copy)]
struct StoreIntArgs(u32);
impl StoreIntArgs {
const fn from_sign(signed: bool) -> Self {
Self((!signed) as u32)
}
const fn is_unsigned(self) -> bool {
self.0 & 0b001 != 0
}
const fn is_signed(self) -> bool {
!self.is_unsigned()
}
const fn is_reversed(self) -> bool {
self.0 & 0b010 != 0
}
const fn is_quiet(self) -> bool {
self.0 & 0b100 != 0
}
fn display(&self) -> DisplayStoreIntArgs<false> {
DisplayStoreIntArgs(self.0)
}
fn display_x(self) -> DisplayStoreIntArgs<true> {
DisplayStoreIntArgs(self.0)
}
}
struct DisplayStoreIntArgs<const X: bool>(u32);
impl<const X: bool> std::fmt::Display for DisplayStoreIntArgs<X> {
fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
let args = StoreIntArgs(self.0);
let sign = if args.is_signed() { "I" } else { "U" };
let x = if X { "X" } else { "" };
let rev = if args.is_reversed() { "R" } else { "" };
let quiet = if args.is_quiet() { "Q" } else { "" };
write!(f, "ST{sign}{x}{rev}{quiet}")
}
}
#[derive(Clone, Copy)]
enum CheckMode {
Bits,
Refs,
BitRefs,
}
impl std::fmt::Display for CheckMode {
fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
f.write_str(match self {
Self::Bits => "BITS",
Self::Refs => "REFS",
Self::BitRefs => "BITREFS",
})
}
}
fn exec_load_int_common(stack: &mut Stack, bits: u16, args: LoadIntArgs) -> VmResult<i32> {
let mut cs = ok!(stack.pop_cs());
if !cs.range().has_remaining(bits, 0) {
if !args.is_quiet() {
vm_bail!(CellError(Error::CellUnderflow));
}
if !args.is_prefetch() {
ok!(stack.push_raw(cs));
}
ok!(stack.push_bool(false));
return Ok(0);
}
let range = {
let mut slice = cs.apply();
let int = slice.load_bigint(bits, args.is_signed())?;
ok!(stack.push_int(int));
slice.range()
};
if !args.is_prefetch() {
SafeRc::make_mut(&mut cs).set_range(range);
ok!(stack.push_raw(cs));
}
if args.is_quiet() {
ok!(stack.push_bool(true));
}
Ok(0)
}
#[derive(Clone, Copy)]
struct LoadLeIntArgs(u32);
impl LoadLeIntArgs {
const fn is_unsigned(self) -> bool {
self.0 & 0b0001 != 0
}
const fn is_signed(self) -> bool {
!self.is_unsigned()
}
const fn is_long(&self) -> bool {
self.0 & 0b0010 != 0
}
const fn is_prefetch(self) -> bool {
self.0 & 0b0100 != 0
}
const fn is_quiet(self) -> bool {
self.0 & 0b1000 != 0
}
}
impl std::fmt::Display for LoadLeIntArgs {
fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
let prefetch = if self.is_prefetch() { "P" } else { "" };
let sign = if self.is_signed() { "I" } else { "U" };
let bytes = if self.is_long() { "8" } else { "4" };
let quiet = if self.is_quiet() { "Q" } else { "" };
write!(f, "{prefetch}LD{sign}LE{bytes}{quiet}")
}
}
#[derive(Clone, Copy)]
struct LoadIntArgs(u32);
impl LoadIntArgs {
const fn from_sign(signed: bool) -> Self {
Self((!signed) as u32)
}
const fn is_unsigned(self) -> bool {
self.0 & 0b001 != 0
}
const fn is_signed(self) -> bool {
!self.is_unsigned()
}
const fn is_prefetch(self) -> bool {
self.0 & 0b010 != 0
}
const fn is_quiet(self) -> bool {
self.0 & 0b100 != 0
}
fn display(&self) -> DisplayLoadIntArgs<false> {
DisplayLoadIntArgs(self.0)
}
fn display_x(self) -> DisplayLoadIntArgs<true> {
DisplayLoadIntArgs(self.0)
}
}
struct DisplayLoadIntArgs<const X: bool>(u32);
impl<const X: bool> std::fmt::Display for DisplayLoadIntArgs<X> {
fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
let args = LoadIntArgs(self.0);
let prefetch = if args.is_prefetch() { "P" } else { "" };
let sign = if args.is_signed() { "I" } else { "U" };
let x = if X { "X" } else { "" };
let quiet = if args.is_quiet() { "Q" } else { "" };
write!(f, "{prefetch}LD{sign}{x}{quiet}")
}
}
fn exec_load_slice_common(stack: &mut Stack, bits: u16, args: LoadSliceArgs) -> VmResult<i32> {
let mut cs = ok!(stack.pop_cs());
if !cs.range().has_remaining(bits, 0) {
if !args.is_quiet() {
vm_bail!(CellError(Error::CellUnderflow));
}
if !args.is_prefetch() {
ok!(stack.push_raw(cs));
}
ok!(stack.push_bool(false));
return Ok(0);
}
let range = {
let mut range = cs.range();
let slice = OwnedCellSlice::from((range.get_prefix(bits, 0), cs.cell().clone()));
ok!(stack.push(slice));
let ok = range.skip_first(bits, 0).is_ok();
debug_assert!(ok);
range
};
if !args.is_prefetch() {
SafeRc::make_mut(&mut cs).set_range(range);
ok!(stack.push_raw(cs));
}
if args.is_quiet() {
ok!(stack.push_bool(true));
}
Ok(0)
}
#[derive(Clone, Copy)]
struct LoadSliceArgs(u32);
impl LoadSliceArgs {
const fn is_prefetch(self) -> bool {
self.0 & 0b01 != 0
}
const fn is_quiet(self) -> bool {
self.0 & 0b10 != 0
}
fn display(&self) -> DisplayLoadSliceArgs<false> {
DisplayLoadSliceArgs(self.0)
}
fn display_x(&self) -> DisplayLoadSliceArgs<true> {
DisplayLoadSliceArgs(self.0)
}
}
struct DisplayLoadSliceArgs<const X: bool>(u32);
impl<const X: bool> std::fmt::Display for DisplayLoadSliceArgs<X> {
fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
let args = LoadSliceArgs(self.0);
let prefetch = if args.is_prefetch() { "P" } else { "" };
let x = if X { "X" } else { "" };
let quiet = if args.is_quiet() { "Q" } else { "" };
write!(f, "{prefetch}LDSLICE{x}{quiet}")
}
}
#[derive(Clone, Copy)]
enum SliceRangeOp {
CutFirst,
SkipFirst,
CutLast,
SkipLast,
}
fn exec_slice_begins_with_common(
stack: &mut Stack,
prefix: &CellSlice<'_>,
quiet: bool,
) -> VmResult<i32> {
let mut cs = ok!(stack.pop_cs());
let range = {
let slice = cs.apply();
let Some(slice) = slice.strip_data_prefix(prefix) else {
if !quiet {
vm_bail!(CellError(Error::CellUnderflow));
}
ok!(stack.push_raw(cs));
ok!(stack.push_bool(false));
return Ok(0);
};
slice.range()
};
SafeRc::make_mut(&mut cs).set_range(range);
ok!(stack.push_raw(cs));
if quiet {
ok!(stack.push_bool(true));
}
Ok(0)
}
#[inline]
fn compute_depth<I: IntoIterator<Item = C>, C: AsRef<DynCell>>(references: I) -> u16 {
let mut depth = 0;
for cell in references {
depth = std::cmp::max(depth, cell.as_ref().repr_depth().saturating_add(1));
}
depth
}
#[derive(Clone, Copy)]
enum LevelOp {
Hash,
Depth,
}
fn exec_cell_level_op_common(stack: &mut Stack, level: u8, op: LevelOp) -> VmResult<i32> {
let cell = ok!(stack.pop_cell());
ok!(match op {
LevelOp::Hash => stack.push_int(BigInt::from_bytes_be(
Sign::Plus,
cell.hash(level).as_array(),
)),
LevelOp::Depth => stack.push_int(cell.depth(level)),
});
Ok(0)
}
#[cfg(test)]
mod tests {
use std::collections::HashMap;
use tracing_test::traced_test;
use tycho_types::boc::Boc;
use tycho_types::cell::{CellBuilder, CellType};
use tycho_types::merkle::{MerkleProof, MerkleUpdate};
use tycho_types::models::SimpleLib;
use super::*;
#[test]
#[traced_test]
fn store_int() {
assert_run_vm!(
r#"
PUSHSLICE x{6_}
PUSHINT -1
OVER
PUSHINT 0
NEWC
PUSHSLICE x{6_} STSLICER
STI 1
PUSHSLICE x{1_} STSLICER
STSLICE
PUSHINT 12321070556741797692511081747621921145552344918445724
STUR 175
STI 1
ENDC
2DROP
"#,
[] => [],
);
}
#[test]
#[traced_test]
fn lexcmp_tests() {
let slice1 = make_uint_cell_slice(0b0, 1);
let slice2 = make_uint_cell_slice(0b1, 1);
assert_run_vm!("SDLEXCMP", [slice slice1.clone(), slice slice1.clone()] => [int 0]);
assert_run_vm!("SDLEXCMP", [slice slice1.clone(), slice slice2.clone()] => [int -1]);
assert_run_vm!("SDLEXCMP", [slice slice2, slice slice1] => [int 1]);
}
#[test]
#[traced_test]
fn store_tests() {
let cell = Cell::default();
let init_builder = init_builder_with_refs(cell.clone(), 0);
let result_builer = init_builder_with_refs(cell.clone(), 1);
assert_run_vm!("STREF", [cell cell.clone(), builder init_builder] => [builder result_builer]);
let init_builder = init_builder_with_refs(cell.clone(), 4);
assert_run_vm!("STREF", [cell cell.clone(), builder init_builder] => [int 0], exit_code: 8);
let init_builder_one = init_builder_with_refs(cell.clone(), 1);
let init_builder_two = init_builder_with_refs(cell.clone(), 0);
let cb = init_builder_two.clone();
let cell = cb.build().unwrap();
let result = init_builder_with_refs(cell.clone(), 2);
assert_run_vm!("STBREFR", [builder init_builder_one.clone(), builder init_builder_two.clone()] => [builder result.clone()]);
assert_run_vm!("ENDCST", [builder init_builder_one.clone(), builder init_builder_two.clone()] => [builder result.clone()]);
assert_run_vm!("ENDC SWAP STREF", [builder init_builder_one.clone(), builder init_builder_two.clone()] => [builder result.clone()]);
}
#[test]
#[traced_test]
fn store_builder_tests() {
let mut builder1 = CellBuilder::new();
builder1.store_uint(1, 512).unwrap();
let mut builder2 = CellBuilder::new();
builder2.store_uint(2, 511).unwrap();
let mut builder3 = CellBuilder::new();
builder3.store_uint(3, 512).unwrap();
let mut result = CellBuilder::new();
result.store_uint(2, 511).unwrap();
result.store_uint(1, 512).unwrap();
assert_run_vm!("STB", [builder builder1.clone(), builder builder2.clone()] => [builder result.clone()]);
assert_run_vm!("STB", [builder builder1.clone(), builder builder3.clone()] => [int 0], exit_code: 8);
assert_run_vm!("STBQ", [builder builder1.clone(), builder builder3.clone()] => [builder builder1.clone(), builder builder3.clone(), int -1]);
assert_run_vm!("STBR", [builder builder2.clone(), builder builder1.clone()] => [builder result.clone()]);
assert_run_vm!("STBRQ", [builder builder2.clone(), builder builder1.clone()] => [builder result.clone(), int 0]);
let empty = CellBuilder::new();
let mut b = CellBuilder::new();
b.store_u32((-123i32 as u32).swap_bytes()).unwrap();
assert_run_vm!("STILE4", [int -123, builder empty.clone()] => [builder b.clone()]);
assert_run_vm!("STILE4", [int u64::MAX, builder empty.clone()] => [int 0], exit_code: 5);
assert_run_vm!("STULE4", [int (-123i32 as u32), builder empty.clone()] => [builder b]);
assert_run_vm!("STULE4", [int -123i32, builder empty.clone()] => [int 0], exit_code: 5);
let mut b = CellBuilder::new();
b.store_u64((-123i64 as u64).swap_bytes()).unwrap();
assert_run_vm!("STILE8", [int -123, builder empty.clone()] => [builder b.clone()]);
assert_run_vm!("STILE8", [int u128::MAX, builder empty.clone()] => [int 0], exit_code: 5);
assert_run_vm!("STULE8", [int (-123i64 as u64), builder empty.clone()] => [builder b]);
assert_run_vm!("STULE8", [int -123i32, builder empty.clone()] => [int 0], exit_code: 5);
}
#[test]
#[traced_test]
fn builder_info_tests() {
let mut builder = CellBuilder::new();
builder.store_uint(1, 512).unwrap();
builder.store_reference(Cell::empty_cell()).unwrap();
assert_run_vm!("BCHKBITS", [builder builder.clone(), int 256] => []);
assert_run_vm!("BCHKBITSQ", [builder builder.clone(), int 256] => [int -1]);
assert_run_vm!("BCHKBITS", [builder builder.clone(), int 512] => [int 0], exit_code: 8);
assert_run_vm!("BCHKBITSQ", [builder builder.clone(), int 512] => [int 0]);
assert_run_vm!("BCHKREFS", [builder builder.clone(), int 3] => []);
assert_run_vm!("BCHKREFS", [builder builder.clone(), int 4] => [int 0], exit_code: 8);
assert_run_vm!("BCHKBITREFS", [builder builder.clone(), int 256, int 3] => []);
assert_run_vm!("BCHKBITREFS", [builder builder.clone(), int 256, int 4] => [int 0], exit_code: 8);
assert_run_vm!("BDEPTH", [builder builder.clone()] => [int 1]);
assert_run_vm!("BBITS", [builder builder.clone()] => [int 512]);
assert_run_vm!("BREFS", [builder builder.clone()] => [int 1]);
assert_run_vm!("BBITREFS", [builder builder.clone()] => [int 512, int 1]);
let mut builder1 = CellBuilder::new();
builder1.store_reference(Cell::empty_cell()).unwrap();
let cell = builder1.build().unwrap();
builder.store_reference(cell).unwrap();
assert_run_vm!("BDEPTH", [builder builder.clone()] => [int 2]);
assert_run_vm!("BDEPTH", [builder CellBuilder::new()] => [int 0]);
let builder = create_builder(1023);
assert_run_vm!("BDEPTH", [builder builder] => [int 1024]); }
fn create_builder(depth: u32) -> CellBuilder {
let mut builder = CellBuilder::new();
let cell = create_complex_cell(0, depth);
builder.store_reference(cell).unwrap();
builder
}
fn create_complex_cell(mut current_depth: u32, depth: u32) -> Cell {
if current_depth == depth {
return Cell::empty_cell();
}
current_depth += 1;
let mut builder = CellBuilder::new();
let cell = create_complex_cell(current_depth, depth);
builder.store_reference(cell).unwrap();
builder.build().unwrap()
}
fn init_builder_with_refs(cell: Cell, ref_count: u8) -> CellBuilder {
let mut cb = CellBuilder::new();
for _ in 0..ref_count {
cb.store_reference(cell.clone()).unwrap();
}
cb
}
#[test]
#[traced_test]
fn ctos_tests() {
let mut cb = CellBuilder::new();
cb.store_u8(1).unwrap();
cb.store_u8(0).unwrap();
let cell = cb.build().unwrap();
let slice = OwnedCellSlice::new_allow_exotic(cell.clone());
assert_run_vm!("CTOS", [cell cell] => [slice slice])
}
#[test]
#[traced_test]
fn ends_tests() {
let cell = Cell::default();
let slice = OwnedCellSlice::new_allow_exotic(cell.clone());
assert_run_vm!("ENDS", [slice slice.clone()] => []);
assert_run_vm!("ENDS", [int 1, slice slice] => [int 1]);
let mut cb = CellBuilder::new();
cb.store_u8(1).unwrap();
let cell = cb.build().unwrap();
let sc = OwnedCellSlice::new_allow_exotic(cell);
assert_run_vm!("ENDS", [slice sc] => [int 0], exit_code: 9);
}
#[test]
#[traced_test]
fn btos_tests() {
let mut cb = CellBuilder::new();
cb.store_u8(1).unwrap();
cb.store_u8(123).unwrap();
assert_run_vm!(
"DUP BTOS SWAP ENDC CTOS SDEQ",
[builder cb] => [int -1]
);
}
#[test]
#[traced_test]
fn ldi_tests() {
let slice = make_int_cell_slice(i128::MIN, 257);
let (left, right) = cut_slice_to_int(&slice, 16);
assert_run_vm!("LDI 16", [slice slice.clone()] => [int left, slice right.clone()]);
let (left, right) = cut_slice_to_int(&slice, 32);
assert_run_vm!("LDI 32", [slice slice.clone()] => [int left, slice right.clone()]);
let (left, right) = cut_slice_to_int(&slice, 1);
assert_run_vm!("LDI 1", [slice slice.clone()] => [int left, slice right.clone()]);
let slice = make_int_cell_slice(i128::MAX, 255);
assert_run_vm!("LDIQ 256", [slice slice.clone()] => [slice slice.clone(), int 0]);
}
#[test]
#[traced_test]
fn ldu_tests() {
let slice = make_int_cell_slice(i128::MIN, 256);
let (left, right) = cut_slice_to_int(&slice, 16);
assert_run_vm!("LDI 16", [slice slice.clone()] => [int left, slice right.clone()]);
let (left, right) = cut_slice_to_int(&slice, 32);
assert_run_vm!("LDI 32", [slice slice.clone()] => [int left, slice right.clone()]);
let (left, right) = cut_slice_to_int(&slice, 1);
assert_run_vm!("LDI 1", [slice slice.clone()] => [int left, slice right.clone()]);
let slice = make_int_cell_slice(i128::MAX, 254);
assert_run_vm!("LDIQ 255", [slice slice.clone()] => [slice slice.clone(), int 0]);
}
#[test]
#[traced_test]
fn ldref_tests() {
let cell = make_cell_with_reference();
let reference = cell.reference_cloned(0).unwrap();
let mut slice = OwnedCellSlice::new_allow_exotic(cell.clone());
let initial_rc_slice = slice.clone();
let mut cs = slice.apply();
cs.skip_first(0, 1).unwrap();
slice.set_range(cs.range());
assert_run_vm!("LDREF", [slice initial_rc_slice.clone()] => [cell reference.clone(), slice slice]);
let slice = OwnedCellSlice::new_allow_exotic(cell.clone());
let mut initial_rc_slice = slice.clone();
let mut cs = initial_rc_slice.apply();
cs.skip_first(0, 1).unwrap();
initial_rc_slice.set_range(cs.range());
assert_run_vm!("LDREF", [slice initial_rc_slice] => [int 0], exit_code: 9);
}
#[test]
#[traced_test]
fn ldrefrtos_tests() {
let cell = make_cell_with_reference();
let reference = cell.reference_cloned(0).unwrap();
let initial_cell = OwnedCellSlice::new_allow_exotic(cell.clone());
let result_slice = OwnedCellSlice::new_allow_exotic(reference.clone());
let mut slice = OwnedCellSlice::new_allow_exotic(cell.clone());
let mut cs = slice.apply();
cs.skip_first(0, 1).unwrap();
slice.set_range(cs.range());
assert_run_vm!("LDREFRTOS", [slice initial_cell] => [slice slice, slice result_slice]);
}
#[test]
#[traced_test]
fn ldslice_tests() {
let slice = make_uint_cell_slice(1000, 32);
let (left, right) = cut_slice(&slice, 16);
assert_run_vm!("LDSLICE 16", [slice slice.clone()] => [slice left, slice right]);
let (left, right) = cut_slice(&slice, 32);
assert_run_vm!("LDSLICE 32", [slice slice.clone()] => [slice left, slice right]);
let (left, right) = cut_slice(&slice, 1);
assert_run_vm!("LDSLICE 1", [slice slice.clone()] => [slice left.clone(), slice right.clone()]);
assert_run_vm!("LDSLICEQ 1", [slice slice.clone()] => [slice left, slice right, int -1]);
assert_run_vm!("LDSLICE 33", [slice slice.clone()] => [int 0], exit_code: 9);
assert_run_vm!("LDSLICEQ 33", [slice slice.clone()] => [slice slice, int 0]);
}
#[test]
#[traced_test]
fn ldslicex_tests() {
let slice = make_uint_cell_slice(1000, 32);
let (left, right) = cut_slice(&slice, 16);
assert_run_vm!("LDSLICEX", [slice slice.clone(), int 16] => [slice left, slice right]);
let (left, right) = cut_slice(&slice, 32);
assert_run_vm!("LDSLICEX", [slice slice.clone(), int 32] => [slice left.clone(), slice right.clone()]);
assert_run_vm!("LDSLICEXQ", [slice slice.clone(), int 32] => [slice left, slice right, int -1]);
let (left, right) = cut_slice(&slice, 1);
assert_run_vm!("LDSLICEX", [slice slice.clone(), int 1] => [slice left, slice right]);
assert_run_vm!("LDSLICEX", [slice slice.clone(), int 33] => [int 0], exit_code: 9);
assert_run_vm!("LDSLICEXQ", [slice slice.clone(), int 33] => [slice slice, int 0]);
}
#[test]
#[traced_test]
fn ldix_tests() {
let slice = make_int_cell_slice(i128::MIN, 257);
let (left, right) = cut_slice_to_int(&slice, 16);
assert_run_vm!("LDIX", [slice slice.clone(), int 16] => [int left, slice right.clone()]);
let (left, right) = cut_slice_to_int(&slice, 32);
assert_run_vm!("LDIX", [slice slice.clone(), int 32] => [int left, slice right.clone()]);
let (left, right) = cut_slice_to_int(&slice, 1);
assert_run_vm!("LDIX", [slice slice.clone(), int 1] => [int left, slice right.clone()]);
assert_run_vm!("LDIX", [slice slice.clone(), int 258] => [int 0], exit_code: 5);
assert_run_vm!("LDIXQ", [slice slice.clone(), int 258] => [int 0], exit_code: 5);
let slice = make_int_cell_slice(i128::MAX, 256);
assert_run_vm!("LDIXQ", [slice slice.clone(), int 257] => [slice slice.clone(), int 0]);
}
#[test]
#[traced_test]
fn ldux_tests() {
let slice = make_uint_cell_slice(u128::MAX, 256);
let (left, right) = cut_slice_to_uint(&slice, 16);
assert_run_vm!("LDIX", [slice slice.clone(), int 16] => [int left, slice right.clone()]);
let (left, right) = cut_slice_to_uint(&slice, 32);
assert_run_vm!("LDUX", [slice slice.clone(), int 32] => [int left, slice right.clone()]);
let (left, right) = cut_slice_to_uint(&slice, 1);
assert_run_vm!("LDUX", [slice slice.clone(), int 1] => [int left, slice right.clone()]);
assert_run_vm!("LDUX", [slice slice.clone(), int 257] => [int 0], exit_code: 5);
assert_run_vm!("LDUXQ", [slice slice.clone(), int 257] => [int 0], exit_code: 5);
let slice = make_uint_cell_slice(u128::MAX, 255);
assert_run_vm!("LDUXQ", [slice slice.clone(), int 256] => [slice slice.clone(), int 0]);
}
#[test]
#[traced_test]
fn pldi_tests() {
let slice = make_int_cell_slice(i128::MIN, 200);
let i = extract_int(&slice, 200);
assert_run_vm!("PLDI 200", [slice slice.clone()] => [int i.clone()]);
assert_run_vm!("PLDIQ 200", [slice slice.clone()] => [int i, int -1]);
let i = extract_int(&slice, 37);
assert_run_vm!("PLDI 37", [slice slice.clone()] => [int i.clone()]);
let _i = extract_int(&slice, 200);
assert_run_vm!("PLDI 210", [slice slice.clone()] => [int 0], exit_code: 9);
assert_run_vm!("PLDIQ 210", [slice slice.clone()] => [int 0]);
}
#[test]
#[traced_test]
fn pldu_tests() {
let slice = make_uint_cell_slice(u128::MAX, 200);
let i = extract_uint(&slice, 200);
assert_run_vm!("PLDI 200", [slice slice.clone()] => [int i.clone()]);
assert_run_vm!("PLDIQ 200", [slice slice.clone()] => [int i, int -1]);
let i = extract_int(&slice, 37);
assert_run_vm!("PLDI 37", [slice slice.clone()] => [int i.clone()]);
let _i = extract_int(&slice, 200);
assert_run_vm!("PLDI 210", [slice slice.clone()] => [int 0], exit_code: 9);
assert_run_vm!("PLDIQ 210", [slice slice.clone()] => [int 0]);
}
#[test]
#[traced_test]
fn pldix_tests() {
let slice = make_int_cell_slice(i128::MIN, 200);
let i = extract_int(&slice, 200);
assert_run_vm!("PLDIX", [slice slice.clone(), int 200] => [int i.clone()]);
assert_run_vm!("PLDIXQ", [slice slice.clone(), int 200] => [int i, int -1]);
let i = extract_int(&slice, 37);
assert_run_vm!("PLDIX", [slice slice.clone(), int 37] => [int i.clone()]);
let _i = extract_int(&slice, 200);
assert_run_vm!("PLDIX", [slice slice.clone(), int 210] => [int 0], exit_code: 9);
assert_run_vm!("PLDIXQ", [slice slice.clone(), int 210] => [int 0]);
}
#[test]
#[traced_test]
fn pldux_tests() {
let slice = make_uint_cell_slice(u128::MAX, 200);
let i = extract_uint(&slice, 200);
assert_run_vm!("PLDUX", [slice slice.clone(), int 200] => [int i.clone()]);
assert_run_vm!("PLDUXQ", [slice slice.clone(), int 200] => [int i, int -1]);
let i = extract_int(&slice, 37);
assert_run_vm!("PLDUX", [slice slice.clone(), int 37] => [int i.clone()]);
let _i = extract_int(&slice, 200);
assert_run_vm!("PLDUX", [slice slice.clone(), int 210] => [int 0], exit_code: 9);
assert_run_vm!("PLDUXQ", [slice slice.clone(), int 210] => [int 0]);
}
#[test]
#[traced_test]
fn pldslice_tests() {
let slice = make_uint_cell_slice(1000, 512);
let prefix = cut_slice_prefix(&slice, 256, 0);
assert_run_vm!("PLDSLICE 256", [slice slice.clone()] => [slice prefix.clone()]);
assert_run_vm!("PLDSLICEQ 256", [slice slice.clone()] => [slice prefix.clone(), int -1]);
let prefix = cut_slice_prefix(&slice, 1, 0);
assert_run_vm!("PLDSLICE 1", [slice slice.clone()] => [slice prefix.clone()]);
let _prefix = cut_slice_prefix(&slice, 0, 0);
let slice = make_uint_cell_slice(1000, 128);
assert_run_vm!("PLDSLICE 129", [slice slice.clone()] => [int 0], exit_code: 9);
assert_run_vm!("PLDSLICEQ 129", [slice slice.clone()] => [int 0]);
}
#[test]
#[traced_test]
fn pldslicex_tests() {
let slice = make_uint_cell_slice(1000, 512);
let prefix = cut_slice_prefix(&slice, 256, 0);
assert_run_vm!("PLDSLICEX", [slice slice.clone(), int 256] => [slice prefix.clone()]);
assert_run_vm!("SDCUTFIRST", [slice slice.clone(), int 256] => [slice prefix.clone()]);
assert_run_vm!("PLDSLICEXQ", [slice slice.clone(), int 256] => [slice prefix.clone(), int -1],);
let prefix = cut_slice_prefix(&slice, 1, 0);
assert_run_vm!("PLDSLICEX", [slice slice.clone(), int 1] => [slice prefix.clone()]);
assert_run_vm!("SDCUTFIRST", [slice slice.clone(), int 1] => [slice prefix.clone()]);
let _prefix = cut_slice_prefix(&slice, 0, 0);
let slice = make_uint_cell_slice(1000, 128);
assert_run_vm!("PLDSLICEX", [slice slice.clone(), int 129] => [int 0], exit_code: 9);
assert_run_vm!("SDCUTFIRST", [slice slice.clone(), int 129] => [int 0], exit_code: 9);
assert_run_vm!("PLDSLICEXQ", [slice slice.clone(), int 129] => [int 0]);
}
#[test]
#[traced_test]
fn cut_tests() {
let slice = make_uint_cell_slice(1000, 512);
let suffix = cut_slice_suffix(&slice, 256, 0);
assert_run_vm!("SDSKIPFIRST", [slice slice.clone(), int 256] => [slice suffix.clone()]);
assert_run_vm!("SDSKIPFIRST", [slice slice.clone(), int 513] => [int 0], exit_code: 9);
assert_run_vm!("LDSLICEX NIP", [slice slice.clone(), int 256] => [slice suffix.clone()]);
assert_run_vm!("LDSLICEX NIP", [slice slice.clone(), int 513] => [int 0], exit_code: 9);
assert_run_vm!("SDCUTLAST", [slice slice.clone(), int 256] => [slice suffix.clone()]);
let suffix = cut_slice_suffix(&slice, 512, 0);
assert_run_vm!("SDCUTLAST", [slice slice.clone(), int 512] => [slice suffix.clone()]);
let suffix = cut_slice_suffix(&slice, 0, 0);
assert_run_vm!("SDCUTLAST", [slice slice.clone(), int 0] => [slice suffix.clone()]);
assert_run_vm!("SDCUTLAST", [slice slice.clone(), int 513] => [int 0], exit_code: 9);
let prefix = cut_slice_prefix(&slice, 512, 0);
assert_run_vm!("SDSKIPLAST", [slice slice.clone(), int 0] => [slice prefix.clone()]);
let prefix = cut_slice_prefix(&slice, 256, 0);
assert_run_vm!("SDSKIPLAST", [slice slice.clone(), int 256] => [slice prefix.clone()]);
let prefix = cut_slice_prefix(&slice, 0, 0);
assert_run_vm!("SDSKIPLAST", [slice slice.clone(), int 512] => [slice prefix.clone()]);
assert_run_vm!("SDSKIPLAST", [slice slice.clone(), int 513] => [int 0], exit_code: 9);
}
#[test]
#[traced_test]
fn cut_with_refs_tests() {
let slice = make_cell_slice_with_refs(1000, 512, 2);
let prefix = cut_slice_prefix(&slice, 256, 2);
assert_run_vm!("SCUTFIRST", [slice slice.clone(), int 256, int 2] => [slice prefix.clone()]);
assert_run_vm!("SCUTFIRST", [slice slice.clone(), int 512, int 2] => [slice slice.clone()]);
assert_run_vm!("SCUTFIRST", [slice slice.clone(), int 256, int 3] => [int 0], exit_code: 9);
assert_run_vm!("SSKIPLAST", [slice slice.clone(), int 256, int 0] => [slice prefix.clone()]);
assert_run_vm!("SSKIPLAST", [slice slice.clone(), int 0, int 0] => [slice slice.clone()]);
assert_run_vm!("SSKIPLAST", [slice slice.clone(), int 256, int 3] => [int 0], exit_code: 9);
let suffix = cut_slice_suffix(&slice, 256, 2);
assert_run_vm!("SCUTLAST", [slice slice.clone(), int 256, int 2] => [slice suffix.clone()]);
assert_run_vm!("SCUTLAST", [slice slice.clone(), int 512, int 2] => [slice slice.clone()]);
assert_run_vm!("SCUTLAST", [slice slice.clone(), int 256, int 3] => [int 0], exit_code: 9);
assert_run_vm!("SSKIPFIRST", [slice slice.clone(), int 256, int 0] => [slice suffix.clone()]);
assert_run_vm!("SSKIPFIRST", [slice slice.clone(), int 0, int 0] => [slice slice.clone()]);
assert_run_vm!("SSKIPFIRST", [slice slice.clone(), int 256, int 3] => [int 0], exit_code: 9);
}
#[test]
#[traced_test]
fn subslice_tests() {
let slice = make_cell_slice_with_refs(1000, 512, 4);
let initial = slice.clone();
let subslice = get_subslice(&slice, 10, 10, 0, 0);
assert_run_vm!("SDSUBSTR", [slice initial.clone(), int 10, int 10] => [slice subslice]);
let subslice = get_subslice(&slice, 0, 0, 0, 0);
assert_run_vm!("SDSUBSTR", [slice initial.clone(), int 0, int 0] => [slice subslice]);
let subslice = get_subslice(&slice, 100, 50, 0, 0);
assert_run_vm!("SDSUBSTR", [slice initial.clone(), int 100, int 50] => [slice subslice]);
assert_run_vm!("SDSUBSTR", [slice initial.clone(), int 100, int 513] => [int 0], exit_code: 9);
assert_run_vm!("SDSUBSTR", [slice initial.clone(), int 513, int 5] => [int 0], exit_code: 9);
let subslice = get_subslice(&slice, 100, 50, 0, 4);
assert_run_vm!("SUBSLICE", [slice initial.clone(), int 100, int 0, int 50, int 4] => [slice subslice]);
let subslice = get_subslice(&slice, 1, 50, 2, 2);
assert_run_vm!("SUBSLICE", [slice initial.clone(), int 1, int 2, int 50, int 2] => [slice subslice]);
assert_run_vm!("SUBSLICE", [slice initial.clone(), int 1, int 2, int 50, int 3] => [int 0], exit_code: 9);
assert_run_vm!("SUBSLICE", [slice initial.clone(), int 100, int 0, int 50, int 5] => [int 0], exit_code: 5);
assert_run_vm!("SUBSLICE", [slice initial.clone(), int 100, int 5, int 50, int 4] => [int 0], exit_code: 5);
}
#[test]
#[traced_test]
fn check_tests() {
let slice = make_cell_slice_with_refs(1000, 512, 2);
assert_run_vm!("SCHKBITS", [slice slice.clone(), int 512] => []);
assert_run_vm!("SCHKBITS", [slice slice.clone(), int 0] => []);
assert_run_vm!("SCHKBITSQ", [slice slice.clone(), int 0] => [int -1]);
assert_run_vm!("SCHKBITS", [slice slice.clone(), int 1023] => [int 0,], exit_code: 8);
assert_run_vm!("SCHKBITS", [slice slice.clone(), int 99999999] => [int 0], exit_code: 5);
assert_run_vm!("SCHKBITSQ", [slice slice.clone(), int 1023] => [int 0]);
assert_run_vm!("SCHKREFS", [slice slice.clone(), int 2] => []);
assert_run_vm!("SCHKREFS", [slice slice.clone(), int 0] => []);
assert_run_vm!("SCHKREFSQ", [slice slice.clone(), int 1] => [int -1]);
assert_run_vm!("SCHKREFS", [slice slice.clone(), int 3] => [int 0], exit_code: 8);
assert_run_vm!("SCHKREFS", [slice slice.clone(), int 99999] => [int 0], exit_code: 5);
assert_run_vm!("SCHKREFSQ", [slice slice.clone(), int 3] => [int 0]);
assert_run_vm!("SCHKBITREFS", [slice slice.clone(), int 512, int 2] => []);
assert_run_vm!("SCHKBITREFS", [slice slice.clone(), int 0, int 0] => []);
assert_run_vm!("SCHKBITREFSQ", [slice slice.clone(), int 0, int 1] => [int -1]);
assert_run_vm!("SCHKBITREFS", [slice slice.clone(), int 1023, int 3] => [int 0], exit_code: 8);
assert_run_vm!("SCHKBITREFSQ", [slice slice.clone(), int 1023, int 3] => [int 0]);
}
#[test]
#[traced_test]
fn info_tests() {
let slice = make_cell_slice_with_refs(1000, 512, 2);
let cs = slice.apply();
let cell = cs.get_reference_cloned(0).unwrap();
assert_run_vm!("PLDREFVAR", [slice slice.clone(), int 0] => [cell cell.clone()]);
assert_run_vm!("PLDREF", [slice slice.clone()] => [cell cell.clone()]);
assert_run_vm!("PLDREFIDX 0", [slice slice.clone()] => [cell cell]);
let cell = cs.get_reference_cloned(1).unwrap();
assert_run_vm!("PLDREFVAR", [slice slice.clone(), int 1] => [cell cell.clone()]);
assert_run_vm!("PLDREFIDX 1", [slice slice.clone()] => [cell cell]);
assert_run_vm!("PLDREFVAR", [slice slice.clone(), int 3] => [int 0], exit_code: 9);
assert_run_vm!("PLDREFIDX 3", [slice slice.clone()] => [int 0], exit_code: 9);
assert_run_vm!("PLDREFVAR", [slice slice.clone(), int 10] => [int 0], exit_code: 5);
assert_run_vm!("SBITS", [slice slice.clone()] => [int 512]);
assert_run_vm!("SREFS", [slice slice.clone()] => [int 2]);
assert_run_vm!("SBITREFS", [slice slice.clone()] => [int 512, int 2]);
}
#[test]
#[traced_test]
fn begins_tests() {
let slice = make_uint_cell_slice(1, 8);
let prefix = make_uint_cell_slice(0b0000, 4);
let result = skip_common(&slice, &prefix);
assert_run_vm!("SDBEGINSX", [slice slice.clone(), slice prefix.clone()] => [slice result.clone()]);
assert_run_vm!("SDBEGINSXQ", [slice slice.clone(), slice prefix.clone()] => [slice result, int -1]);
let prefix = make_uint_cell_slice(0b11, 9);
assert_run_vm!("SDBEGINSX", [slice slice.clone(), slice prefix.clone()] => [int 0], exit_code: 9);
assert_run_vm!("SDBEGINSXQ", [slice slice.clone(), slice prefix.clone()] => [slice slice.clone(), int 0]);
}
#[test]
#[traced_test]
fn load_library_cell() {
let main_code = Boc::decode(tvmasm! {
r#"
PUSHREF @{2b698ae42e5cbea6096608a76572ad2fabd5c706a701bedb6369799c0d175c0f}
XLOAD
CTOS
BLESS
EXECUTE
"#
})
.unwrap();
let library_code = Boc::decode(tvmasm! {
r#"
PUSHINT 10
PUSHINT 11
"#
})
.unwrap();
let libraries = HashMap::from([(
"2b698ae42e5cbea6096608a76572ad2fabd5c706a701bedb6369799c0d175c0f"
.parse::<HashBytes>()
.unwrap(),
SimpleLib {
public: true,
root: library_code,
},
)]);
let mut output = crate::tests::TracingOutput::default();
let mut state = VmState::builder()
.with_version(crate::VmVersion::LATEST_TON)
.with_code(main_code)
.with_libraries(&libraries)
.with_debug(&mut output)
.build();
let result = state.run();
println!("execution result {:?}", !result);
}
#[test]
#[traced_test]
fn load_exotic_cells() -> anyhow::Result<()> {
let cell = Cell::default();
assert_run_vm!("CTOS", [cell cell.clone()] => [slice OwnedCellSlice::new_allow_exotic(cell.clone())]);
assert_run_vm!("XCTOS", [cell cell.clone()] => [slice OwnedCellSlice::new_allow_exotic(cell.clone()), int 0]);
assert_run_vm!("XLOAD", [cell cell.clone()] => [cell cell.clone()]);
assert_run_vm!("XLOADQ", [cell cell.clone()] => [cell cell.clone(), int -1]);
let pruned_branch = tycho_types::merkle::make_pruned_branch(
Cell::empty_cell_ref(),
0,
Cell::empty_context(),
)?;
assert_run_vm!("CTOS", [cell pruned_branch.clone()] => [int 0], exit_code: 9);
assert_run_vm!("XCTOS", [cell pruned_branch.clone()] => [slice OwnedCellSlice::new_allow_exotic(pruned_branch.clone()), int -1]);
assert_run_vm!("XLOAD", [cell pruned_branch.clone()] => [int 0], exit_code: 9);
assert_run_vm!("XLOADQ", [cell pruned_branch.clone()] => [int 0]);
let merkle_proof = CellBuilder::build_from(MerkleProof::default())?;
assert_run_vm!("CTOS", [cell merkle_proof.clone()] => [int 0], exit_code: 9);
assert_run_vm!("XCTOS", [cell merkle_proof.clone()] => [slice OwnedCellSlice::new_allow_exotic(merkle_proof.clone()), int -1]);
assert_run_vm!("XLOAD", [cell merkle_proof.clone()] => [int 0], exit_code: 9);
assert_run_vm!("XLOADQ", [cell merkle_proof.clone()] => [int 0]);
let merkle_update = CellBuilder::build_from(MerkleUpdate::default())?;
assert_run_vm!("CTOS", [cell merkle_update.clone()] => [int 0], exit_code: 9);
assert_run_vm!("XCTOS", [cell merkle_update.clone()] => [slice OwnedCellSlice::new_allow_exotic(merkle_update.clone()), int -1]);
assert_run_vm!("XLOAD", [cell merkle_update.clone()] => [int 0], exit_code: 9);
assert_run_vm!("XLOADQ", [cell merkle_update.clone()] => [int 0]);
let library_code = Boc::decode(tvmasm!("NOP"))?;
let library = {
let mut b = CellBuilder::new();
b.set_exotic(true);
b.store_u8(CellType::LibraryReference.to_byte())?;
b.store_u256(library_code.repr_hash())?;
b.build()?
};
assert_run_vm!("CTOS", [cell library.clone()] => [int 0], exit_code: 9);
assert_run_vm!("XCTOS", [cell library.clone()] => [slice OwnedCellSlice::new_allow_exotic(library.clone()), int -1]);
assert_run_vm!("XLOAD", [cell library.clone()] => [int 0], exit_code: 9);
assert_run_vm!("XLOADQ", [cell library.clone()] => [int 0]);
let libs = HashMap::from([(*library_code.repr_hash(), SimpleLib {
public: true,
root: library_code.clone(),
})]);
assert_run_vm!("CTOS", libs: libs.clone(), [cell library.clone()] => [slice OwnedCellSlice::new_allow_exotic(library_code.clone())]);
assert_run_vm!("XCTOS", libs: libs.clone(), [cell library.clone()] => [slice OwnedCellSlice::new_allow_exotic(library.clone()), int -1]);
assert_run_vm!("XLOAD", libs: libs.clone(), [cell library.clone()] => [cell library_code.clone()]);
assert_run_vm!("XLOADQ", libs: libs.clone(), [cell library.clone()] => [cell library_code.clone(), int -1]);
Ok(())
}
#[test]
#[traced_test]
fn test_build_invalid_exotic() {
assert_run_vm!(
r#"
PUSHINT 0
NEWC
STU 8
TRUE
ENDXC
"#,
[] => [int 0],
exit_code: 8,
);
}
#[test]
#[traced_test]
fn store_uint_quiet() {
let mut b = CellBuilder::new();
for n in [12, 13, 14] {
b.store_zeros(256 - 8).unwrap();
b.store_u8(n).unwrap();
}
assert_run_vm!(
r#"
PUSHINT 15
PUSHINT 14
PUSHINT 13
PUSHINT 12
NEWC
STU 256
STU 256
STU 256
STUQ 256
"#,
[] => [int 15, builder b, int -1],
exit_code: 0,
);
}
fn skip_common(slice: &OwnedCellSlice, prefix: &OwnedCellSlice) -> OwnedCellSlice {
let common = get_common_prefix(slice, prefix);
let mut slice = slice.clone();
slice
.range_mut()
.skip_first(common.range().size_bits(), 0)
.unwrap();
slice
}
fn get_common_prefix(slice: &OwnedCellSlice, prefix: &OwnedCellSlice) -> OwnedCellSlice {
let cs = slice.apply();
let prefix = prefix.apply();
let common = cs.longest_common_data_prefix(&prefix);
let mut slice = slice.clone();
slice.set_range(common.range());
slice
}
fn get_subslice(
slice: &OwnedCellSlice,
from: u16,
len: u16,
from_refs: u8,
refs_count: u8,
) -> OwnedCellSlice {
let mut new = slice.clone();
let mut range = slice.range();
range.skip_first(from, from_refs).unwrap();
range.only_first(len, refs_count).unwrap();
new.set_range(range);
new
}
fn make_cell_slice_with_refs(value: u128, bits: u16, refs_count: u8) -> OwnedCellSlice {
let mut cb = CellBuilder::new();
cb.store_bigint(&BigInt::from(value), bits, false).unwrap();
for _ in 0..refs_count {
let mut rb = CellBuilder::new();
rb.store_u64(u64::MAX).unwrap();
let cell = rb.build().unwrap();
cb.store_reference(cell).unwrap()
}
let cell = cb.build().unwrap();
OwnedCellSlice::new_allow_exotic(cell)
}
fn make_uint_cell_slice(value: u128, bits: u16) -> OwnedCellSlice {
let mut cb = CellBuilder::new();
cb.store_bigint(&BigInt::from(value), bits, false).unwrap();
let cell = cb.build().unwrap();
OwnedCellSlice::new_allow_exotic(cell)
}
fn make_int_cell_slice(value: i128, bits: u16) -> OwnedCellSlice {
let mut cb = CellBuilder::new();
cb.store_bigint(&BigInt::from(value), bits, true).unwrap();
let cell = cb.build().unwrap();
OwnedCellSlice::new_allow_exotic(cell)
}
fn cut_slice(slice: &OwnedCellSlice, bits: u16) -> (OwnedCellSlice, OwnedCellSlice) {
let mut cs = slice.apply();
let prefix = cs.load_prefix(bits, 0).unwrap();
let mut left = slice.clone();
let mut right = slice.clone();
left.set_range(prefix.range());
right.set_range(cs.range());
(left, right)
}
fn cut_slice_prefix(slice: &OwnedCellSlice, bits: u16, refs: u8) -> OwnedCellSlice {
let mut cs = slice.apply();
let prefix = cs.load_prefix(bits, refs).unwrap();
let mut slice = slice.clone();
slice.set_range(prefix.range());
slice
}
fn cut_slice_suffix(slice: &OwnedCellSlice, bits: u16, refs: u8) -> OwnedCellSlice {
let mut cs = slice.apply();
let suffix = cs.load_suffix(bits, refs).unwrap();
let mut slice = slice.clone();
slice.set_range(suffix.range());
slice
}
fn cut_slice_to_uint(slice: &OwnedCellSlice, bits: u16) -> (BigInt, OwnedCellSlice) {
let mut cs = slice.apply();
let prefix = cs.load_bigint(bits, false).unwrap();
let mut right = slice.clone();
right.set_range(cs.range());
(prefix, right)
}
fn cut_slice_to_int(slice: &OwnedCellSlice, bits: u16) -> (BigInt, OwnedCellSlice) {
let mut cs = slice.apply();
let prefix = cs.load_bigint(bits, true).unwrap();
let mut right = slice.clone();
right.set_range(cs.range());
(prefix, right)
}
fn extract_int(slice: &OwnedCellSlice, bits: u16) -> BigInt {
let cs = slice.apply();
get_int_from_slice(&cs, bits, true).unwrap()
}
fn extract_uint(slice: &OwnedCellSlice, bits: u16) -> BigInt {
let cs = slice.apply();
get_int_from_slice(&cs, bits, false).unwrap()
}
fn make_cell_with_reference() -> Cell {
let mut cb = CellBuilder::new();
cb.store_reference(Cell::default()).unwrap();
cb.build().unwrap()
}
fn get_int_from_slice(slice: &CellSlice<'_>, bits: u16, signed: bool) -> Result<BigInt, Error> {
match bits {
0 => Ok(BigInt::default()),
0..=64 if !signed => slice.get_uint(slice.offset_bits(), bits).map(BigInt::from),
0..=64 if signed => slice.get_uint(slice.offset_bits(), bits).map(|mut int| {
if bits < 64 {
int |= ((int >> (bits - 1)) * u64::MAX) << (bits - 1);
}
BigInt::from(int as i64)
}),
_ => {
let rem = bits % 8;
let mut buffer = [0u8; 33];
slice
.get_raw(slice.offset_bits(), &mut buffer, bits)
.map(|buffer| {
let mut int = if signed {
BigInt::from_signed_bytes_be(buffer)
} else {
BigInt::from_bytes_be(Sign::Plus, buffer)
};
if !bits.is_multiple_of(8) {
int >>= 8 - rem;
}
int
})
}
}
}
}