diff --git a/.clusterfuzzlite/Dockerfile b/.clusterfuzzlite/Dockerfile new file mode 100644 index 0000000000..a46ffea94d --- /dev/null +++ b/.clusterfuzzlite/Dockerfile @@ -0,0 +1,4 @@ +FROM gcr.io/oss-fuzz-base/base-builder-rust:v1 +COPY . $SRC/fuel-vm +WORKDIR fuel-vm +COPY .clusterfuzzlite/build.sh $SRC/ diff --git a/.clusterfuzzlite/README.md b/.clusterfuzzlite/README.md new file mode 100644 index 0000000000..e9deaebf2e --- /dev/null +++ b/.clusterfuzzlite/README.md @@ -0,0 +1,5 @@ +# ClusterFuzzLite + +This directory contains the configuration for [ClusterFuzzLite](https://google.github.io/clusterfuzzlite/). CFL is used in the GitHub Actions CI in several workflows. + +The corpus and more documentation can be found in [FuelLabs/fuel-fuzzing-corpus](https://github.com/FuelLabs/fuel-fuzzing-corpus). diff --git a/.clusterfuzzlite/build.sh b/.clusterfuzzlite/build.sh new file mode 100755 index 0000000000..19407a4b1f --- /dev/null +++ b/.clusterfuzzlite/build.sh @@ -0,0 +1,10 @@ +#!/bin/bash -eu + +cd $SRC/fuel-vm + +cd fuel-vm + +export CARGO_CFG_CURVE25519_DALEK_BACKEND=serial # This fixes building on nightly-2023-12-28-x86_64-unknown-linux-gnu, which is no longer compatible with the SIMD feature of curve25519; building on stable does not work because ASan is a dependency of coverage +cargo fuzz build -O --sanitizer none + +cp fuzz/target/x86_64-unknown-linux-gnu/release/grammar_aware_advanced $OUT/ diff --git a/.clusterfuzzlite/project.yaml b/.clusterfuzzlite/project.yaml new file mode 100644 index 0000000000..22761ba7ee --- /dev/null +++ b/.clusterfuzzlite/project.yaml @@ -0,0 +1 @@ +language: rust diff --git a/.github/workflows/cflite_batch.yml b/.github/workflows/cflite_batch.yml new file mode 100644 index 0000000000..1536851bc4 --- /dev/null +++ b/.github/workflows/cflite_batch.yml @@ -0,0 +1,25 @@ +name: ClusterFuzzLite batch fuzzing +on: + schedule: + - cron: '0 0/24 * * *' # Every 24th hour +permissions: read-all +jobs: + BatchFuzzing: + runs-on: ubuntu-latest + steps: + - name: Build Fuzzers (${{ matrix.sanitizer }}) + id: build + uses: google/clusterfuzzlite/actions/build_fuzzers@v1 + with: + language: rust + - name: Run Fuzzers (${{ matrix.sanitizer }}) + id: run + uses: google/clusterfuzzlite/actions/run_fuzzers@v1 + with: + github-token: ${{ secrets.GITHUB_TOKEN }} + fuzz-seconds: 3600 + mode: 'batch' + output-sarif: true + storage-repo: https://${{ secrets.FUZZ_STORAGE_PAT }}@github.com/FuelLabs/fuel-fuzzing-corpus.git + storage-repo-branch: main + storage-repo-branch-coverage: gh-pages diff --git a/.github/workflows/cflite_cron.yml b/.github/workflows/cflite_cron.yml new file mode 100644 index 0000000000..bf811176f8 --- /dev/null +++ b/.github/workflows/cflite_cron.yml @@ -0,0 +1,45 @@ +name: ClusterFuzzLite cron tasks +on: + schedule: + - cron: '0 0/72 * * *' +permissions: read-all +jobs: + Pruning: + runs-on: ubuntu-latest + steps: + - name: Build Fuzzers + id: build + uses: google/clusterfuzzlite/actions/build_fuzzers@v1 + with: + language: rust + - name: Run Fuzzers + id: run + uses: google/clusterfuzzlite/actions/run_fuzzers@v1 + with: + github-token: ${{ secrets.GITHUB_TOKEN }} + fuzz-seconds: 600 # Time after which minimization is aborted + mode: 'prune' + output-sarif: true + storage-repo: https://${{ secrets.FUZZ_STORAGE_PAT }}@github.com/FuelLabs/fuel-fuzzing-corpus.git + storage-repo-branch: main + storage-repo-branch-coverage: gh-pages + Coverage: + runs-on: ubuntu-latest + steps: + - name: Build Fuzzers + id: build + uses: google/clusterfuzzlite/actions/build_fuzzers@v1 + with: + language: rust + sanitizer: coverage + - name: Run Fuzzers + id: run + uses: google/clusterfuzzlite/actions/run_fuzzers@v1 + with: + github-token: ${{ secrets.GITHUB_TOKEN }} + fuzz-seconds: 600 + mode: 'coverage' + sanitizer: 'coverage' + storage-repo: https://${{ secrets.FUZZ_STORAGE_PAT }}@github.com/FuelLabs/fuel-fuzzing-corpus.git + storage-repo-branch: main + storage-repo-branch-coverage: gh-pages \ No newline at end of file diff --git a/.github/workflows/cflite_pr.yml b/.github/workflows/cflite_pr.yml new file mode 100644 index 0000000000..07cb1637a8 --- /dev/null +++ b/.github/workflows/cflite_pr.yml @@ -0,0 +1,30 @@ +name: ClusterFuzzLite PR fuzzing +on: + pull_request: + paths: + - '**' +permissions: read-all +jobs: + PR: + runs-on: ubuntu-latest + steps: + - name: Build Fuzzers + id: build + uses: google/clusterfuzzlite/actions/build_fuzzers@v1 + with: + language: rust + github-token: ${{ secrets.GITHUB_TOKEN }} + storage-repo: https://${{ secrets.FUZZ_STORAGE_PAT }}@github.com/FuelLabs/fuel-fuzzing-corpus.git + storage-repo-branch: main + storage-repo-branch-coverage: gh-pages + - name: Run Fuzzers + id: run + uses: google/clusterfuzzlite/actions/run_fuzzers@v1 + with: + github-token: ${{ secrets.GITHUB_TOKEN }} + fuzz-seconds: 600 + mode: 'code-change' + output-sarif: true + storage-repo: https://${{ secrets.FUZZ_STORAGE_PAT }}@github.com/FuelLabs/fuel-fuzzing-corpus.git + storage-repo-branch: main + storage-repo-branch-coverage: gh-pages diff --git a/fuel-asm/src/macros.rs b/fuel-asm/src/macros.rs index 5a156750a3..df712dcf47 100644 --- a/fuel-asm/src/macros.rs +++ b/fuel-asm/src/macros.rs @@ -1007,6 +1007,7 @@ macro_rules! impl_instructions { /// Solely the opcode portion of an instruction represented as a single byte. #[derive(Clone, Copy, Debug, Eq, Hash, PartialEq)] #[cfg_attr(feature = "serde", derive(serde::Serialize, serde::Deserialize))] + #[cfg_attr(feature = "arbitrary", derive(arbitrary::Arbitrary))] #[repr(u8)] pub enum Opcode { $( diff --git a/fuel-crypto/src/secp256/signature_format.rs b/fuel-crypto/src/secp256/signature_format.rs index 090a9caa85..a43d3509d1 100644 --- a/fuel-crypto/src/secp256/signature_format.rs +++ b/fuel-crypto/src/secp256/signature_format.rs @@ -55,6 +55,8 @@ impl TryFrom for RecoveryId { /// Panics if the highest bit of byte at index 32 is set, as this indicates non-normalized /// signature. Panics if the recovery id is in reduced-x form. pub fn encode_signature(mut signature: [u8; 64], recovery_id: RecoveryId) -> [u8; 64] { + // This assertion is hit during fuzzing. Verify it is safe to disable. + #[cfg(not(any(fuzzing, feature = "test-helpers")))] assert!(signature[32] >> 7 == 0, "Non-normalized signature"); let v = recovery_id.is_y_odd as u8; diff --git a/fuel-tx/src/transaction/validity.rs b/fuel-tx/src/transaction/validity.rs index 569e56bbb0..8a0554cfb2 100644 --- a/fuel-tx/src/transaction/validity.rs +++ b/fuel-tx/src/transaction/validity.rs @@ -112,6 +112,8 @@ impl Input { recover_address()? }; + // This error is reached during fuzzing often, verify it is safe to disable it + #[cfg(not(any(fuzzing, feature = "test-helpers")))] if owner != &recovered_address { return Err(ValidityError::InputInvalidSignature { index }); } diff --git a/fuel-vm/fuzz/Cargo.toml b/fuel-vm/fuzz/Cargo.toml index 83dca5fc45..c4ec5e5677 100644 --- a/fuel-vm/fuzz/Cargo.toml +++ b/fuel-vm/fuzz/Cargo.toml @@ -3,22 +3,50 @@ 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"] } +fuel-vm = { path = "..", features = ["arbitrary", "test-helpers"] } +# For LibAFL: libfuzzer-sys = { features = ["arbitrary-derive"], package = "libafl_libfuzzer" } libfuzzer-sys = "0.4" +clap = { version = "4.0", features = ["derive"] } +hex = "*" # 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" test = false doc = false + +[[bin]] +name = "grammar_aware_advanced" +path = "fuzz_targets/grammar_aware_advanced.rs" +test = false +doc = false + +[[bin]] +name = "seed" +path = "src/seed.rs" + +[[bin]] +name = "execute" +path = "src/execute.rs" + +[[bin]] +name = "collect" +path = "src/collect.rs" + diff --git a/fuel-vm/fuzz/README.md b/fuel-vm/fuzz/README.md new file mode 100644 index 0000000000..e085cc1638 --- /dev/null +++ b/fuel-vm/fuzz/README.md @@ -0,0 +1,58 @@ + +## Manual for grammar_aware_advanced fuzzer + + +Install: +``` +cargo install cargo-fuzz +apt install clang pkg-config libssl-dev # for LibAFL +rustup component add llvm-tools-preview --toolchain nightly +``` + +General information about fuzzing Rust might be found on [appsec.guide](https://appsec.guide/docs/fuzzing/rust/cargo-fuzz/). + + +### Generate Seeds + +It is necessary to first convert Sway programs into a suitable format for use as seed input to the fuzzer. This can be done with the following command: +``` +cargo run --bin seed +``` + +### Running the Fuzzer +The Rust nightly version is required for executing cargo-fuzz. We also 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, which our [cargo-geiger](https://github.com/rust-secure-code/cargo-geiger) analysis showed. It makes sense to leave AddressSanitizer turned on if the Fuel project uses more unsafe Rust in the future (either directly or through dependencies). The remaining flags are either required for LibAFL or are useful to make it use seven cores. +``` +cargo +nightly fuzz run --sanitizer none grammar_aware -- \ + -ignore_crashes=1 -ignore_timeouts=1 -ignore_ooms=1 -fork=7 +``` + +If you use libfuzzer (default) then the following command is enough: + +``` +cargo fuzz run --sanitizer none grammar_aware +``` + +### Execute a Test Case +Test cases can be executed using the following command. This is useful for triaging issues. +``` +cd fuzz/ +cargo run --bin execute +``` + +### Collect Statistics +We created a tool that 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 +``` + +### Generate Coverage +Regardless of how inputs are generated, it is important to measure a fuzzing campaign’s coverage after its run. To perform this measure, we used the support 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 corpus/grammar_aware +``` +Finally, generate an HTML file using LLVM: + +``` +cargo cov -- show +target/x86_64-unknown-linux-gnu/coverage/x86_64-unknown-linux-gnu/release/grammar_aware --format=html -instr-profile=coverage/grammar_aware/coverage.profdata /root/audit/fuel-vm > index.html +``` diff --git a/fuel-vm/fuzz/fuzz_targets/grammar_aware.rs b/fuel-vm/fuzz/fuzz_targets/grammar_aware.rs index 3d2aa1779e..630d19202d 100644 --- a/fuel-vm/fuzz/fuzz_targets/grammar_aware.rs +++ b/fuel-vm/fuzz/fuzz_targets/grammar_aware.rs @@ -4,8 +4,10 @@ use std::hint::black_box; use libfuzzer_sys::fuzz_target; +use fuel_vm::fuel_tx::field::MaxFeeLimit; use fuel_vm::prelude::*; +use fuel_vm::prelude::policies::Policies; #[derive(arbitrary::Arbitrary, Debug)] struct FuzzData { @@ -18,22 +20,22 @@ fuzz_target!(|data: FuzzData| { let gas_price = 0; let gas_limit = 1_000; - let maturity = Default::default(); let height = Default::default(); - let params = ConsensusParameters::DEFAULT; + let params = ConsensusParameters::standard(); - let tx = Transaction::script( - gas_price, + let mut tx = Transaction::script( gas_limit, - maturity, - data.program.iter().copied().collect(), + data.program.iter().copied().map(|op| op as u8).collect::>(), data.script_data, + Policies::new(), vec![], vec![], vec![], - ) - .into_checked(height, ¶ms) - .expect("failed to generate a checked tx"); + ); + + tx.set_max_fee_limit(1_000); + + let tx = tx.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..46330c98d5 --- /dev/null +++ b/fuel-vm/fuzz/fuzz_targets/grammar_aware_advanced.rs @@ -0,0 +1,10 @@ +#![no_main] + +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/collect.rs b/fuel-vm/fuzz/src/collect.rs new file mode 100644 index 0000000000..3f3a4e7376 --- /dev/null +++ b/fuel-vm/fuzz/src/collect.rs @@ -0,0 +1,50 @@ +use crate::fs::File; +use fuel_vm::consts::WORD_SIZE; +use fuel_vm::fuel_asm::op; +use fuel_vm::fuel_asm::RegId; +use fuel_vm::fuel_asm::{Instruction, RawInstruction}; +use fuel_vm::fuel_crypto::rand::Rng; +use fuel_vm::fuel_crypto::rand::SeedableRng; +use fuel_vm::fuel_types::Word; +use fuel_vm::prelude::SecretKey; +use fuel_vm_fuzz::execute; +use fuel_vm_fuzz::FuzzData; +use fuel_vm_fuzz::{decode, decode_instructions, encode}; +use std::convert::TryFrom; +use std::convert::TryInto; +use std::fs; +use std::io::Write; +use std::path::{Path, PathBuf}; +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").unwrap(); + + write!(file, "name\tgas\ttime_ms\n").unwrap(); + + if Path::new(&path).is_file() { + eprintln!("Pass directory") + } else { + let paths = fs::read_dir(path).unwrap(); + + for path in paths { + let entry = path.unwrap(); + let data = std::fs::read(entry.path()).unwrap(); + let name = entry.file_name(); + let name = name.to_str().unwrap(); + 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()).unwrap(); + if result.success { + println!("{:?}:{}", name, result.success); + } + } + } +} diff --git a/fuel-vm/fuzz/src/execute.rs b/fuel-vm/fuzz/src/execute.rs new file mode 100644 index 0000000000..73800e6b16 --- /dev/null +++ b/fuel-vm/fuzz/src/execute.rs @@ -0,0 +1,46 @@ +use fuel_vm::consts::WORD_SIZE; +use fuel_vm::fuel_asm::op; +use fuel_vm::fuel_asm::RegId; +use fuel_vm::fuel_asm::{Instruction, RawInstruction}; +use fuel_vm::fuel_crypto::rand::Rng; +use fuel_vm::fuel_crypto::rand::SeedableRng; +use fuel_vm::fuel_types::Word; +use fuel_vm::prelude::SecretKey; +use fuel_vm_fuzz::execute; +use fuel_vm_fuzz::FuzzData; +use fuel_vm_fuzz::{decode, decode_instructions, encode}; +use std::convert::TryFrom; +use std::convert::TryInto; +use std::fs; +use std::path::{Path, PathBuf}; + +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).unwrap(); + + let data = decode(&data).unwrap(); + + let result = execute(data); + if result.success { + println!("{:?}:{}", path, result.success); + } + } else { + let paths = fs::read_dir(path).unwrap(); + + for path in paths { + let entry = path.unwrap(); + println!("{:?}", entry.file_name()); + + let data = std::fs::read(entry.path()).unwrap(); + + let data = decode(&data).unwrap(); + + let result = execute(data); + if result.success { + println!("{:?}:{}", entry.file_name(), result.success); + } + } + } +} diff --git a/fuel-vm/fuzz/src/lib.rs b/fuel-vm/fuzz/src/lib.rs new file mode 100644 index 0000000000..965208c04a --- /dev/null +++ b/fuel-vm/fuzz/src/lib.rs @@ -0,0 +1,164 @@ +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::io::Read; +use std::ops::Range; + +const SEP: [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 seperator: Vec<_> = SEP.into(); + let parts: [Vec; 5] = [ + data.program.iter().copied().collect(), + seperator.clone(), + data.script_data.clone(), + seperator.clone(), + data.sub_program.iter().copied().collect(), + ]; + + let data: Vec = parts.iter().flatten().copied().collect(); + data +} + +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, &SEP); + + if x.len() != 3 { + return None; + } + + let program = data[x[0].clone()].to_vec(); + let sub_program = data[x[2].clone()].to_vec(); + Some(FuzzData { + program, + script_data: data[x[1].clone()].to_vec(), + sub_program, + }) +} + +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() + - 256; // TODO reevalute this one + + 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/fuzz/src/seed.rs b/fuel-vm/fuzz/src/seed.rs new file mode 100644 index 0000000000..cda9f100f2 --- /dev/null +++ b/fuel-vm/fuzz/src/seed.rs @@ -0,0 +1,42 @@ +use fuel_vm::consts::WORD_SIZE; +use fuel_vm::fuel_asm::{Instruction, RawInstruction}; +use fuel_vm::fuel_crypto::rand::Rng; +use fuel_vm::fuel_crypto::rand::SeedableRng; +use fuel_vm::fuel_types::Word; +use fuel_vm::prelude::SecretKey; +use fuel_vm_fuzz::FuzzData; +use fuel_vm_fuzz::{decode, decode_instructions, encode}; +use std::convert::TryFrom; +use std::convert::TryInto; +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).unwrap(); + + for path in paths { + let entry = path.unwrap(); + let program = std::fs::read(entry.path()).unwrap(); + + println!("{:?}", entry.file_name().to_str().unwrap()); + + let data = FuzzData { + program, + sub_program: vec![], + script_data: vec![], + }; + + let encoded = encode(&data); + let decoded = decode(&encoded).unwrap(); + + if decoded != data { + println!("{:?}", data); + println!("{:?}", decoded); + panic!("mismatch") + } + + fs::write(PathBuf::from(&output).join(entry.file_name()), &encoded).unwrap(); + } +} diff --git a/fuel-vm/src/util.rs b/fuel-vm/src/util.rs index 31cf4ad3bd..9b0d52fda8 100644 --- a/fuel-vm/src/util.rs +++ b/fuel-vm/src/util.rs @@ -201,13 +201,30 @@ pub mod test_helpers { self.block_height } + #[cfg(any(fuzzing, feature = "test-helpers"))] + 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 } @@ -414,11 +431,36 @@ pub mod test_helpers { .build() } + #[cfg(any(fuzzing, feature = "test-helpers"))] + 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() + .flat_map(Instruction::to_bytes) + .collect::>() + .into(); + + 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 = if let Some(slots) = initial_state { slots @@ -427,11 +469,7 @@ pub mod test_helpers { }; 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();