diff --git a/CHANGELOG.md b/CHANGELOG.md index a5fa9db3c5..b0cfa4f8f3 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -8,13 +8,12 @@ and this project adheres to [Semantic Versioning](http://semver.org/). ## [Unreleased] ### Added - - [#670](https://github.com/FuelLabs/fuel-vm/pull/670): Add DA compression functionality to `Transaction` and any types within +- [#733](https://github.com/FuelLabs/fuel-vm/pull/733): Add LibAFL based fuzzer and update `secp256k1` version to 0.29.1. ### Changed #### Breaking - - [#670](https://github.com/FuelLabs/fuel-vm/pull/670): The `predicate` field of `fuel_tx::input::Coin` is now a wrapper struct `PredicateCode`. ## [Version 0.56.0] diff --git a/README.md b/README.md index 9c77b1bf19..81e0693a4b 100644 --- a/README.md +++ b/README.md @@ -88,4 +88,4 @@ It returns `receipts` that contain result of execution. The `assert_panics` can The `fuel-tx` provides `fuel_tx::TransactionBuilder` that simplifies the building of custom transaction for testing purposes. -You can check how `TransactionBuilder::script` or `TransactionBuilder::create` are used for better understanding. \ No newline at end of file +You can check how `TransactionBuilder::script` or `TransactionBuilder::create` are used for better understanding. diff --git a/fuel-asm/Cargo.toml b/fuel-asm/Cargo.toml index d1fcc9fb72..4b67bd9620 100644 --- a/fuel-asm/Cargo.toml +++ b/fuel-asm/Cargo.toml @@ -11,7 +11,6 @@ repository = { workspace = true } description = "Atomic types of the FuelVM." [dependencies] -arbitrary = { version = "1.1", features = ["derive"], optional = true } bitflags = { workspace = true } fuel-types = { workspace = true, default-features = false } serde = { version = "1.0", default-features = false, features = ["derive"], optional = true } diff --git a/fuel-asm/src/args.rs b/fuel-asm/src/args.rs index 45c216e2a6..1998bfd7dd 100644 --- a/fuel-asm/src/args.rs +++ b/fuel-asm/src/args.rs @@ -11,7 +11,6 @@ crate::enum_try_from! { #[cfg_attr(feature = "typescript", wasm_bindgen::prelude::wasm_bindgen)] #[repr(u8)] #[cfg_attr(feature = "serde", derive(serde::Serialize, serde::Deserialize))] - #[cfg_attr(feature = "arbitrary", derive(arbitrary::Arbitrary))] /// Argument list for GM (get metadata) instruction /// The VM is the only who should match this struct, and it *MUST* always perform /// exhaustive match so all offered variants are covered. @@ -51,7 +50,6 @@ crate::enum_try_from! { #[cfg_attr(feature = "typescript", wasm_bindgen::prelude::wasm_bindgen)] #[repr(u16)] #[cfg_attr(feature = "serde", derive(serde::Serialize, serde::Deserialize))] - #[cfg_attr(feature = "arbitrary", derive(arbitrary::Arbitrary))] pub enum GTFArgs { /// Set `$rA` to `tx.type` Type = 0x001, diff --git a/fuel-asm/src/panic_instruction.rs b/fuel-asm/src/panic_instruction.rs index 917661aeea..6c9ebc163c 100644 --- a/fuel-asm/src/panic_instruction.rs +++ b/fuel-asm/src/panic_instruction.rs @@ -11,7 +11,6 @@ use crate::{ #[cfg_attr(feature = "typescript", wasm_bindgen::prelude::wasm_bindgen)] #[cfg_attr(feature = "serde", derive(serde::Serialize, serde::Deserialize))] #[derive(fuel_types::canonical::Deserialize, fuel_types::canonical::Serialize)] -#[cfg_attr(feature = "arbitrary", derive(arbitrary::Arbitrary))] /// Describe a panic reason with the instruction that generated it pub struct PanicInstruction { reason: PanicReason, diff --git a/fuel-asm/src/panic_reason.rs b/fuel-asm/src/panic_reason.rs index ff57240a22..7dae75e6ca 100644 --- a/fuel-asm/src/panic_reason.rs +++ b/fuel-asm/src/panic_reason.rs @@ -27,7 +27,6 @@ enum_from! { #[cfg_attr(feature = "typescript", wasm_bindgen::prelude::wasm_bindgen)] #[cfg_attr(feature = "serde", derive(serde::Serialize, serde::Deserialize))] #[derive(fuel_types::canonical::Serialize, fuel_types::canonical::Deserialize)] - #[cfg_attr(feature = "arbitrary", derive(arbitrary::Arbitrary))] #[repr(u8)] #[non_exhaustive] /// Panic reason representation for the interpreter. diff --git a/fuel-crypto/Cargo.toml b/fuel-crypto/Cargo.toml index 67f4b7a7cf..8b7b2e4afa 100644 --- a/fuel-crypto/Cargo.toml +++ b/fuel-crypto/Cargo.toml @@ -22,7 +22,7 @@ p256 = { version = "0.13", default-features = false, features = ["digest", "ecd rand = { version = "0.8", default-features = false, optional = true } # `rand-std` is used to further protect the blinders from side-channel attacks and won't compromise # the deterministic arguments of the signature (key, nonce, message), as defined in the RFC-6979 -secp256k1 = { version = "0.26", default-features = false, features = ["rand-std", "recovery"], optional = true } +secp256k1 = { version = "0.29.1", default-features = false, features = ["rand-std", "recovery"], optional = true } serde = { version = "1.0", default-features = false, features = ["derive"], optional = true } sha2 = { version = "0.10", default-features = false } zeroize = { version = "1.5", features = ["derive"] } @@ -41,6 +41,9 @@ serde = ["dep:serde", "fuel-types/serde"] std = ["alloc", "coins-bip32", "secp256k1", "coins-bip39", "fuel-types/std", "lazy_static", "rand?/std_rng", "serde?/default"] test-helpers = [] +[lints.rust] +unexpected_cfgs = { level = "warn", check-cfg = ['cfg(fuzzing)'] } + [[bench]] name = "signature" harness = false diff --git a/fuel-crypto/benches/signature.rs b/fuel-crypto/benches/signature.rs index 95a5e2fe14..9c7ab7b7d0 100644 --- a/fuel-crypto/benches/signature.rs +++ b/fuel-crypto/benches/signature.rs @@ -70,8 +70,8 @@ fn signatures(c: &mut Criterion) { let public = PublicKey::from_secret_key(&secp, &key); let message = fuel_crypto::Message::new(message); - let message = - Message::from_slice(message.as_ref()).expect("failed to create secp message"); + let message = Message::from_digest_slice(message.as_ref()) + .expect("failed to create secp message"); let signature = secp_signing.sign_ecdsa(&message, &key); let recoverable = secp.sign_ecdsa_recoverable(&message, &key); diff --git a/fuel-crypto/src/message.rs b/fuel-crypto/src/message.rs index 8678e3b804..929ecc03ea 100644 --- a/fuel-crypto/src/message.rs +++ b/fuel-crypto/src/message.rs @@ -118,6 +118,6 @@ impl fmt::Display for Message { #[cfg(feature = "std")] impl From<&Message> for secp256k1::Message { fn from(message: &Message) -> Self { - secp256k1::Message::from_slice(&*message.0).expect("length always matches") + secp256k1::Message::from_digest_slice(&*message.0).expect("length always matches") } } diff --git a/fuel-tx/Cargo.toml b/fuel-tx/Cargo.toml index 8459f76e16..d0e8a1314f 100644 --- a/fuel-tx/Cargo.toml +++ b/fuel-tx/Cargo.toml @@ -59,3 +59,6 @@ alloc = ["hashbrown", "fuel-types/alloc", "itertools/use_alloc", "derivative", " # serde is requiring alloc because its mandatory for serde_json. to avoid adding a new feature only for serde_json, we just require `alloc` here since as of the moment we don't have a use case of serde without alloc. serde = ["alloc", "fuel-asm/serde", "fuel-crypto/serde", "fuel-merkle/serde", "serde_json", "hashbrown/serde", "bitflags/serde"] da-compression = ["serde", "fuel-compression"] + +[lints.rust] +unexpected_cfgs = { level = "warn", check-cfg = ['cfg(fuzzing)'] } diff --git a/fuel-vm/Cargo.toml b/fuel-vm/Cargo.toml index 07155b004a..16ee3e15fd 100644 --- a/fuel-vm/Cargo.toml +++ b/fuel-vm/Cargo.toml @@ -85,7 +85,6 @@ std = [ "itertools/use_std", ] alloc = ["fuel-asm/alloc", "fuel-tx/alloc", "fuel-tx/alloc"] -arbitrary = ["fuel-asm/arbitrary"] profile-gas = ["profile-any"] profile-coverage = ["profile-any"] profile-any = ["dyn-clone"] # All profiling features should depend on this @@ -109,3 +108,6 @@ test-helpers = [ "tai64", "fuel-crypto/test-helpers", ] + +[lints.rust] +unexpected_cfgs = { level = "warn", check-cfg = ['cfg(fuzzing)'] } diff --git a/fuel-vm/fuzz/.cargo/config.toml b/fuel-vm/fuzz/.cargo/config.toml new file mode 100644 index 0000000000..71d0799c8d --- /dev/null +++ b/fuel-vm/fuzz/.cargo/config.toml @@ -0,0 +1,2 @@ +[build] +rustflags = "--cfg fuzzing" diff --git a/fuel-vm/fuzz/Cargo.toml b/fuel-vm/fuzz/Cargo.toml index 83dca5fc45..29a1cf935b 100644 --- a/fuel-vm/fuzz/Cargo.toml +++ b/fuel-vm/fuzz/Cargo.toml @@ -3,22 +3,41 @@ name = "fuel-vm-fuzz" version = "0.0.0" authors = ["Automatically generated"] publish = false -edition = "2018" +edition = "2021" [package.metadata] cargo-fuzz = true [dependencies] -arbitrary = { version = "1.0", features = ["derive"] } -fuel-vm = { path = "..", features = ["arbitrary"] } -libfuzzer-sys = "0.4" +fuel-vm = { path = "..", features = ["test-helpers"] } +clap = { version = "4.0", features = ["derive"] } +hex = "*" + +[features] +default = ["libfuzzer"] +libfuzzer = ["libfuzzer-sys"] +libafl = ["libafl_libfuzzer"] + +[dependencies.libfuzzer-sys] +version = "0.4" +optional = true + +[dependencies.libafl_libfuzzer] +version = "0.13" +optional = true # Prevent this from interfering with workspaces as this crate requires unstable features. [workspace] members = ["."] +[profile.release] +panic = 'abort' + +[profile.dev] +panic = 'abort' + [[bin]] -name = "grammar_aware" -path = "fuzz_targets/grammar_aware.rs" +name = "grammar_aware_advanced" +path = "fuzz_targets/grammar_aware_advanced.rs" test = false doc = false diff --git a/fuel-vm/fuzz/README.md b/fuel-vm/fuzz/README.md new file mode 100644 index 0000000000..b0ffad2bad --- /dev/null +++ b/fuel-vm/fuzz/README.md @@ -0,0 +1,99 @@ +# Fuzz test for the Fuel VM +This crate provides the `grammar_aware_advanced` fuzz target which can be run with `cargo fuzz` to fuzz test the Fuel VM. + +General information about fuzzing Rust can be found on [appsec.guide](https://appsec.guide/docs/fuzzing/rust/cargo-fuzz/). + +### Installation +The fuzzer requires nightly rust and works with rustc version `1.82.0-nightly`. To be able to run the fuzzer, the following tools must be installed. + +Install: +``` +cargo install cargo-fuzz +apt install clang pkg-config libssl-dev # for LibAFL +rustup component add llvm-tools-preview --toolchain nightly +``` + +### Seeds + +The input to the fuzzer is a byte vector that contains script assembly, script data, and the assembly of a contract to be called. Each of these is separated by a 64-bit magic value `0x00ADBEEF5566CEAA`. + +While the fuzzer can be started without any seeds, it is recommended to generate seeds from compiled sway programs. + +#### Generate your own seeds + +If you want to run the fuzzer with custom input, you can run the `seed` binary against a directory of compiled sway programs. + +``` +cargo run --bin seed +``` + +#### Example: Generating a corpus from the sway examples +This section explains how to use the [sway examples](https://github.com/FuelLabs/sway/tree/master/examples) to generate an initial corpus. + +This can be acieved by doing the following: + +1. Compile the sway examples with `forc`. +``` +# In sway/examples +forc build +``` + +2. Gather all the resulting binaries in a temporary directory (for example `/tmp/corpus`). +``` +# In sway/examples +for file in $(find . -name "*.bin" | rg debug); do cp $file /tmp/corpus; done +``` + +3. Run the `seed binary` against the generated binaries +``` +# In fuel-vm/fuel-vm/fuzz +mkdir generated_seeds +cargo run --bin seed /tmp/corpus ./generated_seeds +``` + +Now the directory `./generated_seeds` contains the newly generated seeds. Copy this over to `corpus/grammar_aware_advanced` to run the fuzzer with these seeds. + +### Running the Fuzzer +The Rust nightly version is required for executing cargo-fuzz. The simplest way to run the fuzzer is to run the following command: +``` +cargo +nightly fuzz run grammar_aware_advanced +``` + +However, we recommend adding a few flags to the command to improve fuzzing efficiency. First, we can add `--no-default-features --features libafl` to ensure we use the LibAFL fuzzer instead of the default libFuzzer. Secondly, we can set `--sanitizer none` to disable AddressSanitizer for a significant speed improvement, as we do not expect memory issues in a Rust program that does not use a significant amount of unsafe code. This has been confirmed by a ToB [cargo-geiger](https://github.com/rust-secure-code/cargo-geiger) analysis showed. It makes sense to leave AddressSanitizer turned on if we use more unsafe Rust in the future (either directly or through dependencies). Finally, the `-ignore_crashes=1 -ignore_timeouts=1 -ignore_ooms=1 -fork=7` flags are useful to ensure a smooth LibAFL experience utilizing 7 cores. + +Putting this together we arrive at the following command. +``` +cargo +nightly fuzz run --no-default-features --features libafl --sanitizer none grammar_aware_advanced -- -ignore_crashes=1 -ignore_timeouts=1 -ignore_ooms=1 -fork=7 +``` + +### Generate Coverage +It is important to measure a fuzzing campaign’s coverage after its run. To perform this measurement, we can use tools provided by cargo-fuzz and [rustc](https://doc.rust-lang.org/stable/rustc/instrument-coverage.html). First, install [cargo-binutils](https://github.com/rust-embedded/cargo-binutils#installation). After that, execute the following command: +``` +cargo +nightly fuzz coverage grammar_aware_advanced corpus/grammar_aware_advanced +``` + +The code coverage report can now be displayed with the following command: + +``` +cargo cov -- report target/x86_64-unknown-linux-gnu/coverage/x86_64-unknown-linux-gnu/release/grammar_aware_advanced -instr-profile=coverage/grammar_aware_advanced/coverage.profdata +``` + +We can also generate a HTML visualization of the code coverage using the following command: + +``` +cargo cov -- show target/x86_64-unknown-linux-gnu/coverage/x86_64-unknown-linux-gnu/release/grammar_aware_advanced --format=html -instr-profile=coverage/grammar_aware_advanced/coverage.profdata $(pwd | sed "s/fuel-vm\/fuzz//") > index.html +``` + +### Execute a Test Case +The fuzzing campain will output any crashes to `artifacts/grammar_aware_advanced`. To further investigate these crashes, the `execute` binary can be used. +``` +cargo run --bin execute artifacts/grammar_aware_advanced/ +``` + +This is useful for triaging issues. + +### Collect Gas Statistics +The `collect` binary writes gas statistics to a file called gas_statistics.csv. This can be used to analyze the execution time versus gas usage on a test corpus. +``` +cargo run --bin collect +``` diff --git a/fuel-vm/fuzz/fuzz_targets/grammar_aware.rs b/fuel-vm/fuzz/fuzz_targets/grammar_aware.rs deleted file mode 100644 index 3d2aa1779e..0000000000 --- a/fuel-vm/fuzz/fuzz_targets/grammar_aware.rs +++ /dev/null @@ -1,39 +0,0 @@ -#![feature(bench_black_box)] -#![no_main] - -use std::hint::black_box; - -use libfuzzer_sys::fuzz_target; - -use fuel_vm::prelude::*; - -#[derive(arbitrary::Arbitrary, Debug)] -struct FuzzData { - program: Vec, - script_data: Vec, -} - -fuzz_target!(|data: FuzzData| { - let mut client = MemoryClient::default(); - - let gas_price = 0; - let gas_limit = 1_000; - let maturity = Default::default(); - let height = Default::default(); - let params = ConsensusParameters::DEFAULT; - - let tx = Transaction::script( - gas_price, - gas_limit, - maturity, - data.program.iter().copied().collect(), - data.script_data, - vec![], - vec![], - vec![], - ) - .into_checked(height, ¶ms) - .expect("failed to generate a checked tx"); - - drop(black_box(client.transact(tx))); -}); diff --git a/fuel-vm/fuzz/fuzz_targets/grammar_aware_advanced.rs b/fuel-vm/fuzz/fuzz_targets/grammar_aware_advanced.rs new file mode 100644 index 0000000000..505372c23f --- /dev/null +++ b/fuel-vm/fuzz/fuzz_targets/grammar_aware_advanced.rs @@ -0,0 +1,13 @@ +#![no_main] + +#[cfg(feature = "libafl")] +extern crate libafl_libfuzzer as libfuzzer_sys; + +use fuel_vm_fuzz::{decode, execute}; +use libfuzzer_sys::fuzz_target; + +fuzz_target!(|data: &[u8]| { + if let Some(data) = decode(data) { + execute(data); + } +}); diff --git a/fuel-vm/fuzz/src/bin/collect.rs b/fuel-vm/fuzz/src/bin/collect.rs new file mode 100644 index 0000000000..27d251e095 --- /dev/null +++ b/fuel-vm/fuzz/src/bin/collect.rs @@ -0,0 +1,39 @@ +use std::fs::File; +use fuel_vm_fuzz::execute; +use fuel_vm_fuzz::decode; +use std::fs; +use std::io::Write; +use std::path::Path; +use std::time::Instant; + +fn main() { + let path = std::env::args().nth(1).expect("no path given"); + let mut file = File::create("gas_statistics.csv").expect("couldn't create a file to write gas statistics to"); + + write!(file, "name\tgas\ttime_ms\n").unwrap(); + + if Path::new(&path).is_file() { + eprintln!("Pass directory") + } else { + let paths = fs::read_dir(path).expect("unable to read dir"); + + for path in paths { + let entry = path.expect("unable to yield entry"); + let data = std::fs::read(entry.path()).expect("unable to read path {}"); + let name = entry.file_name(); + let name = name.to_str().expect("failed to read file name as string"); + println!("{:?}", name); + + let Some(data) = decode(&data) else { eprintln!("unable to decode"); continue; }; + + let now = Instant::now(); + let result = execute(data); + let gas = result.gas_used; + + write!(file, "{name}\t{gas}\t{}\n", now.elapsed().as_millis()).expect("unable to write to gas statistics file"); + if result.success { + println!("{:?}:{}", name, result.success); + } + } + } +} diff --git a/fuel-vm/fuzz/src/bin/execute.rs b/fuel-vm/fuzz/src/bin/execute.rs new file mode 100644 index 0000000000..c89f93300d --- /dev/null +++ b/fuel-vm/fuzz/src/bin/execute.rs @@ -0,0 +1,34 @@ +use fuel_vm_fuzz::execute; +use fuel_vm_fuzz::decode; +use std::path::Path; + +fn main() { + let path = std::env::args().nth(1).expect("no path given"); + + if Path::new(&path).is_file() { + let data = std::fs::read(&path).expect("failed to read file"); + + let data = decode(&data).expect("failed to decode data"); + + let result = execute(data); + if result.success { + println!("{:?}:{}", path, result.success); + } + } else { + let paths = std::fs::read_dir(path).expect("failed to read dir"); + + for path in paths { + let entry = path.expect("failed to yield directory entry"); + println!("{:?}", entry.file_name()); + + let data = std::fs::read(entry.path()).expect("failed to read file"); + + let data = decode(&data).expect("failed to decode"); + + let result = execute(data); + if result.success { + println!("{:?}:{}", entry.file_name(), result.success); + } + } + } +} diff --git a/fuel-vm/fuzz/src/bin/seed.rs b/fuel-vm/fuzz/src/bin/seed.rs new file mode 100644 index 0000000000..cc9c6f8c72 --- /dev/null +++ b/fuel-vm/fuzz/src/bin/seed.rs @@ -0,0 +1,36 @@ +//! See README.md for usage example + +use fuel_vm_fuzz::FuzzData; +use fuel_vm_fuzz::{decode, encode}; +use std::fs; +use std::path::PathBuf; + +fn main() { + let input = std::env::args().nth(1).expect("no input path given"); + let output = std::env::args().nth(2).expect("no output path given"); + let paths = fs::read_dir(input).expect("failed to read directory"); + + for path in paths { + let entry = path.unwrap(); + let program = std::fs::read(entry.path()).expect("failed to read file"); + + println!("{:?}", entry.file_name().to_str().expect("faile to convert to string")); + + let data = FuzzData { + program, + sub_program: vec![], + script_data: vec![], + }; + + let encoded = encode(&data); + let decoded = decode(&encoded).expect("failed to decode"); + + if decoded != data { + println!("{:?}", data); + println!("{:?}", decoded); + panic!("mismatch") + } + + fs::write(PathBuf::from(&output).join(entry.file_name()), &encoded).expect("failed to write file"); + } +} diff --git a/fuel-vm/fuzz/src/lib.rs b/fuel-vm/fuzz/src/lib.rs new file mode 100644 index 0000000000..ee8fdea3f5 --- /dev/null +++ b/fuel-vm/fuzz/src/lib.rs @@ -0,0 +1,155 @@ +use fuel_vm::fuel_asm::op; +use fuel_vm::fuel_asm::{Instruction, InvalidOpcode}; +use fuel_vm::fuel_types::Word; +use fuel_vm::prelude::field::Script; +use fuel_vm::prelude::*; + +use fuel_vm::util::test_helpers::TestBuilder; +use fuel_vm::{fuel_asm, script_with_data_offset}; +use fuel_vm::fuel_types::canonical::Serialize; +use std::ops::Range; + +/// Magic value used as separator between fuzz data components in corpus files. +const MAGIC_VALUE_SEPARATOR: [u8; 8] = [0x00u8, 0xAD, 0xBE, 0xEF, 0x55, 0x66, 0xCE, 0xAA]; + +#[derive(Debug, Eq, PartialEq)] +pub struct FuzzData { + pub program: Vec, + pub sub_program: Vec, + pub script_data: Vec, +} + +pub fn encode(data: &FuzzData) -> Vec { + let separator: Vec = MAGIC_VALUE_SEPARATOR.into(); + data.program.iter() + .copied() + .chain(separator.iter().copied()) + .chain(data.script_data.iter().copied()) + .chain(separator.iter().copied()) + .chain(data.sub_program.iter().copied()) + .collect() +} + +fn split_by_separator(data: &[u8], separator: &[u8]) -> Vec> { + let separator_len = separator.len(); + + let mut last: usize = 0; + + let mut result: Vec<_> = data + .windows(separator_len) + .enumerate() + .filter_map(|(i, window)| { + if window == separator { + let option = Some(last..i); + last = i + separator_len; + return option; + } else { + None + } + }) + .collect(); + + result.push(last..data.len()); + + result +} + +pub fn decode(data: &[u8]) -> Option { + let x = split_by_separator(data, &MAGIC_VALUE_SEPARATOR); + if x.len() != 3 { + return None; + } + Some(FuzzData { + program: data[x[0].clone()].to_vec(), + script_data: data[x[1].clone()].to_vec(), + sub_program: data[x[2].clone()].to_vec(), + }) +} +pub fn decode_instructions(bytes: &[u8]) -> Option> { + let instructions: Vec<_> = fuel_vm::fuel_asm::from_bytes(bytes.iter().cloned()) + .flat_map(|i: Result| i.ok()) + .collect(); + return Some(instructions); +} + +pub struct ExecuteResult { + pub success: bool, + pub gas_used: u64, +} + +pub fn execute(data: FuzzData) -> ExecuteResult { + let gas_limit = 1_000_000; + let asset_id: AssetId = AssetId::new([ + 0xFA, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, + 1, 1, 1, 1, 1, 1, + ]); + + let mut test_context = TestBuilder::new(2322u64); + let subcontract: Vec<_> = [ + // Pass some data to fuzzer + op::addi( + 0x10, + fuel_asm::RegId::FP, + CallFrame::b_offset() as Immediate12, + ), + fuel_vm::fuel_asm::op::lw(0x10, 0x10, 0), // load address word + ] + .iter() + .copied() + .flat_map(Instruction::to_bytes) + .chain(data.sub_program.iter().copied()) + .collect::>(); + + let contract_id = test_context + .setup_contract_bytes(subcontract.clone(), None, None) + .contract_id; + + let max_program_length = 2usize.pow(18) + - test_context.get_tx_params().tx_offset() + - ::script_offset_static(); + + let actual_program = &data.program[..max_program_length.min(data.program.len())]; + + let (script_ops, script_data_offset): (Vec, Immediate18) = script_with_data_offset!( + script_data_offset, + actual_program.to_vec(), + test_context.get_tx_params().tx_offset() + ); + + let call = Call::new(contract_id, 0, script_data_offset as Word).to_bytes(); + let script_data: [&[u8]; 2] = [asset_id.as_ref(), call.as_slice()]; + + // Provide an asset id and the contract_id + let script_data: Vec = script_data.iter().copied().flatten().copied().collect(); + + let script_data = script_data + .iter() + .chain(data.script_data.iter()) + .copied() + .collect::>(); + + let transfer_tx = test_context + .start_script_bytes(script_ops.iter().copied().collect(), script_data) + .script_gas_limit(gas_limit) + .gas_price(0) + .coin_input(asset_id, 1000) + .contract_input(contract_id) + .contract_output(&contract_id) + .change_output(asset_id) + .execute(); + + let gas_used: u64 = *transfer_tx + .receipts() + .iter() + .filter_map(|recipt| match recipt { + Receipt::ScriptResult { gas_used, .. } => Some(gas_used), + _ => None, + }) + .next() + .unwrap(); + + ExecuteResult { + success: !transfer_tx.should_revert(), + gas_used, + } +} diff --git a/fuel-vm/src/interpreter/diff/tests.rs b/fuel-vm/src/interpreter/diff/tests.rs index 003bb7ffc4..599fd9c9cd 100644 --- a/fuel-vm/src/interpreter/diff/tests.rs +++ b/fuel-vm/src/interpreter/diff/tests.rs @@ -49,7 +49,7 @@ use crate::interpreter::InterpreterParams; fn record_and_invert_storage() { let arb_gas_price = 1; let interpreter_params = - InterpreterParams::new(arb_gas_price, &ConsensusParameters::standard()); + InterpreterParams::new(arb_gas_price, ConsensusParameters::standard()); let a = Interpreter::<_, _, Script>::with_storage( crate::interpreter::MemoryInstance::new(), diff --git a/fuel-vm/src/util.rs b/fuel-vm/src/util.rs index 841f1c42e2..041756ddcc 100644 --- a/fuel-vm/src/util.rs +++ b/fuel-vm/src/util.rs @@ -211,13 +211,29 @@ pub mod test_helpers { self.block_height } + pub fn start_script_bytes( + &mut self, + script: Vec, + script_data: Vec, + ) -> &mut Self { + self.start_script_inner(script, script_data) + } + pub fn start_script( &mut self, script: Vec, script_data: Vec, ) -> &mut Self { - let bytecode = script.into_iter().collect(); - self.builder = TransactionBuilder::script(bytecode, script_data); + let script = script.into_iter().collect(); + self.start_script_inner(script, script_data) + } + + fn start_script_inner( + &mut self, + script: Vec, + script_data: Vec, + ) -> &mut Self { + self.builder = TransactionBuilder::script(script, script_data); self.builder.script_gas_limit(self.script_gas_limit); self } @@ -430,20 +446,36 @@ pub mod test_helpers { .build() } + pub fn setup_contract_bytes( + &mut self, + contract: Vec, + initial_balance: Option<(AssetId, Word)>, + initial_state: Option>, + ) -> CreatedContract { + self.setup_contract_inner(contract, initial_balance, initial_state) + } + pub fn setup_contract( &mut self, contract: Vec, initial_balance: Option<(AssetId, Word)>, initial_state: Option>, + ) -> CreatedContract { + let contract = contract.into_iter().collect(); + + self.setup_contract_inner(contract, initial_balance, initial_state) + } + + fn setup_contract_inner( + &mut self, + contract: Vec, + initial_balance: Option<(AssetId, Word)>, + initial_state: Option>, ) -> CreatedContract { let storage_slots = initial_state.unwrap_or_default(); let salt: Salt = self.rng.gen(); - let program: Witness = contract - .into_iter() - .flat_map(Instruction::to_bytes) - .collect::>() - .into(); + let program: Witness = contract.into(); let storage_root = Contract::initial_state_root(storage_slots.iter()); let contract = Contract::from(program.as_ref()); let contract_root = contract.root();