From 3374af4e4d11c0acedfdf26373f1c86e247f5180 Mon Sep 17 00:00:00 2001 From: tuturu-tech Date: Fri, 29 Mar 2024 16:00:39 +0100 Subject: [PATCH] add erc20 example and update readme --- README.md | 10 +- tests/test_data/src/TestERC20.sol | 7 + .../test/fuzzing/actors/ActorDefault.sol | 84 ++--------- .../test/fuzzing/harnesses/DefaultHarness.sol | 134 ------------------ .../test/fuzzing/harnesses/ERC20Harness.sol | 64 +++++++++ 5 files changed, 92 insertions(+), 207 deletions(-) create mode 100644 tests/test_data/src/TestERC20.sol delete mode 100644 tests/test_data/test/fuzzing/harnesses/DefaultHarness.sol create mode 100644 tests/test_data/test/fuzzing/harnesses/ERC20Harness.sol diff --git a/README.md b/README.md index 4740e7e..a23d073 100644 --- a/README.md +++ b/README.md @@ -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 diff --git a/tests/test_data/src/TestERC20.sol b/tests/test_data/src/TestERC20.sol new file mode 100644 index 0000000..7b40000 --- /dev/null +++ b/tests/test_data/src/TestERC20.sol @@ -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) {} +} \ No newline at end of file diff --git a/tests/test_data/test/fuzzing/actors/ActorDefault.sol b/tests/test_data/test/fuzzing/actors/ActorDefault.sol index d4b8de3..1ecffee 100644 --- a/tests/test_data/test/fuzzing/actors/ActorDefault.sol +++ b/tests/test_data/test/fuzzing/actors/ActorDefault.sol @@ -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); } } \ No newline at end of file diff --git a/tests/test_data/test/fuzzing/harnesses/DefaultHarness.sol b/tests/test_data/test/fuzzing/harnesses/DefaultHarness.sol deleted file mode 100644 index ae0e436..0000000 --- a/tests/test_data/test/fuzzing/harnesses/DefaultHarness.sol +++ /dev/null @@ -1,134 +0,0 @@ -// 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 DefaultHarness --test-mode assertion --test-limit 100000 --corpus-dir echidna-corpora/corpus-DefaultHarness -/// Medusa: medusa fuzz --target --assertion-mode --test-limit 100000 --deployment-order "DefaultHarness" --corpus-dir medusa-corpora/corpus-DefaultHarness -/// Foundry: forge test --match-contract DefaultHarness -/// -------------------------------------------------------------------- - -import "properties/util/PropertiesHelper.sol"; -import "src/BasicTypes.sol"; -import "../actors/ActorDefault.sol"; - -contract DefaultHarness is PropertiesAsserts { - BasicTypes basictypes; - ActorDefault[] Default_actors; - - constructor() { - basictypes = new BasicTypes(); - for(uint256 i; i < 3; i++) { - Default_actors.push(new ActorDefault(address(basictypes))); - } - } - - // ------------------------------------- - // ActorDefault functions - // test/fuzzing/actors/ActorDefault.sol - // ------------------------------------- - - function setBool(uint256 actorIndex, bool set) public { - ActorDefault selectedActor = Default_actors[clampBetween(actorIndex, 0, Default_actors.length - 1)]; - selectedActor.setBool(set); - } - - function check_bool(uint256 actorIndex) public { - ActorDefault selectedActor = Default_actors[clampBetween(actorIndex, 0, Default_actors.length - 1)]; - selectedActor.check_bool(); - } - - function setUint256(uint256 actorIndex, uint256 input) public { - ActorDefault selectedActor = Default_actors[clampBetween(actorIndex, 0, Default_actors.length - 1)]; - selectedActor.setUint256(input); - } - - function check_uint256(uint256 actorIndex) public { - ActorDefault selectedActor = Default_actors[clampBetween(actorIndex, 0, Default_actors.length - 1)]; - selectedActor.check_uint256(); - } - - function check_large_uint256(uint256 actorIndex) public { - ActorDefault selectedActor = Default_actors[clampBetween(actorIndex, 0, Default_actors.length - 1)]; - selectedActor.check_large_uint256(); - } - - function setInt256(uint256 actorIndex, int256 input) public { - ActorDefault selectedActor = Default_actors[clampBetween(actorIndex, 0, Default_actors.length - 1)]; - selectedActor.setInt256(input); - } - - function check_int256(uint256 actorIndex) public { - ActorDefault selectedActor = Default_actors[clampBetween(actorIndex, 0, Default_actors.length - 1)]; - selectedActor.check_int256(); - } - - function check_large_positive_int256(uint256 actorIndex) public { - ActorDefault selectedActor = Default_actors[clampBetween(actorIndex, 0, Default_actors.length - 1)]; - selectedActor.check_large_positive_int256(); - } - - function check_large_negative_int256(uint256 actorIndex) public { - ActorDefault selectedActor = Default_actors[clampBetween(actorIndex, 0, Default_actors.length - 1)]; - selectedActor.check_large_negative_int256(); - } - - function setAddress(uint256 actorIndex, address payable input) public { - ActorDefault selectedActor = Default_actors[clampBetween(actorIndex, 0, Default_actors.length - 1)]; - selectedActor.setAddress(input); - } - - function check_address(uint256 actorIndex) public { - ActorDefault selectedActor = Default_actors[clampBetween(actorIndex, 0, Default_actors.length - 1)]; - selectedActor.check_address(); - } - - function setString(uint256 actorIndex, string memory input) public { - ActorDefault selectedActor = Default_actors[clampBetween(actorIndex, 0, Default_actors.length - 1)]; - selectedActor.setString(input); - } - - function check_string(uint256 actorIndex) public { - ActorDefault selectedActor = Default_actors[clampBetween(actorIndex, 0, Default_actors.length - 1)]; - selectedActor.check_string(); - } - - function check_specific_string(uint256 actorIndex, string memory provided) public { - ActorDefault selectedActor = Default_actors[clampBetween(actorIndex, 0, Default_actors.length - 1)]; - selectedActor.check_specific_string(provided); - } - - function setBytes(uint256 actorIndex, bytes memory input) public { - ActorDefault selectedActor = Default_actors[clampBetween(actorIndex, 0, Default_actors.length - 1)]; - selectedActor.setBytes(input); - } - - function check_bytes(uint256 actorIndex) public { - ActorDefault selectedActor = Default_actors[clampBetween(actorIndex, 0, Default_actors.length - 1)]; - selectedActor.check_bytes(); - } - - function setCombination(uint256 actorIndex, bool bool_input, uint256 unsigned_input, int256 signed_input, address payable address_input, string memory str_input, bytes memory bytes_input) public { - ActorDefault selectedActor = Default_actors[clampBetween(actorIndex, 0, Default_actors.length - 1)]; - selectedActor.setCombination(bool_input, unsigned_input, signed_input, address_input, str_input, bytes_input); - } - - function check_combined_input(uint256 actorIndex) public { - ActorDefault selectedActor = Default_actors[clampBetween(actorIndex, 0, Default_actors.length - 1)]; - selectedActor.check_combined_input(); - } -} \ No newline at end of file diff --git a/tests/test_data/test/fuzzing/harnesses/ERC20Harness.sol b/tests/test_data/test/fuzzing/harnesses/ERC20Harness.sol new file mode 100644 index 0000000..8dee76e --- /dev/null +++ b/tests/test_data/test/fuzzing/harnesses/ERC20Harness.sol @@ -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); + } +} \ No newline at end of file