فهرست منبع

Solve conflicts with halmos

ernestognw 1 سال پیش
والد
کامیت
1e70e6117d
35فایلهای تغییر یافته به همراه531 افزوده شده و 414 حذف شده
  1. 5 0
      .changeset/chilly-humans-warn.md
  2. 5 0
      .changeset/cold-cheetahs-check.md
  3. 5 0
      .changeset/spotty-falcons-explain.md
  4. 1 0
      .github/actions/gas-compare/action.yml
  5. 1 0
      .github/actions/setup/action.yml
  6. 1 0
      .github/actions/storage-layout/action.yml
  7. 4 4
      .github/workflows/checks.yml
  8. 20 2
      .github/workflows/formal-verification.yml
  9. 3 0
      .gitmodules
  10. 1 1
      contracts/governance/IGovernor.sol
  11. 2 0
      contracts/mocks/Stateless.sol
  12. 4 4
      contracts/proxy/transparent/ProxyAdmin.sol
  13. 3 0
      contracts/utils/Arrays.sol
  14. 3 0
      contracts/utils/README.adoc
  15. 22 6
      contracts/utils/math/Math.sol
  16. 20 2
      contracts/utils/math/SignedMath.sol
  17. 130 0
      contracts/utils/structs/CircularBuffer.sol
  18. 4 0
      fv-requirements.txt
  19. 1 0
      lib/halmos-cheatcodes
  20. 28 358
      package-lock.json
  21. 1 1
      package.json
  22. 1 1
      scripts/checks/compare-layout.js
  23. 1 0
      scripts/generate/templates/Arrays.js
  24. 41 5
      scripts/generate/templates/SlotDerivation.t.js
  25. 1 1
      test/governance/Governor.t.sol
  26. 2 2
      test/governance/extensions/GovernorVotesQuorumFraction.test.js
  27. 1 1
      test/proxy/Clones.t.sol
  28. 17 1
      test/utils/Arrays.t.sol
  29. 1 1
      test/utils/Create2.t.sol
  30. 2 2
      test/utils/Packing.t.sol
  31. 63 9
      test/utils/ShortStrings.t.sol
  32. 35 10
      test/utils/SlotDerivation.t.sol
  33. 10 0
      test/utils/math/Math.t.sol
  34. 13 3
      test/utils/math/SignedMath.t.sol
  35. 79 0
      test/utils/structs/CircularBuffer.test.js

+ 5 - 0
.changeset/chilly-humans-warn.md

@@ -0,0 +1,5 @@
+---
+'openzeppelin-solidity': patch
+---
+
+`ProxyAdmin`: Fixed documentation for `UPGRADE_INTERFACE_VERSION` getter.

+ 5 - 0
.changeset/cold-cheetahs-check.md

@@ -0,0 +1,5 @@
+---
+'openzeppelin-solidity': minor
+---
+
+`CircularBuffer`: Add a data structure that stores the last `N` values pushed to it.

+ 5 - 0
.changeset/spotty-falcons-explain.md

@@ -0,0 +1,5 @@
+---
+'openzeppelin-solidity': minor
+---
+
+`Math`, `SignedMath`: Add a branchless `ternary` function that computes`cond ? a : b` in constant gas cost.

+ 1 - 0
.github/actions/gas-compare/action.yml

@@ -1,4 +1,5 @@
 name: Compare gas costs
+description: Compare gas costs between branches
 inputs:
   token:
     description: github token

+ 1 - 0
.github/actions/setup/action.yml

@@ -1,4 +1,5 @@
 name: Setup
+description: Common environment setup
 
 runs:
   using: composite

+ 1 - 0
.github/actions/storage-layout/action.yml

@@ -1,4 +1,5 @@
 name: Compare storage layouts
+description: Compare storage layouts between branches
 inputs:
   token:
     description: github token

+ 4 - 4
.github/workflows/checks.yml

@@ -93,9 +93,9 @@ jobs:
         uses: ./.github/actions/setup
       - name: Run coverage
         run: npm run coverage
-      - uses: codecov/codecov-action@v3
-        with:
-          token: ${{ secrets.CODECOV_TOKEN }}
+      - uses: codecov/codecov-action@v4
+        env:
+          CODECOV_TOKEN: ${{ secrets.CODECOV_TOKEN }}
 
   harnesses:
     runs-on: ubuntu-latest
@@ -115,7 +115,7 @@ jobs:
       - name: Set up environment
         uses: ./.github/actions/setup
       - run: rm foundry.toml
-      - uses: crytic/slither-action@v0.3.2
+      - uses: crytic/slither-action@v0.4.0
         with:
           node-version: 18.15
           slither-version: 0.10.1

+ 20 - 2
.github/workflows/formal-verification.yml

@@ -44,12 +44,13 @@ jobs:
           fi
           echo "result=$RESULT" >> "$GITHUB_OUTPUT"
       - name: Install python
-        uses: actions/setup-python@v4
+        uses: actions/setup-python@v5
         with:
           python-version: ${{ env.PIP_VERSION }}
           cache: 'pip'
+          cache-dependency-path: 'fv-requirements.txt'
       - name: Install python packages
-        run: pip install -r requirements.txt
+        run: pip install -r fv-requirements.txt
       - name: Install java
         uses: actions/setup-java@v3
         with:
@@ -66,3 +67,20 @@ jobs:
           node certora/run.js ${{ steps.arguments.outputs.result }} >> "$GITHUB_STEP_SUMMARY"
         env:
           CERTORAKEY: ${{ secrets.CERTORAKEY }}
+
+  halmos:
+    runs-on: ubuntu-latest
+    steps:
+      - uses: actions/checkout@v4
+      - name: Set up environment
+        uses: ./.github/actions/setup
+      - name: Install python
+        uses: actions/setup-python@v5
+        with:
+          python-version: ${{ env.PIP_VERSION }}
+          cache: 'pip'
+          cache-dependency-path: 'fv-requirements.txt'
+      - name: Install python packages
+        run: pip install -r fv-requirements.txt
+      - name: Run Halmos
+        run: halmos --match-test '^symbolic|^testSymbolic' -vv

+ 3 - 0
.gitmodules

@@ -5,3 +5,6 @@
 [submodule "lib/erc4626-tests"]
 	path = lib/erc4626-tests
 	url = https://github.com/a16z/erc4626-tests.git
+[submodule "lib/halmos-cheatcodes"]
+	path = lib/halmos-cheatcodes
+	url = https://github.com/a16z/halmos-cheatcodes

+ 1 - 1
contracts/governance/IGovernor.sol

