Skip to content

Commit

Permalink
feat: disable fuzz fixtures in Solidity
Browse files Browse the repository at this point in the history
  • Loading branch information
agostbiro committed Sep 11, 2024
1 parent 0d676f9 commit af1f8ca
Show file tree
Hide file tree
Showing 9 changed files with 115 additions and 40 deletions.
1 change: 1 addition & 0 deletions Cargo.lock

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

2 changes: 2 additions & 0 deletions crates/edr_napi/src/solidity_tests/config.rs
Original file line number Diff line number Diff line change
Expand Up @@ -294,6 +294,8 @@ impl TryFrom<SolidityTestRunnerConfigArgs> for SolidityTestRunnerConfig {
evm_opts,
fuzz,
invariant,
// Solidity fuzz fixtures are not supported by the JS backend
solidity_fuzz_fixtures: false,
})
}
}
Expand Down
1 change: 1 addition & 0 deletions crates/foundry/forge/Cargo.toml
Original file line number Diff line number Diff line change
Expand Up @@ -35,6 +35,7 @@ alloy-primitives = { workspace = true, features = ["serde"] }
tokio = { workspace = true, features = ["time"] }
evm-disassembler.workspace = true
thiserror = "1.0.61"
log = "0.4.22"

[dev-dependencies]
edr_test_utils.workspace = true
Expand Down
2 changes: 2 additions & 0 deletions crates/foundry/forge/src/config.rs
Original file line number Diff line number Diff line change
Expand Up @@ -36,6 +36,8 @@ pub struct SolidityTestRunnerConfig {
pub coverage: bool,
/// Whether to support the `testFail` prefix
pub test_fail: bool,
/// Whether to enable solidity fuzz fixtures support
pub solidity_fuzz_fixtures: bool,
/// Cheats configuration options
pub cheats_config_options: CheatsConfigOptions,
/// EVM options
Expand Down
5 changes: 5 additions & 0 deletions crates/foundry/forge/src/multi_runner.rs
Original file line number Diff line number Diff line change
Expand Up @@ -74,6 +74,8 @@ pub struct MultiContractRunner {
debug: bool,
/// Whether to support the `testFail` prefix
test_fail: bool,
/// Whether to enable solidity fuzz fixtures support
solidity_fuzz_fixtures: bool,
/// Settings related to fuzz and/or invariant tests
test_options: TestOptions,
}
Expand Down Expand Up @@ -104,6 +106,7 @@ impl MultiContractRunner {
cheats_config_options,
fuzz,
invariant,
solidity_fuzz_fixtures,
} = config;

// Do canonicalization in blocking context.
Expand All @@ -130,6 +133,7 @@ impl MultiContractRunner {
trace,
debug,
test_fail,
solidity_fuzz_fixtures,
test_options,
})
}
Expand Down Expand Up @@ -280,6 +284,7 @@ impl MultiContractRunner {
sender: self.evm_opts.sender,
debug: self.debug,
test_fail: self.test_fail,
solidity_fuzz_fixtures: self.solidity_fuzz_fixtures,
},
);
let r = runner.run_tests(
Expand Down
95 changes: 56 additions & 39 deletions crates/foundry/forge/src/runner.rs
Original file line number Diff line number Diff line change
Expand Up @@ -65,6 +65,8 @@ pub struct ContractRunner<'a> {
pub debug: bool,
/// Whether to support the `testFail` prefix
pub test_fail: bool,
/// Whether to enable solidity fuzz fixtures support
pub solidity_fuzz_fixtures: bool,
}

/// Options for [`ContractRunner`].
Expand All @@ -78,6 +80,8 @@ pub struct ContractRunnerOptions {
pub debug: bool,
/// Whether to support the `testFail` prefix
pub test_fail: bool,
/// whether to enable solidity fuzz fixtures support
pub solidity_fuzz_fixtures: bool,
}

