1

Foundry’s invariant testing allow us to declare a set of truths that should always hold no matter which action or sequence of action is performed. We declare these truths and then Foundry sets the fuzzer loose on the contract and continually tests whether these truths remain true.

In this example, an invariance test is used to brute force a password: What's the difference between invariant and fuzz testing?.

I tried to replicate the idea of using Invariant Testing to brute force the first Ethernaut challenge but couldn't.

Ethernaut Challenge 1:

// SPDX-License-Identifier: MIT
pragma solidity ^0.8.18;

contract Instance {

  string public password;
  uint8 public infoNum = 42;
  string public theMethodName = 'The method name is method7123949.';
  bool private cleared = false;

  constructor(string memory _password) public {
    password = _password;
  }

  function info() public pure returns (string memory) {
    return 'You will find what you need in info1().';
  }

  function info1() public pure returns (string memory) {
    return 'Try info2(), but with "hello" as a parameter.';
  }

  function info2(string memory param) public pure returns (string memory) {
    if(keccak256(abi.encodePacked(param)) == keccak256(abi.encodePacked('hello'))) {
      return 'The property infoNum holds the number of the next info method to call.';
    }
    return 'Wrong parameter.';
  }

  function info42() public pure returns (string memory) {
    return 'theMethodName is the name of the next method.';
  }

  function method7123949() public pure returns (string memory) {
    return 'If you know the password, submit it to authenticate().';
  }

  function authenticate(string memory passkey) public {
    if(keccak256(abi.encodePacked(passkey)) == keccak256(abi.encodePacked(password))) {
      cleared = true;
    }
  }

  function getCleared() public view returns (bool) {
    return cleared;
  }
}

Invariant test:

// SPDX-License-Identifier: UNLICENSED
pragma solidity ^0.8.18;

import {Instance} from "../instances/Ilevel00.sol";
import {Test} from "forge-std/Test.sol";

contract Attacker is Test {
    Instance public level0;

    function setUp() public {
        level0 = new Instance("123asd123");
    }

    function invariant_testBruteForceFuzzing() external {
        assertEq(level0.getCleared(), false);
    }
}

Returns:

[PASS] invariant_testBruteForceFuzzing() (runs: 256, calls: 38400, reverts: 0)

This isn't surprising since Invariant testing isn't designed to brute force. What's more interesting is whether or not it's possible to bound the fuzzed input so that it only uses the outputs of other functions in the contract.

In the given example, we would want the fuzzer to test this callstack.

level0.authenticate(level0.password()); // using output of one func as input of another
level0.getCleared(); // at this point the invariant would not hold

Does anyone know if/how this can be done?

I'm considering ways to declare the target contract's ABI in a Handler and using a random seed to select a function, bounding the input in this manner. Based on my research, it seems that Echidna already performs similar operations under the hood, possibly in conjunction with Slither.

Thanks

0

Your Answer

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

Browse other questions tagged or ask your own question.