@@ -145,7 +145,7 @@ interface IGovernor is IERC165, IERC6372 {
      * @dev Emitted when a vote is cast with params.
      *
      * Note: `support` values should be seen as buckets. Their interpretation depends on the voting module used.
-     * `params` are additional encoded parameters. Their interpepretation also depends on the voting module used.
+     * `params` are additional encoded parameters. Their interpretation  also depends on the voting module used.
      */
     event VoteCastWithParams(
         address indexed voter,

+ 2 - 0
contracts/mocks/Stateless.sol

@@ -10,6 +10,7 @@ import {AuthorityUtils} from "../access/manager/AuthorityUtils.sol";
 import {Base64} from "../utils/Base64.sol";
 import {BitMaps} from "../utils/structs/BitMaps.sol";
 import {Checkpoints} from "../utils/structs/Checkpoints.sol";
+import {CircularBuffer} from "../utils/structs/CircularBuffer.sol";
 import {Clones} from "../proxy/Clones.sol";
 import {Create2} from "../utils/Create2.sol";
 import {DoubleEndedQueue} from "../utils/structs/DoubleEndedQueue.sol";
@@ -24,6 +25,7 @@ import {ERC721Holder} from "../token/ERC721/utils/ERC721Holder.sol";
 import {Math} from "../utils/math/Math.sol";
 import {MerkleProof} from "../utils/cryptography/MerkleProof.sol";
 import {MessageHashUtils} from "../utils/cryptography/MessageHashUtils.sol";
+import {Panic} from "../utils/Panic.sol";
 import {Packing} from "../utils/Packing.sol";
 import {SafeCast} from "../utils/math/SafeCast.sol";
 import {SafeERC20} from "../token/ERC20/utils/SafeERC20.sol";

+ 4 - 4
contracts/proxy/transparent/ProxyAdmin.sol

@@ -12,10 +12,10 @@ import {Ownable} from "../../access/Ownable.sol";
  */
 contract ProxyAdmin is Ownable {
     /**
-     * @dev The version of the upgrade interface of the contract. If this getter is missing, both `upgrade(address)`
-     * and `upgradeAndCall(address,bytes)` are present, and `upgradeTo` must be used if no function should be called,
-     * while `upgradeAndCall` will invoke the `receive` function if the second argument is the empty byte string.
-     * If the getter returns `"5.0.0"`, only `upgradeAndCall(address,bytes)` is present, and the second argument must
+     * @dev The version of the upgrade interface of the contract. If this getter is missing, both `upgrade(address,address)`
+     * and `upgradeAndCall(address,address,bytes)` are present, and `upgrade` must be used if no function should be called,
+     * while `upgradeAndCall` will invoke the `receive` function if the third argument is the empty byte string.
+     * If the getter returns `"5.0.0"`, only `upgradeAndCall(address,address,bytes)` is present, and the third argument must
      * be the empty byte string if no function should be called, making it impossible to invoke the `receive` function
      * during an upgrade.
      */

+ 3 - 0
contracts/utils/Arrays.sol

@@ -455,6 +455,7 @@ library Arrays {
      * WARNING: this does not clear elements if length is reduced, of initialize elements if length is increased.
      */
     function unsafeSetLength(address[] storage array, uint256 len) internal {
+        /// @solidity memory-safe-assembly
         assembly {
             sstore(array.slot, len)
         }
@@ -466,6 +467,7 @@ library Arrays {
      * WARNING: this does not clear elements if length is reduced, of initialize elements if length is increased.
      */
     function unsafeSetLength(bytes32[] storage array, uint256 len) internal {
+        /// @solidity memory-safe-assembly
         assembly {
             sstore(array.slot, len)
         }
@@ -477,6 +479,7 @@ library Arrays {
      * WARNING: this does not clear elements if length is reduced, of initialize elements if length is increased.
      */
     function unsafeSetLength(uint256[] storage array, uint256 len) internal {
+        /// @solidity memory-safe-assembly
         assembly {
             sstore(array.slot, len)
         }

+ 3 - 0
contracts/utils/README.adoc

@@ -21,6 +21,7 @@ Miscellaneous contracts and libraries containing utility functions you can use t
  * {EnumerableMap}: A type like Solidity's https://solidity.readthedocs.io/en/latest/types.html#mapping-types[`mapping`], but with key-value _enumeration_: this will let you know how many entries a mapping has, and iterate over them (which is not possible with `mapping`).
  * {EnumerableSet}: Like {EnumerableMap}, but for https://en.wikipedia.org/wiki/Set_(abstract_data_type)[sets]. Can be used to store privileged accounts, issued IDs, etc.
  * {DoubleEndedQueue}: An implementation of a https://en.wikipedia.org/wiki/Double-ended_queue[double ended queue] whose values can be removed added or remove from both sides. Useful for FIFO and LIFO structures.
+ * {CircularBuffer}: A data structure to store the last N values pushed to it.
  * {Checkpoints}: A data structure to store values mapped to an strictly increasing key. Can be used for storing and accessing values over time.
  * {MerkleTree}: A library with https://wikipedia.org/wiki/Merkle_Tree[Merkle Tree] data structures and helper functions.
  * {Create2}: Wrapper around the https://blog.openzeppelin.com/getting-the-most-out-of-create2/[`CREATE2` EVM opcode] for safe use without having to deal with low-level assembly.
@@ -95,6 +96,8 @@ Ethereum contracts have no native concept of an interface, so applications must
 
 {{DoubleEndedQueue}}
 
+{{CircularBuffer}}
+
 {{Checkpoints}}
 
 {{MerkleTree}}

+ 22 - 6
contracts/utils/math/Math.sol

@@ -73,18 +73,34 @@ library Math {
         }
     }
 
+    /**
+     * @dev Branchless ternary evaluation for `a ? b : c`. Gas costs are constant.
+     *
+     * IMPORTANT: This function may reduce bytecode size and consume less gas when used standalone.
+     * However, the compiler may optimize Solidity ternary operations (i.e. `a ? b : c`) to only compute
+     * one branch when needed, making this function more expensive.
+     */
+    function ternary(bool condition, uint256 a, uint256 b) internal pure returns (uint256) {
+        unchecked {
+            // branchless ternary works because:
+            // b ^ (a ^ b) == a
+            // b ^ 0 == b
+            return b ^ ((a ^ b) * SafeCast.toUint(condition));
+        }
+    }
+
     /**
      * @dev Returns the largest of two numbers.
      */
     function max(uint256 a, uint256 b) internal pure returns (uint256) {
-        return a > b ? a : b;
+        return ternary(a > b, a, b);
     }
 
     /**
      * @dev Returns the smallest of two numbers.
      */
     function min(uint256 a, uint256 b) internal pure returns (uint256) {
-        return a < b ? a : b;
+        return ternary(a < b, a, b);
     }
 
     /**
@@ -114,7 +130,7 @@ library Math {
         // but the largest value we can obtain is type(uint256).max - 1, which happens
         // when a = type(uint256).max and b = 1.
         unchecked {
-            return a == 0 ? 0 : (a - 1) / b + 1;
+            return SafeCast.toUint(a > 0) * ((a - 1) / b + 1);
         }
     }
 
@@ -147,7 +163,7 @@ library Math {
 
             // Make sure the result is less than 2²⁵⁶. Also prevents denominator == 0.
             if (denominator <= prod1) {
-                Panic.panic(denominator == 0 ? Panic.DIVISION_BY_ZERO : Panic.UNDER_OVERFLOW);
+                Panic.panic(ternary(denominator == 0, Panic.DIVISION_BY_ZERO, Panic.UNDER_OVERFLOW));
             }
 
             ///////////////////////////////////////////////
@@ -268,7 +284,7 @@ library Math {
             }
 
             if (gcd != 1) return 0; // No inverse exists.
-            return x < 0 ? (n - uint256(-x)) : uint256(x); // Wrap the result if it's negative.
+            return ternary(x < 0, n - uint256(-x), uint256(x)); // Wrap the result if it's negative.
         }
     }
 
@@ -295,7 +311,7 @@ library Math {
 
     /**
      * @dev Returns the modular exponentiation of the specified base, exponent and modulus (b ** e % m).
-     * It includes a success flag indicating if the operation succeeded. Operation will be marked has failed if trying
+     * It includes a success flag indicating if the operation succeeded. Operation will be marked as failed if trying
      * to operate modulo 0 or if the underlying precompile reverted.
      *
      * IMPORTANT: The result is only valid if the success flag is true. When using this function, make sure the chain

+ 20 - 2
contracts/utils/math/SignedMath.sol

@@ -3,22 +3,40 @@
 
 pragma solidity ^0.8.20;
 
+import {SafeCast} from "./SafeCast.sol";
+
 /**
  * @dev Standard signed math utilities missing in the Solidity language.
  */
 library SignedMath {
+    /**
+     * @dev Branchless ternary evaluation for `a ? b : c`. Gas costs are constant.
+     *
+     * IMPORTANT: This function may reduce bytecode size and consume less gas when used standalone.
+     * However, the compiler may optimize Solidity ternary operations (i.e. `a ? b : c`) to only compute
+     * one branch when needed, making this function more expensive.
+     */
+    function ternary(bool condition, int256 a, int256 b) internal pure returns (int256) {
+        unchecked {
+            // branchless ternary works because:
+            // b ^ (a ^ b) == a
+            // b ^ 0 == b
+            return b ^ ((a ^ b) * int256(SafeCast.toUint(condition)));
+        }
+    }
+
     /**
      * @dev Returns the largest of two signed numbers.
      */
     function max(int256 a, int256 b) internal pure returns (int256) {
-        return a > b ? a : b;
+        return ternary(a > b, a, b);
     }
 
     /**
      * @dev Returns the smallest of two signed numbers.
      */
     function min(int256 a, int256 b) internal pure returns (int256) {
-        return a < b ? a : b;
+        return ternary(a < b, a, b);
     }
 
     /**

+ 130 - 0
contracts/utils/structs/CircularBuffer.sol

@@ -0,0 +1,130 @@
+// SPDX-License-Identifier: MIT
+pragma solidity ^0.8.20;
+
+import {Math} from "../math/Math.sol";
+import {Arrays} from "../Arrays.sol";
+import {Panic} from "../Panic.sol";
+
+/**
+ * @dev A fixed-size buffer for keeping `bytes32` items in storage.
+ *
+ * This data structure allows for pushing elements to it, and when its length exceeds the specified fixed size,
+ * new items take the place of the oldest element in the buffer, keeping at most `N` elements in the
+ * structure.
+ *
+ * Elements can't be removed but the data structure can be cleared. See {clear}.
+ *
+ * Complexity:
+ * - insertion ({push}): O(1)
+ * - lookup ({last}): O(1)
+ * - inclusion ({includes}): O(N) (worst case)
+ * - reset ({clear}): O(1)
+ *
+ * * The struct is called `Bytes32CircularBuffer`. Other types can be cast to and from `bytes32`. This data structure
+ * can only be used in storage, and not in memory.
+ *
+ * Example usage:
+ *
+ * ```solidity
+ * contract Example {
+ *     // Add the library methods
+ *     using CircularBuffer for CircularBuffer.Bytes32CircularBuffer;
+ *
+ *     // Declare a buffer storage variable
+ *     CircularBuffer.Bytes32CircularBuffer private myBuffer;
+ * }
+ * ```
+ */
+library CircularBuffer {
+    /**
+     * @dev Counts the number of items that have been pushed to the buffer. The residuo modulo _data.length indicates
+     * where the next value should be stored.
+     *
+     * Struct members have an underscore prefix indicating that they are "private" and should not be read or written to
+     * directly. Use the functions provided below instead. Modifying the struct manually may violate assumptions and
+     * lead to unexpected behavior.
+     *
+     * The last item is at data[(index - 1) % data.length] and the last item is at data[index % data.length]. This
+     * range can wrap around.
+     */
+    struct Bytes32CircularBuffer {
+        uint256 _count;
+        bytes32[] _data;
+    }
+
+    /**
+     * @dev Initialize a new CircularBuffer of given size.
+     *
+     * If the CircularBuffer was already setup and used, calling that function again will reset it to a blank state.
+     *
+     * NOTE: The size of the buffer will affect the execution of {includes} function, as it has a complexity of O(N).
+     * Consider a large buffer size may render the function unusable.
+     */
+    function setup(Bytes32CircularBuffer storage self, uint256 size) internal {
+        clear(self);
+        Arrays.unsafeSetLength(self._data, size);
+    }
+
+    /**
+     * @dev Clear all data in the buffer without resetting memory, keeping the existing size.
+     */
+    function clear(Bytes32CircularBuffer storage self) internal {
+        self._count = 0;
+    }
+
+    /**
+     * @dev Push a new value to the buffer. If the buffer is already full, the new value replaces the oldest value in
+     * the buffer.
+     */
+    function push(Bytes32CircularBuffer storage self, bytes32 value) internal {
+        uint256 index = self._count++;
+        uint256 modulus = self._data.length;
+        Arrays.unsafeAccess(self._data, index % modulus).value = value;
+    }
+
+    /**
+     * @dev Number of values currently in the buffer. This value is 0 for an empty buffer, and cannot exceed the size of
+     * the buffer.
+     */
+    function count(Bytes32CircularBuffer storage self) internal view returns (uint256) {
+        return Math.min(self._count, self._data.length);
+    }
+
+    /**
+     * @dev Length of the buffer. This is the maximum number of elements kepts in the buffer.
+     */
+    function length(Bytes32CircularBuffer storage self) internal view returns (uint256) {
+        return self._data.length;
+    }
+
+    /**
+     * @dev Getter for the i-th value in the buffer, from the end.
+     *
+     * Reverts with {Panic-ARRAY_OUT_OF_BOUNDS} if trying to access an element that was not pushed, or that was
+     * dropped to make room for newer elements.
+     */
+    function last(Bytes32CircularBuffer storage self, uint256 i) internal view returns (bytes32) {
+        uint256 index = self._count;
+        uint256 modulus = self._data.length;
+        uint256 total = Math.min(index, modulus); // count(self)
+        if (i >= total) {
+            Panic.panic(Panic.ARRAY_OUT_OF_BOUNDS);
+        }
+        return Arrays.unsafeAccess(self._data, (index - i - 1) % modulus).value;
+    }
+
+    /**
+     * @dev Check if a given value is in the buffer.
+     */
+    function includes(Bytes32CircularBuffer storage self, bytes32 value) internal view returns (bool) {
+        uint256 index = self._count;
+        uint256 modulus = self._data.length;
+        uint256 total = Math.min(index, modulus); // count(self)
+        for (uint256 i = 0; i < total; ++i) {
+            if (Arrays.unsafeAccess(self._data, (index - i - 1) % modulus).value == value) {
+                return true;
+            }
+        }
+        return false;
+    }
+}

+ 4 - 0
fv-requirements.txt

@@ -0,0 +1,4 @@
+certora-cli==4.13.1
+# File uses a custom name (fv-requirements.txt) so that it isn't picked by Netlify's build
+# whose latest Python version is 0.3.8, incompatible with most recent versions of Halmos
+halmos==0.1.12

+ 1 - 0
lib/halmos-cheatcodes

@@ -0,0 +1 @@
+Subproject commit c0d865508c0fee0a11b97732c5e90f9cad6b65a5

+ 28 - 358
package-lock.json

@@ -37,7 +37,7 @@
         "prettier-plugin-solidity": "^1.1.0",
         "rimraf": "^5.0.1",
         "semver": "^7.3.5",
-        "solhint": "^4.0.0",
+        "solhint": "^5.0.0",
         "solhint-plugin-openzeppelin": "file:scripts/solhint-custom",
         "solidity-ast": "^0.4.50",
         "solidity-coverage": "^0.8.5",
@@ -2431,6 +2431,12 @@
         "url": "https://github.com/sindresorhus/is?sponsor=1"
       }
     },
+    "node_modules/@solidity-parser/parser": {
+      "version": "0.18.0",
+      "resolved": "https://registry.npmjs.org/@solidity-parser/parser/-/parser-0.18.0.tgz",
+      "integrity": "sha512-yfORGUIPgLck41qyN7nbwJRAx17/jAIXCTanHOJZhB6PJ1iAk/84b/xlsVKFSyNyLXIj0dhppoE0+CRws7wlzA==",
+      "dev": true
+    },
     "node_modules/@szmarczak/http-timer": {
       "version": "5.0.1",
       "resolved": "https://registry.npmjs.org/@szmarczak/http-timer/-/http-timer-5.0.1.tgz",
@@ -2718,15 +2724,6 @@
         "string-width": "^4.1.0"
       }
     },
-    "node_modules/ansi-align/node_modules/ansi-regex": {
-      "version": "5.0.1",
-      "resolved": "https://registry.npmjs.org/ansi-regex/-/ansi-regex-5.0.1.tgz",
-      "integrity": "sha512-quJQXlTSUGL2LH9SUXo8VwsY4soanhgo6LNSm84E1LBcE8s3O0wpdiRzyR9z/ZZJMlMWv37qOOb9pdJlMUEKFQ==",
-      "dev": true,
-      "engines": {
-        "node": ">=8"
-      }
-    },
     "node_modules/ansi-align/node_modules/is-fullwidth-code-point": {
       "version": "3.0.0",
       "resolved": "https://registry.npmjs.org/is-fullwidth-code-point/-/is-fullwidth-code-point-3.0.0.tgz",
@@ -2750,18 +2747,6 @@
         "node": ">=8"
       }
     },
-    "node_modules/ansi-align/node_modules/strip-ansi": {
-      "version": "6.0.1",
-      "resolved": "https://registry.npmjs.org/strip-ansi/-/strip-ansi-6.0.1.tgz",
-      "integrity": "sha512-Y38VPSHcqkFrCpFnQ9vuSXmquuv5oXOKpGeT6aGrr3o3Gc9AlVa6JBfUSOCnbxGGZF+/0ooI7KrPuUSztUdU5A==",
-      "dev": true,
-      "dependencies": {
-        "ansi-regex": "^5.0.1"
-      },
-      "engines": {
-        "node": ">=8"
-      }
-    },
     "node_modules/ansi-colors": {
       "version": "4.1.3",
       "resolved": "https://registry.npmjs.org/ansi-colors/-/ansi-colors-4.1.3.tgz",
@@ -2798,6 +2783,15 @@
         "url": "https://github.com/sponsors/sindresorhus"
       }
     },
+    "node_modules/ansi-regex": {
+      "version": "5.0.1",
+      "resolved": "https://registry.npmjs.org/ansi-regex/-/ansi-regex-5.0.1.tgz",
+      "integrity": "sha512-quJQXlTSUGL2LH9SUXo8VwsY4soanhgo6LNSm84E1LBcE8s3O0wpdiRzyR9z/ZZJMlMWv37qOOb9pdJlMUEKFQ==",
+      "dev": true,
+      "engines": {
+        "node": ">=8"
+      }
+    },
     "node_modules/ansi-styles": {
       "version": "3.2.1",
       "resolved": "https://registry.npmjs.org/ansi-styles/-/ansi-styles-3.2.1.tgz",
@@ -3073,15 +3067,6 @@
         "url": "https://github.com/sponsors/sindresorhus"
       }
     },
-    "node_modules/boxen/node_modules/ansi-regex": {
-      "version": "5.0.1",
-      "resolved": "https://registry.npmjs.org/ansi-regex/-/ansi-regex-5.0.1.tgz",
-      "integrity": "sha512-quJQXlTSUGL2LH9SUXo8VwsY4soanhgo6LNSm84E1LBcE8s3O0wpdiRzyR9z/ZZJMlMWv37qOOb9pdJlMUEKFQ==",
-      "dev": true,
-      "engines": {
-        "node": ">=8"
-      }
-    },
     "node_modules/boxen/node_modules/ansi-styles": {
       "version": "4.3.0",
       "resolved": "https://registry.npmjs.org/ansi-styles/-/ansi-styles-4.3.0.tgz",
@@ -3163,18 +3148,6 @@
         "node": ">=8"
       }
     },
-    "node_modules/boxen/node_modules/strip-ansi": {
-      "version": "6.0.1",
-      "resolved": "https://registry.npmjs.org/strip-ansi/-/strip-ansi-6.0.1.tgz",
-      "integrity": "sha512-Y38VPSHcqkFrCpFnQ9vuSXmquuv5oXOKpGeT6aGrr3o3Gc9AlVa6JBfUSOCnbxGGZF+/0ooI7KrPuUSztUdU5A==",
-      "dev": true,
-      "dependencies": {
-        "ansi-regex": "^5.0.1"
-      },
-      "engines": {
-        "node": ">=8"
-      }
-    },
     "node_modules/boxen/node_modules/supports-color": {
       "version": "7.2.0",
       "resolved": "https://registry.npmjs.org/supports-color/-/supports-color-7.2.0.tgz",
@@ -3566,15 +3539,6 @@
         "node": ">=12"
       }
     },
-    "node_modules/cliui/node_modules/ansi-regex": {
-      "version": "5.0.1",
-      "resolved": "https://registry.npmjs.org/ansi-regex/-/ansi-regex-5.0.1.tgz",
-      "integrity": "sha512-quJQXlTSUGL2LH9SUXo8VwsY4soanhgo6LNSm84E1LBcE8s3O0wpdiRzyR9z/ZZJMlMWv37qOOb9pdJlMUEKFQ==",
-      "dev": true,
-      "engines": {
-        "node": ">=8"
-      }
-    },
     "node_modules/cliui/node_modules/is-fullwidth-code-point": {
       "version": "3.0.0",
       "resolved": "https://registry.npmjs.org/is-fullwidth-code-point/-/is-fullwidth-code-point-3.0.0.tgz",
@@ -3598,18 +3562,6 @@
         "node": ">=8"
       }
     },
