Skip to content

Commit

Permalink
add erc20 example and update readme
Browse files Browse the repository at this point in the history
  • Loading branch information
tuturu-tech committed Mar 29, 2024
1 parent a16a410 commit 3374af4
Show file tree
Hide file tree
Showing 5 changed files with 92 additions and 207 deletions.
10 changes: 7 additions & 3 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -72,12 +72,16 @@ The `template` command is used to generate a fuzzing harness. The harness can in

**Example**

In order to generate a fuzzing harness for the [BasicTypes.sol](tests/test_data/src/BasicTypes.sol) contract, we need to `cd` into the `tests/test_data/` directory which contains the Foundry project and run the command:
In order to generate a fuzzing harness for the [TestERC20.sol](tests/test_data/src/TestERC20.sol) contract, we need to `cd` into the `tests/test_data/` directory which contains the Foundry project and run the command:
```bash
fuzz-utils template ./src/BasicType.sol --name "DefaultHarness" --contracts BasicTypes
fuzz-utils template ./src/TestERC20.sol --name "ERC20Harness" --contracts TestERC20
```

Running this command should generate the directory structure in [tests/test_data/test/fuzzing](tests/test_data/test/fuzzing), which contains the fuzzing harness [DefaultHarness](tests/test_data/test/fuzzing/harnesses/DefaultHarness.sol) and the Actor contract [DefaultActor](tests/test_data/test/fuzzing/actors/ActorDefault.sol).
Running this command should generate the directory structure in [tests/test_data/test/fuzzing](tests/test_data/test/fuzzing), which contains the fuzzing harness [ERC20Harness](tests/test_data/test/fuzzing/harnesses/ERC20Harness.sol) and the Actor contract [DefaultActor](tests/test_data/test/fuzzing/actors/ActorDefault.sol).

We can see that the tool has generated the `DefaultActor` contract which contains all the functions of our ERC20 token, and that our fuzzing harness `ERC20Harness` is able to call each of these functions by randomly selecting one of the deployed actors, simulating different users.

This reduces the amount of time you need to set up fuzzing harness boilerplate and let's you focus on what really matters, defining invariants and testing the system.

## Utilities

Expand Down
7 changes: 7 additions & 0 deletions tests/test_data/src/TestERC20.sol
Original file line number Diff line number Diff line change
@@ -0,0 +1,7 @@
pragma solidity ^0.8.0;

import {ERC20} from "solmate/tokens/ERC20.sol";

contract TestERC20 is ERC20 {
constructor() ERC20("TestERC20", "TEST", 18) {}
}
84 changes: 14 additions & 70 deletions tests/test_data/test/fuzzing/actors/ActorDefault.sol
Original file line number Diff line number Diff line change
Expand Up @@ -14,87 +14,31 @@ pragma solidity ^0.8.0;
/// `directoryName/=directoryName/` to foundry.toml or remappings.txt

