Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Add more memory range test cases #380

Merged
merged 11 commits into from
Mar 17, 2023
13 changes: 2 additions & 11 deletions fuel-vm/src/tests/alu.rs
Original file line number Diff line number Diff line change
@@ -1,18 +1,9 @@
use super::test_helpers;
use fuel_asm::RegId;
use fuel_asm::{op, Instruction};
use fuel_vm::prelude::*;

/// Set a register `r` to a Word-sized number value using left-shifts
fn set_full_word(r: RegisterId, v: Word) -> Vec<Instruction> {
let r = u8::try_from(r).unwrap();
let mut ops = vec![op::movi(r, 0)];
for byte in v.to_be_bytes() {
ops.push(op::ori(r, r, byte as Immediate12));
ops.push(op::slli(r, r, 8));
}
ops.pop().unwrap(); // Remove last shift
ops
}
use test_helpers::set_full_word;

fn alu(registers_init: &[(RegisterId, Word)], ins: Instruction, reg: RegisterId, expected: Word) {
let storage = MemoryStorage::default();
Expand Down
61 changes: 61 additions & 0 deletions fuel-vm/src/tests/log.rs
Original file line number Diff line number Diff line change
@@ -0,0 +1,61 @@
use super::test_helpers;
use fuel_asm::*;
use fuel_tx::Receipt;
use fuel_types::Immediate24;
use fuel_vm::consts::{VM_MAX_RAM, VM_REGISTER_COUNT};
use test_helpers::{assert_panics, assert_success, run_script, set_full_word};

#[test]
fn all_registers_can_be_logged() {
let mut script = Vec::new();
for reg in 0..(VM_REGISTER_COUNT as u8) {
script.push(op::movi(0x30, reg as Immediate24));
script.push(op::log(0x30, reg, RegId::ZERO, RegId::ZERO));
}
script.push(op::ret(RegId::ONE));

let receipts = run_script(script.into_iter().collect());
assert_success(&receipts);

let mut receipts_it = receipts.into_iter();
for reg in 0..VM_REGISTER_COUNT {
if let Receipt::Log { ra, .. } = receipts_it.next().expect("Missing receipt") {
assert_eq!(ra, reg as u64);
} else {
unreachable!("Exptected a log receipt");
}
}
}

#[test]
fn logd_memory_range_overflow() {
let script = vec![
op::not(0x30, RegId::ZERO),
op::logd(RegId::ZERO, RegId::ZERO, 0x30, 1),
op::ret(RegId::ONE),
];

let receipts = run_script(script.into_iter().collect());
assert_panics(&receipts, PanicReason::MemoryOverflow);
}

#[test]
fn logd_memory_out_of_range_fails() {
let mut script = set_full_word(0x30, VM_MAX_RAM);
script.push(op::logd(RegId::ZERO, RegId::ZERO, 0x30, 1));
script.push(op::ret(RegId::ONE));

let receipts = run_script(script.into_iter().collect());
assert_panics(&receipts, PanicReason::MemoryOverflow);
}

#[test]
fn logd_just_below_memory_limit_succeeds() {
let mut script = set_full_word(0x30, VM_MAX_RAM - 100);
script.push(op::movi(0x31, 100));
script.push(op::logd(RegId::ZERO, RegId::ZERO, 0x30, 0x31));
script.push(op::ret(RegId::ONE));

let receipts = run_script(script.into_iter().collect());
assert_success(&receipts);
}
2 changes: 2 additions & 0 deletions fuel-vm/src/tests/mod.rs
Original file line number Diff line number Diff line change
Expand Up @@ -8,11 +8,13 @@ mod crypto;
mod encoding;
mod flow;
mod gas_factor;
mod log;
mod memory;
mod metadata;
mod outputs;
mod predicate;
mod profile_gas;
mod serde_profile;
mod spec;
mod test_helpers;
mod validation;
77 changes: 36 additions & 41 deletions fuel-vm/src/tests/spec.rs
Original file line number Diff line number Diff line change
@@ -1,54 +1,25 @@
use fuel_asm::*;
use fuel_tx::{ConsensusParameters, Receipt, ScriptExecutionResult, Transaction};
use fuel_tx::Receipt;
use fuel_types::Immediate18;
use fuel_vm::prelude::{IntoChecked, MemoryClient};
use fuel_vm::consts::VM_MAX_RAM;

use super::test_helpers;
use rstest::rstest;

/// Assert that transaction didn't panic
fn assert_success(receipts: &[Receipt]) {
if let Receipt::ScriptResult { result, .. } = receipts.last().unwrap() {
if *result != ScriptExecutionResult::Success {
panic!("Expected vm success, got {result:?} instead");
}
} else {
unreachable!("No script result");
}
}

/// Assert that transaction receipts end in a panic with the given reason
fn assert_panics(receipts: &[Receipt], reason: PanicReason) {
if let Receipt::ScriptResult { result, .. } = receipts.last().unwrap() {
if *result != ScriptExecutionResult::Panic {
panic!("Expected vm panic, got {result:?} instead");
}
} else {
unreachable!("No script result");
}

let n = receipts.len();
assert!(n >= 2, "Invalid receipts len");
if let Receipt::Panic { reason: pr, .. } = receipts.get(n - 2).unwrap() {
assert_eq!(reason, *pr.reason());
} else {
unreachable!("No script receipt for a paniced tx");
}
}
use test_helpers::{assert_panics, assert_success, run_script, set_full_word};

/// Setup some useful values
/// * 0x31 to all ones, i.e. max word
/// * 0x32 to two
/// * 0x33 to VM_MAX_RAM
/// * 0x33 to (VM_MAX_RAM / instruction_size)
fn common_setup() -> Vec<Instruction> {
vec![op::not(0x31, RegId::ZERO), op::movi(0x32, 2)]
}
let mut setup = vec![op::not(0x31, RegId::ZERO), op::movi(0x32, 2)];

fn run_script(script: Vec<u8>) -> Vec<Receipt> {
let mut client = MemoryClient::default();
let tx = Transaction::script(0, 1_000_000, 0, script, vec![], vec![], vec![], vec![])
.into_checked(0, &ConsensusParameters::DEFAULT, client.gas_costs())
.expect("failed to generate a checked tx");
client.transact(tx);
client.receipts().expect("Expected receipts").to_vec()
setup.extend(&set_full_word(0x33, VM_MAX_RAM));
setup.extend(&set_full_word(0x34, VM_MAX_RAM / (Instruction::SIZE as u64)));

setup
}

#[rstest]
Expand All @@ -72,7 +43,7 @@ fn spec_unsafemath_flag(
script.push(op::log(RegId::IS, RegId::PC, RegId::OF, RegId::ERR));
script.push(op::ret(RegId::ONE));

let receipts = run_script(script.into_iter().collect());
let receipts = run_script(script);

if flag {
if let Receipt::Log { rd: err, .. } = receipts[0] {
Expand Down Expand Up @@ -344,6 +315,30 @@ fn spec_incr_pc_by_four(
}
}

#[rstest]
fn spec_jmp_beyond_ram_panics(
#[values(
op::jmp(0x20),
op::ji(Imm24::MAX.into()),
op::jne(RegId::ZERO, RegId::ONE, 0x20),
// TODO: Test JNEI and JNZI instructions
// The immediates of these are 18 and 12 bits long, so they
// cannot hold large-enough values to go over the RAM limit,
// since the transaction size is limited. It could be possible
// to setup a suitable scenario by repeatedly calling contract
// until the $is is close enough to the RAM limit.
)]
case: Instruction,
) {
let mut script = common_setup();
// This is just beyond ram
script.push(op::add(0x20, RegId::IS, 0x34));
script.push(case);

let receipts = run_script(script.into_iter().collect());
assert_panics(&receipts, PanicReason::MemoryOverflow);
}

#[rstest]
fn spec_can_write_allowed_flag_combinations(#[values(0b00, 0b01, 0b10, 0b11)] flags: Immediate18) {
let mut script = common_setup();
Expand Down
57 changes: 57 additions & 0 deletions fuel-vm/src/tests/test_helpers.rs
Original file line number Diff line number Diff line change
@@ -0,0 +1,57 @@
#![allow(dead_code)] // This module is used by other test, but clippy doesn't understand that

use fuel_asm::{op, Instruction};
use fuel_vm::prelude::*;

/// Set a register `r` to a Word-sized number value using left-shifts
pub fn set_full_word(r: RegisterId, v: Word) -> Vec<Instruction> {
let r = u8::try_from(r).unwrap();
let mut ops = vec![op::movi(r, 0)];
for byte in v.to_be_bytes() {
ops.push(op::ori(r, r, byte as Immediate12));
ops.push(op::slli(r, r, 8));
}
ops.pop().unwrap(); // Remove last shift
ops
}

/// Run a instructions-only script with reasonable defaults, and return receipts
pub fn run_script(script: Vec<Instruction>) -> Vec<Receipt> {
let script = script.into_iter().collect();
let mut client = MemoryClient::default();
let tx = Transaction::script(0, 1_000_000, 0, script, vec![], vec![], vec![], vec![])
.into_checked(0, &ConsensusParameters::DEFAULT, client.gas_costs())
.expect("failed to generate a checked tx");
client.transact(tx);
client.receipts().expect("Expected receipts").to_vec()
}

/// Assert that transaction didn't panic
pub fn assert_success(receipts: &[Receipt]) {
if let Receipt::ScriptResult { result, .. } = receipts.last().unwrap() {
if *result != ScriptExecutionResult::Success {
panic!("Expected vm success, got {result:?} instead");
}
} else {
unreachable!("No script result");
}
}

/// Assert that transaction receipts end in a panic with the given reason
pub fn assert_panics(receipts: &[Receipt], reason: PanicReason) {
if let Receipt::ScriptResult { result, .. } = receipts.last().unwrap() {
if *result != ScriptExecutionResult::Panic {
panic!("Expected vm panic, got {result:?} instead");
}
} else {
unreachable!("No script result");
}

let n = receipts.len();
assert!(n >= 2, "Invalid receipts len");
if let Receipt::Panic { reason: pr, .. } = receipts.get(n - 2).unwrap() {
assert_eq!(*pr.reason(), reason, "Panic reason differs for the expected reason");
} else {
unreachable!("No script receipt for a paniced tx");
}
}