-    "node_modules/cliui/node_modules/strip-ansi": {
-      "version": "6.0.1",
-      "resolved": "https://registry.npmjs.org/strip-ansi/-/strip-ansi-6.0.1.tgz",
-      "integrity": "sha512-Y38VPSHcqkFrCpFnQ9vuSXmquuv5oXOKpGeT6aGrr3o3Gc9AlVa6JBfUSOCnbxGGZF+/0ooI7KrPuUSztUdU5A==",
-      "dev": true,
-      "dependencies": {
-        "ansi-regex": "^5.0.1"
-      },
-      "engines": {
-        "node": ">=8"
-      }
-    },
     "node_modules/clone": {
       "version": "1.0.4",
       "resolved": "https://registry.npmjs.org/clone/-/clone-1.0.4.tgz",
@@ -4134,27 +4086,6 @@
         "node": ">=8.6"
       }
     },
-    "node_modules/enquirer/node_modules/ansi-regex": {
-      "version": "5.0.1",
-      "resolved": "https://registry.npmjs.org/ansi-regex/-/ansi-regex-5.0.1.tgz",
-      "integrity": "sha512-quJQXlTSUGL2LH9SUXo8VwsY4soanhgo6LNSm84E1LBcE8s3O0wpdiRzyR9z/ZZJMlMWv37qOOb9pdJlMUEKFQ==",
-      "dev": true,
-      "engines": {
-        "node": ">=8"
-      }
-    },
-    "node_modules/enquirer/node_modules/strip-ansi": {
-      "version": "6.0.1",
-      "resolved": "https://registry.npmjs.org/strip-ansi/-/strip-ansi-6.0.1.tgz",
-      "integrity": "sha512-Y38VPSHcqkFrCpFnQ9vuSXmquuv5oXOKpGeT6aGrr3o3Gc9AlVa6JBfUSOCnbxGGZF+/0ooI7KrPuUSztUdU5A==",
-      "dev": true,
-      "dependencies": {
-        "ansi-regex": "^5.0.1"
-      },
-      "engines": {
-        "node": ">=8"
-      }
-    },
     "node_modules/env-paths": {
       "version": "2.2.1",
       "resolved": "https://registry.npmjs.org/env-paths/-/env-paths-2.2.1.tgz",
@@ -4494,15 +4425,6 @@
         "url": "https://opencollective.com/eslint"
       }
     },