impl<'a> ContractRunner<'a> {
Expand All @@ -93,6 +97,7 @@ impl<'a> ContractRunner<'a> {
sender,
debug,
test_fail,
solidity_fuzz_fixtures,
} = options;

Self {
Expand All @@ -104,6 +109,7 @@ impl<'a> ContractRunner<'a> {
sender,
debug,
test_fail,
solidity_fuzz_fixtures,
}
}
}
Expand Down Expand Up @@ -278,51 +284,62 @@ impl<'a> ContractRunner<'a> {
/// returns an array of addresses to be used for fuzzing `owner` named
/// parameter in scope of the current test.
fn fuzz_fixtures(&mut self, address: Address) -> FuzzFixtures {
let mut fixtures = HashMap::new();
self.contract
let fixture_funcs = self
.contract
.abi
.functions()
.filter(|func| func.is_fixture())
.for_each(|func| {
if func.inputs.is_empty() {
// Read fixtures declared as functions.
.filter(|func| func.is_fixture());

// No-op if the feature is disabled
if !self.solidity_fuzz_fixtures {
fixture_funcs.for_each(|func| {
log::warn!("Possible fuzz fixture usage detected: '{}', but solidity fuzz fixtures are disabled.", &func.name);
});

return FuzzFixtures::default();
};

let mut fixtures = HashMap::new();
fixture_funcs.for_each(|func| {
if func.inputs.is_empty() {
// Read fixtures declared as functions.
if let Ok(CallResult {
raw: _,
decoded_result,
}) = self
.executor
.call(CALLER, address, func, &[], U256::ZERO, None)
{
fixtures.insert(fixture_name(func.name.clone()), decoded_result);
}
} else {
// For reading fixtures from storage arrays we collect values by calling the
// function with incremented indexes until there's an error.
let mut vals = Vec::new();
let mut index = 0;
loop {
if let Ok(CallResult {
raw: _,
decoded_result,
}) = self
.executor
.call(CALLER, address, func, &[], U256::ZERO, None)
{
fixtures.insert(fixture_name(func.name.clone()), decoded_result);
}
} else {
// For reading fixtures from storage arrays we collect values by calling the
// function with incremented indexes until there's an error.
let mut vals = Vec::new();
let mut index = 0;
loop {
if let Ok(CallResult {
raw: _,
decoded_result,
}) = self.executor.call(
CALLER,
address,
func,
&[DynSolValue::Uint(U256::from(index), 256)],
U256::ZERO,
None,
) {
vals.push(decoded_result);
} else {
// No result returned for this index, we reached the end of storage
// array or the function is not a valid fixture.
break;
}
index += 1;
}) = self.executor.call(
CALLER,
address,
func,
&[DynSolValue::Uint(U256::from(index), 256)],
U256::ZERO,
None,
) {
vals.push(decoded_result);
} else {
// No result returned for this index, we reached the end of storage
// array or the function is not a valid fixture.
break;
}
fixtures.insert(fixture_name(func.name.clone()), DynSolValue::Array(vals));
};
});
index += 1;
}
fixtures.insert(fixture_name(func.name.clone()), DynSolValue::Array(vals));
};
});

FuzzFixtures::new(fixtures)
}
Expand Down
1 change: 1 addition & 0 deletions crates/foundry/forge/tests/it/test_helpers.rs
Original file line number Diff line number Diff line change
Expand Up @@ -92,6 +92,7 @@ impl ForgeTestProfile {
invariant: TestInvariantConfig::default().into(),
coverage: false,
test_fail: true,
solidity_fuzz_fixtures: true,
}
}

Expand Down
33 changes: 33 additions & 0 deletions js/integration-tests/solidity-tests/contracts/FuzzFixture.t.sol
Original file line number Diff line number Diff line change
@@ -0,0 +1,33 @@
// SPDX-License-Identifier: MIT
pragma solidity ^0.8.24;

import "./test.sol";
import "./Vm.sol";

// Contract to be tested with overflow vulnerability
contract IdentityContract {
function identity(uint256 amount) view public returns(uint256) {
require(amount != 7191815684697958081204101901807852913954269296144377099693178655035380638910, "Got value from fixture");
return amount;
}
}

// Test that fuzz fixtures specified in Solidity are not supported.
contract FuzzFixtureTest is DSTest {
Vm constant vm = Vm(HEVM_ADDRESS);

IdentityContract testDummy;

uint256[] public fixtureAmount = [
// This is a random value
7191815684697958081204101901807852913954269296144377099693178655035380638910
];

function setUp() public {
testDummy = new IdentityContract();
}

function testFuzzDummy(uint256 amount) public {
assertEq(testDummy.identity(amount), amount);
}
}
15 changes: 14 additions & 1 deletion js/integration-tests/solidity-tests/test/fuzz.ts
Original file line number Diff line number Diff line change
Expand Up @@ -2,7 +2,6 @@ import chai, { assert, expect } from "chai";
import chaiAsPromised from "chai-as-promised";
import { TestContext } from "./testContext";
import fs from "node:fs/promises";
import { FuzzConfigArgs } from "@nomicfoundation/edr";

chai.use(chaiAsPromised);

Expand Down Expand Up @@ -41,6 +40,20 @@ describe("Fuzz and invariant testing", function () {
await assert.isFulfilled(fs.stat(failureDir));
});

it("FuzzFixture is not supported", async function () {
const result = await testContext.runTestsWithStats("FuzzFixtureTest", {
fuzz: {
runs: 256,
dictionaryWeight: 1,
includeStorage: false,
includePushBytes: false,
seed: "0x7bb9ee74aaa2abe5e2ca8a116382a9f2ed70b651e70b430e1052eff52a74ffe3",
},
});
assert.equal(result.failedTests, 0);
assert.equal(result.totalTests, 1);
});

// One test as steps should be sequential
it("FailingInvariant", async function () {
const failureDir = testContext.invariantFailuresPersistDir;
Expand Down

0 comments on commit af1f8ca

Please sign in to comment.