diff --git a/executor/src/phase/action.rs b/executor/src/phase/action.rs index abdd769..f3ef492 100644 --- a/executor/src/phase/action.rs +++ b/executor/src/phase/action.rs @@ -157,6 +157,10 @@ impl ExecutorState<'_> { // Action list itself is ok. res.action_phase.valid = true; + let pre_action_balance = ctx + .received_message + .as_ref() + .map(|msg| msg.balance_remaining.clone()); // Execute actions. let mut action_ctx = ActionContext { @@ -231,6 +235,13 @@ impl ExecutorState<'_> { // Apply flags. res.bounce |= action_ctx.need_bounce_on_fail; + if self.config.global.version >= 14 + && let Some(msg) = action_ctx.received_message.as_deref_mut() + && let Some(balance) = pre_action_balance + { + msg.balance_remaining = balance; + } + // Ignore all other action. return Ok(res); } @@ -268,6 +279,13 @@ impl ExecutorState<'_> { )?; // Apply flags. + if self.config.global.version >= 14 + && let Some(msg) = action_ctx.received_message.as_deref_mut() + && let Some(balance) = pre_action_balance + { + msg.balance_remaining = balance; + } + res.bounce |= action_ctx.need_bounce_on_fail; res.action_phase.result_code = ResultCode::StateOutOfLimits as i32; res.state_exceeds_limits = true; diff --git a/executor/src/phase/compute.rs b/executor/src/phase/compute.rs index b10f6f9..75f43b8 100644 --- a/executor/src/phase/compute.rs +++ b/executor/src/phase/compute.rs @@ -306,7 +306,7 @@ impl ExecutorState<'_> { // NOTE: Not all versions change the smc info, so we need to bump // this value manually. - let real_version = tycho_vm::VmVersion::Ton(12); + let real_version = tycho_vm::VmVersion::Ton(14); if let Some(inspector) = ctx.inspector.as_deref_mut() && let Some(modify_smc_info) = inspector.modify_smc_info.as_deref_mut() diff --git a/vm/src/cont.rs b/vm/src/cont.rs index d6542f0..43aa570 100644 --- a/vm/src/cont.rs +++ b/vm/src/cont.rs @@ -197,15 +197,25 @@ impl ControlRegs { } pub fn define(&mut self, i: usize, value: RcStackValue) -> VmResult<()> { + self.define_ext(i, value, false) + } + + pub fn define_ext(&mut self, i: usize, value: RcStackValue, silent: bool) -> VmResult<()> { if i < Self::CONT_REG_COUNT { let cont = ok!(value.into_cont()); - vm_ensure!(self.c[i].is_none(), ControlRegisterRedefined); - self.c[i] = Some(cont); + if self.c[i].is_none() { + self.c[i] = Some(cont); + } else { + vm_ensure!(silent, ControlRegisterRedefined); + } } else if Self::DATA_REG_RANGE.contains(&i) { let cell = ok!(value.into_cell()); let d = &mut self.d[i - Self::DATA_REG_OFFSET]; - vm_ensure!(d.is_none(), ControlRegisterRedefined); - *d = Some(SafeRc::unwrap_or_clone(cell)); + if d.is_none() { + *d = Some(SafeRc::unwrap_or_clone(cell)); + } else { + vm_ensure!(silent, ControlRegisterRedefined); + } } else if i == 7 { let tuple = ok!(value.into_tuple()); diff --git a/vm/src/instr/arithops.rs b/vm/src/instr/arithops.rs index 2b346dc..e80a202 100644 --- a/vm/src/instr/arithops.rs +++ b/vm/src/instr/arithops.rs @@ -2,7 +2,7 @@ use std::cmp::Ordering; use num_bigint::{BigInt, Sign}; use num_integer::Integer; -use num_traits::Zero; +use num_traits::{ToPrimitive, Zero}; #[cfg(feature = "dump")] use tycho_types::prelude::*; use tycho_vm_proc::vm_module; @@ -353,11 +353,18 @@ impl ArithOps { let stack = SafeRc::make_mut(&mut st.stack); let y = match y { - Some(y) => y, - None => ok!(stack.pop_smallint_range(0, 256)), + Some(y) => Some(y), + None if quiet && st.version.is_ton(14..) => { + if let Some(item) = ok!(stack.pop_int_or_nan()) { + item.to_u32().filter(|item| (0..=256).contains(item)) + } else { + None + } + } + None => Some(ok!(stack.pop_smallint_range(0, 256))), }; - if y == 0 { + if y == Some(0) { round_mode = RoundMode::Floor; } let w = if add { @@ -366,8 +373,14 @@ impl ArithOps { None }; - match ok!(stack.pop_int_or_nan()) { - Some(mut x) => match operation { + match (ok!(stack.pop_int_or_nan()), y) { + (_, None) if quiet => { + ok!(stack.push_nan()); + if let Operation::RShiftMod = operation { + ok!(stack.push_nan()); + } + } + (Some(mut x), Some(y)) => match operation { Operation::RShift => { let res = int_rshift(&x, y, round_mode); ok!(stack.push_raw_int(update_or_new_rc(x, res), quiet)); @@ -394,7 +407,7 @@ impl ArithOps { ok!(stack.push_raw_int(SafeRc::new(r), quiet)); } }, - _ if quiet => { + (None, _) if quiet => { ok!(stack.push_nan()); if let Operation::RShiftMod = operation { ok!(stack.push_nan()); @@ -1264,6 +1277,8 @@ mod tests { assert_run_vm!("MODPOW2", [nan, int 3] => [int 0], exit_code: 4); assert_run_vm!("QUIET MODPOW2", [int 5, int 2] => [int 1]); assert_run_vm!("QUIET MODPOW2", [nan, int 3] => [nan]); + assert_run_vm!("QUIET MODPOW2", [int 5, nan] => [nan]); + assert_run_vm!("QUIET MODPOW2", [int 5, int 257] => [nan]); assert_run_vm!("RSHIFTMOD", [int 5, int 2] => [int 1, int 1]); assert_run_vm!("RSHIFTMOD", [int 5, int 0] => [int 5, int 0]); @@ -1273,6 +1288,8 @@ mod tests { assert_run_vm!("RSHIFTMOD", [nan, int 3] => [int 0], exit_code: 4); assert_run_vm!("QUIET RSHIFTMOD", [int 5, int 2] => [int 1, int 1]); assert_run_vm!("QUIET RSHIFTMOD", [nan, int 3] => [nan, nan]); + assert_run_vm!("QUIET RSHIFTMOD", [int 5, nan] => [nan, nan]); + assert_run_vm!("QUIET RSHIFTMOD", [int 5, int 257] => [nan, nan]); assert_run_vm!("ADDRSHIFTMOD", [int 3, int 2, int 2] => [int 1, int 1]); assert_run_vm!("ADDRSHIFTMOD", [int 3, int 2, int 0] => [int 5, int 0]); @@ -1284,6 +1301,8 @@ mod tests { assert_run_vm!("QUIET ADDRSHIFTMOD", [int 3, int 2, int 2] => [int 1, int 1]); assert_run_vm!("QUIET ADDRSHIFTMOD", [nan, int 1, int 3] => [nan, nan]); assert_run_vm!("QUIET ADDRSHIFTMOD", [int 1, nan, int 3] => [nan, nan]); + assert_run_vm!("QUIET ADDRSHIFTMOD", [int 3, int 2, nan] => [nan, nan]); + assert_run_vm!("QUIET ADDRSHIFTMOD", [int 3, int 2, int 257] => [nan, nan]); } // TODO: Add more tests diff --git a/vm/src/instr/contops.rs b/vm/src/instr/contops.rs index 3ecf452..f4b007c 100644 --- a/vm/src/instr/contops.rs +++ b/vm/src/instr/contops.rs @@ -748,7 +748,9 @@ impl ContOps { let stack = SafeRc::make_mut(&mut st.stack); let mut cont = ok!(stack.pop_cont()); let value = ok!(stack.pop()); - ok!(force_cdata(&mut cont).save.define(i as _, value)); + ok!(force_cdata(&mut cont) + .save + .define_ext(i as _, value, st.version.is_ton(14..))); ok!(stack.push_raw(cont)); Ok(0) } @@ -758,7 +760,9 @@ impl ContOps { vm_ensure!(ControlRegs::is_valid_idx(i as _), InvalidOpcode); let cont = st.cr.c[0].as_mut().expect("c0 should always be set"); let value = ok!(SafeRc::make_mut(&mut st.stack).pop()); - ok!(force_cdata(cont).save.define(i as _, value)); + ok!(force_cdata(cont) + .save + .define_ext(i as _, value, st.version.is_ton(14..))); Ok(0) } @@ -768,7 +772,9 @@ impl ContOps { // TODO: Check if c1 is always set let cont = st.cr.c[1].as_mut().expect("c1 should always be set"); let value = ok!(SafeRc::make_mut(&mut st.stack).pop()); - ok!(force_cdata(cont).save.define(i as _, value)); + ok!(force_cdata(cont) + .save + .define_ext(i as _, value, st.version.is_ton(14..))); Ok(0) } @@ -818,7 +824,9 @@ impl ContOps { .get_as_stack_value(i as _) .unwrap_or_else(Stack::make_null); - ok!(force_cdata(&mut c0).save.define(i as _, value)); + ok!(force_cdata(&mut c0) + .save + .define_ext(i as _, value, st.version.is_ton(14..))); st.cr.c[0] = Some(c0); Ok(0) } @@ -835,7 +843,9 @@ impl ContOps { .get_as_stack_value(i as _) .unwrap_or_else(Stack::make_null); - ok!(force_cdata(&mut c1).save.define(i as _, value)); + ok!(force_cdata(&mut c1) + .save + .define_ext(i as _, value, st.version.is_ton(14..))); st.cr.c[1] = Some(c1); Ok(0) } @@ -895,7 +905,9 @@ impl ContOps { ); let mut cont = ok!(stack.pop_cont()); let value = ok!(stack.pop()); - ok!(force_cdata(&mut cont).save.define(idx, value)); + ok!(force_cdata(&mut cont) + .save + .define_ext(idx, value, st.version.is_ton(14..))); ok!(stack.push_raw(cont)); Ok(0) } @@ -925,7 +937,9 @@ impl ContOps { actual: StackValueType::Null as _ }) }; - ok!(force_cdata(&mut cont).save.define(i, st_value)); + ok!(force_cdata(&mut cont) + .save + .define_ext(i, st_value, st.version.is_ton(14..))); } ok!(stack.push_raw(cont)); @@ -2197,6 +2211,23 @@ mod tests { ); } + #[test] + #[traced_test] + fn control_savelist_redefinition() { + assert_run_vm!( + r#" + PUSHREF x{1111} + PUSHCONT { PUSH c4 CTOS } + SETCONTCTR c4 + PUSHREF x{2222} + SWAP + SETCONTCTR c4 + EXECUTE + "#, + [] => [slice make_slice(0x1111_u16)] + ); + } + #[test] #[traced_test] fn runvm_simple() { diff --git a/vm/src/instr/cryptops.rs b/vm/src/instr/cryptops.rs index 95234ae..ce7f615 100644 --- a/vm/src/instr/cryptops.rs +++ b/vm/src/instr/cryptops.rs @@ -170,6 +170,13 @@ impl CryptOps { st.gas.try_consume_check_signature_gas()?; let is_valid = 'valid: { + if st.version.is_ton(14..) + && key_bytes[0] == 1 + && key_bytes[1..].iter().all(|v| *v == 0) + { + break 'valid false; + } + let Some(pubkey) = ed25519::PublicKey::from_bytes(key_bytes.as_slice().try_into().unwrap()) else { @@ -681,6 +688,52 @@ mod tests { Ok(()) } + #[test] + #[traced_test] + fn chksign_rejects_identity_pubkey_v14() { + use crate::VmVersion; + let data = []; + let mut signature = [0; 64]; + signature[0] = 1; + let mut identity = [0; 32]; + identity[0] = 1; + + assert_run_vm!( + "CHKSIGNS", + state: |st| st.version = VmVersion::Ton(13), + [ + raw build_slice(data), + raw build_slice(signature), + raw build_int(identity), + ] => [int -1] + ); + assert_run_vm!( + "CHKSIGNS", + [ + raw build_slice(data), + raw build_slice(signature), + raw build_int(identity), + ] => [int 0] + ); + assert_run_vm!( + "CHKSIGNU", + state: |st| st.version = VmVersion::Ton(13), + [ + int 0, + raw build_slice(signature), + raw build_int(identity), + ] => [int -1] + ); + assert_run_vm!( + "CHKSIGNU", + [ + int 0, + raw build_slice(signature), + raw build_int(identity), + ] => [int 0] + ); + } + #[test] #[traced_test] fn signdomain_stack_only() { diff --git a/vm/src/instr/currencyops.rs b/vm/src/instr/currencyops.rs index b683b7e..0f01ab3 100644 --- a/vm/src/instr/currencyops.rs +++ b/vm/src/instr/currencyops.rs @@ -10,7 +10,7 @@ use crate::gas::GasConsumer; use crate::instr::cellops::{finish_store_ok, finish_store_overflow}; use crate::saferc::SafeRc; use crate::smc_info::VmVersion; -use crate::stack::{RcStackValue, Stack, StackValue, Tuple}; +use crate::stack::{RcStackValue, Stack, StackValue, StackValueType, Tuple}; use crate::state::VmState; use crate::util::{OwnedCellSlice, load_uint_leq}; @@ -246,28 +246,37 @@ impl CurrencyOps { fn exec_store_opt_std_address(st: &mut VmState, quiet: bool) -> VmResult { let stack = SafeRc::make_mut(&mut st.stack); let mut builder: SafeRc = ok!(stack.pop_builder()); - match ok!(stack.pop_cs_opt()) { - None => { - if !builder.has_capacity(2, 0) { - return finish_store_overflow(stack, SafeRc::new(()), builder, quiet); - } + let value = ok!(stack.pop()); + if value.is_null() { + if !builder.has_capacity(2, 0) { + return finish_store_overflow(stack, SafeRc::new(()), builder, quiet); + } - SafeRc::make_mut(&mut builder).store_zeros(2)?; - ok!(stack.push_raw(builder)); - if quiet { - ok!(stack.push_bool(false)); - } - Ok(0) + SafeRc::make_mut(&mut builder).store_zeros(2)?; + ok!(stack.push_raw(builder)); + if quiet { + ok!(stack.push_bool(false)); } - Some(csr) => { - let cs = csr.apply(); - if !csr.fits_into(&builder) || !is_valid_std_address(&cs, &st.version) { - finish_store_overflow(stack, csr, builder, quiet) - } else { - SafeRc::make_mut(&mut builder).store_slice(cs)?; - finish_store_ok(stack, builder, quiet) - } + Ok(0) + } else if value.as_cell_slice().is_some() { + let csr = ok!(value.into_cell_slice()); + let cs = csr.apply(); + if !csr.fits_into(&builder) || !is_valid_std_address(&cs, &st.version) { + finish_store_overflow(stack, csr, builder, quiet) + } else { + SafeRc::make_mut(&mut builder).store_slice(cs)?; + finish_store_ok(stack, builder, quiet) } + } else if quiet && st.version.is_ton(14..) { + ok!(stack.push_raw(value)); + ok!(stack.push_raw(builder)); + ok!(stack.push_bool(true)); + Ok(0) + } else { + vm_bail!(InvalidType { + expected: StackValueType::Slice as _, + actual: value.raw_ty(), + }) } } } @@ -689,6 +698,15 @@ mod test { Ok(()) } + #[test] + #[traced_test] + fn store_std_opt_address_bad_type_test() { + let builder = CellBuilder::new(); + let rc_buider = SafeRc::new_dyn_value(builder); + + assert_run_vm!("STOPTSTDADDRQ", [int 100, raw rc_buider.clone()] => [int 100, raw rc_buider, int -1]); + } + #[test] #[traced_test] fn load_varint_u16_test() -> anyhow::Result<()> { diff --git a/vm/src/instr/messageops.rs b/vm/src/instr/messageops.rs index 5522200..46d8e55 100644 --- a/vm/src/instr/messageops.rs +++ b/vm/src/instr/messageops.rs @@ -219,7 +219,11 @@ impl MessageOps { // Compute fees and final message layout. let update_fees = |stats: CellTreeStats, fwd_fee: &mut Tokens| { let fwd_fee_short = prices.compute_fwd_fee(stats); - *fwd_fee = std::cmp::max(fwd_fee_short, user_fwd_fee); + *fwd_fee = if st.version.is_ton(14..) { + fwd_fee_short + } else { + std::cmp::max(fwd_fee_short, user_fwd_fee) + }; }; let compute_msg_root_bits = diff --git a/vm/src/smc_info.rs b/vm/src/smc_info.rs index 623de33..d94c500 100644 --- a/vm/src/smc_info.rs +++ b/vm/src/smc_info.rs @@ -24,7 +24,7 @@ pub enum VmVersion { } impl VmVersion { - pub const LATEST_TON: Self = Self::Ton(12); + pub const LATEST_TON: Self = Self::Ton(14); pub fn is_ton>(&self, range: R) -> bool { matches!(self, Self::Ton(version) if range.contains(version))