-    "node_modules/eslint/node_modules/ansi-regex": {
-      "version": "5.0.1",
-      "resolved": "https://registry.npmjs.org/ansi-regex/-/ansi-regex-5.0.1.tgz",
-      "integrity": "sha512-quJQXlTSUGL2LH9SUXo8VwsY4soanhgo6LNSm84E1LBcE8s3O0wpdiRzyR9z/ZZJMlMWv37qOOb9pdJlMUEKFQ==",
-      "dev": true,
-      "engines": {
-        "node": ">=8"
-      }
-    },
     "node_modules/eslint/node_modules/ansi-styles": {
       "version": "4.3.0",
       "resolved": "https://registry.npmjs.org/ansi-styles/-/ansi-styles-4.3.0.tgz",
@@ -4649,18 +4571,6 @@
         "url": "https://github.com/sponsors/sindresorhus"
       }
     },
-    "node_modules/eslint/node_modules/strip-ansi": {
-      "version": "6.0.1",
-      "resolved": "https://registry.npmjs.org/strip-ansi/-/strip-ansi-6.0.1.tgz",
-      "integrity": "sha512-Y38VPSHcqkFrCpFnQ9vuSXmquuv5oXOKpGeT6aGrr3o3Gc9AlVa6JBfUSOCnbxGGZF+/0ooI7KrPuUSztUdU5A==",
-      "dev": true,
-      "dependencies": {
-        "ansi-regex": "^5.0.1"
-      },
-      "engines": {
-        "node": ">=8"
-      }
-    },
     "node_modules/eslint/node_modules/supports-color": {
       "version": "7.2.0",
       "resolved": "https://registry.npmjs.org/supports-color/-/supports-color-7.2.0.tgz",
@@ -5784,21 +5694,6 @@
         "url": "https://paulmillr.com/funding/"
       }
     },
-    "node_modules/hardhat-gas-reporter/node_modules/@solidity-parser/parser": {
-      "version": "0.18.0",
-      "resolved": "https://registry.npmjs.org/@solidity-parser/parser/-/parser-0.18.0.tgz",
-      "integrity": "sha512-yfORGUIPgLck41qyN7nbwJRAx17/jAIXCTanHOJZhB6PJ1iAk/84b/xlsVKFSyNyLXIj0dhppoE0+CRws7wlzA==",
-      "dev": true
-    },
-    "node_modules/hardhat-gas-reporter/node_modules/ansi-regex": {
-      "version": "5.0.1",
-      "resolved": "https://registry.npmjs.org/ansi-regex/-/ansi-regex-5.0.1.tgz",
-      "integrity": "sha512-quJQXlTSUGL2LH9SUXo8VwsY4soanhgo6LNSm84E1LBcE8s3O0wpdiRzyR9z/ZZJMlMWv37qOOb9pdJlMUEKFQ==",
-      "dev": true,
-      "engines": {
-        "node": ">=8"
-      }
-    },
     "node_modules/hardhat-gas-reporter/node_modules/ansi-styles": {
       "version": "4.3.0",
       "resolved": "https://registry.npmjs.org/ansi-styles/-/ansi-styles-4.3.0.tgz",
@@ -5920,18 +5815,6 @@
         "node": ">=8"
       }
     },
-    "node_modules/hardhat-gas-reporter/node_modules/strip-ansi": {
-      "version": "6.0.1",
-      "resolved": "https://registry.npmjs.org/strip-ansi/-/strip-ansi-6.0.1.tgz",
-      "integrity": "sha512-Y38VPSHcqkFrCpFnQ9vuSXmquuv5oXOKpGeT6aGrr3o3Gc9AlVa6JBfUSOCnbxGGZF+/0ooI7KrPuUSztUdU5A==",
-      "dev": true,
-      "dependencies": {
-        "ansi-regex": "^5.0.1"
-      },
-      "engines": {
-        "node": ">=8"
-      }
-    },
     "node_modules/hardhat-gas-reporter/node_modules/supports-color": {
       "version": "7.2.0",
       "resolved": "https://registry.npmjs.org/supports-color/-/supports-color-7.2.0.tgz",
@@ -7518,15 +7401,6 @@
         "node": ">=6"
       }
     },
-    "node_modules/mocha/node_modules/ansi-regex": {
-      "version": "5.0.1",
-      "resolved": "https://registry.npmjs.org/ansi-regex/-/ansi-regex-5.0.1.tgz",
-      "integrity": "sha512-quJQXlTSUGL2LH9SUXo8VwsY4soanhgo6LNSm84E1LBcE8s3O0wpdiRzyR9z/ZZJMlMWv37qOOb9pdJlMUEKFQ==",
-      "dev": true,
-      "engines": {
-        "node": ">=8"
-      }
-    },
     "node_modules/mocha/node_modules/argparse": {
       "version": "2.0.1",
       "resolved": "https://registry.npmjs.org/argparse/-/argparse-2.0.1.tgz",
@@ -7705,18 +7579,6 @@
         "node": ">=8"
       }
     },
-    "node_modules/mocha/node_modules/strip-ansi": {
-      "version": "6.0.1",
-      "resolved": "https://registry.npmjs.org/strip-ansi/-/strip-ansi-6.0.1.tgz",
-      "integrity": "sha512-Y38VPSHcqkFrCpFnQ9vuSXmquuv5oXOKpGeT6aGrr3o3Gc9AlVa6JBfUSOCnbxGGZF+/0ooI7KrPuUSztUdU5A==",
-      "dev": true,
-      "dependencies": {
-        "ansi-regex": "^5.0.1"
-      },
-      "engines": {
-        "node": ">=8"
-      }
-    },
     "node_modules/mocha/node_modules/supports-color": {
       "version": "8.1.1",
       "resolved": "https://registry.npmjs.org/supports-color/-/supports-color-8.1.1.tgz",
@@ -9341,15 +9203,6 @@
         "node": ">=6"
       }
     },
-    "node_modules/smartwrap/node_modules/ansi-regex": {
-      "version": "5.0.1",
-      "resolved": "https://registry.npmjs.org/ansi-regex/-/ansi-regex-5.0.1.tgz",
-      "integrity": "sha512-quJQXlTSUGL2LH9SUXo8VwsY4soanhgo6LNSm84E1LBcE8s3O0wpdiRzyR9z/ZZJMlMWv37qOOb9pdJlMUEKFQ==",
-      "dev": true,
-      "engines": {
-        "node": ">=8"
-      }
-    },
     "node_modules/smartwrap/node_modules/ansi-styles": {
       "version": "4.3.0",
       "resolved": "https://registry.npmjs.org/ansi-styles/-/ansi-styles-4.3.0.tgz",
@@ -9417,18 +9270,6 @@
         "node": ">=8"
       }
     },
-    "node_modules/smartwrap/node_modules/strip-ansi": {
-      "version": "6.0.1",
-      "resolved": "https://registry.npmjs.org/strip-ansi/-/strip-ansi-6.0.1.tgz",
-      "integrity": "sha512-Y38VPSHcqkFrCpFnQ9vuSXmquuv5oXOKpGeT6aGrr3o3Gc9AlVa6JBfUSOCnbxGGZF+/0ooI7KrPuUSztUdU5A==",
-      "dev": true,
-      "dependencies": {
-        "ansi-regex": "^5.0.1"
-      },
-      "engines": {
-        "node": ">=8"
-      }
-    },
     "node_modules/smartwrap/node_modules/wrap-ansi": {
       "version": "6.2.0",
       "resolved": "https://registry.npmjs.org/wrap-ansi/-/wrap-ansi-6.2.0.tgz",
@@ -9472,9 +9313,9 @@
       }
     },
     "node_modules/solhint": {
-      "version": "4.5.2",
-      "resolved": "https://registry.npmjs.org/solhint/-/solhint-4.5.2.tgz",
-      "integrity": "sha512-o7MNYS5QPgE6l+PTGOTAUtCzo0ZLnffQsv586hntSHBe2JbSDfkoxfhAOcjZjN4OesTgaX4UEEjCjH9y/4BP5w==",
+      "version": "5.0.0",
+      "resolved": "https://registry.npmjs.org/solhint/-/solhint-5.0.0.tgz",
+      "integrity": "sha512-pSRKkzRsruia6/xa9L5VSyd7dMZkiiTi/aYZcvUQo7KK+S16ojPwIbt2jfjbH5WEJ83grzIIE4WrYQfAxGWh/A==",
       "dev": true,
       "dependencies": {
         "@solidity-parser/parser": "^0.18.0",
@@ -9507,21 +9348,6 @@
       "resolved": "scripts/solhint-custom",
       "link": true
     },
-    "node_modules/solhint/node_modules/@solidity-parser/parser": {
-      "version": "0.18.0",
-      "resolved": "https://registry.npmjs.org/@solidity-parser/parser/-/parser-0.18.0.tgz",
-      "integrity": "sha512-yfORGUIPgLck41qyN7nbwJRAx17/jAIXCTanHOJZhB6PJ1iAk/84b/xlsVKFSyNyLXIj0dhppoE0+CRws7wlzA==",
-      "dev": true
-    },
-    "node_modules/solhint/node_modules/ansi-regex": {
-      "version": "5.0.1",
-      "resolved": "https://registry.npmjs.org/ansi-regex/-/ansi-regex-5.0.1.tgz",
-      "integrity": "sha512-quJQXlTSUGL2LH9SUXo8VwsY4soanhgo6LNSm84E1LBcE8s3O0wpdiRzyR9z/ZZJMlMWv37qOOb9pdJlMUEKFQ==",
-      "dev": true,
-      "engines": {
-        "node": ">=8"
-      }
-    },
     "node_modules/solhint/node_modules/ansi-styles": {
       "version": "4.3.0",
       "resolved": "https://registry.npmjs.org/ansi-styles/-/ansi-styles-4.3.0.tgz",
@@ -9654,18 +9480,6 @@
         "url": "https://github.com/prettier/prettier?sponsor=1"
       }
     },
