use alloc::vec;
use crate::{
prelude::*,
tests::test_helpers::{
assert_panics,
assert_success,
run_script,
},
};
use fuel_asm::{
RegId,
op,
};
#[test]
fn jump_and_link__allows_discarding_return_address() {
let script = vec![
op::jal(RegId::ZERO, RegId::PC, 1), op::ret(RegId::ONE),
];
let receipts = run_script(script);
assert_success(&receipts);
}
#[test]
fn jump_and_link__cannot_write_reserved_registers() {
let script = vec![
op::jal(RegId::ONE, RegId::PC, 1), op::ret(RegId::ONE),
];
let receipts = run_script(script);
assert_panics(&receipts, PanicReason::ReservedRegisterNotWritable);
}
#[test]
fn jump_and_link__subroutine_call_works() {
let reg_fn_addr = RegId::new(0x10);
let reg_return_addr = RegId::new(0x11);
let reg_tmp = RegId::new(0x12);
let canary = 0x1337;
let subroutine = vec![
op::movi(reg_tmp, canary as _),
op::log(reg_tmp, RegId::ZERO, RegId::ZERO, RegId::ZERO),
op::jal(RegId::ZERO, reg_return_addr, 0), ];
const MAIN_LEN: usize = 3; let mut script = vec![
op::addi(reg_fn_addr, RegId::PC, (Instruction::SIZE * MAIN_LEN) as _),
op::jal(reg_return_addr, reg_fn_addr, 0), op::ret(RegId::ONE), ];
assert_eq!(MAIN_LEN, script.len());
script.extend(subroutine);
let receipts = run_script(script);
assert_success(&receipts);
if let Some(Receipt::Log { ra, .. }) = receipts.first() {
assert!(*ra == canary, "Expected canary value to be logged");
} else {
panic!("Expected a log receipt");
};
}
#[test]
fn jump_and_link__immediate_count_is_instructions() {
let reg_return_addr = RegId::new(0x11);
let reg_tmp = RegId::new(0x12);
let skip = 3;
let script = vec![
op::movi(reg_tmp, 5),
op::jal(reg_return_addr, RegId::PC, (skip + 1) as _),
op::subi(reg_tmp, reg_tmp, 1),
op::subi(reg_tmp, reg_tmp, 1),
op::subi(reg_tmp, reg_tmp, 1),
op::subi(reg_tmp, reg_tmp, 1),
op::subi(reg_tmp, reg_tmp, 1),
op::log(reg_tmp, RegId::ZERO, RegId::ZERO, RegId::ZERO),
op::ret(RegId::ONE),
];
let receipts = run_script(script);
assert_success(&receipts);
if let Some(Receipt::Log { ra, .. }) = receipts.first() {
assert_eq!(*ra, skip, "Expected correct number of skipped instructions");
} else {
panic!("Expected a log receipt");
};
}
#[test]
fn jump_and_link__recursive_fibonacci() {
fn rust_fibo(n: u64) -> u64 {
if n <= 1 {
n
} else {
rust_fibo(n - 1) + rust_fibo(n - 2)
}
}
fn fuel_fibo(n: u64) -> u64 {
let reg_fnarg = RegId::new(0x10); let reg_return_addr = RegId::new(0x11);
let reg_local1 = RegId::new(0x12); let reg_local2 = RegId::new(0x13); let reg_local3 = RegId::new(0x14);
let script = vec![
op::movi(reg_fnarg, n as _),
op::jal(reg_return_addr, RegId::PC, 3), op::log(reg_fnarg, RegId::ZERO, RegId::ZERO, RegId::ZERO),
op::ret(RegId::ONE),
op::pshl(0b11110), op::subi(reg_local3, RegId::PC, Instruction::SIZE as _),
op::movi(reg_local1, 2),
op::lt(reg_local1, reg_fnarg, reg_local1),
op::jnzf(reg_local1, RegId::ZERO, 8), op::subi(reg_local2, reg_fnarg, 2), op::subi(reg_fnarg, reg_fnarg, 1), op::jal(reg_return_addr, reg_local3, 0), op::move_(reg_local1, reg_fnarg), op::move_(reg_fnarg, reg_local2), op::jal(reg_return_addr, reg_local3, 0), op::move_(reg_local2, reg_fnarg), op::add(reg_fnarg, reg_local1, reg_local2), op::popl(0b11110), op::jal(RegId::ZERO, reg_return_addr, 0), ];
let receipts = run_script(script);
assert_success(&receipts);
let Some(Receipt::Log { ra, .. }) = receipts.first() else {
panic!("Expected a log receipt");
};
*ra
}
assert_eq!(rust_fibo(10), 55, "Sanity check");
for n in 0..=10 {
let f = fuel_fibo(n);
let r = rust_fibo(n);
assert_eq!(f, r, "Wrong result for fibo({n}), got {f}, expected {r}");
}
}