From 62cb16168273c7220b5dbd8f13653b52b68eb087 Mon Sep 17 00:00:00 2001 From: Alex Roan Date: Wed, 2 Nov 2022 00:33:49 +0000 Subject: [PATCH] Invariant structure --- .gas-snapshot | 13 +++--- foundry.toml | 10 ++++ lib/forge-std | 2 +- src/Modulus.sol | 3 ++ test/Invariant/Actors/ActorModAdmin.t.sol | 18 ++++++++ test/Invariant/Actors/ActorStranger.t.sol | 17 +++++++ test/Invariant/InvariantsBase.t.sol | 56 +++++++++++++++++++++++ test/Invariant/Modulus.t.sol | 45 ++++++++++++++++++ test/Unit/Modulus.t.sol | 13 +++--- 9 files changed, 163 insertions(+), 14 deletions(-) create mode 100644 test/Invariant/Actors/ActorModAdmin.t.sol create mode 100644 test/Invariant/Actors/ActorStranger.t.sol create mode 100644 test/Invariant/InvariantsBase.t.sol create mode 100644 test/Invariant/Modulus.t.sol diff --git a/.gas-snapshot b/.gas-snapshot index c2db8c5..134ce5f 100644 --- a/.gas-snapshot +++ b/.gas-snapshot @@ -1,14 +1,15 @@ -ModulusFuzz:test_mod_resultIsAlwaysLessThanModDivisor(uint256,uint256) (runs: 256, μ: 38396, ~: 40651) -GAS_ModulusGas_ModAdmin:test_setModDivisor() (gas: 13812) +ModulusFuzz:test_mod_resultIsAlwaysLessThanModDivisor(uint256,uint256) (runs: 256, μ: 38497, ~: 40674) +GAS_ModulusGas_ModAdmin:test_setModDivisor() (gas: 13835) GAS_ModulusGas_Owner:test_setModAdmin() (gas: 14155) GAS_ModulusGas_Stranger:test_getModDivisor() (gas: 7514) GAS_ModulusGas_Stranger:test_getOwner() (gas: 7577) GAS_ModulusGas_Stranger:test_getResult() (gas: 7546) GAS_ModulusGas_Stranger:test_mod() (gas: 12544) +ModulusInvariants:invariant_gettersNeverRevert() (runs: 512, calls: 262144, reverts: 0) ModulusUnit:test_constructor() (gas: 10608) -ModulusUnit:test_mod() (gas: 30677) -ModulusUnit:test_mod_revertsIfModDivisorIsZero() (gas: 13544) +ModulusUnit:test_mod() (gas: 30655) ModulusUnit:test_setModAdmin() (gas: 15159) -ModulusUnit:test_setModAdmin_onlyOwner_reverts() (gas: 11908) -ModulusUnit:test_setModDivisor() (gas: 17945) +ModulusUnit:test_setModAdmin_onlyOwner_reverts() (gas: 11986) +ModulusUnit:test_setModDivisor() (gas: 17968) ModulusUnit:test_setModDivisor_onlyModAdmin_reverts() (gas: 11895) +ModulusUnit:test_setModDivisor_zeroValue_reverts() (gas: 11166) diff --git a/foundry.toml b/foundry.toml index e6810b2..99c6eec 100644 --- a/foundry.toml +++ b/foundry.toml @@ -3,4 +3,14 @@ src = 'src' out = 'out' libs = ['lib'] +[fuzz] +runs = 256 +max_test_rejects = 120000 +seed = '0xB00B1E5' + +[invariant] +runs = 512 +depth = 512 +fail_on_revert = true + # See more config options https://github.com/foundry-rs/foundry/tree/master/config \ No newline at end of file diff --git a/lib/forge-std b/lib/forge-std index 17656a2..b8a11f5 160000 --- a/lib/forge-std +++ b/lib/forge-std @@ -1 +1 @@ -Subproject commit 17656a2fa5453f495d8c1302a0cedded912457eb +Subproject commit b8a11f5ba2eaac60949324c1f50b2f748f38b4be diff --git a/src/Modulus.sol b/src/Modulus.sol index c2f16f0..f098579 100644 --- a/src/Modulus.sol +++ b/src/Modulus.sol @@ -8,6 +8,7 @@ contract Modulus { error OnlyOwner(address expected, address actual); error OnlyModAdmin(address expected, address actual); + error ModDivisorCannotBeZero(); /// @notice Owner of the contract - Can set the modAdmin address private s_owner; @@ -21,6 +22,7 @@ contract Modulus { constructor(address modAdmin) { s_owner = msg.sender; s_modAdmin = modAdmin; + s_modDivisor = 1; } function setModAdmin(address modAdmin) external onlyOwner() { @@ -30,6 +32,7 @@ contract Modulus { } function setModDivisor(uint256 modDivisor) external onlyModAdmin() { + if (modDivisor == 0) revert ModDivisorCannotBeZero(); uint256 previous = s_modDivisor; s_modDivisor = modDivisor; emit ModDivisorSet(previous, modDivisor); diff --git a/test/Invariant/Actors/ActorModAdmin.t.sol b/test/Invariant/Actors/ActorModAdmin.t.sol new file mode 100644 index 0000000..408c478 --- /dev/null +++ b/test/Invariant/Actors/ActorModAdmin.t.sol @@ -0,0 +1,18 @@ +// SPDX-License-Identifier: MIT +pragma solidity 0.8.16; + +import {Test} from "forge-std/Test.sol"; +import {Modulus} from "../../../src/Modulus.sol"; + +contract ActorModAdmin is Test { + Modulus internal s_modulus; + + function storeModulus(Modulus modulus) external { + s_modulus = modulus; + } + + function setModDivisor(uint256 modDivisor) external { + if (modDivisor == 0) modDivisor = 1; + s_modulus.setModDivisor(modDivisor); + } +} \ No newline at end of file diff --git a/test/Invariant/Actors/ActorStranger.t.sol b/test/Invariant/Actors/ActorStranger.t.sol new file mode 100644 index 0000000..22a98ce --- /dev/null +++ b/test/Invariant/Actors/ActorStranger.t.sol @@ -0,0 +1,17 @@ +// SPDX-License-Identifier: MIT +pragma solidity 0.8.16; + +import {Test} from "forge-std/Test.sol"; +import {Modulus} from "../../../src/Modulus.sol"; + +contract ActorStranger is Test { + Modulus internal s_modulus; + + function storeModulus(Modulus modulus) external { + s_modulus = modulus; + } + + function mod(uint256 numerator) external { + s_modulus.mod(numerator); + } +} \ No newline at end of file diff --git a/test/Invariant/InvariantsBase.t.sol b/test/Invariant/InvariantsBase.t.sol new file mode 100644 index 0000000..9b6aa8b --- /dev/null +++ b/test/Invariant/InvariantsBase.t.sol @@ -0,0 +1,56 @@ +// SPDX-License-Identifier: MIT +pragma solidity 0.8.16; + +import {Test} from "forge-std/Test.sol"; + +contract InvariantsBase is Test { + + struct FuzzSelector { + address addr; + bytes4[] selectors; + } + + address[] private s_excludeContracts; + address[] private s_targetContracts; + FuzzSelector[] private s_targetSelectors; + address[] private s_targetSenders; + + constructor() { + // https://github.com/foundry-rs/foundry/issues/2963 + s_targetSenders.push(address(1)); + } + + function excludeContracts() public view returns (address[] memory) { + return s_excludeContracts; + } + + function targetContracts() public view returns (address[] memory) { + return s_targetContracts; + } + + function targetSelectors() public view returns (FuzzSelector[] memory) { + return s_targetSelectors; + } + + function targetSenders() public view returns (address[] memory) { + return s_targetSenders; + } + + // To avoid calling auxiliary functions that are inherited but not under test + // such as forge-std/Test.sol functions. + function addSelectors( + address newSelectorAddress, + bytes4[] memory newSelectors + ) public { + s_targetSelectors.push(FuzzSelector(newSelectorAddress, newSelectors)); + } + + function addSender(address newSenderAddress) public { + s_targetSenders.push(newSenderAddress); + } + + // Utility function to exclude contracts that shouldn't be called + function excludeContract(address excludedContractAddress) public { + s_excludeContracts.push(excludedContractAddress); + } +} diff --git a/test/Invariant/Modulus.t.sol b/test/Invariant/Modulus.t.sol new file mode 100644 index 0000000..d132f27 --- /dev/null +++ b/test/Invariant/Modulus.t.sol @@ -0,0 +1,45 @@ +// SPDX-License-Identifier: MIT +pragma solidity 0.8.16; + +import {Modulus} from "../../src/Modulus.sol"; +import {Test} from "forge-std/Test.sol"; +import {Constants} from "../Constants.t.sol"; +import {InvariantsBase} from "./InvariantsBase.t.sol"; +import {ActorModAdmin} from "./Actors/ActorModAdmin.t.sol"; +import {ActorStranger} from "./Actors/ActorStranger.t.sol"; + +contract ModulusInvariants is Test, InvariantsBase, Constants { + Modulus internal s_modulus; + + function setUp() public virtual { + // Deploy actors + ActorModAdmin modAdmin = new ActorModAdmin(); + ActorStranger stranger = new ActorStranger(); + + // Deploy Modulus contract + changePrank(address(OWNER)); + s_modulus = new Modulus(address(modAdmin)); + excludeContract(address(s_modulus)); + + // Store the Modulus address on each of the actors + modAdmin.storeModulus(s_modulus); + stranger.storeModulus(s_modulus); + + // Add mod admin selectors to callable functions + bytes4[] memory modAdminSelectors = new bytes4[](1); + modAdminSelectors[0] = ActorModAdmin.setModDivisor.selector; + addSelectors(address(modAdmin), modAdminSelectors); + + // Add stranger selectors to callable functions + bytes4[] memory strangerSelectors = new bytes4[](1); + strangerSelectors[0] = ActorStranger.mod.selector; + addSelectors(address(stranger), strangerSelectors); + } + + function invariant_gettersNeverRevert() public { + s_modulus.getResult(); + s_modulus.getModDivisor(); + s_modulus.getModAdmin(); + s_modulus.getOwner(); + } +} \ No newline at end of file diff --git a/test/Unit/Modulus.t.sol b/test/Unit/Modulus.t.sol index 96030c2..f391aa8 100644 --- a/test/Unit/Modulus.t.sol +++ b/test/Unit/Modulus.t.sol @@ -50,6 +50,12 @@ contract ModulusUnit is Test, Constants { s_modulus.setModDivisor(123); } + function test_setModDivisor_zeroValue_reverts() public { + changePrank(MOD_ADMIN); + vm.expectRevert(Modulus.ModDivisorCannotBeZero.selector); + s_modulus.setModDivisor(0); + } + function test_setModDivisor() public { changePrank(MOD_ADMIN); s_modulus.setModDivisor(MOD_DIVISOR_2); @@ -67,11 +73,4 @@ contract ModulusUnit is Test, Constants { s_modulus.mod(numerator); assertEq(s_modulus.getResult(), expected); } - - function test_mod_revertsIfModDivisorIsZero() public { - changePrank(MOD_ADMIN); - s_modulus.setModDivisor(0); - vm.expectRevert(); - s_modulus.mod(50); - } }