-    "node_modules/solhint/node_modules/strip-ansi": {
-      "version": "6.0.1",
-      "resolved": "https://registry.npmjs.org/strip-ansi/-/strip-ansi-6.0.1.tgz",
-      "integrity": "sha512-Y38VPSHcqkFrCpFnQ9vuSXmquuv5oXOKpGeT6aGrr3o3Gc9AlVa6JBfUSOCnbxGGZF+/0ooI7KrPuUSztUdU5A==",
-      "dev": true,
-      "dependencies": {
-        "ansi-regex": "^5.0.1"
-      },
-      "engines": {
-        "node": ">=8"
-      }
-    },
     "node_modules/solhint/node_modules/supports-color": {
       "version": "7.2.0",
       "resolved": "https://registry.npmjs.org/supports-color/-/supports-color-7.2.0.tgz",
@@ -10186,15 +10000,6 @@
         "node": ">=8"
       }
     },
-    "node_modules/string-width-cjs/node_modules/ansi-regex": {
-      "version": "5.0.1",
-      "resolved": "https://registry.npmjs.org/ansi-regex/-/ansi-regex-5.0.1.tgz",
-      "integrity": "sha512-quJQXlTSUGL2LH9SUXo8VwsY4soanhgo6LNSm84E1LBcE8s3O0wpdiRzyR9z/ZZJMlMWv37qOOb9pdJlMUEKFQ==",
-      "dev": true,
-      "engines": {
-        "node": ">=8"
-      }
-    },
     "node_modules/string-width-cjs/node_modules/is-fullwidth-code-point": {
       "version": "3.0.0",
       "resolved": "https://registry.npmjs.org/is-fullwidth-code-point/-/is-fullwidth-code-point-3.0.0.tgz",
@@ -10204,18 +10009,6 @@
         "node": ">=8"
       }
     },
-    "node_modules/string-width-cjs/node_modules/strip-ansi": {
-      "version": "6.0.1",
-      "resolved": "https://registry.npmjs.org/strip-ansi/-/strip-ansi-6.0.1.tgz",
-      "integrity": "sha512-Y38VPSHcqkFrCpFnQ9vuSXmquuv5oXOKpGeT6aGrr3o3Gc9AlVa6JBfUSOCnbxGGZF+/0ooI7KrPuUSztUdU5A==",
-      "dev": true,
-      "dependencies": {
-        "ansi-regex": "^5.0.1"
-      },
-      "engines": {
-        "node": ">=8"
-      }
-    },
     "node_modules/string.prototype.trim": {
       "version": "1.2.8",
       "resolved": "https://registry.npmjs.org/string.prototype.trim/-/string.prototype.trim-1.2.8.tgz",
@@ -10261,8 +10054,7 @@
         "url": "https://github.com/sponsors/ljharb"
       }
     },
-    "node_modules/strip-ansi-cjs": {
-      "name": "strip-ansi",
+    "node_modules/strip-ansi": {
       "version": "6.0.1",
       "resolved": "https://registry.npmjs.org/strip-ansi/-/strip-ansi-6.0.1.tgz",
       "integrity": "sha512-Y38VPSHcqkFrCpFnQ9vuSXmquuv5oXOKpGeT6aGrr3o3Gc9AlVa6JBfUSOCnbxGGZF+/0ooI7KrPuUSztUdU5A==",
@@ -10274,11 +10066,15 @@
         "node": ">=8"
       }
     },
-    "node_modules/strip-ansi-cjs/node_modules/ansi-regex": {
-      "version": "5.0.1",
-      "resolved": "https://registry.npmjs.org/ansi-regex/-/ansi-regex-5.0.1.tgz",
-      "integrity": "sha512-quJQXlTSUGL2LH9SUXo8VwsY4soanhgo6LNSm84E1LBcE8s3O0wpdiRzyR9z/ZZJMlMWv37qOOb9pdJlMUEKFQ==",
+    "node_modules/strip-ansi-cjs": {
+      "name": "strip-ansi",
+      "version": "6.0.1",
+      "resolved": "https://registry.npmjs.org/strip-ansi/-/strip-ansi-6.0.1.tgz",
+      "integrity": "sha512-Y38VPSHcqkFrCpFnQ9vuSXmquuv5oXOKpGeT6aGrr3o3Gc9AlVa6JBfUSOCnbxGGZF+/0ooI7KrPuUSztUdU5A==",
       "dev": true,
+      "dependencies": {
+        "ansi-regex": "^5.0.1"
+      },
       "engines": {
         "node": ">=8"
       }
@@ -10361,15 +10157,6 @@
         "url": "https://github.com/sponsors/epoberezkin"
       }
     },
-    "node_modules/table/node_modules/ansi-regex": {
-      "version": "5.0.1",
-      "resolved": "https://registry.npmjs.org/ansi-regex/-/ansi-regex-5.0.1.tgz",
-      "integrity": "sha512-quJQXlTSUGL2LH9SUXo8VwsY4soanhgo6LNSm84E1LBcE8s3O0wpdiRzyR9z/ZZJMlMWv37qOOb9pdJlMUEKFQ==",
-      "dev": true,
-      "engines": {
-        "node": ">=8"
-      }
-    },
     "node_modules/table/node_modules/is-fullwidth-code-point": {
       "version": "3.0.0",
       "resolved": "https://registry.npmjs.org/is-fullwidth-code-point/-/is-fullwidth-code-point-3.0.0.tgz",
@@ -10408,18 +10195,6 @@
         "node": ">=8"
       }
     },
-    "node_modules/table/node_modules/strip-ansi": {
-      "version": "6.0.1",
-      "resolved": "https://registry.npmjs.org/strip-ansi/-/strip-ansi-6.0.1.tgz",
-      "integrity": "sha512-Y38VPSHcqkFrCpFnQ9vuSXmquuv5oXOKpGeT6aGrr3o3Gc9AlVa6JBfUSOCnbxGGZF+/0ooI7KrPuUSztUdU5A==",
-      "dev": true,
-      "dependencies": {
-        "ansi-regex": "^5.0.1"
-      },
-      "engines": {
-        "node": ">=8"
-      }
-    },
     "node_modules/term-size": {
       "version": "2.2.1",
       "resolved": "https://registry.npmjs.org/term-size/-/term-size-2.2.1.tgz",
@@ -10575,15 +10350,6 @@
         "node": ">=8.0.0"
       }
     },
-    "node_modules/tty-table/node_modules/ansi-regex": {
-      "version": "5.0.1",
-      "resolved": "https://registry.npmjs.org/ansi-regex/-/ansi-regex-5.0.1.tgz",
-      "integrity": "sha512-quJQXlTSUGL2LH9SUXo8VwsY4soanhgo6LNSm84E1LBcE8s3O0wpdiRzyR9z/ZZJMlMWv37qOOb9pdJlMUEKFQ==",
-      "dev": true,
-      "engines": {
-        "node": ">=8"
-      }
-    },
     "node_modules/tty-table/node_modules/ansi-styles": {
       "version": "4.3.0",
       "resolved": "https://registry.npmjs.org/ansi-styles/-/ansi-styles-4.3.0.tgz",
@@ -10642,18 +10408,6 @@
         "node": ">=8"
       }
     },
-    "node_modules/tty-table/node_modules/strip-ansi": {
-      "version": "6.0.1",
-      "resolved": "https://registry.npmjs.org/strip-ansi/-/strip-ansi-6.0.1.tgz",
-      "integrity": "sha512-Y38VPSHcqkFrCpFnQ9vuSXmquuv5oXOKpGeT6aGrr3o3Gc9AlVa6JBfUSOCnbxGGZF+/0ooI7KrPuUSztUdU5A==",
-      "dev": true,
-      "dependencies": {
-        "ansi-regex": "^5.0.1"
-      },
-      "engines": {
-        "node": ">=8"
-      }
-    },
     "node_modules/tty-table/node_modules/supports-color": {
       "version": "7.2.0",
       "resolved": "https://registry.npmjs.org/supports-color/-/supports-color-7.2.0.tgz",
@@ -11153,15 +10907,6 @@
         "node": ">=8"
       }
     },
-    "node_modules/widest-line/node_modules/ansi-regex": {
-      "version": "5.0.1",
-      "resolved": "https://registry.npmjs.org/ansi-regex/-/ansi-regex-5.0.1.tgz",
-      "integrity": "sha512-quJQXlTSUGL2LH9SUXo8VwsY4soanhgo6LNSm84E1LBcE8s3O0wpdiRzyR9z/ZZJMlMWv37qOOb9pdJlMUEKFQ==",
-      "dev": true,
-      "engines": {
-        "node": ">=8"
-      }
-    },
     "node_modules/widest-line/node_modules/is-fullwidth-code-point": {
       "version": "3.0.0",
       "resolved": "https://registry.npmjs.org/is-fullwidth-code-point/-/is-fullwidth-code-point-3.0.0.tgz",
@@ -11185,18 +10930,6 @@
         "node": ">=8"
       }
     },