import "properties/util/PropertiesHelper.sol";
import "src/BasicTypes.sol";
import "src/TestERC20.sol";
contract ActorDefault is PropertiesAsserts {
BasicTypes basictypes;
constructor(address _basictypes){
basictypes = BasicTypes(_basictypes);
TestERC20 testerc20;
constructor(address _testerc20){
testerc20 = TestERC20(_testerc20);
}

// -------------------------------------
// BasicTypes functions
// src/BasicTypes.sol
// ERC20 functions
// lib/solmate/src/tokens/ERC20.sol
// -------------------------------------

function setBool(bool set) public {
basictypes.setBool(set);
function approve(address payable spender, uint256 amount) public returns (bool) {
testerc20.approve(spender, amount);
}

function check_bool() public {
basictypes.check_bool();
function transfer(address payable to, uint256 amount) public returns (bool) {
testerc20.transfer(to, amount);
}

function setUint256(uint256 input) public {
basictypes.setUint256(input);
function transferFrom(address payable from, address payable to, uint256 amount) public returns (bool) {
testerc20.transferFrom(from, to, amount);
}

function check_uint256() public {
basictypes.check_uint256();
}

function check_large_uint256() public {
basictypes.check_large_uint256();
}

function setInt256(int256 input) public {
basictypes.setInt256(input);
}

function check_int256() public {
basictypes.check_int256();
}

function check_large_positive_int256() public {
basictypes.check_large_positive_int256();
}

function check_large_negative_int256() public {
basictypes.check_large_negative_int256();
}

function setAddress(address payable input) public {
basictypes.setAddress(input);
}

function check_address() public {
basictypes.check_address();
}

function setString(string memory input) public {
basictypes.setString(input);
}

function check_string() public {
basictypes.check_string();
}

function check_specific_string(string memory provided) public {
basictypes.check_specific_string(provided);
}

function setBytes(bytes memory input) public {
basictypes.setBytes(input);
}

function check_bytes() public {
basictypes.check_bytes();
}

function setCombination(bool bool_input, uint256 unsigned_input, int256 signed_input, address payable address_input, string memory str_input, bytes memory bytes_input) public {
basictypes.setCombination(bool_input, unsigned_input, signed_input, address_input, str_input, bytes_input);
}

function check_combined_input() public {
basictypes.check_combined_input();
function permit(address payable owner, address payable spender, uint256 value, uint256 deadline, uint8 v, bytes32 r, bytes32 s) public {
testerc20.permit(owner, spender, value, deadline, v, r, s);
}
}
134 changes: 0 additions & 134 deletions tests/test_data/test/fuzzing/harnesses/DefaultHarness.sol

This file was deleted.

64 changes: 64 additions & 0 deletions tests/test_data/test/fuzzing/harnesses/ERC20Harness.sol
Original file line number Diff line number Diff line change
@@ -0,0 +1,64 @@
// SPDX-License-Identifier: UNLICENSED
pragma solidity ^0.8.0;

/// --------------------------------------------------------------------
/// @notice This file was automatically generated using fuzz-utils
///
/// -- [ Prerequisites ]
/// 1. The generated contracts depend on crytic/properties utilities
/// which need to be installed, this can be done by running:
/// `forge install crytic/properties`
/// 2. Absolute paths are used for contract inheritance, requiring
/// the main directory that contains the contracts to be added to
/// the Foundry remappings. This can be done by adding:
/// `directoryName/=directoryName/` to foundry.toml or remappings.txt
///
/// -- [ Running the fuzzers ]
/// * The below commands contain example values which you can modify based
/// on your needs. For further information on the configuration options
/// please reference the fuzzer documentation *
/// Echidna: echidna --contract ERC20Harness --test-mode assertion --test-limit 100000 --corpus-dir echidna-corpora/corpus-ERC20Harness
/// Medusa: medusa fuzz --target --assertion-mode --test-limit 100000 --deployment-order "ERC20Harness" --corpus-dir medusa-corpora/corpus-ERC20Harness
/// Foundry: forge test --match-contract ERC20Harness
/// --------------------------------------------------------------------

import "properties/util/PropertiesHelper.sol";
import "src/TestERC20.sol";
import "../actors/ActorDefault.sol";

contract ERC20Harness is PropertiesAsserts {
TestERC20 testerc20;
ActorDefault[] Default_actors;

constructor() {
testerc20 = new TestERC20();
for(uint256 i; i < 3; i++) {
Default_actors.push(new ActorDefault(address(testerc20)));
}
}

// -------------------------------------
// ActorDefault functions
// test/fuzzing/actors/ActorDefault.sol
// -------------------------------------

function approve(uint256 actorIndex, address payable spender, uint256 amount) public returns (bool) {
ActorDefault selectedActor = Default_actors[clampBetween(actorIndex, 0, Default_actors.length - 1)];
selectedActor.approve(spender, amount);
}

function transfer(uint256 actorIndex, address payable to, uint256 amount) public returns (bool) {
ActorDefault selectedActor = Default_actors[clampBetween(actorIndex, 0, Default_actors.length - 1)];
selectedActor.transfer(to, amount);
}

function transferFrom(uint256 actorIndex, address payable from, address payable to, uint256 amount) public returns (bool) {
ActorDefault selectedActor = Default_actors[clampBetween(actorIndex, 0, Default_actors.length - 1)];
selectedActor.transferFrom(from, to, amount);
}

function permit(uint256 actorIndex, address payable owner, address payable spender, uint256 value, uint256 deadline, uint8 v, bytes32 r, bytes32 s) public {
ActorDefault selectedActor = Default_actors[clampBetween(actorIndex, 0, Default_actors.length - 1)];
selectedActor.permit(owner, spender, value, deadline, v, r, s);
}
}

0 comments on commit 3374af4

Please sign in to comment.