0

Context

After looking at this documentation about fuzz testing, I tried to apply it to the setUp() function in my test. However, that throws an error. I would like to test that the random stuff that I like to test, does not only work for that one hardcoded setUp() configuration that I choose, but also works for different configurations. The simple solution is to copy the initialization content of the SetUp() function into the testFuzz_ function, however that leads to a lot of duplicate code.

Question

Hence, I was wondering: is possible to let foundry run the setUp() function with random parameters?

Code

To make it into a concrete question, here is the relevant code:

// SPDX-License-Identifier: UNLICENSED
pragma solidity >=0.8.25 <0.9.0;

import "forge-std/src/Vm.sol" as vm;
import { PRBTest } from "@prb/test/src/PRBTest.sol";
import { StdCheats } from "forge-std/src/StdCheats.sol";

import { DecentralisedInvestmentManager } from "../../../../src/DecentralisedInvestmentManager.sol";
import { InitialiseDim } from "test/InitialiseDim.sol";
import { console2 } from "forge-std/src/console2.sol";

interface IFuzzTriggerReturnAll {
  function setUp(
    uint256 firstCeiling,
    uint256 secondCeiling,
    uint256 thirdCeiling,
    uint8 firstMultiple,
    uint8 secondMultiple,
    uint8 thirdMultiple
  ) external;

  function testFuzzRaisePeriodReturnSingleInvestment() external;

  function testFuzzKeepInvestmentsForSuccesfullRaise() external;
}

/**
Tests whether the _dim.triggerReturnAll() function ensures the investments are:
- returned if the investment target is not reached, after the raisePeriod has passed.
- not returned if the investment target is reached, after the raisePeriod has passed.
TODO: test whether the investments are:
- not returned if the investment target is not reached, before the raisePeriod has passed.
- not returned if the investment target is reached, before the raisePeriod has passed.
*/
contract FuzzTriggerReturnAll is PRBTest, StdCheats, IFuzzTriggerReturnAll {
  address internal _projectLead;
  address payable private _firstInvestorWallet;
  address payable private _secondInvestorWallet;

  uint256 private _firstInvestmentAmount;

  DecentralisedInvestmentManager private _dim;

  /// @dev A function invoked before each test case is run.
  function setUp(
    uint256 firstCeiling,
    uint256 secondCeiling,
    uint256 thirdCeiling,
    uint8 firstMultiple,
    uint8 secondMultiple,
    uint8 thirdMultiple
  ) public virtual override {
    // Instantiate the attribute for the contract-under-test.
    _projectLead = 0xf39Fd6e51aad88F6F4ce6aB8827279cffFb92266;
    uint256[] memory ceilings = new uint256[](3);
    ceilings[0] = firstCeiling;
    ceilings[1] = secondCeiling;
    ceilings[2] = thirdCeiling;
    uint8[] memory multiples = new uint8[](3);
    multiples[0] = firstMultiple;
    multiples[1] = secondMultiple;
    multiples[2] = thirdMultiple;
    console2.log("Hi");
    if (firstCeiling < secondCeiling && secondCeiling < thirdCeiling) {
      InitialiseDim initDim = new InitialiseDim({
        ceilings: ceilings,
        multiples: multiples,
        raisePeriod: 12 weeks,
        investmentTarget: 0.6 ether,
        projectLead: _projectLead,
        projectLeadFracNumerator: 4,
        projectLeadFracDenominator: 10
      });
      _dim = initDim.getDim();

      _firstInvestorWallet = payable(address(uint160(uint256(keccak256(bytes("1"))))));
      deal(_firstInvestorWallet, 3 ether);
      _secondInvestorWallet = payable(address(uint160(uint256(keccak256(bytes("2"))))));
      deal(_secondInvestorWallet, 4 ether);

      _firstInvestmentAmount = 0.5 ether;

      // Set the msg.sender address to that of the _firstInvestorWallet for the next call.
      vm.prank(address(_firstInvestorWallet));
      // Send investment directly from the investor wallet into the receiveInvestment function.
      _dim.receiveInvestment{ value: _firstInvestmentAmount }();
      assertEq(_dim.getTierInvestmentLength(), 1, "Error, the _tierInvestments.length was not as expected.");
    }
  }

  /**
  @dev The investor has invested 0.5 eth, and the investment target is 0.6 eth after 12 weeks.
  So the investment target is not reached, so all the funds should be returned.
   */
  function testFuzzRaisePeriodReturnSingleInvestment() public virtual override {
    // Simulate 3 weeks passing by
    // solhint-disable-next-line not-rely-on-time
    vm.warp(block.timestamp + 3 weeks);

    vm.expectRevert(bytes("The fund raising period has not passed yet."));
    _dim.triggerReturnAll();
    assertEq(address(_dim).balance, 0.5 ether, "The _dim did not contain 0.5 ether.");

    // solhint-disable-next-line not-rely-on-time
    vm.warp(block.timestamp + 15 weeks);

    vm.prank(_projectLead);
    _dim.triggerReturnAll();
    assertEq(address(_dim).balance, 0 ether, "The _dim did not contain 0 ether after returning all investments.");
  }

  /**
  Tests whether two investments together that reach the investment target result prevents the
  funds from being returned to the investors. (Because if the investment target is reached, the funds
  should be allocated to development, instead of being returned.) */
  function testFuzzKeepInvestmentsForSuccesfullRaise() public virtual override {
    // Simulate 3 weeks passing by
    // solhint-disable-next-line not-rely-on-time
    vm.warp(block.timestamp + 3 weeks);

    vm.expectRevert(bytes("The fund raising period has not passed yet."));
    _dim.triggerReturnAll();
    assertEq(address(_dim).balance, 0.5 ether, "The _dim did not contain 0.5 ether.");

    // Set the msg.sender address to that of the _firstInvestorWallet for the next call.
    vm.prank(address(_firstInvestorWallet));
    // Send investment directly from the investor wallet into the receiveInvestment function.
    _dim.receiveInvestment{ value: 2.5 ether }();

    // solhint-disable-next-line not-rely-on-time
    vm.warp(block.timestamp + 15 weeks);

    vm.expectRevert(bytes("Investment target reached!"));
    _dim.triggerReturnAll();
    assertEq(address(_dim).balance, 3 ether, "The _dim did not contain 3 ether.");
  }
}