-    "node_modules/widest-line/node_modules/strip-ansi": {
-      "version": "6.0.1",
-      "resolved": "https://registry.npmjs.org/strip-ansi/-/strip-ansi-6.0.1.tgz",
-      "integrity": "sha512-Y38VPSHcqkFrCpFnQ9vuSXmquuv5oXOKpGeT6aGrr3o3Gc9AlVa6JBfUSOCnbxGGZF+/0ooI7KrPuUSztUdU5A==",
-      "dev": true,
-      "dependencies": {
-        "ansi-regex": "^5.0.1"
-      },
-      "engines": {
-        "node": ">=8"
-      }
-    },
     "node_modules/word-wrap": {
       "version": "1.2.5",
       "resolved": "https://registry.npmjs.org/word-wrap/-/word-wrap-1.2.5.tgz",
@@ -11253,15 +10986,6 @@
         "url": "https://github.com/chalk/wrap-ansi?sponsor=1"
       }
     },
-    "node_modules/wrap-ansi-cjs/node_modules/ansi-regex": {
-      "version": "5.0.1",
-      "resolved": "https://registry.npmjs.org/ansi-regex/-/ansi-regex-5.0.1.tgz",
-      "integrity": "sha512-quJQXlTSUGL2LH9SUXo8VwsY4soanhgo6LNSm84E1LBcE8s3O0wpdiRzyR9z/ZZJMlMWv37qOOb9pdJlMUEKFQ==",
-      "dev": true,
-      "engines": {
-        "node": ">=8"
-      }
-    },
     "node_modules/wrap-ansi-cjs/node_modules/ansi-styles": {
       "version": "4.3.0",
       "resolved": "https://registry.npmjs.org/ansi-styles/-/ansi-styles-4.3.0.tgz",
@@ -11318,27 +11042,6 @@
         "node": ">=8"
       }
     },
-    "node_modules/wrap-ansi-cjs/node_modules/strip-ansi": {
-      "version": "6.0.1",
-      "resolved": "https://registry.npmjs.org/strip-ansi/-/strip-ansi-6.0.1.tgz",
-      "integrity": "sha512-Y38VPSHcqkFrCpFnQ9vuSXmquuv5oXOKpGeT6aGrr3o3Gc9AlVa6JBfUSOCnbxGGZF+/0ooI7KrPuUSztUdU5A==",
-      "dev": true,
-      "dependencies": {
-        "ansi-regex": "^5.0.1"
-      },
-      "engines": {
-        "node": ">=8"
-      }
-    },
-    "node_modules/wrap-ansi/node_modules/ansi-regex": {
-      "version": "5.0.1",
-      "resolved": "https://registry.npmjs.org/ansi-regex/-/ansi-regex-5.0.1.tgz",
-      "integrity": "sha512-quJQXlTSUGL2LH9SUXo8VwsY4soanhgo6LNSm84E1LBcE8s3O0wpdiRzyR9z/ZZJMlMWv37qOOb9pdJlMUEKFQ==",
-      "dev": true,
-      "engines": {
-        "node": ">=8"
-      }
-    },
     "node_modules/wrap-ansi/node_modules/ansi-styles": {
       "version": "4.3.0",
       "resolved": "https://registry.npmjs.org/ansi-styles/-/ansi-styles-4.3.0.tgz",
@@ -11395,18 +11098,6 @@
         "node": ">=8"
       }
     },
-    "node_modules/wrap-ansi/node_modules/strip-ansi": {
-      "version": "6.0.1",
-      "resolved": "https://registry.npmjs.org/strip-ansi/-/strip-ansi-6.0.1.tgz",
-      "integrity": "sha512-Y38VPSHcqkFrCpFnQ9vuSXmquuv5oXOKpGeT6aGrr3o3Gc9AlVa6JBfUSOCnbxGGZF+/0ooI7KrPuUSztUdU5A==",
-      "dev": true,
-      "dependencies": {
-        "ansi-regex": "^5.0.1"
-      },
-      "engines": {
-        "node": ">=8"
-      }
-    },
     "node_modules/wrappy": {
       "version": "1.0.2",
       "resolved": "https://registry.npmjs.org/wrappy/-/wrappy-1.0.2.tgz",
@@ -11519,15 +11210,6 @@
         "node": ">=8"
       }
     },
-    "node_modules/yargs/node_modules/ansi-regex": {
-      "version": "5.0.1",
-      "resolved": "https://registry.npmjs.org/ansi-regex/-/ansi-regex-5.0.1.tgz",
-      "integrity": "sha512-quJQXlTSUGL2LH9SUXo8VwsY4soanhgo6LNSm84E1LBcE8s3O0wpdiRzyR9z/ZZJMlMWv37qOOb9pdJlMUEKFQ==",
-      "dev": true,
-      "engines": {
-        "node": ">=8"
-      }
-    },
     "node_modules/yargs/node_modules/is-fullwidth-code-point": {
       "version": "3.0.0",
       "resolved": "https://registry.npmjs.org/is-fullwidth-code-point/-/is-fullwidth-code-point-3.0.0.tgz",
@@ -11551,18 +11233,6 @@
         "node": ">=8"
       }
     },
-    "node_modules/yargs/node_modules/strip-ansi": {
-      "version": "6.0.1",
-      "resolved": "https://registry.npmjs.org/strip-ansi/-/strip-ansi-6.0.1.tgz",
-      "integrity": "sha512-Y38VPSHcqkFrCpFnQ9vuSXmquuv5oXOKpGeT6aGrr3o3Gc9AlVa6JBfUSOCnbxGGZF+/0ooI7KrPuUSztUdU5A==",
-      "dev": true,
-      "dependencies": {
-        "ansi-regex": "^5.0.1"
-      },
-      "engines": {
-        "node": ">=8"
-      }
-    },
     "node_modules/yargs/node_modules/yargs-parser": {
       "version": "21.1.1",
       "resolved": "https://registry.npmjs.org/yargs-parser/-/yargs-parser-21.1.1.tgz",

+ 1 - 1
package.json

@@ -78,7 +78,7 @@
     "prettier-plugin-solidity": "^1.1.0",
     "rimraf": "^5.0.1",
     "semver": "^7.3.5",
-    "solhint": "^4.0.0",
+    "solhint": "^5.0.0",
     "solhint-plugin-openzeppelin": "file:scripts/solhint-custom",
     "solidity-ast": "^0.4.50",
     "solidity-coverage": "^0.8.5",

+ 1 - 1
scripts/checks/compare-layout.js

@@ -10,7 +10,7 @@ for (const name in oldLayout) {
   if (name in newLayout) {
     const report = getStorageUpgradeReport(oldLayout[name], newLayout[name], {});
     if (!report.ok) {
-      console.log(`Storage layout incompatilibity found in ${name}:`);
+      console.log(`Storage layout incompatibility found in ${name}:`);
       console.log(report.explain());
       process.exitCode = 1;
     }

+ 1 - 0
scripts/generate/templates/Arrays.js

@@ -356,6 +356,7 @@ const unsafeSetLength = type => `
  * WARNING: this does not clear elements if length is reduced, of initialize elements if length is increased.
  */
 function unsafeSetLength(${type}[] storage array, uint256 len) internal {
+    /// @solidity memory-safe-assembly
     assembly {
         sstore(array.slot, len)
     }

+ 41 - 5
scripts/generate/templates/SlotDerivation.t.js

@@ -6,17 +6,26 @@ const header = `\
 pragma solidity ^0.8.20;
 
 import {Test} from "forge-std/Test.sol";
-
+import {SymTest} from "halmos-cheatcodes/SymTest.sol";
 import {SlotDerivation} from "@openzeppelin/contracts/utils/SlotDerivation.sol";
 `;
 
 const array = `\
 bytes[] private _array;
 
+function symbolicDeriveArray(uint256 length, uint256 offset) public {
+  vm.assume(length > 0);
+  vm.assume(offset < length);
+  _assertDeriveArray(length, offset);
+}
+
 function testDeriveArray(uint256 length, uint256 offset) public {
   length = bound(length, 1, type(uint256).max);
   offset = bound(offset, 0, length - 1);
+  _assertDeriveArray(length, offset);
+}
 
+function _assertDeriveArray(uint256 length, uint256 offset) public {
   bytes32 baseSlot;
   assembly {
     baseSlot := _array.slot
@@ -33,10 +42,10 @@ function testDeriveArray(uint256 length, uint256 offset) public {
 }
 `;
 
-const mapping = ({ type, name, isValueType }) => `\
+const mapping = ({ type, name }) => `\
 mapping(${type} => bytes) private _${type}Mapping;
 
-function testDeriveMapping${name}(${type} ${isValueType ? '' : 'memory'} key) public {
+function testSymbolicDeriveMapping${name}(${type} key) public {
   bytes32 baseSlot;
   assembly {
     baseSlot := _${type}Mapping.slot
@@ -52,10 +61,37 @@ function testDeriveMapping${name}(${type} ${isValueType ? '' : 'memory'} key) pu
 }
 `;
 
+const boundedMapping = ({ type, name }) => `\
+mapping(${type} => bytes) private _${type}Mapping;
+
+function testDeriveMapping${name}(${type} memory key) public {
+  _assertDeriveMapping${name}(key);
+}
+
+function symbolicDeriveMapping${name}() public {
+  _assertDeriveMapping${name}(svm.create${name}(256, "DeriveMapping${name}Input"));
+}
+
+function _assertDeriveMapping${name}(${type} memory key) internal {
+  bytes32 baseSlot;
+  assembly {
+      baseSlot := _${type}Mapping.slot
+  }
+
+  bytes storage derived = _${type}Mapping[key];
+  bytes32 derivedSlot;
+  assembly {
+      derivedSlot := derived.slot
+  }
+
+  assertEq(baseSlot.deriveMapping(key), derivedSlot);
+}
+`;
+
 // GENERATE
 module.exports = format(
   header.trimEnd(),
-  'contract SlotDerivationTest is Test {',
+  'contract SlotDerivationTest is Test, SymTest {',
   'using SlotDerivation for bytes32;',
   '',
   array,
@@ -68,6 +104,6 @@ module.exports = format(
         isValueType: type.isValueType,
       })),
     ),
-  ).map(type => mapping(type)),
+  ).map(type => (type.isValueType ? mapping(type) : boundedMapping(type))),
   '}',
 );