Output

Running the code above with:

clear && forge test -vvvvvv --match-test testFuzz

yields:

Ran 1 test for test/functional/DecentralisedInvestmentManager/triggerReturnAll/FuzzTriggerReturnAll.t.sol:FuzzTriggerReturnAll
[FAIL. Reason: setup failed: EvmError: Revert] setUp() (gas: 0)
Traces:
  [149] FuzzTriggerReturnAll::setUp()
    └─ ← [Revert] EvmError: Revert

Suite result: FAILED. 0 passed; 1 failed; 0 skipped; finished in 1.18ms (0.00ns CPU time)

Ran 1 test suite in 591.61ms (1.18ms CPU time): 0 tests passed, 1 failed, 0 skipped (1 total tests)

Failing tests:
Encountered 1 failing test in test/functional/DecentralisedInvestmentManager/triggerReturnAll/FuzzTriggerReturnAll.t.sol:FuzzTriggerReturnAll
[FAIL. Reason: setup failed: EvmError: Revert] setUp() (gas: 0)

Encountered a total of 1 failing tests, 0 tests succeeded

1 Answer 1

1

I was looking to do this as well, but the setUp() function does not take any parameter, thus making fuzzing impossible.

This was requested as a functionality in this issue : https://github.com/foundry-rs/foundry/issues/1924, but denied due to complexity, the way fuzz testing works (shrinking), and some other open questions.

Your Answer

By clicking “Post Your Answer”, you agree to our terms of service and acknowledge you have read our privacy policy.

Not the answer you're looking for? Browse other questions tagged or ask your own question.