+ 1 - 1
test/governance/Governor.t.sol

@@ -26,7 +26,7 @@ contract GovernorInternalTest is Test, Governor {
         assertFalse(_isValidDescriptionForProposer(actualProposer, description));
     }
 
-    // We don't need to truly implement implement the missing functions because we are just testing
+    // We don't need to truly implement the missing functions because we are just testing
     // internal helpers.
 
     function clock() public pure override returns (uint48) {}

+ 2 - 2
test/governance/extensions/GovernorVotesQuorumFraction.test.js

@@ -74,7 +74,7 @@ describe('GovernorVotesQuorumFraction', function () {
         );
       });
 
-      it('quroum reached', async function () {
+      it('quorum reached', async function () {
         await this.helper.propose();
         await this.helper.waitForSnapshot();
         await this.helper.connect(this.voter1).vote({ support: VoteType.For });
@@ -82,7 +82,7 @@ describe('GovernorVotesQuorumFraction', function () {
         await this.helper.execute();
       });
 
-      it('quroum not reached', async function () {
+      it('quorum not reached', async function () {
         await this.helper.propose();
         await this.helper.waitForSnapshot();
         await this.helper.connect(this.voter2).vote({ support: VoteType.For });

+ 1 - 1
test/proxy/Clones.t.sol

@@ -6,7 +6,7 @@ import {Test} from "forge-std/Test.sol";
 import {Clones} from "@openzeppelin/contracts/proxy/Clones.sol";
 
 contract ClonesTest is Test {
-    function testPredictDeterministicAddressSpillage(address implementation, bytes32 salt) public {
+    function testSymbolicPredictDeterministicAddressSpillage(address implementation, bytes32 salt) public {
         address predicted = Clones.predictDeterministicAddress(implementation, salt);
         bytes32 spillage;
         /// @solidity memory-safe-assembly

+ 17 - 1
test/utils/Arrays.t.sol

@@ -3,11 +3,27 @@
 pragma solidity ^0.8.20;
 
 import {Test} from "forge-std/Test.sol";
+import {SymTest} from "halmos-cheatcodes/SymTest.sol";
 import {Arrays} from "@openzeppelin/contracts/utils/Arrays.sol";
 
-contract ArraysTest is Test {
+contract ArraysTest is Test, SymTest {
     function testSort(uint256[] memory values) public {
         Arrays.sort(values);
+        _assertSort(values);
+    }
+
+    function symbolicSort() public {
+        uint256[] memory values = new uint256[](3);
+        for (uint256 i = 0; i < 3; i++) {
+            values[i] = svm.createUint256("arrayElement");
+        }
+        Arrays.sort(values);
+        _assertSort(values);
+    }
+
+    /// Asserts
+
+    function _assertSort(uint256[] memory values) internal {
         for (uint256 i = 1; i < values.length; ++i) {
             assertLe(values[i - 1], values[i]);
         }

+ 1 - 1
test/utils/Create2.t.sol

@@ -6,7 +6,7 @@ import {Test} from "forge-std/Test.sol";
 import {Create2} from "@openzeppelin/contracts/utils/Create2.sol";
 
 contract Create2Test is Test {
-    function testComputeAddressSpillage(bytes32 salt, bytes32 bytecodeHash, address deployer) public {
+    function testSymbolicComputeAddressSpillage(bytes32 salt, bytes32 bytecodeHash, address deployer) public {
         address predicted = Create2.computeAddress(salt, bytecodeHash, deployer);
         bytes32 spillage;
         /// @solidity memory-safe-assembly

+ 2 - 2
test/utils/Packing.t.sol

@@ -9,7 +9,7 @@ contract PackingTest is Test {
     using Packing for *;
 
     // Pack a pair of arbitrary uint128, and check that split recovers the correct values
-    function testUint128x2(uint128 first, uint128 second) external {
+    function testSymbolicUint128x2(uint128 first, uint128 second) external {
         Packing.Uint128x2 packed = Packing.pack(first, second);
         assertEq(packed.first(), first);
         assertEq(packed.second(), second);
@@ -20,7 +20,7 @@ contract PackingTest is Test {
     }
 
     // split an arbitrary bytes32 into a pair of uint128, and check that repack matches the input
-    function testUint128x2(bytes32 input) external {
+    function testSymbolicUint128x2(bytes32 input) external {
         (uint128 first, uint128 second) = input.asUint128x2().split();
         assertEq(Packing.pack(first, second).asBytes32(), input);
     }

+ 63 - 9
test/utils/ShortStrings.t.sol

@@ -3,48 +3,102 @@
 pragma solidity ^0.8.20;
 
 import {Test} from "forge-std/Test.sol";
+import {SymTest} from "halmos-cheatcodes/SymTest.sol";
 
 import {ShortStrings, ShortString} from "@openzeppelin/contracts/utils/ShortStrings.sol";
 
-contract ShortStringsTest is Test {
+contract ShortStringsTest is Test, SymTest {
     string _fallback;
 
     function testRoundtripShort(string memory input) external {
         vm.assume(_isShort(input));
+        _assertRoundtripShort(input);
+    }
+
+    function symbolicRoundtripShort() external {
+        string memory input = svm.createString(31, "RoundtripShortInput");
+        _assertRoundtripShort(input);
+    }
+
+    function testRoundtripWithFallback(string memory input, string memory fallbackInitial) external {
+        _assertRoundtripWithFallback(input, fallbackInitial);
+    }
+
+    function symbolicRoundtripWithFallbackLong() external {
+        string memory input = svm.createString(256, "RoundtripWithFallbackInput");
+        string memory fallbackInitial = svm.createString(256, "RoundtripWithFallbackFallbackInitial");
+        _assertRoundtripWithFallback(input, fallbackInitial);
+    }
+
+    function symbolicRoundtripWithFallbackShort() external {
+        string memory input = svm.createString(31, "RoundtripWithFallbackInput");
+        string memory fallbackInitial = svm.createString(31, "RoundtripWithFallbackFallbackInitial");
+        _assertRoundtripWithFallback(input, fallbackInitial);
+    }
+
+    function testRevertLong(string memory input) external {
+        vm.assume(!_isShort(input));
+        _assertRevertLong(input);
+    }
+
+    function testLengthShort(string memory input) external {
+        vm.assume(_isShort(input));
+        _assertLengthShort(input);
+    }
+
+    function symbolicLengthShort() external {
+        string memory input = svm.createString(31, "LengthShortInput");
+        _assertLengthShort(input);
+    }
+
+    function testLengthWithFallback(string memory input, string memory fallbackInitial) external {
+        _fallback = fallbackInitial;
+        _assertLengthWithFallback(input);
+    }
+
+    function symbolicLengthWithFallback() external {
+        uint256 length = 256;
+        string memory input = svm.createString(length, "LengthWithFallbackInput");
+        string memory fallbackInitial = svm.createString(length, "LengthWithFallbackFallbackInitial");
+        _fallback = fallbackInitial;
+        _assertLengthWithFallback(input);
+    }
+
+    /// Assertions
+
+    function _assertRoundtripShort(string memory input) internal {
         ShortString short = ShortStrings.toShortString(input);
         string memory output = ShortStrings.toString(short);
         assertEq(input, output);
     }
 
-    function testRoundtripWithFallback(string memory input, string memory fallbackInitial) external {
+    function _assertRoundtripWithFallback(string memory input, string memory fallbackInitial) internal {
         _fallback = fallbackInitial; // Make sure that the initial value has no effect
         ShortString short = ShortStrings.toShortStringWithFallback(input, _fallback);
         string memory output = ShortStrings.toStringWithFallback(short, _fallback);
         assertEq(input, output);
     }
 
-    function testRevertLong(string memory input) external {
-        vm.assume(!_isShort(input));
+    function _assertRevertLong(string memory input) internal {
         vm.expectRevert(abi.encodeWithSelector(ShortStrings.StringTooLong.selector, input));
         this.toShortString(input);
     }
 
-    function testLengthShort(string memory input) external {
-        vm.assume(_isShort(input));
-        uint256 inputLength = bytes(input).length;
+    function _assertLengthShort(string memory input) internal {
         ShortString short = ShortStrings.toShortString(input);
         uint256 shortLength = ShortStrings.byteLength(short);
+        uint256 inputLength = bytes(input).length;
         assertEq(inputLength, shortLength);
     }
 
-    function testLengthWithFallback(string memory input, string memory fallbackInitial) external {
-        _fallback = fallbackInitial;
+    function _assertLengthWithFallback(string memory input) internal {
         uint256 inputLength = bytes(input).length;
         ShortString short = ShortStrings.toShortStringWithFallback(input, _fallback);
         uint256 shortLength = ShortStrings.byteLengthWithFallback(short, _fallback);
         assertEq(inputLength, shortLength);
     }
 
+    /// Helpers
     function toShortString(string memory input) external pure returns (ShortString) {
         return ShortStrings.toShortString(input);
     }

+ 35 - 10
test/utils/SlotDerivation.t.sol

@@ -4,18 +4,27 @@
 pragma solidity ^0.8.20;
 
 import {Test} from "forge-std/Test.sol";
-
+import {SymTest} from "halmos-cheatcodes/SymTest.sol";
 import {SlotDerivation} from "@openzeppelin/contracts/utils/SlotDerivation.sol";
 
-contract SlotDerivationTest is Test {
+contract SlotDerivationTest is Test, SymTest {
     using SlotDerivation for bytes32;
 
     bytes[] private _array;
 
+    function symbolicDeriveArray(uint256 length, uint256 offset) public {
+        vm.assume(length > 0);
+        vm.assume(offset < length);
+        _assertDeriveArray(length, offset);
+    }
+
     function testDeriveArray(uint256 length, uint256 offset) public {
         length = bound(length, 1, type(uint256).max);
         offset = bound(offset, 0, length - 1);
+        _assertDeriveArray(length, offset);
+    }
 
+    function _assertDeriveArray(uint256 length, uint256 offset) public {
         bytes32 baseSlot;
         assembly {
             baseSlot := _array.slot
@@ -33,7 +42,7 @@ contract SlotDerivationTest is Test {
 
     mapping(address => bytes) private _addressMapping;
 
-    function testDeriveMappingAddress(address key) public {
+    function testSymbolicDeriveMappingAddress(address key) public {
         bytes32 baseSlot;
         assembly {
             baseSlot := _addressMapping.slot
@@ -50,7 +59,7 @@ contract SlotDerivationTest is Test {
 
     mapping(bool => bytes) private _boolMapping;
 
-    function testDeriveMappingBoolean(bool key) public {
+    function testSymbolicDeriveMappingBoolean(bool key) public {
         bytes32 baseSlot;
         assembly {
             baseSlot := _boolMapping.slot
@@ -67,7 +76,7 @@ contract SlotDerivationTest is Test {
 
     mapping(bytes32 => bytes) private _bytes32Mapping;
 
-    function testDeriveMappingBytes32(bytes32 key) public {
+    function testSymbolicDeriveMappingBytes32(bytes32 key) public {
         bytes32 baseSlot;
         assembly {
             baseSlot := _bytes32Mapping.slot
@@ -84,7 +93,7 @@ contract SlotDerivationTest is Test {
 
     mapping(bytes4 => bytes) private _bytes4Mapping;
 
-    function testDeriveMappingBytes4(bytes4 key) public {
+    function testSymbolicDeriveMappingBytes4(bytes4 key) public {
         bytes32 baseSlot;
         assembly {
             baseSlot := _bytes4Mapping.slot
@@ -101,7 +110,7 @@ contract SlotDerivationTest is Test {
 
     mapping(uint256 => bytes) private _uint256Mapping;
 
-    function testDeriveMappingUint256(uint256 key) public {
+    function testSymbolicDeriveMappingUint256(uint256 key) public {
         bytes32 baseSlot;
         assembly {
             baseSlot := _uint256Mapping.slot
@@ -118,7 +127,7 @@ contract SlotDerivationTest is Test {
 
     mapping(uint32 => bytes) private _uint32Mapping;
 
-    function testDeriveMappingUint32(uint32 key) public {
+    function testSymbolicDeriveMappingUint32(uint32 key) public {
         bytes32 baseSlot;
         assembly {
             baseSlot := _uint32Mapping.slot
@@ -135,7 +144,7 @@ contract SlotDerivationTest is Test {
 
     mapping(int256 => bytes) private _int256Mapping;
 
-    function testDeriveMappingInt256(int256 key) public {
+    function testSymbolicDeriveMappingInt256(int256 key) public {
         bytes32 baseSlot;
         assembly {
             baseSlot := _int256Mapping.slot
@@ -152,7 +161,7 @@ contract SlotDerivationTest is Test {
 
     mapping(int32 => bytes) private _int32Mapping;
 
-    function testDeriveMappingInt32(int32 key) public {
+    function testSymbolicDeriveMappingInt32(int32 key) public {
         bytes32 baseSlot;
         assembly {
             baseSlot := _int32Mapping.slot
@@ -170,6 +179,14 @@ contract SlotDerivationTest is Test {
     mapping(string => bytes) private _stringMapping;
 
     function testDeriveMappingString(string memory key) public {
+        _assertDeriveMappingString(key);
+    }
+
+    function symbolicDeriveMappingString() public {
+        _assertDeriveMappingString(svm.createString(256, "DeriveMappingStringInput"));
+    }
+
+    function _assertDeriveMappingString(string memory key) internal {
         bytes32 baseSlot;
         assembly {
             baseSlot := _stringMapping.slot
@@ -187,6 +204,14 @@ contract SlotDerivationTest is Test {
     mapping(bytes => bytes) private _bytesMapping;
 
     function testDeriveMappingBytes(bytes memory key) public {
+        _assertDeriveMappingBytes(key);
+    }
+
+    function symbolicDeriveMappingBytes() public {
+        _assertDeriveMappingBytes(svm.createBytes(256, "DeriveMappingBytesInput"));
+    }
+
+    function _assertDeriveMappingBytes(bytes memory key) internal {
         bytes32 baseSlot;
         assembly {
             baseSlot := _bytesMapping.slot

+ 10 - 0
test/utils/math/Math.t.sol

@@ -7,6 +7,16 @@ import {Test, stdError} from "forge-std/Test.sol";
 import {Math} from "@openzeppelin/contracts/utils/math/Math.sol";
 
 contract MathTest is Test {
+    function testSymbolicTernary(bool f, uint256 a, uint256 b) public {
+        assertEq(Math.ternary(f, a, b), f ? a : b);
+    }
+
+    // MIN & MAX
+    function testSymbolicMinMax(uint256 a, uint256 b) public {
+        assertEq(Math.min(a, b), a < b ? a : b);
+        assertEq(Math.max(a, b), a > b ? a : b);
+    }
+
     // CEILDIV
     function testCeilDiv(uint256 a, uint256 b) public {
         vm.assume(b > 0);

+ 13 - 3
test/utils/math/SignedMath.t.sol

@@ -8,8 +8,18 @@ import {Math} from "../../../contracts/utils/math/Math.sol";
 import {SignedMath} from "../../../contracts/utils/math/SignedMath.sol";
 
 contract SignedMathTest is Test {
+    function testSymbolicTernary(bool f, int256 a, int256 b) public {
+        assertEq(SignedMath.ternary(f, a, b), f ? a : b);
+    }
+
+    // MIN & MAX
+    function testSymbolicMinMax(int256 a, int256 b) public {
+        assertEq(SignedMath.min(a, b), a < b ? a : b);
+        assertEq(SignedMath.max(a, b), a > b ? a : b);
+    }
+
     // MIN
-    function testMin(int256 a, int256 b) public {
+    function testSymbolicMin(int256 a, int256 b) public {
         int256 result = SignedMath.min(a, b);
 
         assertLe(result, a);
@@ -18,7 +28,7 @@ contract SignedMathTest is Test {
     }
 
     // MAX
-    function testMax(int256 a, int256 b) public {
+    function testSymbolicMax(int256 a, int256 b) public {
         int256 result = SignedMath.max(a, b);
 
         assertGe(result, a);
@@ -59,7 +69,7 @@ contract SignedMathTest is Test {
     }
 
     // ABS
-    function testAbs(int256 a) public {
+    function testSymbolicAbs(int256 a) public {
         uint256 result = SignedMath.abs(a);
 
         unchecked {

+ 79 - 0
test/utils/structs/CircularBuffer.test.js

@@ -0,0 +1,79 @@
+const { ethers } = require('hardhat');
+const { expect } = require('chai');
+const { loadFixture } = require('@nomicfoundation/hardhat-network-helpers');
+const { PANIC_CODES } = require('@nomicfoundation/hardhat-chai-matchers/panic');
+
+const { generators } = require('../../helpers/random');
+
+const LENGTH = 4;
+
+async function fixture() {
+  const mock = await ethers.deployContract('$CircularBuffer');
+  await mock.$setup(0, LENGTH);
+  return { mock };
+}
+
+describe('CircularBuffer', function () {
+  beforeEach(async function () {
+    Object.assign(this, await loadFixture(fixture));
+  });
+
+  it('starts empty', async function () {
+    expect(await this.mock.$count(0)).to.equal(0n);
+    expect(await this.mock.$length(0)).to.equal(LENGTH);
+    expect(await this.mock.$includes(0, ethers.ZeroHash)).to.be.false;
+    await expect(this.mock.$last(0, 0)).to.be.revertedWithPanic(PANIC_CODES.ARRAY_ACCESS_OUT_OF_BOUNDS);
+  });
+
+  it('push', async function () {
+    const values = Array.from({ length: LENGTH + 3 }, generators.bytes32);
+
+    for (const [i, value] of values.map((v, i) => [i, v])) {
+      // push value
+      await this.mock.$push(0, value);
+
+      // view of the values
+      const pushed = values.slice(0, i + 1);
+      const stored = pushed.slice(-LENGTH);
+      const dropped = pushed.slice(0, -LENGTH);
+
+      // check count
+      expect(await this.mock.$length(0)).to.equal(LENGTH);
+      expect(await this.mock.$count(0)).to.equal(stored.length);
+
+      // check last
+      for (const j in stored) {
+        expect(await this.mock.$last(0, j)).to.equal(stored.at(-j - 1));
+      }
+      await expect(this.mock.$last(0, stored.length + 1)).to.be.revertedWithPanic(
+        PANIC_CODES.ARRAY_ACCESS_OUT_OF_BOUNDS,
+      );
+
+      // check included and non-included values
+      for (const v of stored) {
+        expect(await this.mock.$includes(0, v)).to.be.true;
+      }
+      for (const v of dropped) {
+        expect(await this.mock.$includes(0, v)).to.be.false;
+      }
+      expect(await this.mock.$includes(0, ethers.ZeroHash)).to.be.false;
+    }
+  });
+
+  it('clear', async function () {
+    const value = generators.bytes32();
+    await this.mock.$push(0, value);
+
+    expect(await this.mock.$count(0)).to.equal(1n);
+    expect(await this.mock.$length(0)).to.equal(LENGTH);
+    expect(await this.mock.$includes(0, value)).to.be.true;
+    await this.mock.$last(0, 0); // not revert
+
+    await this.mock.$clear(0);
+
+    expect(await this.mock.$count(0)).to.equal(0n);
+    expect(await this.mock.$length(0)).to.equal(LENGTH);
+    expect(await this.mock.$includes(0, value)).to.be.false;
+    await expect(this.mock.$last(0, 0)).to.be.revertedWithPanic(PANIC_CODES.ARRAY_ACCESS_OUT_OF_BOUNDS);
+  });
+});