├── .gas-snapshot ├── .github ├── pull_request_template.md └── workflows │ └── ci.yml ├── .gitignore ├── LICENSE.txt ├── README.md ├── foundry.toml ├── logo.svg ├── package.json ├── src └── utils │ ├── LibString.sol │ ├── LibT.sol │ └── ReentrancyGuard.sol └── test ├── LibString.t.sol ├── LibT.t.sol ├── README.md ├── ReentrancyGuard.t.sol └── utils ├── InvariantTest.sol ├── SoladyTest.sol ├── TestPlus.sol ├── forge-std ├── Script.sol ├── Test.sol ├── Vm.sol └── ds-test │ └── test.sol └── mocks └── MockReentrancyGuard.sol /.gas-snapshot: -------------------------------------------------------------------------------- 1 | LibStringTest:testAddressToHexStringZeroRightPadded(address) (runs: 256, μ: 3384, ~: 3384) 2 | LibStringTest:testBytesToHexString() (gas: 6422) 3 | LibStringTest:testBytesToHexString(bytes) (runs: 256, μ: 690707, ~: 594542) 4 | LibStringTest:testBytesToHexStringNoPrefix() (gas: 6039) 5 | LibStringTest:testBytesToHexStringNoPrefix(bytes) (runs: 256, μ: 809930, ~: 594334) 6 | LibStringTest:testContains() (gas: 43812) 7 | LibStringTest:testFromAddressToHexString() (gas: 3765) 8 | LibStringTest:testFromAddressToHexStringChecksummed() (gas: 40345) 9 | LibStringTest:testFromAddressToHexStringChecksummedDifferential(uint256) (runs: 256, μ: 692226, ~: 583789) 10 | LibStringTest:testFromAddressToHexStringWithLeadingZeros() (gas: 3741) 11 | LibStringTest:testHexStringNoPrefixVariants(uint256,uint256) (runs: 256, μ: 641350, ~: 593918) 12 | LibStringTest:testNormalizeSmallString() (gas: 1349) 13 | LibStringTest:testNormalizeSmallString(bytes32) (runs: 256, μ: 3105, ~: 4183) 14 | LibStringTest:testStringConcat() (gas: 6735) 15 | LibStringTest:testStringConcat(string,string) (runs: 256, μ: 672637, ~: 575375) 16 | LibStringTest:testStringConcatOriginal() (gas: 7994) 17 | LibStringTest:testStringDirectReturn() (gas: 8186) 18 | LibStringTest:testStringDirectReturn(string) (runs: 256, μ: 3585, ~: 3426) 19 | LibStringTest:testStringEndsWith() (gas: 2798) 20 | LibStringTest:testStringEndsWith(uint256) (runs: 256, μ: 828826, ~: 608504) 21 | LibStringTest:testStringEq(string,string) (runs: 256, μ: 1576, ~: 1577) 22 | LibStringTest:testStringEqs() (gas: 2149) 23 | LibStringTest:testStringEscapeHTML() (gas: 11844) 24 | LibStringTest:testStringEscapeHTML(uint256) (runs: 256, μ: 779111, ~: 625012) 25 | LibStringTest:testStringEscapeJSON() (gas: 53068) 26 | LibStringTest:testStringEscapeJSONHexEncode() (gas: 736800) 27 | LibStringTest:testStringIndexOf() (gas: 17348) 28 | LibStringTest:testStringIndexOf(uint256) (runs: 256, μ: 838879, ~: 619190) 29 | LibStringTest:testStringIndicesOf() (gas: 11642) 30 | LibStringTest:testStringIndicesOf(uint256) (runs: 256, μ: 747502, ~: 617517) 31 | LibStringTest:testStringIs7BitASCII() (gas: 203501) 32 | LibStringTest:testStringIs7BitASCIIDifferential(bytes) (runs: 256, μ: 708983, ~: 574187) 33 | LibStringTest:testStringLastIndexOf() (gas: 23952) 34 | LibStringTest:testStringLastIndexOf(uint256) (runs: 256, μ: 808848, ~: 615043) 35 | LibStringTest:testStringLowerDifferential() (gas: 3618133) 36 | LibStringTest:testStringLowerDifferential(string) (runs: 256, μ: 8873, ~: 8506) 37 | LibStringTest:testStringLowerOriginal() (gas: 1771) 38 | LibStringTest:testStringPackAndUnpackOne() (gas: 724714) 39 | LibStringTest:testStringPackAndUnpackOne(string) (runs: 256, μ: 700607, ~: 573980) 40 | LibStringTest:testStringPackAndUnpackOneDifferential(string) (runs: 256, μ: 688368, ~: 573268) 41 | LibStringTest:testStringPackAndUnpackTwo() (gas: 885198) 42 | LibStringTest:testStringPackAndUnpackTwo(string,string) (runs: 256, μ: 661591, ~: 575678) 43 | LibStringTest:testStringPackAndUnpackTwoDifferential(string,string) (runs: 256, μ: 673562, ~: 574227) 44 | LibStringTest:testStringRepeat() (gas: 8445) 45 | LibStringTest:testStringRepeat(string,uint256) (runs: 256, μ: 693370, ~: 576818) 46 | LibStringTest:testStringRepeatOriginal() (gas: 13538) 47 | LibStringTest:testStringReplace(uint256) (runs: 256, μ: 667960, ~: 622556) 48 | LibStringTest:testStringReplaceLong() (gas: 9615) 49 | LibStringTest:testStringReplaceMedium() (gas: 8368) 50 | LibStringTest:testStringReplaceShort() (gas: 18628) 51 | LibStringTest:testStringRuneCount() (gas: 2975992) 52 | LibStringTest:testStringRuneCountDifferential(string) (runs: 256, μ: 6043, ~: 5821) 53 | LibStringTest:testStringSlice() (gas: 17049) 54 | LibStringTest:testStringSlice(uint256) (runs: 256, μ: 682270, ~: 618818) 55 | LibStringTest:testStringSplit() (gas: 19683) 56 | LibStringTest:testStringSplit(uint256) (runs: 256, μ: 718602, ~: 617835) 57 | LibStringTest:testStringStartsWith() (gas: 2558) 58 | LibStringTest:testStringStartsWith(uint256) (runs: 256, μ: 708358, ~: 605784) 59 | LibStringTest:testStringUpperDifferential() (gas: 3549675) 60 | LibStringTest:testStringUpperDifferential(string) (runs: 256, μ: 8874, ~: 8507) 61 | LibStringTest:testStringUpperOriginal() (gas: 1769) 62 | LibStringTest:testToHexStringFixedLengthInsufficientLength() (gas: 3390) 63 | LibStringTest:testToHexStringFixedLengthPositiveNumberLong() (gas: 4446) 64 | LibStringTest:testToHexStringFixedLengthPositiveNumberShort() (gas: 1494) 65 | LibStringTest:testToHexStringFixedLengthUint256Max() (gas: 4491) 66 | LibStringTest:testToHexStringFixedLengthZeroRightPadded(uint256,uint256) (runs: 256, μ: 8101, ~: 4897) 67 | LibStringTest:testToHexStringPositiveNumber() (gas: 1399) 68 | LibStringTest:testToHexStringUint256Max() (gas: 4210) 69 | LibStringTest:testToHexStringZero() (gas: 1336) 70 | LibStringTest:testToHexStringZeroRightPadded(uint256) (runs: 256, μ: 2020, ~: 1337) 71 | LibStringTest:testToMinimalHexStringNoPrefixPositiveNumber() (gas: 5959) 72 | LibStringTest:testToMinimalHexStringNoPrefixUint256Max() (gas: 4054) 73 | LibStringTest:testToMinimalHexStringNoPrefixZero() (gas: 1340) 74 | LibStringTest:testToMinimalHexStringNoPrefixZeroRightPadded(uint256) (runs: 256, μ: 2011, ~: 1335) 75 | LibStringTest:testToMinimalHexStringPositiveNumber() (gas: 6092) 76 | LibStringTest:testToMinimalHexStringUint256Max() (gas: 4237) 77 | LibStringTest:testToMinimalHexStringZero() (gas: 1383) 78 | LibStringTest:testToMinimalHexStringZeroRightPadded(uint256) (runs: 256, μ: 2044, ~: 1361) 79 | LibStringTest:testToSmallString() (gas: 4420) 80 | LibStringTest:testToStringPositiveNumber() (gas: 1461) 81 | LibStringTest:testToStringPositiveNumberBrutalized() (gas: 1444820) 82 | LibStringTest:testToStringSignedDifferential(int256) (runs: 256, μ: 649797, ~: 574779) 83 | LibStringTest:testToStringSignedGas() (gas: 7212) 84 | LibStringTest:testToStringSignedMemory(int256) (runs: 256, μ: 709754, ~: 573844) 85 | LibStringTest:testToStringSignedOriginalGas() (gas: 9760) 86 | LibStringTest:testToStringUint256Max() (gas: 7432) 87 | LibStringTest:testToStringUint256MaxBrutalized() (gas: 586571) 88 | LibStringTest:testToStringZero() (gas: 1186) 89 | LibStringTest:testToStringZeroBrutalized() (gas: 573986) 90 | LibStringTest:testToStringZeroRightPadded(uint256) (runs: 256, μ: 689385, ~: 573834) 91 | LibStringTest:test__codesize() (gas: 42004) 92 | LibTTest:testLibT(bytes32,uint256) (runs: 256, μ: 1003, ~: 1003) 93 | LibTTest:test__codesize() (gas: 1805) 94 | ReentrancyGuardTest:testRecursiveDirectUnguardedCall() (gas: 32203) 95 | ReentrancyGuardTest:testRecursiveIndirectUnguardedCall() (gas: 45600) 96 | ReentrancyGuardTest:testRevertGuardLocked() (gas: 31984) 97 | ReentrancyGuardTest:testRevertReadGuardLocked() (gas: 31687) 98 | ReentrancyGuardTest:testRevertRecursiveDirectGuardedCall() (gas: 33238) 99 | ReentrancyGuardTest:testRevertRecursiveIndirectGuardedCall() (gas: 34631) 100 | ReentrancyGuardTest:testRevertRemoteCallback() (gas: 34267) 101 | ReentrancyGuardTest:test__codesize() (gas: 5438) 102 | SoladyTest:test__codesize() (gas: 1075) 103 | TestPlus:test__codesize() (gas: 398) -------------------------------------------------------------------------------- /.github/pull_request_template.md: -------------------------------------------------------------------------------- 1 | ## Description 2 | 3 | Describe the changes made in your pull request here. 4 | 5 | ## Checklist 6 | 7 | Ensure you completed **all of the steps** below before submitting your pull request: 8 | 9 | - [ ] Ran `forge fmt`? 10 | - [ ] Ran `forge snapshot`? 11 | - [ ] Ran `forge test`? 12 | 13 | _Pull requests with an incomplete checklist will be thrown out._ 14 | 15 | 16 | 17 | 18 | 19 | 20 | 21 | 22 | 23 | 24 | 25 | -------------------------------------------------------------------------------- /.github/workflows/ci.yml: -------------------------------------------------------------------------------- 1 | name: ci 2 | 3 | on: 4 | push: 5 | paths: 6 | - '**.sol' 7 | 8 | jobs: 9 | tests: 10 | name: Forge Testing 11 | runs-on: ubuntu-latest 12 | 13 | strategy: 14 | matrix: 15 | profile: [via-ir,intense] 16 | 17 | steps: 18 | - uses: actions/checkout@v4 19 | 20 | - name: Install Foundry 21 | uses: foundry-rs/foundry-toolchain@v1 22 | with: 23 | version: nightly 24 | 25 | - name: Install Dependencies 26 | run: forge install 27 | 28 | - name: Run Lint Check 29 | run: forge fmt --check 30 | 31 | - name: Run Tests with ${{ matrix.profile }} 32 | run: FOUNDRY_PROFILE=${{ matrix.profile }} forge test 33 | 34 | codespell: 35 | runs-on: ${{ matrix.os }} 36 | strategy: 37 | matrix: 38 | os: 39 | - ubuntu-latest 40 | 41 | steps: 42 | - name: Checkout 43 | uses: actions/checkout@v4 44 | 45 | - name: Run codespell 46 | uses: codespell-project/actions-codespell@v2.0 47 | with: 48 | check_filenames: true 49 | ignore_words_list: usera 50 | skip: ./.git,package-lock.json,ackee-blockchain-solady-report.pdf,EIP712Mock.sol 51 | -------------------------------------------------------------------------------- /.gitignore: -------------------------------------------------------------------------------- 1 | # NodeJS files 2 | node_modules/ 3 | coverage 4 | coverage.json 5 | typechain 6 | 7 | # Hardhat files 8 | cache 9 | artifacts 10 | 11 | cache/ 12 | out/ 13 | 14 | # Ignore Environment Variables! 15 | .env 16 | .env.prod 17 | 18 | # Ignore all vscode settings 19 | .vscode/ 20 | 21 | # Ignore flattened files 22 | flattened.txt 23 | 24 | broadcast 25 | 26 | # Coverage 27 | lcov.info 28 | 29 | # Woke testing 30 | .woke-build 31 | .woke-logs 32 | pytypes 33 | __pycache__/ 34 | *.py[cod] 35 | .hypothesis/ 36 | woke-coverage.cov 37 | -------------------------------------------------------------------------------- /LICENSE.txt: -------------------------------------------------------------------------------- 1 | MIT License 2 | 3 | Copyright (c) 2022 Solady. 4 | 5 | Permission is hereby granted, free of charge, to any person obtaining a copy 6 | of this software and associated documentation files (the "Software"), to deal 7 | in the Software without restriction, including without limitation the rights 8 | to use, copy, modify, merge, publish, distribute, sublicense, and/or sell 9 | copies of the Software, and to permit persons to whom the Software is 10 | furnished to do so, subject to the following conditions: 11 | 12 | The above copyright notice and this permission notice shall be included in all 13 | copies or substantial portions of the Software. 14 | 15 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR 16 | IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, 17 | FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE 18 | AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER 19 | LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, 20 | OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE 21 | SOFTWARE. -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | # soledge 2 | 3 | [![NPM][npm-shield]][npm-url] 4 | [![CI][ci-shield]][ci-url] 5 | 6 | Solidity snippets too edgy to be in [**Solady**](https://github.com/Vectorized/solady). 7 | 8 | For a future of EVMs fragmentation, where the latest opcodes are not supported on most L2s for years even after their inception on mainnet. 9 | 10 | ## Installation 11 | 12 | To install with [**Foundry**](https://github.com/gakonst/foundry): 13 | 14 | ```sh 15 | forge install vectorized/soledge 16 | ``` 17 | 18 | To install with [**Hardhat**](https://github.com/nomiclabs/hardhat): 19 | 20 | ```sh 21 | npm install soledge 22 | ``` 23 | 24 | ## Contracts 25 | 26 | The Solidity smart contracts are located in the `src` directory. 27 | 28 | ```ml 29 | utils 30 | ├─ LibT — "Transient storage helper" 31 | ├─ ReentrancyGuard — "Reentrancy guard mixin" 32 | └─ LibString - "Library for converting numbers into strings and other string operations" 33 | ``` 34 | 35 | ## Directories 36 | 37 | ```ml 38 | src — "Solidity smart contracts" 39 | test — "Foundry Forge tests" 40 | ``` 41 | 42 | ## Contributing 43 | 44 | Feel free to make a pull request. 45 | 46 | Guidelines same as [Solady's](https://github.com/Vectorized/solady/issues/19). 47 | 48 | ## Safety 49 | 50 | This is **experimental software** and is provided on an "as is" and "as available" basis. 51 | 52 | We **do not give any warranties** and **will not be liable for any loss** incurred through any use of this codebase. 53 | 54 | While Soledge has been heavily tested, there may be parts that may exhibit unexpected emergent behavior when used with other code, or may break in future Solidity versions. 55 | 56 | Please always include your own thorough tests when using Soledge to make sure it works correctly with your code. 57 | 58 | ## Upgradability 59 | 60 | Most contracts in Soledge are compatible with both upgradeable and non-upgradeable (i.e. regular) contracts. 61 | 62 | Please call any required internal initialization methods accordingly. 63 | 64 | ## EVM Compatibility 65 | 66 | Some parts of Soledge may not be compatible with chains with partial EVM equivalence. 67 | 68 | Please always check and test for compatibility accordingly. 69 | 70 | ## Acknowledgements 71 | 72 | This repository is inspired by or directly modified from many sources, primarily: 73 | 74 | - [Solmate](https://github.com/transmissions11/solmate) 75 | - [OpenZeppelin](https://github.com/OpenZeppelin/openzeppelin-contracts) 76 | - [ERC721A](https://github.com/chiru-labs/ERC721A) 77 | - [Zolidity](https://github.com/z0r0z/zolidity) 78 | - [🐍 Snekmate](https://github.com/pcaversaccio/snekmate) 79 | - [Femplate](https://github.com/abigger87/femplate) 80 | 81 | [npm-shield]: https://img.shields.io/npm/v/soledge.svg 82 | [npm-url]: https://www.npmjs.com/package/soledge 83 | 84 | [ci-shield]: https://img.shields.io/github/actions/workflow/status/vectorized/soledge/ci.yml?branch=main&label=build 85 | [ci-url]: https://github.com/vectorized/soledge/actions/workflows/ci.yml 86 | -------------------------------------------------------------------------------- /foundry.toml: -------------------------------------------------------------------------------- 1 | # Foundry Configuration File 2 | # Default definitions: https://github.com/gakonst/foundry/blob/b7917fa8491aedda4dd6db53fbb206ea233cd531/config/src/lib.rs#L782 3 | # See more config options at: https://github.com/gakonst/foundry/tree/master/config 4 | 5 | # The Default Profile 6 | [profile.default] 7 | solc_version = "0.8.24" 8 | evm_version = "cancun" 9 | auto_detect_solc = false 10 | ignored_error_codes = [2394] 11 | optimizer = true 12 | optimizer_runs = 1_000 13 | gas_limit = 100_000_000 # ETH is 30M, but we use a higher value. 14 | remappings = [ 15 | "forge-std=test/utils/forge-std/" 16 | ] 17 | 18 | [fmt] 19 | line_length = 100 # While we allow up to 120, we lint at 100 for readability. 20 | 21 | [profile.default.fuzz] 22 | runs = 256 23 | 24 | [profile.intense.fuzz] 25 | runs = 5_000 26 | 27 | [profile.via-ir] 28 | via_ir = true 29 | 30 | [profile.via-ir.fuzz] 31 | runs = 1_000 32 | 33 | [profile.min-solc] 34 | solc_version = "0.8.4" 35 | 36 | [profile.min-solc.fuzz] 37 | runs = 1_000 38 | 39 | [profile.min-solc-via-ir] 40 | via_ir = true 41 | solc_version = "0.8.4" 42 | 43 | [profile.min-solc-via-ir.fuzz] 44 | runs = 1_000 45 | -------------------------------------------------------------------------------- /logo.svg: -------------------------------------------------------------------------------- 1 | solady -------------------------------------------------------------------------------- /package.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "soledge", 3 | "license": "MIT", 4 | "version": "0.0.5", 5 | "description": "Edgelord Solidity snippets.", 6 | "files": [ 7 | "src/**/*.sol" 8 | ], 9 | "repository": { 10 | "type": "git", 11 | "url": "git+https://github.com/vectorized/soledge.git" 12 | } 13 | } 14 | -------------------------------------------------------------------------------- /src/utils/LibString.sol: -------------------------------------------------------------------------------- 1 | // SPDX-License-Identifier: MIT 2 | pragma solidity ^0.8.24; 3 | 4 | /// @notice Library for converting numbers into strings and other string operations. 5 | /// @author Solady (https://github.com/vectorized/solady/blob/main/src/utils/LibString.sol) 6 | /// 7 | /// @dev Note: This implementation utilizes the `MCOPY` opcode. 8 | /// Please ensure that the chain you are deploying on supports them. 9 | /// 10 | /// For performance and bytecode compactness, most of the string operations are restricted to 11 | /// byte strings (7-bit ASCII), except where otherwise specified. 12 | /// Usage of byte string operations on charsets with runes spanning two or more bytes 13 | /// can lead to undefined behavior. 14 | library LibString { 15 | /*«-«-«-«-«-«-«-«-«-«-«-«-«-«-«-«-«-«-«-«-«-«-«-«-«-«-«-«-«-«-*/ 16 | /* CUSTOM ERRORS */ 17 | /*-»-»-»-»-»-»-»-»-»-»-»-»-»-»-»-»-»-»-»-»-»-»-»-»-»-»-»-»-»-»*/ 18 | 19 | /// @dev The length of the output is too small to contain all the hex digits. 20 | error HexLengthInsufficient(); 21 | 22 | /// @dev The length of the string is more than 32 bytes. 23 | error TooBigForSmallString(); 24 | 25 | /*«-«-«-«-«-«-«-«-«-«-«-«-«-«-«-«-«-«-«-«-«-«-«-«-«-«-«-«-«-«-*/ 26 | /* CONSTANTS */ 27 | /*-»-»-»-»-»-»-»-»-»-»-»-»-»-»-»-»-»-»-»-»-»-»-»-»-»-»-»-»-»-»*/ 28 | 29 | /// @dev The constant returned when the `search` is not found in the string. 30 | uint256 internal constant NOT_FOUND = type(uint256).max; 31 | 32 | /*«-«-«-«-«-«-«-«-«-«-«-«-«-«-«-«-«-«-«-«-«-«-«-«-«-«-«-«-«-«-*/ 33 | /* DECIMAL OPERATIONS */ 34 | /*-»-»-»-»-»-»-»-»-»-»-»-»-»-»-»-»-»-»-»-»-»-»-»-»-»-»-»-»-»-»*/ 35 | 36 | /// @dev Returns the base 10 decimal representation of `value`. 37 | function toString(uint256 value) internal pure returns (string memory str) { 38 | /// @solidity memory-safe-assembly 39 | assembly { 40 | // The maximum value of a uint256 contains 78 digits (1 byte per digit), but 41 | // we allocate 0xa0 bytes to keep the free memory pointer 32-byte word aligned. 42 | // We will need 1 word for the trailing zeros padding, 1 word for the length, 43 | // and 3 words for a maximum of 78 digits. 44 | str := add(mload(0x40), 0x80) 45 | // Update the free memory pointer to allocate. 46 | mstore(0x40, add(str, 0x20)) 47 | // Zeroize the slot after the string. 48 | mstore(str, 0) 49 | 50 | // Cache the end of the memory to calculate the length later. 51 | let end := str 52 | 53 | let w := not(0) // Tsk. 54 | // We write the string from rightmost digit to leftmost digit. 55 | // The following is essentially a do-while loop that also handles the zero case. 56 | for { let temp := value } 1 {} { 57 | str := add(str, w) // `sub(str, 1)`. 58 | // Write the character to the pointer. 59 | // The ASCII index of the '0' character is 48. 60 | mstore8(str, add(48, mod(temp, 10))) 61 | // Keep dividing `temp` until zero. 62 | temp := div(temp, 10) 63 | if iszero(temp) { break } 64 | } 65 | 66 | let length := sub(end, str) 67 | // Move the pointer 32 bytes leftwards to make room for the length. 68 | str := sub(str, 0x20) 69 | // Store the length. 70 | mstore(str, length) 71 | } 72 | } 73 | 74 | /// @dev Returns the base 10 decimal representation of `value`. 75 | function toString(int256 value) internal pure returns (string memory str) { 76 | if (value >= 0) { 77 | return toString(uint256(value)); 78 | } 79 | unchecked { 80 | str = toString(~uint256(value) + 1); 81 | } 82 | /// @solidity memory-safe-assembly 83 | assembly { 84 | // We still have some spare memory space on the left, 85 | // as we have allocated 3 words (96 bytes) for up to 78 digits. 86 | let length := mload(str) // Load the string length. 87 | mstore(str, 0x2d) // Store the '-' character. 88 | str := sub(str, 1) // Move back the string pointer by a byte. 89 | mstore(str, add(length, 1)) // Update the string length. 90 | } 91 | } 92 | 93 | /*«-«-«-«-«-«-«-«-«-«-«-«-«-«-«-«-«-«-«-«-«-«-«-«-«-«-«-«-«-«-*/ 94 | /* HEXADECIMAL OPERATIONS */ 95 | /*-»-»-»-»-»-»-»-»-»-»-»-»-»-»-»-»-»-»-»-»-»-»-»-»-»-»-»-»-»-»*/ 96 | 97 | /// @dev Returns the hexadecimal representation of `value`, 98 | /// left-padded to an input length of `length` bytes. 99 | /// The output is prefixed with "0x" encoded using 2 hexadecimal digits per byte, 100 | /// giving a total length of `length * 2 + 2` bytes. 101 | /// Reverts if `length` is too small for the output to contain all the digits. 102 | function toHexString(uint256 value, uint256 length) internal pure returns (string memory str) { 103 | str = toHexStringNoPrefix(value, length); 104 | /// @solidity memory-safe-assembly 105 | assembly { 106 | let strLength := add(mload(str), 2) // Compute the length. 107 | mstore(str, 0x3078) // Write the "0x" prefix. 108 | str := sub(str, 2) // Move the pointer. 109 | mstore(str, strLength) // Write the length. 110 | } 111 | } 112 | 113 | /// @dev Returns the hexadecimal representation of `value`, 114 | /// left-padded to an input length of `length` bytes. 115 | /// The output is prefixed with "0x" encoded using 2 hexadecimal digits per byte, 116 | /// giving a total length of `length * 2` bytes. 117 | /// Reverts if `length` is too small for the output to contain all the digits. 118 | function toHexStringNoPrefix(uint256 value, uint256 length) 119 | internal 120 | pure 121 | returns (string memory str) 122 | { 123 | /// @solidity memory-safe-assembly 124 | assembly { 125 | // We need 0x20 bytes for the trailing zeros padding, `length * 2` bytes 126 | // for the digits, 0x02 bytes for the prefix, and 0x20 bytes for the length. 127 | // We add 0x20 to the total and round down to a multiple of 0x20. 128 | // (0x20 + 0x20 + 0x02 + 0x20) = 0x62. 129 | str := add(mload(0x40), and(add(shl(1, length), 0x42), not(0x1f))) 130 | // Allocate the memory. 131 | mstore(0x40, add(str, 0x20)) 132 | // Zeroize the slot after the string. 133 | mstore(str, 0) 134 | 135 | // Cache the end to calculate the length later. 136 | let end := str 137 | // Store "0123456789abcdef" in scratch space. 138 | mstore(0x0f, 0x30313233343536373839616263646566) 139 | 140 | let start := sub(str, add(length, length)) 141 | let w := not(1) // Tsk. 142 | let temp := value 143 | // We write the string from rightmost digit to leftmost digit. 144 | // The following is essentially a do-while loop that also handles the zero case. 145 | for {} 1 {} { 146 | str := add(str, w) // `sub(str, 2)`. 147 | mstore8(add(str, 1), mload(and(temp, 15))) 148 | mstore8(str, mload(and(shr(4, temp), 15))) 149 | temp := shr(8, temp) 150 | if iszero(xor(str, start)) { break } 151 | } 152 | 153 | if temp { 154 | mstore(0x00, 0x2194895a) // `HexLengthInsufficient()`. 155 | revert(0x1c, 0x04) 156 | } 157 | 158 | // Compute the string's length. 159 | let strLength := sub(end, str) 160 | // Move the pointer and write the length. 161 | str := sub(str, 0x20) 162 | mstore(str, strLength) 163 | } 164 | } 165 | 166 | /// @dev Returns the hexadecimal representation of `value`. 167 | /// The output is prefixed with "0x" and encoded using 2 hexadecimal digits per byte. 168 | /// As address are 20 bytes long, the output will left-padded to have 169 | /// a length of `20 * 2 + 2` bytes. 170 | function toHexString(uint256 value) internal pure returns (string memory str) { 171 | str = toHexStringNoPrefix(value); 172 | /// @solidity memory-safe-assembly 173 | assembly { 174 | let strLength := add(mload(str), 2) // Compute the length. 175 | mstore(str, 0x3078) // Write the "0x" prefix. 176 | str := sub(str, 2) // Move the pointer. 177 | mstore(str, strLength) // Write the length. 178 | } 179 | } 180 | 181 | /// @dev Returns the hexadecimal representation of `value`. 182 | /// The output is prefixed with "0x". 183 | /// The output excludes leading "0" from the `toHexString` output. 184 | /// `0x00: "0x0", 0x01: "0x1", 0x12: "0x12", 0x123: "0x123"`. 185 | function toMinimalHexString(uint256 value) internal pure returns (string memory str) { 186 | str = toHexStringNoPrefix(value); 187 | /// @solidity memory-safe-assembly 188 | assembly { 189 | let o := eq(byte(0, mload(add(str, 0x20))), 0x30) // Whether leading zero is present. 190 | let strLength := add(mload(str), 2) // Compute the length. 191 | mstore(add(str, o), 0x3078) // Write the "0x" prefix, accounting for leading zero. 192 | str := sub(add(str, o), 2) // Move the pointer, accounting for leading zero. 193 | mstore(str, sub(strLength, o)) // Write the length, accounting for leading zero. 194 | } 195 | } 196 | 197 | /// @dev Returns the hexadecimal representation of `value`. 198 | /// The output excludes leading "0" from the `toHexStringNoPrefix` output. 199 | /// `0x00: "0", 0x01: "1", 0x12: "12", 0x123: "123"`. 200 | function toMinimalHexStringNoPrefix(uint256 value) internal pure returns (string memory str) { 201 | str = toHexStringNoPrefix(value); 202 | /// @solidity memory-safe-assembly 203 | assembly { 204 | let o := eq(byte(0, mload(add(str, 0x20))), 0x30) // Whether leading zero is present. 205 | let strLength := mload(str) // Get the length. 206 | str := add(str, o) // Move the pointer, accounting for leading zero. 207 | mstore(str, sub(strLength, o)) // Write the length, accounting for leading zero. 208 | } 209 | } 210 | 211 | /// @dev Returns the hexadecimal representation of `value`. 212 | /// The output is encoded using 2 hexadecimal digits per byte. 213 | /// As address are 20 bytes long, the output will left-padded to have 214 | /// a length of `20 * 2` bytes. 215 | function toHexStringNoPrefix(uint256 value) internal pure returns (string memory str) { 216 | /// @solidity memory-safe-assembly 217 | assembly { 218 | // We need 0x20 bytes for the trailing zeros padding, 0x20 bytes for the length, 219 | // 0x02 bytes for the prefix, and 0x40 bytes for the digits. 220 | // The next multiple of 0x20 above (0x20 + 0x20 + 0x02 + 0x40) is 0xa0. 221 | str := add(mload(0x40), 0x80) 222 | // Allocate the memory. 223 | mstore(0x40, add(str, 0x20)) 224 | // Zeroize the slot after the string. 225 | mstore(str, 0) 226 | 227 | // Cache the end to calculate the length later. 228 | let end := str 229 | // Store "0123456789abcdef" in scratch space. 230 | mstore(0x0f, 0x30313233343536373839616263646566) 231 | 232 | let w := not(1) // Tsk. 233 | // We write the string from rightmost digit to leftmost digit. 234 | // The following is essentially a do-while loop that also handles the zero case. 235 | for { let temp := value } 1 {} { 236 | str := add(str, w) // `sub(str, 2)`. 237 | mstore8(add(str, 1), mload(and(temp, 15))) 238 | mstore8(str, mload(and(shr(4, temp), 15))) 239 | temp := shr(8, temp) 240 | if iszero(temp) { break } 241 | } 242 | 243 | // Compute the string's length. 244 | let strLength := sub(end, str) 245 | // Move the pointer and write the length. 246 | str := sub(str, 0x20) 247 | mstore(str, strLength) 248 | } 249 | } 250 | 251 | /// @dev Returns the hexadecimal representation of `value`. 252 | /// The output is prefixed with "0x", encoded using 2 hexadecimal digits per byte, 253 | /// and the alphabets are capitalized conditionally according to 254 | /// https://eips.ethereum.org/EIPS/eip-55 255 | function toHexStringChecksummed(address value) internal pure returns (string memory str) { 256 | str = toHexString(value); 257 | /// @solidity memory-safe-assembly 258 | assembly { 259 | let mask := shl(6, div(not(0), 255)) // `0b010000000100000000 ...` 260 | let o := add(str, 0x22) 261 | let hashed := and(keccak256(o, 40), mul(34, mask)) // `0b10001000 ... ` 262 | let t := shl(240, 136) // `0b10001000 << 240` 263 | for { let i := 0 } 1 {} { 264 | mstore(add(i, i), mul(t, byte(i, hashed))) 265 | i := add(i, 1) 266 | if eq(i, 20) { break } 267 | } 268 | mstore(o, xor(mload(o), shr(1, and(mload(0x00), and(mload(o), mask))))) 269 | o := add(o, 0x20) 270 | mstore(o, xor(mload(o), shr(1, and(mload(0x20), and(mload(o), mask))))) 271 | } 272 | } 273 | 274 | /// @dev Returns the hexadecimal representation of `value`. 275 | /// The output is prefixed with "0x" and encoded using 2 hexadecimal digits per byte. 276 | function toHexString(address value) internal pure returns (string memory str) { 277 | str = toHexStringNoPrefix(value); 278 | /// @solidity memory-safe-assembly 279 | assembly { 280 | let strLength := add(mload(str), 2) // Compute the length. 281 | mstore(str, 0x3078) // Write the "0x" prefix. 282 | str := sub(str, 2) // Move the pointer. 283 | mstore(str, strLength) // Write the length. 284 | } 285 | } 286 | 287 | /// @dev Returns the hexadecimal representation of `value`. 288 | /// The output is encoded using 2 hexadecimal digits per byte. 289 | function toHexStringNoPrefix(address value) internal pure returns (string memory str) { 290 | /// @solidity memory-safe-assembly 291 | assembly { 292 | str := mload(0x40) 293 | 294 | // Allocate the memory. 295 | // We need 0x20 bytes for the trailing zeros padding, 0x20 bytes for the length, 296 | // 0x02 bytes for the prefix, and 0x28 bytes for the digits. 297 | // The next multiple of 0x20 above (0x20 + 0x20 + 0x02 + 0x28) is 0x80. 298 | mstore(0x40, add(str, 0x80)) 299 | 300 | // Store "0123456789abcdef" in scratch space. 301 | mstore(0x0f, 0x30313233343536373839616263646566) 302 | 303 | str := add(str, 2) 304 | mstore(str, 40) 305 | 306 | let o := add(str, 0x20) 307 | mstore(add(o, 40), 0) 308 | 309 | value := shl(96, value) 310 | 311 | // We write the string from rightmost digit to leftmost digit. 312 | // The following is essentially a do-while loop that also handles the zero case. 313 | for { let i := 0 } 1 {} { 314 | let p := add(o, add(i, i)) 315 | let temp := byte(i, value) 316 | mstore8(add(p, 1), mload(and(temp, 15))) 317 | mstore8(p, mload(shr(4, temp))) 318 | i := add(i, 1) 319 | if eq(i, 20) { break } 320 | } 321 | } 322 | } 323 | 324 | /// @dev Returns the hex encoded string from the raw bytes. 325 | /// The output is encoded using 2 hexadecimal digits per byte. 326 | function toHexString(bytes memory raw) internal pure returns (string memory str) { 327 | str = toHexStringNoPrefix(raw); 328 | /// @solidity memory-safe-assembly 329 | assembly { 330 | let strLength := add(mload(str), 2) // Compute the length. 331 | mstore(str, 0x3078) // Write the "0x" prefix. 332 | str := sub(str, 2) // Move the pointer. 333 | mstore(str, strLength) // Write the length. 334 | } 335 | } 336 | 337 | /// @dev Returns the hex encoded string from the raw bytes. 338 | /// The output is encoded using 2 hexadecimal digits per byte. 339 | function toHexStringNoPrefix(bytes memory raw) internal pure returns (string memory str) { 340 | /// @solidity memory-safe-assembly 341 | assembly { 342 | let length := mload(raw) 343 | str := add(mload(0x40), 2) // Skip 2 bytes for the optional prefix. 344 | mstore(str, add(length, length)) // Store the length of the output. 345 | 346 | // Store "0123456789abcdef" in scratch space. 347 | mstore(0x0f, 0x30313233343536373839616263646566) 348 | 349 | let o := add(str, 0x20) 350 | let end := add(raw, length) 351 | 352 | for {} iszero(eq(raw, end)) {} { 353 | raw := add(raw, 1) 354 | mstore8(add(o, 1), mload(and(mload(raw), 15))) 355 | mstore8(o, mload(and(shr(4, mload(raw)), 15))) 356 | o := add(o, 2) 357 | } 358 | mstore(o, 0) // Zeroize the slot after the string. 359 | mstore(0x40, add(o, 0x20)) // Allocate the memory. 360 | } 361 | } 362 | 363 | /*«-«-«-«-«-«-«-«-«-«-«-«-«-«-«-«-«-«-«-«-«-«-«-«-«-«-«-«-«-«-*/ 364 | /* RUNE STRING OPERATIONS */ 365 | /*-»-»-»-»-»-»-»-»-»-»-»-»-»-»-»-»-»-»-»-»-»-»-»-»-»-»-»-»-»-»*/ 366 | 367 | /// @dev Returns the number of UTF characters in the string. 368 | function runeCount(string memory s) internal pure returns (uint256 result) { 369 | /// @solidity memory-safe-assembly 370 | assembly { 371 | if mload(s) { 372 | mstore(0x00, div(not(0), 255)) 373 | mstore(0x20, 0x0202020202020202020202020202020202020202020202020303030304040506) 374 | let o := add(s, 0x20) 375 | let end := add(o, mload(s)) 376 | for { result := 1 } 1 { result := add(result, 1) } { 377 | o := add(o, byte(0, mload(shr(250, mload(o))))) 378 | if iszero(lt(o, end)) { break } 379 | } 380 | } 381 | } 382 | } 383 | 384 | /// @dev Returns if this string is a 7-bit ASCII string. 385 | /// (i.e. all characters codes are in [0..127]) 386 | function is7BitASCII(string memory s) internal pure returns (bool result) { 387 | /// @solidity memory-safe-assembly 388 | assembly { 389 | let mask := shl(7, div(not(0), 255)) 390 | result := 1 391 | let n := mload(s) 392 | if n { 393 | let o := add(s, 0x20) 394 | let end := add(o, n) 395 | let last := mload(end) 396 | mstore(end, 0) 397 | for {} 1 {} { 398 | if and(mask, mload(o)) { 399 | result := 0 400 | break 401 | } 402 | o := add(o, 0x20) 403 | if iszero(lt(o, end)) { break } 404 | } 405 | mstore(end, last) 406 | } 407 | } 408 | } 409 | 410 | /*«-«-«-«-«-«-«-«-«-«-«-«-«-«-«-«-«-«-«-«-«-«-«-«-«-«-«-«-«-«-*/ 411 | /* BYTE STRING OPERATIONS */ 412 | /*-»-»-»-»-»-»-»-»-»-»-»-»-»-»-»-»-»-»-»-»-»-»-»-»-»-»-»-»-»-»*/ 413 | 414 | // For performance and bytecode compactness, byte string operations are restricted 415 | // to 7-bit ASCII strings. All offsets are byte offsets, not UTF character offsets. 416 | // Usage of byte string operations on charsets with runes spanning two or more bytes 417 | // can lead to undefined behavior. 418 | 419 | /// @dev Returns `subject` all occurrences of `search` replaced with `replacement`. 420 | function replace(string memory subject, string memory search, string memory replacement) 421 | internal 422 | pure 423 | returns (string memory result) 424 | { 425 | /// @solidity memory-safe-assembly 426 | assembly { 427 | let subjectLength := mload(subject) 428 | let searchLength := mload(search) 429 | let replacementLength := mload(replacement) 430 | 431 | subject := add(subject, 0x20) 432 | search := add(search, 0x20) 433 | replacement := add(replacement, 0x20) 434 | result := add(mload(0x40), 0x20) 435 | 436 | let subjectEnd := add(subject, subjectLength) 437 | // search <= original string 438 | if iszero(gt(searchLength, subjectLength)) { 439 | // subjectSearchend = subjectEnd - searchLength 440 | let subjectSearchEnd := add(sub(subjectEnd, searchLength), 1) 441 | let h := 0 442 | if iszero(lt(searchLength, 0x20)) { h := keccak256(search, searchLength) } 443 | // 8 * searchLength 444 | let m := shl(3, sub(0x20, and(searchLength, 0x1f))) 445 | let s := mload(search) 446 | for {} 1 {} { 447 | let t := mload(subject) 448 | // Whether the first `searchLength % 32` bytes of 449 | // `subject` and `search` matches. 450 | if iszero(shr(m, xor(t, s))) { 451 | if h { 452 | if iszero(eq(keccak256(subject, searchLength), h)) { 453 | mstore(result, t) 454 | result := add(result, 1) 455 | subject := add(subject, 1) 456 | if iszero(lt(subject, subjectSearchEnd)) { break } 457 | continue 458 | } 459 | } 460 | // Copy the `replacement` string. 461 | mcopy(result, replacement, replacementLength) 462 | result := add(result, replacementLength) 463 | subject := add(subject, searchLength) 464 | if searchLength { 465 | if iszero(lt(subject, subjectSearchEnd)) { break } 466 | continue 467 | } 468 | } 469 | mstore(result, t) 470 | result := add(result, 1) 471 | subject := add(subject, 1) 472 | if iszero(lt(subject, subjectSearchEnd)) { break } 473 | } 474 | } 475 | let resultRemainder := result 476 | result := add(mload(0x40), 0x20) 477 | let k := add(sub(resultRemainder, result), sub(subjectEnd, subject)) 478 | // Copy the rest of the string. 479 | mcopy(resultRemainder, subject, subjectEnd) 480 | result := sub(result, 0x20) 481 | let last := add(add(result, 0x20), k) // Zeroize the slot after the string. 482 | mstore(last, 0) 483 | mstore(0x40, add(last, 0x20)) // Allocate the memory. 484 | mstore(result, k) // Store the length. 485 | } 486 | } 487 | 488 | /// @dev Returns the byte index of the first location of `search` in `subject`, 489 | /// searching from left to right, starting from `from`. 490 | /// Returns `NOT_FOUND` (i.e. `type(uint256).max`) if the `search` is not found. 491 | function indexOf(string memory subject, string memory search, uint256 from) 492 | internal 493 | pure 494 | returns (uint256 result) 495 | { 496 | /// @solidity memory-safe-assembly 497 | assembly { 498 | for { let subjectLength := mload(subject) } 1 {} { 499 | if iszero(mload(search)) { 500 | if iszero(gt(from, subjectLength)) { 501 | result := from 502 | break 503 | } 504 | result := subjectLength 505 | break 506 | } 507 | let searchLength := mload(search) 508 | let subjectStart := add(subject, 0x20) 509 | 510 | result := not(0) // Initialize to `NOT_FOUND`. 511 | 512 | subject := add(subjectStart, from) 513 | let end := add(sub(add(subjectStart, subjectLength), searchLength), 1) 514 | 515 | let m := shl(3, sub(0x20, and(searchLength, 0x1f))) 516 | let s := mload(add(search, 0x20)) 517 | 518 | if iszero(and(lt(subject, end), lt(from, subjectLength))) { break } 519 | 520 | if iszero(lt(searchLength, 0x20)) { 521 | for { let h := keccak256(add(search, 0x20), searchLength) } 1 {} { 522 | if iszero(shr(m, xor(mload(subject), s))) { 523 | if eq(keccak256(subject, searchLength), h) { 524 | result := sub(subject, subjectStart) 525 | break 526 | } 527 | } 528 | subject := add(subject, 1) 529 | if iszero(lt(subject, end)) { break } 530 | } 531 | break 532 | } 533 | for {} 1 {} { 534 | if iszero(shr(m, xor(mload(subject), s))) { 535 | result := sub(subject, subjectStart) 536 | break 537 | } 538 | subject := add(subject, 1) 539 | if iszero(lt(subject, end)) { break } 540 | } 541 | break 542 | } 543 | } 544 | } 545 | 546 | /// @dev Returns the byte index of the first location of `search` in `subject`, 547 | /// searching from left to right. 548 | /// Returns `NOT_FOUND` (i.e. `type(uint256).max`) if the `search` is not found. 549 | function indexOf(string memory subject, string memory search) 550 | internal 551 | pure 552 | returns (uint256 result) 553 | { 554 | result = indexOf(subject, search, 0); 555 | } 556 | 557 | /// @dev Returns the byte index of the first location of `search` in `subject`, 558 | /// searching from right to left, starting from `from`. 559 | /// Returns `NOT_FOUND` (i.e. `type(uint256).max`) if the `search` is not found. 560 | function lastIndexOf(string memory subject, string memory search, uint256 from) 561 | internal 562 | pure 563 | returns (uint256 result) 564 | { 565 | /// @solidity memory-safe-assembly 566 | assembly { 567 | for {} 1 {} { 568 | result := not(0) // Initialize to `NOT_FOUND`. 569 | let searchLength := mload(search) 570 | if gt(searchLength, mload(subject)) { break } 571 | let w := result 572 | 573 | let fromMax := sub(mload(subject), searchLength) 574 | if iszero(gt(fromMax, from)) { from := fromMax } 575 | 576 | let end := add(add(subject, 0x20), w) 577 | subject := add(add(subject, 0x20), from) 578 | if iszero(gt(subject, end)) { break } 579 | // As this function is not too often used, 580 | // we shall simply use keccak256 for smaller bytecode size. 581 | for { let h := keccak256(add(search, 0x20), searchLength) } 1 {} { 582 | if eq(keccak256(subject, searchLength), h) { 583 | result := sub(subject, add(end, 1)) 584 | break 585 | } 586 | subject := add(subject, w) // `sub(subject, 1)`. 587 | if iszero(gt(subject, end)) { break } 588 | } 589 | break 590 | } 591 | } 592 | } 593 | 594 | /// @dev Returns the byte index of the first location of `search` in `subject`, 595 | /// searching from right to left. 596 | /// Returns `NOT_FOUND` (i.e. `type(uint256).max`) if the `search` is not found. 597 | function lastIndexOf(string memory subject, string memory search) 598 | internal 599 | pure 600 | returns (uint256 result) 601 | { 602 | result = lastIndexOf(subject, search, uint256(int256(-1))); 603 | } 604 | 605 | /// @dev Returns true if `search` is found in `subject`, false otherwise. 606 | function contains(string memory subject, string memory search) internal pure returns (bool) { 607 | return indexOf(subject, search) != NOT_FOUND; 608 | } 609 | 610 | /// @dev Returns whether `subject` starts with `search`. 611 | function startsWith(string memory subject, string memory search) 612 | internal 613 | pure 614 | returns (bool result) 615 | { 616 | /// @solidity memory-safe-assembly 617 | assembly { 618 | let searchLength := mload(search) 619 | // Just using keccak256 directly is actually cheaper. 620 | // forgefmt: disable-next-item 621 | result := and( 622 | iszero(gt(searchLength, mload(subject))), 623 | eq( 624 | keccak256(add(subject, 0x20), searchLength), 625 | keccak256(add(search, 0x20), searchLength) 626 | ) 627 | ) 628 | } 629 | } 630 | 631 | /// @dev Returns whether `subject` ends with `search`. 632 | function endsWith(string memory subject, string memory search) 633 | internal 634 | pure 635 | returns (bool result) 636 | { 637 | /// @solidity memory-safe-assembly 638 | assembly { 639 | let searchLength := mload(search) 640 | let subjectLength := mload(subject) 641 | // Whether `search` is not longer than `subject`. 642 | let withinRange := iszero(gt(searchLength, subjectLength)) 643 | // Just using keccak256 directly is actually cheaper. 644 | // forgefmt: disable-next-item 645 | result := and( 646 | withinRange, 647 | eq( 648 | keccak256( 649 | // `subject + 0x20 + max(subjectLength - searchLength, 0)`. 650 | add(add(subject, 0x20), mul(withinRange, sub(subjectLength, searchLength))), 651 | searchLength 652 | ), 653 | keccak256(add(search, 0x20), searchLength) 654 | ) 655 | ) 656 | } 657 | } 658 | 659 | /// @dev Returns `subject` repeated `times`. 660 | function repeat(string memory subject, uint256 times) 661 | internal 662 | pure 663 | returns (string memory result) 664 | { 665 | /// @solidity memory-safe-assembly 666 | assembly { 667 | let subjectLength := mload(subject) 668 | if iszero(or(iszero(times), iszero(subjectLength))) { 669 | subject := add(subject, 0x20) 670 | result := mload(0x40) 671 | let output := add(result, 0x20) 672 | for {} 1 {} { 673 | // Copy the `subject`. 674 | mcopy(output, subject, subjectLength) 675 | output := add(output, subjectLength) 676 | times := sub(times, 1) 677 | if iszero(times) { break } 678 | } 679 | mstore(output, 0) // Zeroize the slot after the string. 680 | let resultLength := sub(output, add(result, 0x20)) 681 | mstore(result, resultLength) // Store the length. 682 | // Allocate the memory. 683 | mstore(0x40, add(result, add(resultLength, 0x20))) 684 | } 685 | } 686 | } 687 | 688 | /// @dev Returns a copy of `subject` sliced from `start` to `end` (exclusive). 689 | /// `start` and `end` are byte offsets. 690 | function slice(string memory subject, uint256 start, uint256 end) 691 | internal 692 | pure 693 | returns (string memory result) 694 | { 695 | /// @solidity memory-safe-assembly 696 | assembly { 697 | let subjectLength := mload(subject) 698 | if iszero(gt(subjectLength, end)) { end := subjectLength } 699 | if iszero(gt(subjectLength, start)) { start := subjectLength } 700 | if lt(start, end) { 701 | result := mload(0x40) 702 | let resultLength := sub(end, start) 703 | mstore(result, resultLength) 704 | // Copy the `subject` from `start` to `end`. 705 | mcopy(add(result, 0x20), add(0x20, add(subject, start)), resultLength) 706 | // Zeroize the slot after the string. 707 | mstore(add(add(result, 0x20), resultLength), 0) 708 | // Allocate memory for the length and the bytes, 709 | // rounded up to a multiple of 32. 710 | mstore(0x40, add(result, and(add(resultLength, 0x3f), not(0x1f)))) 711 | } 712 | } 713 | } 714 | 715 | /// @dev Returns a copy of `subject` sliced from `start` to the end of the string. 716 | /// `start` is a byte offset. 717 | function slice(string memory subject, uint256 start) 718 | internal 719 | pure 720 | returns (string memory result) 721 | { 722 | result = slice(subject, start, uint256(int256(-1))); 723 | } 724 | 725 | /// @dev Returns all the indices of `search` in `subject`. 726 | /// The indices are byte offsets. 727 | function indicesOf(string memory subject, string memory search) 728 | internal 729 | pure 730 | returns (uint256[] memory result) 731 | { 732 | /// @solidity memory-safe-assembly 733 | assembly { 734 | let subjectLength := mload(subject) 735 | let searchLength := mload(search) 736 | 737 | if iszero(gt(searchLength, subjectLength)) { 738 | subject := add(subject, 0x20) 739 | search := add(search, 0x20) 740 | result := add(mload(0x40), 0x20) 741 | 742 | let subjectStart := subject 743 | let subjectSearchEnd := add(sub(add(subject, subjectLength), searchLength), 1) 744 | let h := 0 745 | if iszero(lt(searchLength, 0x20)) { h := keccak256(search, searchLength) } 746 | let m := shl(3, sub(0x20, and(searchLength, 0x1f))) 747 | let s := mload(search) 748 | for {} 1 {} { 749 | let t := mload(subject) 750 | // Whether the first `searchLength % 32` bytes of 751 | // `subject` and `search` matches. 752 | if iszero(shr(m, xor(t, s))) { 753 | if h { 754 | if iszero(eq(keccak256(subject, searchLength), h)) { 755 | subject := add(subject, 1) 756 | if iszero(lt(subject, subjectSearchEnd)) { break } 757 | continue 758 | } 759 | } 760 | // Append to `result`. 761 | mstore(result, sub(subject, subjectStart)) 762 | result := add(result, 0x20) 763 | // Advance `subject` by `searchLength`. 764 | subject := add(subject, searchLength) 765 | if searchLength { 766 | if iszero(lt(subject, subjectSearchEnd)) { break } 767 | continue 768 | } 769 | } 770 | subject := add(subject, 1) 771 | if iszero(lt(subject, subjectSearchEnd)) { break } 772 | } 773 | let resultEnd := result 774 | // Assign `result` to the free memory pointer. 775 | result := mload(0x40) 776 | // Store the length of `result`. 777 | mstore(result, shr(5, sub(resultEnd, add(result, 0x20)))) 778 | // Allocate memory for result. 779 | // We allocate one more word, so this array can be recycled for {split}. 780 | mstore(0x40, add(resultEnd, 0x20)) 781 | } 782 | } 783 | } 784 | 785 | /// @dev Returns a arrays of strings based on the `delimiter` inside of the `subject` string. 786 | function split(string memory subject, string memory delimiter) 787 | internal 788 | pure 789 | returns (string[] memory result) 790 | { 791 | uint256[] memory indices = indicesOf(subject, delimiter); 792 | /// @solidity memory-safe-assembly 793 | assembly { 794 | let w := not(0x1f) 795 | let indexPtr := add(indices, 0x20) 796 | let indicesEnd := add(indexPtr, shl(5, add(mload(indices), 1))) 797 | mstore(add(indicesEnd, w), mload(subject)) 798 | mstore(indices, add(mload(indices), 1)) 799 | let prevIndex := 0 800 | for {} 1 {} { 801 | let index := mload(indexPtr) 802 | mstore(indexPtr, 0x60) 803 | if iszero(eq(index, prevIndex)) { 804 | let element := mload(0x40) 805 | let elementLength := sub(index, prevIndex) 806 | mstore(element, elementLength) 807 | // Copy the `subject` string. 808 | mcopy(add(element, 0x20), add(add(subject, prevIndex), 0x20), elementLength) 809 | // Zeroize the slot after the string. 810 | mstore(add(add(element, 0x20), elementLength), 0) 811 | // Allocate memory for the length and the bytes, 812 | // rounded up to a multiple of 32. 813 | mstore(0x40, add(element, and(add(elementLength, 0x3f), w))) 814 | // Store the `element` into the array. 815 | mstore(indexPtr, element) 816 | } 817 | prevIndex := add(index, mload(delimiter)) 818 | indexPtr := add(indexPtr, 0x20) 819 | if iszero(lt(indexPtr, indicesEnd)) { break } 820 | } 821 | result := indices 822 | if iszero(mload(delimiter)) { 823 | result := add(indices, 0x20) 824 | mstore(result, sub(mload(indices), 2)) 825 | } 826 | } 827 | } 828 | 829 | /// @dev Returns a concatenated string of `a` and `b`. 830 | /// Cheaper than `string.concat()` and does not de-align the free memory pointer. 831 | function concat(string memory a, string memory b) 832 | internal 833 | pure 834 | returns (string memory result) 835 | { 836 | /// @solidity memory-safe-assembly 837 | assembly { 838 | result := mload(0x40) 839 | let o := add(result, 0x20) 840 | let aLength := mload(a) 841 | let bLength := mload(b) 842 | // Copy `a` into `result`. 843 | mcopy(o, add(a, 0x20), aLength) 844 | o := add(o, aLength) 845 | // Copy `b` into `result`. 846 | mcopy(o, add(b, 0x20), bLength) 847 | // Stores the length. 848 | mstore(result, add(aLength, bLength)) 849 | let last := add(o, bLength) 850 | // Zeroize the slot after the string. 851 | mstore(last, 0) 852 | // Allocate memory for the length and the bytes, 853 | // rounded up to a multiple of 32. 854 | mstore(0x40, and(add(last, 0x1f), not(0x1f))) 855 | } 856 | } 857 | 858 | /// @dev Returns a copy of the string in either lowercase or UPPERCASE. 859 | /// WARNING! This function is only compatible with 7-bit ASCII strings. 860 | function toCase(string memory subject, bool toUpper) 861 | internal 862 | pure 863 | returns (string memory result) 864 | { 865 | /// @solidity memory-safe-assembly 866 | assembly { 867 | let length := mload(subject) 868 | if length { 869 | result := add(mload(0x40), 0x20) 870 | subject := add(subject, 1) 871 | let flags := shl(add(70, shl(5, toUpper)), 0x3ffffff) 872 | let w := not(0) 873 | for { let o := length } 1 {} { 874 | o := add(o, w) 875 | let b := and(0xff, mload(add(subject, o))) 876 | mstore8(add(result, o), xor(b, and(shr(b, flags), 0x20))) 877 | if iszero(o) { break } 878 | } 879 | result := mload(0x40) 880 | mstore(result, length) // Store the length. 881 | let last := add(add(result, 0x20), length) 882 | mstore(last, 0) // Zeroize the slot after the string. 883 | mstore(0x40, add(last, 0x20)) // Allocate the memory. 884 | } 885 | } 886 | } 887 | 888 | /// @dev Returns a string from a small bytes32 string. 889 | /// `s` must be null-terminated, or behavior will be undefined. 890 | function fromSmallString(bytes32 s) internal pure returns (string memory result) { 891 | /// @solidity memory-safe-assembly 892 | assembly { 893 | result := mload(0x40) 894 | let n := 0 895 | for {} byte(n, s) { n := add(n, 1) } {} // Scan for '\0'. 896 | mstore(result, n) 897 | let o := add(result, 0x20) 898 | mstore(o, s) 899 | mstore(add(o, n), 0) 900 | mstore(0x40, add(result, 0x40)) 901 | } 902 | } 903 | 904 | /// @dev Returns the small string, with all bytes after the first null byte zeroized. 905 | function normalizeSmallString(bytes32 s) internal pure returns (bytes32 result) { 906 | /// @solidity memory-safe-assembly 907 | assembly { 908 | for {} byte(result, s) { result := add(result, 1) } {} // Scan for '\0'. 909 | mstore(0x00, s) 910 | mstore(result, 0x00) 911 | result := mload(0x00) 912 | } 913 | } 914 | 915 | /// @dev Returns the string as a normalized null-terminated small string. 916 | function toSmallString(string memory s) internal pure returns (bytes32 result) { 917 | /// @solidity memory-safe-assembly 918 | assembly { 919 | result := mload(s) 920 | if iszero(lt(result, 33)) { 921 | mstore(0x00, 0xec92f9a3) // `TooBigForSmallString()`. 922 | revert(0x1c, 0x04) 923 | } 924 | result := shl(shl(3, sub(32, result)), mload(add(s, result))) 925 | } 926 | } 927 | 928 | /// @dev Returns a lowercased copy of the string. 929 | /// WARNING! This function is only compatible with 7-bit ASCII strings. 930 | function lower(string memory subject) internal pure returns (string memory result) { 931 | result = toCase(subject, false); 932 | } 933 | 934 | /// @dev Returns an UPPERCASED copy of the string. 935 | /// WARNING! This function is only compatible with 7-bit ASCII strings. 936 | function upper(string memory subject) internal pure returns (string memory result) { 937 | result = toCase(subject, true); 938 | } 939 | 940 | /// @dev Escapes the string to be used within HTML tags. 941 | function escapeHTML(string memory s) internal pure returns (string memory result) { 942 | /// @solidity memory-safe-assembly 943 | assembly { 944 | let end := add(s, mload(s)) 945 | result := add(mload(0x40), 0x20) 946 | // Store the bytes of the packed offsets and strides into the scratch space. 947 | // `packed = (stride << 5) | offset`. Max offset is 20. Max stride is 6. 948 | mstore(0x1f, 0x900094) 949 | mstore(0x08, 0xc0000000a6ab) 950 | // Store ""&'<>" into the scratch space. 951 | mstore(0x00, shl(64, 0x2671756f743b26616d703b262333393b266c743b2667743b)) 952 | for {} iszero(eq(s, end)) {} { 953 | s := add(s, 1) 954 | let c := and(mload(s), 0xff) 955 | // Not in `["\"","'","&","<",">"]`. 956 | if iszero(and(shl(c, 1), 0x500000c400000000)) { 957 | mstore8(result, c) 958 | result := add(result, 1) 959 | continue 960 | } 961 | let t := shr(248, mload(c)) 962 | mstore(result, mload(and(t, 0x1f))) 963 | result := add(result, shr(5, t)) 964 | } 965 | let last := result 966 | mstore(last, 0) // Zeroize the slot after the string. 967 | result := mload(0x40) 968 | mstore(result, sub(last, add(result, 0x20))) // Store the length. 969 | mstore(0x40, add(last, 0x20)) // Allocate the memory. 970 | } 971 | } 972 | 973 | /// @dev Escapes the string to be used within double-quotes in a JSON. 974 | /// If `addDoubleQuotes` is true, the result will be enclosed in double-quotes. 975 | function escapeJSON(string memory s, bool addDoubleQuotes) 976 | internal 977 | pure 978 | returns (string memory result) 979 | { 980 | /// @solidity memory-safe-assembly 981 | assembly { 982 | let end := add(s, mload(s)) 983 | result := add(mload(0x40), 0x20) 984 | if addDoubleQuotes { 985 | mstore8(result, 34) 986 | result := add(1, result) 987 | } 988 | // Store "\\u0000" in scratch space. 989 | // Store "0123456789abcdef" in scratch space. 990 | // Also, store `{0x08:"b", 0x09:"t", 0x0a:"n", 0x0c:"f", 0x0d:"r"}`. 991 | // into the scratch space. 992 | mstore(0x15, 0x5c75303030303031323334353637383961626364656662746e006672) 993 | // Bitmask for detecting `["\"","\\"]`. 994 | let e := or(shl(0x22, 1), shl(0x5c, 1)) 995 | for {} iszero(eq(s, end)) {} { 996 | s := add(s, 1) 997 | let c := and(mload(s), 0xff) 998 | if iszero(lt(c, 0x20)) { 999 | if iszero(and(shl(c, 1), e)) { 1000 | // Not in `["\"","\\"]`. 1001 | mstore8(result, c) 1002 | result := add(result, 1) 1003 | continue 1004 | } 1005 | mstore8(result, 0x5c) // "\\". 1006 | mstore8(add(result, 1), c) 1007 | result := add(result, 2) 1008 | continue 1009 | } 1010 | if iszero(and(shl(c, 1), 0x3700)) { 1011 | // Not in `["\b","\t","\n","\f","\d"]`. 1012 | mstore8(0x1d, mload(shr(4, c))) // Hex value. 1013 | mstore8(0x1e, mload(and(c, 15))) // Hex value. 1014 | mstore(result, mload(0x19)) // "\\u00XX". 1015 | result := add(result, 6) 1016 | continue 1017 | } 1018 | mstore8(result, 0x5c) // "\\". 1019 | mstore8(add(result, 1), mload(add(c, 8))) 1020 | result := add(result, 2) 1021 | } 1022 | if addDoubleQuotes { 1023 | mstore8(result, 34) 1024 | result := add(1, result) 1025 | } 1026 | let last := result 1027 | mstore(last, 0) // Zeroize the slot after the string. 1028 | result := mload(0x40) 1029 | mstore(result, sub(last, add(result, 0x20))) // Store the length. 1030 | mstore(0x40, add(last, 0x20)) // Allocate the memory. 1031 | } 1032 | } 1033 | 1034 | /// @dev Escapes the string to be used within double-quotes in a JSON. 1035 | function escapeJSON(string memory s) internal pure returns (string memory result) { 1036 | result = escapeJSON(s, false); 1037 | } 1038 | 1039 | /// @dev Returns whether `a` equals `b`. 1040 | function eq(string memory a, string memory b) internal pure returns (bool result) { 1041 | /// @solidity memory-safe-assembly 1042 | assembly { 1043 | result := eq(keccak256(add(a, 0x20), mload(a)), keccak256(add(b, 0x20), mload(b))) 1044 | } 1045 | } 1046 | 1047 | /// @dev Returns whether `a` equals `b`, where `b` is a null-terminated small string. 1048 | function eqs(string memory a, bytes32 b) internal pure returns (bool result) { 1049 | /// @solidity memory-safe-assembly 1050 | assembly { 1051 | // These should be evaluated on compile time, as far as possible. 1052 | let m := not(shl(7, div(not(iszero(b)), 255))) // `0x7f7f ...`. 1053 | let x := not(or(m, or(b, add(m, and(b, m))))) 1054 | let r := shl(7, iszero(iszero(shr(128, x)))) 1055 | r := or(r, shl(6, iszero(iszero(shr(64, shr(r, x)))))) 1056 | r := or(r, shl(5, lt(0xffffffff, shr(r, x)))) 1057 | r := or(r, shl(4, lt(0xffff, shr(r, x)))) 1058 | r := or(r, shl(3, lt(0xff, shr(r, x)))) 1059 | // forgefmt: disable-next-item 1060 | result := gt(eq(mload(a), add(iszero(x), xor(31, shr(3, r)))), 1061 | xor(shr(add(8, r), b), shr(add(8, r), mload(add(a, 0x20))))) 1062 | } 1063 | } 1064 | 1065 | /// @dev Packs a single string with its length into a single word. 1066 | /// Returns `bytes32(0)` if the length is zero or greater than 31. 1067 | function packOne(string memory a) internal pure returns (bytes32 result) { 1068 | /// @solidity memory-safe-assembly 1069 | assembly { 1070 | // We don't need to zero right pad the string, 1071 | // since this is our own custom non-standard packing scheme. 1072 | result := 1073 | mul( 1074 | // Load the length and the bytes. 1075 | mload(add(a, 0x1f)), 1076 | // `length != 0 && length < 32`. Abuses underflow. 1077 | // Assumes that the length is valid and within the block gas limit. 1078 | lt(sub(mload(a), 1), 0x1f) 1079 | ) 1080 | } 1081 | } 1082 | 1083 | /// @dev Unpacks a string packed using {packOne}. 1084 | /// Returns the empty string if `packed` is `bytes32(0)`. 1085 | /// If `packed` is not an output of {packOne}, the output behavior is undefined. 1086 | function unpackOne(bytes32 packed) internal pure returns (string memory result) { 1087 | /// @solidity memory-safe-assembly 1088 | assembly { 1089 | // Grab the free memory pointer. 1090 | result := mload(0x40) 1091 | // Allocate 2 words (1 for the length, 1 for the bytes). 1092 | mstore(0x40, add(result, 0x40)) 1093 | // Zeroize the length slot. 1094 | mstore(result, 0) 1095 | // Store the length and bytes. 1096 | mstore(add(result, 0x1f), packed) 1097 | // Right pad with zeroes. 1098 | mstore(add(add(result, 0x20), mload(result)), 0) 1099 | } 1100 | } 1101 | 1102 | /// @dev Packs two strings with their lengths into a single word. 1103 | /// Returns `bytes32(0)` if combined length is zero or greater than 30. 1104 | function packTwo(string memory a, string memory b) internal pure returns (bytes32 result) { 1105 | /// @solidity memory-safe-assembly 1106 | assembly { 1107 | let aLength := mload(a) 1108 | // We don't need to zero right pad the strings, 1109 | // since this is our own custom non-standard packing scheme. 1110 | result := 1111 | mul( 1112 | // Load the length and the bytes of `a` and `b`. 1113 | or( 1114 | shl(shl(3, sub(0x1f, aLength)), mload(add(a, aLength))), 1115 | mload(sub(add(b, 0x1e), aLength)) 1116 | ), 1117 | // `totalLength != 0 && totalLength < 31`. Abuses underflow. 1118 | // Assumes that the lengths are valid and within the block gas limit. 1119 | lt(sub(add(aLength, mload(b)), 1), 0x1e) 1120 | ) 1121 | } 1122 | } 1123 | 1124 | /// @dev Unpacks strings packed using {packTwo}. 1125 | /// Returns the empty strings if `packed` is `bytes32(0)`. 1126 | /// If `packed` is not an output of {packTwo}, the output behavior is undefined. 1127 | function unpackTwo(bytes32 packed) 1128 | internal 1129 | pure 1130 | returns (string memory resultA, string memory resultB) 1131 | { 1132 | /// @solidity memory-safe-assembly 1133 | assembly { 1134 | // Grab the free memory pointer. 1135 | resultA := mload(0x40) 1136 | resultB := add(resultA, 0x40) 1137 | // Allocate 2 words for each string (1 for the length, 1 for the byte). Total 4 words. 1138 | mstore(0x40, add(resultB, 0x40)) 1139 | // Zeroize the length slots. 1140 | mstore(resultA, 0) 1141 | mstore(resultB, 0) 1142 | // Store the lengths and bytes. 1143 | mstore(add(resultA, 0x1f), packed) 1144 | mstore(add(resultB, 0x1f), mload(add(add(resultA, 0x20), mload(resultA)))) 1145 | // Right pad with zeroes. 1146 | mstore(add(add(resultA, 0x20), mload(resultA)), 0) 1147 | mstore(add(add(resultB, 0x20), mload(resultB)), 0) 1148 | } 1149 | } 1150 | 1151 | /// @dev Directly returns `a` without copying. 1152 | function directReturn(string memory a) internal pure { 1153 | assembly { 1154 | // Assumes that the string does not start from the scratch space. 1155 | let retStart := sub(a, 0x20) 1156 | let retSize := add(mload(a), 0x40) 1157 | // Right pad with zeroes. Just in case the string is produced 1158 | // by a method that doesn't zero right pad. 1159 | mstore(add(retStart, retSize), 0) 1160 | // Store the return offset. 1161 | mstore(retStart, 0x20) 1162 | // End the transaction, returning the string. 1163 | return(retStart, retSize) 1164 | } 1165 | } 1166 | } 1167 | -------------------------------------------------------------------------------- /src/utils/LibT.sol: -------------------------------------------------------------------------------- 1 | // SPDX-License-Identifier: MIT 2 | pragma solidity ^0.8.24; 3 | 4 | /// @notice Transient storage helper. 5 | /// @author Soledge (https://github.com/vectorized/soledge/blob/main/src/utils/LibT.sol) 6 | /// 7 | /// @dev Note: This implementation utilizes `TLOAD`, and `TSTORE` opcodes. 8 | /// Please ensure that the chain you are deploying on supports them. 9 | library LibT { 10 | /// @dev Returns the value at `tSlot` in transient storage. 11 | function get(bytes32 tSlot) internal view returns (bytes32 result) { 12 | /// @solidity memory-safe-assembly 13 | assembly { 14 | result := tload(tSlot) 15 | } 16 | } 17 | 18 | /// @dev Sets the value at `tSlot` in transient storage to `value`. 19 | function set(bytes32 tSlot, bytes32 value) internal { 20 | /// @solidity memory-safe-assembly 21 | assembly { 22 | tstore(tSlot, value) 23 | } 24 | } 25 | 26 | /// @dev Resets the value at `tSlot` in transient storage to zero. 27 | function clear(bytes32 tSlot) internal { 28 | /// @solidity memory-safe-assembly 29 | assembly { 30 | tstore(tSlot, 0) 31 | } 32 | } 33 | } 34 | -------------------------------------------------------------------------------- /src/utils/ReentrancyGuard.sol: -------------------------------------------------------------------------------- 1 | // SPDX-License-Identifier: MIT 2 | pragma solidity ^0.8.24; 3 | 4 | /// @notice Reentrancy guard mixin. 5 | /// @author Soledge (https://github.com/vectorized/soledge/blob/main/src/utils/ReentrancyGuard.sol) 6 | /// 7 | /// @dev Note: This implementation utilizes the `TSTORE` and `TLOAD` opcodes. 8 | /// Please ensure that the chain you are deploying on supports them. 9 | abstract contract ReentrancyGuard { 10 | /*«-«-«-«-«-«-«-«-«-«-«-«-«-«-«-«-«-«-«-«-«-«-«-«-«-«-«-«-«-«-*/ 11 | /* CUSTOM ERRORS */ 12 | /*-»-»-»-»-»-»-»-»-»-»-»-»-»-»-»-»-»-»-»-»-»-»-»-»-»-»-»-»-»-»*/ 13 | 14 | /// @dev Unauthorized reentrant call. 15 | error Reentrancy(); 16 | 17 | /*«-«-«-«-«-«-«-«-«-«-«-«-«-«-«-«-«-«-«-«-«-«-«-«-«-«-«-«-«-«-*/ 18 | /* STORAGE */ 19 | /*-»-»-»-»-»-»-»-»-»-»-»-»-»-»-»-»-»-»-»-»-»-»-»-»-»-»-»-»-»-»*/ 20 | 21 | /// @dev Equivalent to: `uint72(bytes9(keccak256("_REENTRANCY_GUARD_SLOT")))`. 22 | /// 9 bytes is large enough to avoid collisions in practice, 23 | /// but not too large to result in excessive bytecode bloat. 24 | uint256 private constant _REENTRANCY_GUARD_SLOT = 0x929eee149b4bd21268; 25 | 26 | /*«-«-«-«-«-«-«-«-«-«-«-«-«-«-«-«-«-«-«-«-«-«-«-«-«-«-«-«-«-«-*/ 27 | /* REENTRANCY GUARD */ 28 | /*-»-»-»-»-»-»-»-»-»-»-»-»-»-»-»-»-»-»-»-»-»-»-»-»-»-»-»-»-»-»*/ 29 | 30 | /// @dev Guards a function from reentrancy. 31 | modifier nonReentrant() virtual { 32 | /// @solidity memory-safe-assembly 33 | assembly { 34 | if tload(_REENTRANCY_GUARD_SLOT) { 35 | mstore(0x00, 0xab143c06) // `Reentrancy()`. 36 | revert(0x1c, 0x04) 37 | } 38 | tstore(_REENTRANCY_GUARD_SLOT, address()) 39 | } 40 | _; 41 | /// @solidity memory-safe-assembly 42 | assembly { 43 | tstore(_REENTRANCY_GUARD_SLOT, 0) 44 | } 45 | } 46 | 47 | /// @dev Guards a view function from read-only reentrancy. 48 | modifier nonReadReentrant() virtual { 49 | /// @solidity memory-safe-assembly 50 | assembly { 51 | if tload(_REENTRANCY_GUARD_SLOT) { 52 | mstore(0x00, 0xab143c06) // `Reentrancy()`. 53 | revert(0x1c, 0x04) 54 | } 55 | } 56 | _; 57 | } 58 | } 59 | -------------------------------------------------------------------------------- /test/LibT.t.sol: -------------------------------------------------------------------------------- 1 | // SPDX-License-Identifier: MIT 2 | pragma solidity ^0.8.24; 3 | 4 | import "./utils/SoladyTest.sol"; 5 | import {LibT} from "../src/utils/LibT.sol"; 6 | 7 | contract LibTTest is SoladyTest { 8 | function testLibT(bytes32 s, uint256 i) public { 9 | unchecked { 10 | assertEq(LibT.get(s), bytes32(0)); 11 | LibT.set(s, bytes32(i)); 12 | assertEq(LibT.get(s), bytes32(i)); 13 | LibT.clear(s); 14 | assertEq(LibT.get(s), bytes32(0)); 15 | } 16 | } 17 | } 18 | -------------------------------------------------------------------------------- /test/README.md: -------------------------------------------------------------------------------- 1 | ## WARNING! 2 | 3 | All test files are strictly intended for testing purposes only. 4 | 5 | Do NOT copy anything here into production code unless you really know what you are doing. -------------------------------------------------------------------------------- /test/ReentrancyGuard.t.sol: -------------------------------------------------------------------------------- 1 | // SPDX-License-Identifier: MIT 2 | pragma solidity ^0.8.24; 3 | 4 | import "./utils/SoladyTest.sol"; 5 | import {ReentrancyGuard} from "../src/utils/ReentrancyGuard.sol"; 6 | import {MockReentrancyGuard, ReentrancyAttack} from "./utils/mocks/MockReentrancyGuard.sol"; 7 | 8 | contract ReentrancyGuardTest is SoladyTest { 9 | MockReentrancyGuard immutable target = new MockReentrancyGuard(); 10 | ReentrancyAttack immutable reentrancyAttack = new ReentrancyAttack(); 11 | 12 | // Before and after each test, the reentrancy guard should be unlocked. 13 | modifier expectBeforeAfterReentrancyGuardUnlocked() { 14 | assertEq(target.isReentrancyGuardLocked(), false); 15 | _; 16 | assertEq(target.isReentrancyGuardLocked(), false); 17 | } 18 | 19 | function testRevertGuardLocked() external expectBeforeAfterReentrancyGuardUnlocked { 20 | // Attempt to call a `nonReentrant` methiod with an unprotected method. 21 | // Expect a success. 22 | target.callUnguardedToGuarded(); 23 | assertEq(target.enterTimes(), 1); 24 | 25 | // Attempt to call a `nonReentrant` method within a `nonReentrant` method. 26 | // Expect a revert with the `Reentrancy` error. 27 | vm.expectRevert(ReentrancyGuard.Reentrancy.selector); 28 | target.callGuardedToGuarded(); 29 | } 30 | 31 | function testRevertReadGuardLocked() external expectBeforeAfterReentrancyGuardUnlocked { 32 | // Attempt to call a `nonReadReentrant` methiod with an unprotected method. 33 | // Expect a success. 34 | target.callUnguardedToReadGuarded(); 35 | assertEq(target.enterTimes(), 1); 36 | 37 | // Attempt to call a `nonReadReentrant` method within a `nonReentrant` method. 38 | // Expect a revert with the `Reentrancy` error. 39 | vm.expectRevert(ReentrancyGuard.Reentrancy.selector); 40 | target.callGuardedToReadGuarded(); 41 | } 42 | 43 | function testRevertRemoteCallback() external expectBeforeAfterReentrancyGuardUnlocked { 44 | // Attempt to reenter a `nonReentrant` method from a remote contract. 45 | vm.expectRevert(ReentrancyAttack.ReentrancyAttackFailed.selector); 46 | target.countAndCall(reentrancyAttack); 47 | } 48 | 49 | function testRecursiveDirectUnguardedCall() external expectBeforeAfterReentrancyGuardUnlocked { 50 | // Expect to be able to call unguarded methods recursively. 51 | // Expect a success. 52 | target.countUnguardedDirectRecursive(10); 53 | assertEq(target.enterTimes(), 10); 54 | } 55 | 56 | function testRevertRecursiveDirectGuardedCall() 57 | external 58 | expectBeforeAfterReentrancyGuardUnlocked 59 | { 60 | // Attempt to reenter a `nonReentrant` method from a direct call. 61 | // Expect a revert with the `Reentrancy` error. 62 | vm.expectRevert(ReentrancyGuard.Reentrancy.selector); 63 | target.countGuardedDirectRecursive(10); 64 | assertEq(target.enterTimes(), 0); 65 | } 66 | 67 | function testRecursiveIndirectUnguardedCall() 68 | external 69 | expectBeforeAfterReentrancyGuardUnlocked 70 | { 71 | // Expect to be able to call unguarded methods recursively. 72 | // Expect a success. 73 | target.countUnguardedIndirectRecursive(10); 74 | assertEq(target.enterTimes(), 10); 75 | } 76 | 77 | function testRevertRecursiveIndirectGuardedCall() 78 | external 79 | expectBeforeAfterReentrancyGuardUnlocked 80 | { 81 | // Attempt to reenter a `nonReentrant` method from an indirect call. 82 | vm.expectRevert(ReentrancyGuard.Reentrancy.selector); 83 | target.countGuardedIndirectRecursive(10); 84 | assertEq(target.enterTimes(), 0); 85 | } 86 | } 87 | -------------------------------------------------------------------------------- /test/utils/InvariantTest.sol: -------------------------------------------------------------------------------- 1 | // SPDX-License-Identifier: MIT 2 | pragma solidity ^0.8.4; 3 | 4 | contract InvariantTest { 5 | address[] private _targets; 6 | 7 | function targetContracts() public view virtual returns (address[] memory) { 8 | require(_targets.length > 0, "NO_TARGET_CONTRACTS"); 9 | return _targets; 10 | } 11 | 12 | function _addTargetContract(address newTargetContract) internal virtual { 13 | _targets.push(newTargetContract); 14 | } 15 | } 16 | -------------------------------------------------------------------------------- /test/utils/SoladyTest.sol: -------------------------------------------------------------------------------- 1 | // SPDX-License-Identifier: MIT 2 | pragma solidity ^0.8.4; 3 | 4 | import "./forge-std/Test.sol"; 5 | import "./TestPlus.sol"; 6 | 7 | contract SoladyTest is Test, TestPlus { 8 | /// @dev Alias for `_hem`. 9 | function _bound(uint256 x, uint256 min, uint256 max) internal pure virtual returns (uint256) { 10 | return _hem(x, min, max); 11 | } 12 | } 13 | -------------------------------------------------------------------------------- /test/utils/TestPlus.sol: -------------------------------------------------------------------------------- 1 | // SPDX-License-Identifier: MIT 2 | pragma solidity ^0.8.4; 3 | 4 | contract TestPlus { 5 | event LogString(string name, string value); 6 | event LogBytes(string name, bytes value); 7 | event LogUint(string name, uint256 value); 8 | event LogInt(string name, int256 value); 9 | 10 | /// @dev `address(bytes20(uint160(uint256(keccak256("hevm cheat code")))))`. 11 | address private constant _VM_ADDRESS = 0x7109709ECfa91a80626fF3989D68f67F5b1DD12D; 12 | 13 | /// @dev Fills the memory with junk, for more robust testing of inline assembly 14 | /// which reads/write to the memory. 15 | function _brutalizeMemory() internal view { 16 | // To prevent a solidity 0.8.13 bug. 17 | // See: https://blog.soliditylang.org/2022/06/15/inline-assembly-memory-side-effects-bug 18 | // Basically, we need to access a solidity variable from the assembly to 19 | // tell the compiler that this assembly block is not in isolation. 20 | uint256 zero; 21 | /// @solidity memory-safe-assembly 22 | assembly { 23 | let offset := mload(0x40) // Start the offset at the free memory pointer. 24 | calldatacopy(offset, zero, calldatasize()) 25 | 26 | // Fill the 64 bytes of scratch space with garbage. 27 | mstore(zero, add(caller(), gas())) 28 | mstore(0x20, keccak256(offset, calldatasize())) 29 | mstore(zero, keccak256(zero, 0x40)) 30 | 31 | let r0 := mload(zero) 32 | let r1 := mload(0x20) 33 | 34 | let cSize := add(codesize(), iszero(codesize())) 35 | if iszero(lt(cSize, 32)) { cSize := sub(cSize, and(mload(0x02), 0x1f)) } 36 | let start := mod(mload(0x10), cSize) 37 | let size := mul(sub(cSize, start), gt(cSize, start)) 38 | let times := div(0x7ffff, cSize) 39 | if iszero(lt(times, 128)) { times := 128 } 40 | 41 | // Occasionally offset the offset by a pseudorandom large amount. 42 | // Can't be too large, or we will easily get out-of-gas errors. 43 | offset := add(offset, mul(iszero(and(r1, 0xf)), and(r0, 0xfffff))) 44 | 45 | // Fill the free memory with garbage. 46 | // prettier-ignore 47 | for { let w := not(0) } 1 {} { 48 | mstore(offset, r0) 49 | mstore(add(offset, 0x20), r1) 50 | offset := add(offset, 0x40) 51 | // We use codecopy instead of the identity precompile 52 | // to avoid polluting the `forge test -vvvv` output with tons of junk. 53 | codecopy(offset, start, size) 54 | codecopy(add(offset, size), 0, start) 55 | offset := add(offset, cSize) 56 | times := add(times, w) // `sub(times, 1)`. 57 | if iszero(times) { break } 58 | } 59 | } 60 | } 61 | 62 | /// @dev Fills the scratch space with junk, for more robust testing of inline assembly 63 | /// which reads/write to the memory. 64 | function _brutalizeScratchSpace() internal view { 65 | // To prevent a solidity 0.8.13 bug. 66 | // See: https://blog.soliditylang.org/2022/06/15/inline-assembly-memory-side-effects-bug 67 | // Basically, we need to access a solidity variable from the assembly to 68 | // tell the compiler that this assembly block is not in isolation. 69 | uint256 zero; 70 | /// @solidity memory-safe-assembly 71 | assembly { 72 | let offset := mload(0x40) // Start the offset at the free memory pointer. 73 | calldatacopy(offset, zero, calldatasize()) 74 | 75 | // Fill the 64 bytes of scratch space with garbage. 76 | mstore(zero, add(caller(), gas())) 77 | mstore(0x20, keccak256(offset, calldatasize())) 78 | mstore(zero, keccak256(zero, 0x40)) 79 | } 80 | } 81 | 82 | /// @dev Fills the memory with junk, for more robust testing of inline assembly 83 | /// which reads/write to the memory. 84 | modifier brutalizeMemory() { 85 | _brutalizeMemory(); 86 | _; 87 | _checkMemory(); 88 | } 89 | 90 | /// @dev Fills the scratch space with junk, for more robust testing of inline assembly 91 | /// which reads/write to the memory. 92 | modifier brutalizeScratchSpace() { 93 | _brutalizeScratchSpace(); 94 | _; 95 | _checkMemory(); 96 | } 97 | 98 | /// @dev Returns a pseudorandom random number from [0 .. 2**256 - 1] (inclusive). 99 | /// For usage in fuzz tests, please ensure that the function has an unnamed uint256 argument. 100 | /// e.g. `testSomething(uint256) public`. 101 | function _random() internal returns (uint256 r) { 102 | /// @solidity memory-safe-assembly 103 | assembly { 104 | // This is the keccak256 of a very long string I randomly mashed on my keyboard. 105 | let sSlot := 0xd715531fe383f818c5f158c342925dcf01b954d24678ada4d07c36af0f20e1ee 106 | let sValue := sload(sSlot) 107 | 108 | mstore(0x20, sValue) 109 | r := keccak256(0x20, 0x40) 110 | 111 | // If the storage is uninitialized, initialize it to the keccak256 of the calldata. 112 | if iszero(sValue) { 113 | sValue := sSlot 114 | let m := mload(0x40) 115 | calldatacopy(m, 0, calldatasize()) 116 | r := keccak256(m, calldatasize()) 117 | } 118 | sstore(sSlot, add(r, 1)) 119 | 120 | // Do some biased sampling for more robust tests. 121 | // prettier-ignore 122 | for {} 1 {} { 123 | let d := byte(0, r) 124 | // With a 1/256 chance, randomly set `r` to any of 0,1,2. 125 | if iszero(d) { 126 | r := and(r, 3) 127 | break 128 | } 129 | // With a 1/2 chance, set `r` to near a random power of 2. 130 | if iszero(and(2, d)) { 131 | // Set `t` either `not(0)` or `xor(sValue, r)`. 132 | let t := xor(not(0), mul(iszero(and(4, d)), not(xor(sValue, r)))) 133 | // Set `r` to `t` shifted left or right by a random multiple of 8. 134 | switch and(8, d) 135 | case 0 { 136 | if iszero(and(16, d)) { t := 1 } 137 | r := add(shl(shl(3, and(byte(3, r), 0x1f)), t), sub(and(r, 7), 3)) 138 | } 139 | default { 140 | if iszero(and(16, d)) { t := shl(255, 1) } 141 | r := add(shr(shl(3, and(byte(3, r), 0x1f)), t), sub(and(r, 7), 3)) 142 | } 143 | // With a 1/2 chance, negate `r`. 144 | if iszero(and(0x20, d)) { r := not(r) } 145 | break 146 | } 147 | // Otherwise, just set `r` to `xor(sValue, r)`. 148 | r := xor(sValue, r) 149 | break 150 | } 151 | } 152 | } 153 | 154 | /// @dev Returns a random signer and its private key. 155 | function _randomSigner() internal returns (address signer, uint256 privateKey) { 156 | uint256 privateKeyMax = 0xfffffffffffffffffffffffffffffffebaaedce6af48a03bbfd25e8cd0364140; 157 | privateKey = _hem(_random(), 1, privateKeyMax); 158 | /// @solidity memory-safe-assembly 159 | assembly { 160 | mstore(0x00, 0xffa18649) // `addr(uint256)`. 161 | mstore(0x20, privateKey) 162 | if iszero(call(gas(), _VM_ADDRESS, 0, 0x1c, 0x24, 0x00, 0x20)) { revert(0, 0) } 163 | signer := mload(0x00) 164 | } 165 | } 166 | 167 | /// @dev Returns a random address. 168 | function _randomAddress() internal returns (address result) { 169 | result = address(uint160(_random())); 170 | } 171 | 172 | /// @dev Returns a random non-zero address. 173 | function _randomNonZeroAddress() internal returns (address result) { 174 | do { 175 | result = address(uint160(_random())); 176 | } while (result == address(0)); 177 | } 178 | 179 | /// @dev Rounds up the free memory pointer to the next word boundary. 180 | /// Sometimes, some Solidity operations cause the free memory pointer to be misaligned. 181 | function _roundUpFreeMemoryPointer() internal pure { 182 | // To prevent a solidity 0.8.13 bug. 183 | // See: https://blog.soliditylang.org/2022/06/15/inline-assembly-memory-side-effects-bug 184 | // Basically, we need to access a solidity variable from the assembly to 185 | // tell the compiler that this assembly block is not in isolation. 186 | uint256 twoWords = 0x40; 187 | /// @solidity memory-safe-assembly 188 | assembly { 189 | mstore(twoWords, and(add(mload(twoWords), 0x1f), not(0x1f))) 190 | } 191 | } 192 | 193 | /// @dev Misaligns the free memory pointer. 194 | /// The free memory pointer has a 1/32 chance to be aligned. 195 | function _misalignFreeMemoryPointer() internal pure { 196 | uint256 twoWords = 0x40; 197 | /// @solidity memory-safe-assembly 198 | assembly { 199 | let m := mload(twoWords) 200 | m := add(m, mul(and(keccak256(0x00, twoWords), 0x1f), iszero(and(m, 0x1f)))) 201 | mstore(twoWords, m) 202 | } 203 | } 204 | 205 | /// @dev Check if the free memory pointer and the zero slot are not contaminated. 206 | /// Useful for cases where these slots are used for temporary storage. 207 | function _checkMemory() internal pure { 208 | bool zeroSlotIsNotZero; 209 | bool freeMemoryPointerOverflowed; 210 | /// @solidity memory-safe-assembly 211 | assembly { 212 | // Write ones to the free memory, to make subsequent checks fail if 213 | // insufficient memory is allocated. 214 | mstore(mload(0x40), not(0)) 215 | // Test at a lower, but reasonable limit for more safety room. 216 | if gt(mload(0x40), 0xffffffff) { freeMemoryPointerOverflowed := 1 } 217 | // Check the value of the zero slot. 218 | zeroSlotIsNotZero := mload(0x60) 219 | } 220 | if (freeMemoryPointerOverflowed) revert("`0x40` overflowed!"); 221 | if (zeroSlotIsNotZero) revert("`0x60` is not zero!"); 222 | } 223 | 224 | /// @dev Check if `s`: 225 | /// - Has sufficient memory allocated. 226 | /// - Is zero right padded (cuz some frontends like Etherscan has issues 227 | /// with decoding non-zero-right-padded strings). 228 | function _checkMemory(bytes memory s) internal pure { 229 | bool notZeroRightPadded; 230 | bool insufficientMalloc; 231 | /// @solidity memory-safe-assembly 232 | assembly { 233 | // Write ones to the free memory, to make subsequent checks fail if 234 | // insufficient memory is allocated. 235 | mstore(mload(0x40), not(0)) 236 | let length := mload(s) 237 | let lastWord := mload(add(add(s, 0x20), and(length, not(0x1f)))) 238 | let remainder := and(length, 0x1f) 239 | if remainder { if shl(mul(8, remainder), lastWord) { notZeroRightPadded := 1 } } 240 | // Check if the memory allocated is sufficient. 241 | if length { if gt(add(add(s, 0x20), length), mload(0x40)) { insufficientMalloc := 1 } } 242 | } 243 | if (notZeroRightPadded) revert("Not zero right padded!"); 244 | if (insufficientMalloc) revert("Insufficient memory allocation!"); 245 | _checkMemory(); 246 | } 247 | 248 | /// @dev For checking the memory allocation for string `s`. 249 | function _checkMemory(string memory s) internal pure { 250 | _checkMemory(bytes(s)); 251 | } 252 | 253 | /// @dev Adapted from `bound`: 254 | /// https://github.com/foundry-rs/forge-std/blob/ff4bf7db008d096ea5a657f2c20516182252a3ed/src/StdUtils.sol#L10 255 | /// Differentially fuzzed tested against the original implementation. 256 | function _hem(uint256 x, uint256 min, uint256 max) 257 | internal 258 | pure 259 | virtual 260 | returns (uint256 result) 261 | { 262 | require(min <= max, "Max is less than min."); 263 | 264 | /// @solidity memory-safe-assembly 265 | assembly { 266 | // prettier-ignore 267 | for {} 1 {} { 268 | // If `x` is between `min` and `max`, return `x` directly. 269 | // This is to ensure that dictionary values 270 | // do not get shifted if the min is nonzero. 271 | // More info: https://github.com/foundry-rs/forge-std/issues/188 272 | if iszero(or(lt(x, min), gt(x, max))) { 273 | result := x 274 | break 275 | } 276 | 277 | let size := add(sub(max, min), 1) 278 | if and(iszero(gt(x, 3)), gt(size, x)) { 279 | result := add(min, x) 280 | break 281 | } 282 | 283 | let w := not(0) 284 | if and(iszero(lt(x, sub(0, 4))), gt(size, sub(w, x))) { 285 | result := sub(max, sub(w, x)) 286 | break 287 | } 288 | 289 | // Otherwise, wrap x into the range [min, max], 290 | // i.e. the range is inclusive. 291 | if iszero(lt(x, max)) { 292 | let d := sub(x, max) 293 | let r := mod(d, size) 294 | if iszero(r) { 295 | result := max 296 | break 297 | } 298 | result := add(add(min, r), w) 299 | break 300 | } 301 | let d := sub(min, x) 302 | let r := mod(d, size) 303 | if iszero(r) { 304 | result := min 305 | break 306 | } 307 | result := add(sub(max, r), 1) 308 | break 309 | } 310 | } 311 | } 312 | 313 | /// @dev Deploys a contract via 0age's immutable create 2 factory for testing. 314 | function _safeCreate2(uint256 payableAmount, bytes32 salt, bytes memory initializationCode) 315 | internal 316 | returns (address deploymentAddress) 317 | { 318 | // Canonical address of 0age's immutable create 2 factory. 319 | address c2f = 0x0000000000FFe8B47B3e2130213B802212439497; 320 | uint256 c2fCodeLength; 321 | /// @solidity memory-safe-assembly 322 | assembly { 323 | c2fCodeLength := extcodesize(c2f) 324 | } 325 | if (c2fCodeLength == 0) { 326 | bytes memory ic2fBytecode = 327 | hex"60806040526004361061003f5760003560e01c806308508b8f1461004457806364e030871461009857806385cf97ab14610138578063a49a7c90146101bc575b600080fd5b34801561005057600080fd5b506100846004803603602081101561006757600080fd5b503573ffffffffffffffffffffffffffffffffffffffff166101ec565b604080519115158252519081900360200190f35b61010f600480360360408110156100ae57600080fd5b813591908101906040810160208201356401000000008111156100d057600080fd5b8201836020820111156100e257600080fd5b8035906020019184600183028401116401000000008311171561010457600080fd5b509092509050610217565b6040805173ffffffffffffffffffffffffffffffffffffffff9092168252519081900360200190f35b34801561014457600080fd5b5061010f6004803603604081101561015b57600080fd5b8135919081019060408101602082013564010000000081111561017d57600080fd5b82018360208201111561018f57600080fd5b803590602001918460018302840111640100000000831117156101b157600080fd5b509092509050610592565b3480156101c857600080fd5b5061010f600480360360408110156101df57600080fd5b508035906020013561069e565b73ffffffffffffffffffffffffffffffffffffffff1660009081526020819052604090205460ff1690565b600083606081901c33148061024c57507fffffffffffffffffffffffffffffffffffffffff0000000000000000000000008116155b6102a1576040517f08c379a00000000000000000000000000000000000000000000000000000000081526004018080602001828103825260458152602001806107746045913960600191505060405180910390fd5b606084848080601f0160208091040260200160405190810160405280939291908181526020018383808284376000920182905250604051855195965090943094508b93508692506020918201918291908401908083835b6020831061033557805182527fffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffe090920191602091820191016102f8565b51815160209384036101000a7fffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff018019909216911617905260408051929094018281037fffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffe00183528085528251928201929092207fff000000000000000000000000000000000000000000000000000000000000008383015260609890981b7fffffffffffffffffffffffffffffffffffffffff00000000000000000000000016602183015260358201969096526055808201979097528251808203909701875260750182525084519484019490942073ffffffffffffffffffffffffffffffffffffffff81166000908152938490529390922054929350505060ff16156104a7576040517f08c379a000000000000000000000000000000000000000000000000000000000815260040180806020018281038252603f815260200180610735603f913960400191505060405180910390fd5b81602001825188818334f5955050508073ffffffffffffffffffffffffffffffffffffffff168473ffffffffffffffffffffffffffffffffffffffff161461053a576040517f08c379a00000000000000000000000000000000000000000000000000000000081526004018080602001828103825260468152602001806107b96046913960600191505060405180910390fd5b50505073ffffffffffffffffffffffffffffffffffffffff8116600090815260208190526040902080547fffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff001660011790559392505050565b6000308484846040516020018083838082843760408051919093018181037fffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffe001825280845281516020928301207fff000000000000000000000000000000000000000000000000000000000000008383015260609990991b7fffffffffffffffffffffffffffffffffffffffff000000000000000000000000166021820152603581019790975260558088019890985282518088039098018852607590960182525085519585019590952073ffffffffffffffffffffffffffffffffffffffff81166000908152948590529490932054939450505060ff909116159050610697575060005b9392505050565b604080517fff000000000000000000000000000000000000000000000000000000000000006020808301919091523060601b6021830152603582018590526055808301859052835180840390910181526075909201835281519181019190912073ffffffffffffffffffffffffffffffffffffffff81166000908152918290529190205460ff161561072e575060005b9291505056fe496e76616c696420636f6e7472616374206372656174696f6e202d20636f6e74726163742068617320616c7265616479206265656e206465706c6f7965642e496e76616c69642073616c74202d206669727374203230206279746573206f66207468652073616c74206d757374206d617463682063616c6c696e6720616464726573732e4661696c656420746f206465706c6f7920636f6e7472616374207573696e672070726f76696465642073616c7420616e6420696e697469616c697a6174696f6e20636f64652ea265627a7a723058202bdc55310d97c4088f18acf04253db593f0914059f0c781a9df3624dcef0d1cf64736f6c634300050a0032"; 328 | /// @solidity memory-safe-assembly 329 | assembly { 330 | let m := mload(0x40) 331 | mstore(m, 0xb4d6c782) // `etch(address,bytes)`. 332 | mstore(add(m, 0x20), c2f) 333 | mstore(add(m, 0x40), 0x40) 334 | let n := mload(ic2fBytecode) 335 | mstore(add(m, 0x60), n) 336 | for { let i := 0 } lt(i, n) { i := add(0x20, i) } { 337 | mstore(add(add(m, 0x80), i), mload(add(add(ic2fBytecode, 0x20), i))) 338 | } 339 | if iszero(call(gas(), _VM_ADDRESS, 0, add(m, 0x1c), add(n, 0x64), 0x00, 0x00)) { 340 | revert(0, 0) 341 | } 342 | } 343 | } 344 | /// @solidity memory-safe-assembly 345 | assembly { 346 | let m := mload(0x40) 347 | let n := mload(initializationCode) 348 | mstore(m, 0x64e03087) // `safeCreate2(bytes32,bytes)`. 349 | mstore(add(m, 0x20), salt) 350 | mstore(add(m, 0x40), 0x40) 351 | mstore(add(m, 0x60), n) 352 | // prettier-ignore 353 | for { let i := 0 } lt(i, n) { i := add(i, 0x20) } { 354 | mstore(add(add(m, 0x80), i), mload(add(add(initializationCode, 0x20), i))) 355 | } 356 | if iszero(call(gas(), c2f, payableAmount, add(m, 0x1c), add(n, 0x64), m, 0x20)) { 357 | returndatacopy(m, m, returndatasize()) 358 | revert(m, returndatasize()) 359 | } 360 | deploymentAddress := mload(m) 361 | } 362 | } 363 | 364 | /// @dev Deploys a contract via 0age's immutable create 2 factory for testing. 365 | function _safeCreate2(bytes32 salt, bytes memory initializationCode) 366 | internal 367 | returns (address deploymentAddress) 368 | { 369 | deploymentAddress = _safeCreate2(0, salt, initializationCode); 370 | } 371 | 372 | /// @dev This function will make forge's gas output display the approximate codesize of 373 | /// the test contract as the amount of gas burnt. Useful for quick guess checking if 374 | /// certain optimizations actually compiles to similar bytecode. 375 | function test__codesize() external view { 376 | /// @solidity memory-safe-assembly 377 | assembly { 378 | // If the caller is the contract itself (i.e. recursive call), burn all the gas. 379 | if eq(caller(), address()) { invalid() } 380 | mstore(0x00, 0xf09ff470) // Store the function selector of `test__codesize()`. 381 | pop(staticcall(codesize(), address(), 0x1c, 0x04, 0x00, 0x00)) 382 | } 383 | } 384 | } 385 | -------------------------------------------------------------------------------- /test/utils/forge-std/Script.sol: -------------------------------------------------------------------------------- 1 | // SPDX-License-Identifier: MIT 2 | pragma solidity >=0.6.0 <0.9.0; 3 | 4 | import "./Vm.sol"; 5 | 6 | abstract contract Script { 7 | bool public IS_SCRIPT = true; 8 | 9 | /// @dev `address(bytes20(uint160(uint256(keccak256("hevm cheat code")))))`. 10 | Vm public constant vm = Vm(0x7109709ECfa91a80626fF3989D68f67F5b1DD12D); 11 | } 12 | -------------------------------------------------------------------------------- /test/utils/forge-std/Test.sol: -------------------------------------------------------------------------------- 1 | // SPDX-License-Identifier: MIT 2 | pragma solidity >=0.6.0 <0.9.0; 3 | 4 | import "./Script.sol"; 5 | import "./ds-test/test.sol"; 6 | 7 | // Wrappers around Cheatcodes to avoid footguns 8 | abstract contract Test is DSTest, Script { 9 | uint256 internal constant UINT256_MAX = 10 | 115792089237316195423570985008687907853269984665640564039457584007913129639935; 11 | 12 | /*////////////////////////////////////////////////////////////////////////// 13 | STD-LOGS 14 | //////////////////////////////////////////////////////////////////////////*/ 15 | 16 | event log_array(uint256[] val); 17 | event log_array(int256[] val); 18 | event log_array(address[] val); 19 | event log_named_array(string key, uint256[] val); 20 | event log_named_array(string key, int256[] val); 21 | event log_named_array(string key, address[] val); 22 | 23 | /*////////////////////////////////////////////////////////////////////////// 24 | STD-ASSERTIONS 25 | //////////////////////////////////////////////////////////////////////////*/ 26 | 27 | function fail(string memory err) internal virtual { 28 | emit log_named_string("Error", err); 29 | fail(); 30 | } 31 | 32 | function assertFalse(bool data) internal virtual { 33 | assertTrue(!data); 34 | } 35 | 36 | function assertFalse(bool data, string memory err) internal virtual { 37 | assertTrue(!data, err); 38 | } 39 | 40 | function assertEq(bool a, bool b) internal { 41 | if (a != b) { 42 | emit log("Error: a == b not satisfied [bool]"); 43 | emit log_named_string(" Expected", b ? "true" : "false"); 44 | emit log_named_string(" Actual", a ? "true" : "false"); 45 | fail(); 46 | } 47 | } 48 | 49 | function assertEq(bool a, bool b, string memory err) internal { 50 | if (a != b) { 51 | emit log_named_string("Error", err); 52 | assertEq(a, b); 53 | } 54 | } 55 | 56 | function assertEq(bytes memory a, bytes memory b) internal { 57 | assertEq0(a, b); 58 | } 59 | 60 | function assertEq(bytes memory a, bytes memory b, string memory err) internal { 61 | assertEq0(a, b, err); 62 | } 63 | 64 | function assertEq(uint256[] memory a, uint256[] memory b) internal { 65 | bool inputsEq; 66 | /// @solidity memory-safe-assembly 67 | assembly { 68 | inputsEq := 69 | eq(keccak256(a, shl(5, add(mload(a), 1))), keccak256(b, shl(5, add(mload(b), 1)))) 70 | } 71 | if (!inputsEq) { 72 | emit log("Error: a == b not satisfied [uint[]]"); 73 | emit log_named_array(" Expected", b); 74 | emit log_named_array(" Actual", a); 75 | fail(); 76 | } 77 | } 78 | 79 | function assertEq(int256[] memory a, int256[] memory b) internal { 80 | bool inputsEq; 81 | /// @solidity memory-safe-assembly 82 | assembly { 83 | inputsEq := 84 | eq(keccak256(a, shl(5, add(mload(a), 1))), keccak256(b, shl(5, add(mload(b), 1)))) 85 | } 86 | if (!inputsEq) { 87 | emit log("Error: a == b not satisfied [int[]]"); 88 | emit log_named_array(" Expected", b); 89 | emit log_named_array(" Actual", a); 90 | fail(); 91 | } 92 | } 93 | 94 | function assertEq(address[] memory a, address[] memory b) internal { 95 | bool inputsEq; 96 | /// @solidity memory-safe-assembly 97 | assembly { 98 | inputsEq := 99 | eq(keccak256(a, shl(5, add(mload(a), 1))), keccak256(b, shl(5, add(mload(b), 1)))) 100 | } 101 | if (!inputsEq) { 102 | emit log("Error: a == b not satisfied [address[]]"); 103 | emit log_named_array(" Expected", b); 104 | emit log_named_array(" Actual", a); 105 | fail(); 106 | } 107 | } 108 | 109 | function assertEq(uint256[] memory a, uint256[] memory b, string memory err) internal { 110 | bool inputsEq; 111 | /// @solidity memory-safe-assembly 112 | assembly { 113 | inputsEq := 114 | eq(keccak256(a, shl(5, add(mload(a), 1))), keccak256(b, shl(5, add(mload(b), 1)))) 115 | } 116 | if (!inputsEq) { 117 | emit log_named_string("Error", err); 118 | assertEq(a, b); 119 | } 120 | } 121 | 122 | function assertEq(int256[] memory a, int256[] memory b, string memory err) internal { 123 | bool inputsEq; 124 | /// @solidity memory-safe-assembly 125 | assembly { 126 | inputsEq := 127 | eq(keccak256(a, shl(5, add(mload(a), 1))), keccak256(b, shl(5, add(mload(b), 1)))) 128 | } 129 | if (!inputsEq) { 130 | emit log_named_string("Error", err); 131 | assertEq(a, b); 132 | } 133 | } 134 | 135 | function assertEq(address[] memory a, address[] memory b, string memory err) internal { 136 | bool inputsEq; 137 | /// @solidity memory-safe-assembly 138 | assembly { 139 | inputsEq := 140 | eq(keccak256(a, shl(5, add(mload(a), 1))), keccak256(b, shl(5, add(mload(b), 1)))) 141 | } 142 | if (!inputsEq) { 143 | emit log_named_string("Error", err); 144 | assertEq(a, b); 145 | } 146 | } 147 | } 148 | 149 | /*////////////////////////////////////////////////////////////////////////// 150 | STD-ERRORS 151 | //////////////////////////////////////////////////////////////////////////*/ 152 | 153 | library stdError { 154 | bytes public constant assertionError = abi.encodeWithSignature("Panic(uint256)", 0x01); 155 | bytes public constant arithmeticError = abi.encodeWithSignature("Panic(uint256)", 0x11); 156 | bytes public constant divisionError = abi.encodeWithSignature("Panic(uint256)", 0x12); 157 | bytes public constant enumConversionError = abi.encodeWithSignature("Panic(uint256)", 0x21); 158 | bytes public constant encodeStorageError = abi.encodeWithSignature("Panic(uint256)", 0x22); 159 | bytes public constant popError = abi.encodeWithSignature("Panic(uint256)", 0x31); 160 | bytes public constant indexOOBError = abi.encodeWithSignature("Panic(uint256)", 0x32); 161 | bytes public constant memOverflowError = abi.encodeWithSignature("Panic(uint256)", 0x41); 162 | bytes public constant zeroVarError = abi.encodeWithSignature("Panic(uint256)", 0x51); 163 | // DEPRECATED: Use Vm's `expectRevert` without any arguments instead 164 | bytes public constant lowLevelError = bytes(""); // `0x` 165 | } 166 | -------------------------------------------------------------------------------- /test/utils/forge-std/Vm.sol: -------------------------------------------------------------------------------- 1 | // SPDX-License-Identifier: MIT 2 | pragma solidity >=0.4.22 <0.9.0; 3 | 4 | interface VmSafe { 5 | struct Log { 6 | bytes32[] topics; 7 | bytes data; 8 | address emitter; 9 | } 10 | 11 | struct Rpc { 12 | string key; 13 | string url; 14 | } 15 | 16 | struct FsMetadata { 17 | bool isDir; 18 | bool isSymlink; 19 | uint256 length; 20 | bool readOnly; 21 | uint256 modified; 22 | uint256 accessed; 23 | uint256 created; 24 | } 25 | 26 | // Loads a storage slot from an address 27 | function load(address target, bytes32 slot) external view returns (bytes32 data); 28 | // Signs data 29 | function sign(uint256 privateKey, bytes32 digest) 30 | external 31 | pure 32 | returns (uint8 v, bytes32 r, bytes32 s); 33 | // Gets the address for a given private key 34 | function addr(uint256 privateKey) external pure returns (address keyAddr); 35 | // Gets the nonce of an account 36 | function getNonce(address account) external view returns (uint64 nonce); 37 | // Performs a foreign function call via the terminal 38 | function ffi(string[] calldata commandInput) external returns (bytes memory result); 39 | // Sets environment variables 40 | function setEnv(string calldata name, string calldata value) external; 41 | // Reads environment variables, (name) => (value) 42 | function envBool(string calldata name) external view returns (bool value); 43 | function envUint(string calldata name) external view returns (uint256 value); 44 | function envInt(string calldata name) external view returns (int256 value); 45 | function envAddress(string calldata name) external view returns (address value); 46 | function envBytes32(string calldata name) external view returns (bytes32 value); 47 | function envString(string calldata name) external view returns (string memory value); 48 | function envBytes(string calldata name) external view returns (bytes memory value); 49 | // Reads environment variables as arrays 50 | function envBool(string calldata name, string calldata delim) 51 | external 52 | view 53 | returns (bool[] memory value); 54 | function envUint(string calldata name, string calldata delim) 55 | external 56 | view 57 | returns (uint256[] memory value); 58 | function envInt(string calldata name, string calldata delim) 59 | external 60 | view 61 | returns (int256[] memory value); 62 | function envAddress(string calldata name, string calldata delim) 63 | external 64 | view 65 | returns (address[] memory value); 66 | function envBytes32(string calldata name, string calldata delim) 67 | external 68 | view 69 | returns (bytes32[] memory value); 70 | function envString(string calldata name, string calldata delim) 71 | external 72 | view 73 | returns (string[] memory value); 74 | function envBytes(string calldata name, string calldata delim) 75 | external 76 | view 77 | returns (bytes[] memory value); 78 | // Read environment variables with default value 79 | function envOr(string calldata name, bool defaultValue) external returns (bool value); 80 | function envOr(string calldata name, uint256 defaultValue) external returns (uint256 value); 81 | function envOr(string calldata name, int256 defaultValue) external returns (int256 value); 82 | function envOr(string calldata name, address defaultValue) external returns (address value); 83 | function envOr(string calldata name, bytes32 defaultValue) external returns (bytes32 value); 84 | function envOr(string calldata name, string calldata defaultValue) 85 | external 86 | returns (string memory value); 87 | function envOr(string calldata name, bytes calldata defaultValue) 88 | external 89 | returns (bytes memory value); 90 | // Read environment variables as arrays with default value 91 | function envOr(string calldata name, string calldata delim, bool[] calldata defaultValue) 92 | external 93 | returns (bool[] memory value); 94 | function envOr(string calldata name, string calldata delim, uint256[] calldata defaultValue) 95 | external 96 | returns (uint256[] memory value); 97 | function envOr(string calldata name, string calldata delim, int256[] calldata defaultValue) 98 | external 99 | returns (int256[] memory value); 100 | function envOr(string calldata name, string calldata delim, address[] calldata defaultValue) 101 | external 102 | returns (address[] memory value); 103 | function envOr(string calldata name, string calldata delim, bytes32[] calldata defaultValue) 104 | external 105 | returns (bytes32[] memory value); 106 | function envOr(string calldata name, string calldata delim, string[] calldata defaultValue) 107 | external 108 | returns (string[] memory value); 109 | function envOr(string calldata name, string calldata delim, bytes[] calldata defaultValue) 110 | external 111 | returns (bytes[] memory value); 112 | // Records all storage reads and writes 113 | function record() external; 114 | // Gets all accessed reads and write slot from a recording session, for a given address 115 | function accesses(address target) 116 | external 117 | returns (bytes32[] memory readSlots, bytes32[] memory writeSlots); 118 | // Gets the _creation_ bytecode from an artifact file. Takes in the relative path to the json file 119 | function getCode(string calldata artifactPath) 120 | external 121 | view 122 | returns (bytes memory creationBytecode); 123 | // Gets the _deployed_ bytecode from an artifact file. Takes in the relative path to the json file 124 | function getDeployedCode(string calldata artifactPath) 125 | external 126 | view 127 | returns (bytes memory runtimeBytecode); 128 | // Labels an address in call traces 129 | function label(address account, string calldata newLabel) external; 130 | // Using the address that calls the test contract, has the next call (at this call depth only) create a transaction that can later be signed and sent onchain 131 | function broadcast() external; 132 | // Has the next call (at this call depth only) create a transaction with the address provided as the sender that can later be signed and sent onchain 133 | function broadcast(address signer) external; 134 | // Has the next call (at this call depth only) create a transaction with the private key provided as the sender that can later be signed and sent onchain 135 | function broadcast(uint256 privateKey) external; 136 | // Using the address that calls the test contract, has all subsequent calls (at this call depth only) create transactions that can later be signed and sent onchain 137 | function startBroadcast() external; 138 | // Has all subsequent calls (at this call depth only) create transactions with the address provided that can later be signed and sent onchain 139 | function startBroadcast(address signer) external; 140 | // Has all subsequent calls (at this call depth only) create transactions with the private key provided that can later be signed and sent onchain 141 | function startBroadcast(uint256 privateKey) external; 142 | // Stops collecting onchain transactions 143 | function stopBroadcast() external; 144 | // Reads the entire content of file to string 145 | function readFile(string calldata path) external view returns (string memory data); 146 | // Reads the entire content of file as binary. Path is relative to the project root. 147 | function readFileBinary(string calldata path) external view returns (bytes memory data); 148 | // Get the path of the current project root 149 | function projectRoot() external view returns (string memory path); 150 | // Get the metadata for a file/directory 151 | function fsMetadata(string calldata fileOrDir) external returns (FsMetadata memory metadata); 152 | // Reads next line of file to string 153 | function readLine(string calldata path) external view returns (string memory line); 154 | // Writes data to file, creating a file if it does not exist, and entirely replacing its contents if it does. 155 | function writeFile(string calldata path, string calldata data) external; 156 | // Writes binary data to a file, creating a file if it does not exist, and entirely replacing its contents if it does. 157 | // Path is relative to the project root. 158 | function writeFileBinary(string calldata path, bytes calldata data) external; 159 | // Writes line to file, creating a file if it does not exist. 160 | function writeLine(string calldata path, string calldata data) external; 161 | // Closes file for reading, resetting the offset and allowing to read it from beginning with readLine. 162 | function closeFile(string calldata path) external; 163 | // Removes file. This cheatcode will revert in the following situations, but is not limited to just these cases: 164 | // - Path points to a directory. 165 | // - The file doesn't exist. 166 | // - The user lacks permissions to remove the file. 167 | function removeFile(string calldata path) external; 168 | // Convert values to a string 169 | function toString(address value) external pure returns (string memory stringifiedValue); 170 | function toString(bytes calldata value) 171 | external 172 | pure 173 | returns (string memory stringifiedValue); 174 | function toString(bytes32 value) external pure returns (string memory stringifiedValue); 175 | function toString(bool value) external pure returns (string memory stringifiedValue); 176 | function toString(uint256 value) external pure returns (string memory stringifiedValue); 177 | function toString(int256 value) external pure returns (string memory stringifiedValue); 178 | // Convert values from a string 179 | function parseBytes(string calldata stringifiedValue) 180 | external 181 | pure 182 | returns (bytes memory parsedValue); 183 | function parseAddress(string calldata stringifiedValue) 184 | external 185 | pure 186 | returns (address parsedValue); 187 | function parseUint(string calldata stringifiedValue) 188 | external 189 | pure 190 | returns (uint256 parsedValue); 191 | function parseInt(string calldata stringifiedValue) 192 | external 193 | pure 194 | returns (int256 parsedValue); 195 | function parseBytes32(string calldata stringifiedValue) 196 | external 197 | pure 198 | returns (bytes32 parsedValue); 199 | function parseBool(string calldata stringifiedValue) external pure returns (bool parsedValue); 200 | // Record all the transaction logs 201 | function recordLogs() external; 202 | // Gets all the recorded logs 203 | function getRecordedLogs() external returns (Log[] memory logs); 204 | // Derive a private key from a provided mnenomic string (or mnenomic file path) at the derivation path m/44'/60'/0'/0/{index} 205 | function deriveKey(string calldata mnemonic, uint32 index) 206 | external 207 | pure 208 | returns (uint256 privateKey); 209 | // Derive a private key from a provided mnenomic string (or mnenomic file path) at {derivationPath}{index} 210 | function deriveKey(string calldata mnemonic, string calldata derivationPath, uint32 index) 211 | external 212 | pure 213 | returns (uint256 privateKey); 214 | // Adds a private key to the local forge wallet and returns the address 215 | function rememberKey(uint256 privateKey) external returns (address keyAddr); 216 | // 217 | // parseJson 218 | // 219 | // ---- 220 | // In case the returned value is a JSON object, it's encoded as a ABI-encoded tuple. As JSON objects 221 | // don't have the notion of ordered, but tuples do, they JSON object is encoded with it's fields ordered in 222 | // ALPHABETICAL order. That means that in order to successfully decode the tuple, we need to define a tuple that 223 | // encodes the fields in the same order, which is alphabetical. In the case of Solidity structs, they are encoded 224 | // as tuples, with the attributes in the order in which they are defined. 225 | // For example: json = { 'a': 1, 'b': 0xa4tb......3xs} 226 | // a: uint256 227 | // b: address 228 | // To decode that json, we need to define a struct or a tuple as follows: 229 | // struct json = { uint256 a; address b; } 230 | // If we defined a json struct with the opposite order, meaning placing the address b first, it would try to 231 | // decode the tuple in that order, and thus fail. 232 | // ---- 233 | // Given a string of JSON, return it as ABI-encoded 234 | function parseJson(string calldata json, string calldata key) 235 | external 236 | pure 237 | returns (bytes memory abiEncodedData); 238 | function parseJson(string calldata json) external pure returns (bytes memory abiEncodedData); 239 | 240 | // The following parseJson cheatcodes will do type coercion, for the type that they indicate. 241 | // For example, parseJsonUint will coerce all values to a uint256. That includes stringified numbers '12' 242 | // and hex numbers '0xEF'. 243 | // Type coercion works ONLY for discrete values or arrays. That means that the key must return a value or array, not 244 | // a JSON object. 245 | function parseJsonUint(string calldata, string calldata) external returns (uint256); 246 | function parseJsonUintArray(string calldata, string calldata) 247 | external 248 | returns (uint256[] memory); 249 | function parseJsonInt(string calldata, string calldata) external returns (int256); 250 | function parseJsonIntArray(string calldata, string calldata) 251 | external 252 | returns (int256[] memory); 253 | function parseJsonBool(string calldata, string calldata) external returns (bool); 254 | function parseJsonBoolArray(string calldata, string calldata) 255 | external 256 | returns (bool[] memory); 257 | function parseJsonAddress(string calldata, string calldata) external returns (address); 258 | function parseJsonAddressArray(string calldata, string calldata) 259 | external 260 | returns (address[] memory); 261 | function parseJsonString(string calldata, string calldata) external returns (string memory); 262 | function parseJsonStringArray(string calldata, string calldata) 263 | external 264 | returns (string[] memory); 265 | function parseJsonBytes(string calldata, string calldata) external returns (bytes memory); 266 | function parseJsonBytesArray(string calldata, string calldata) 267 | external 268 | returns (bytes[] memory); 269 | function parseJsonBytes32(string calldata, string calldata) external returns (bytes32); 270 | function parseJsonBytes32Array(string calldata, string calldata) 271 | external 272 | returns (bytes32[] memory); 273 | 274 | // Serialize a key and value to a JSON object stored in-memory that can be later written to a file 275 | // It returns the stringified version of the specific JSON file up to that moment. 276 | function serializeBool(string calldata objectKey, string calldata valueKey, bool value) 277 | external 278 | returns (string memory json); 279 | function serializeUint(string calldata objectKey, string calldata valueKey, uint256 value) 280 | external 281 | returns (string memory json); 282 | function serializeInt(string calldata objectKey, string calldata valueKey, int256 value) 283 | external 284 | returns (string memory json); 285 | function serializeAddress(string calldata objectKey, string calldata valueKey, address value) 286 | external 287 | returns (string memory json); 288 | function serializeBytes32(string calldata objectKey, string calldata valueKey, bytes32 value) 289 | external 290 | returns (string memory json); 291 | function serializeString( 292 | string calldata objectKey, 293 | string calldata valueKey, 294 | string calldata value 295 | ) external returns (string memory json); 296 | function serializeBytes( 297 | string calldata objectKey, 298 | string calldata valueKey, 299 | bytes calldata value 300 | ) external returns (string memory json); 301 | 302 | function serializeBool( 303 | string calldata objectKey, 304 | string calldata valueKey, 305 | bool[] calldata values 306 | ) external returns (string memory json); 307 | function serializeUint( 308 | string calldata objectKey, 309 | string calldata valueKey, 310 | uint256[] calldata values 311 | ) external returns (string memory json); 312 | function serializeInt( 313 | string calldata objectKey, 314 | string calldata valueKey, 315 | int256[] calldata values 316 | ) external returns (string memory json); 317 | function serializeAddress( 318 | string calldata objectKey, 319 | string calldata valueKey, 320 | address[] calldata values 321 | ) external returns (string memory json); 322 | function serializeBytes32( 323 | string calldata objectKey, 324 | string calldata valueKey, 325 | bytes32[] calldata values 326 | ) external returns (string memory json); 327 | function serializeString( 328 | string calldata objectKey, 329 | string calldata valueKey, 330 | string[] calldata values 331 | ) external returns (string memory json); 332 | function serializeBytes( 333 | string calldata objectKey, 334 | string calldata valueKey, 335 | bytes[] calldata values 336 | ) external returns (string memory json); 337 | 338 | // 339 | // writeJson 340 | // 341 | // ---- 342 | // Write a serialized JSON object to a file. If the file exists, it will be overwritten. 343 | // Let's assume we want to write the following JSON to a file: 344 | // 345 | // { "boolean": true, "number": 342, "object": { "title": "finally json serialization" } } 346 | // 347 | // ``` 348 | // string memory json1 = "some key"; 349 | // vm.serializeBool(json1, "boolean", true); 350 | // vm.serializeBool(json1, "number", uint256(342)); 351 | // json2 = "some other key"; 352 | // string memory output = vm.serializeString(json2, "title", "finally json serialization"); 353 | // string memory finalJson = vm.serialize(json1, "object", output); 354 | // vm.writeJson(finalJson, "./output/example.json"); 355 | // ``` 356 | // The critical insight is that every invocation of serialization will return the stringified version of the JSON 357 | // up to that point. That means we can construct arbitrary JSON objects and then use the return stringified version 358 | // to serialize them as values to another JSON object. 359 | // 360 | // json1 and json2 are simply keys used by the backend to keep track of the objects. So vm.serializeJson(json1,..) 361 | // will find the object in-memory that is keyed by "some key". 362 | function writeJson(string calldata json, string calldata path) external; 363 | // Write a serialized JSON object to an **existing** JSON file, replacing a value with key = 364 | // This is useful to replace a specific value of a JSON file, without having to parse the entire thing 365 | function writeJson(string calldata json, string calldata path, string calldata valueKey) 366 | external; 367 | // Returns the RPC url for the given alias 368 | function rpcUrl(string calldata rpcAlias) external view returns (string memory json); 369 | // Returns all rpc urls and their aliases `[alias, url][]` 370 | function rpcUrls() external view returns (string[2][] memory urls); 371 | // Returns all rpc urls and their aliases as structs. 372 | function rpcUrlStructs() external view returns (Rpc[] memory urls); 373 | // If the condition is false, discard this run's fuzz inputs and generate new ones. 374 | function assume(bool condition) external pure; 375 | // Pauses gas metering (i.e. gas usage is not counted). Noop if already paused. 376 | function pauseGasMetering() external; 377 | // Resumes gas metering (i.e. gas usage is counted again). Noop if already on. 378 | function resumeGasMetering() external; 379 | } 380 | 381 | interface Vm is VmSafe { 382 | // Sets block.timestamp 383 | function warp(uint256 newTimestamp) external; 384 | // Sets block.height 385 | function roll(uint256 newHeight) external; 386 | // Sets block.basefee 387 | function fee(uint256 newBasefee) external; 388 | // Sets block.difficulty 389 | function difficulty(uint256 newDifficulty) external; 390 | // Sets block.chainid 391 | function chainId(uint256 newChainId) external; 392 | // Stores a value to an address' storage slot. 393 | function store(address target, bytes32 slot, bytes32 value) external; 394 | // Sets the nonce of an account; must be higher than the current nonce of the account 395 | function setNonce(address account, uint64 newNonce) external; 396 | // Sets the *next* call's msg.sender to be the input address 397 | function prank(address msgSender) external; 398 | // Sets all subsequent calls' msg.sender to be the input address until `stopPrank` is called 399 | function startPrank(address msgSender) external; 400 | // Sets the *next* call's msg.sender to be the input address, and the tx.origin to be the second input 401 | function prank(address msgSender, address txOrigin) external; 402 | // Sets all subsequent calls' msg.sender to be the input address until `stopPrank` is called, and the tx.origin to be the second input 403 | function startPrank(address msgSender, address txOrigin) external; 404 | // Resets subsequent calls' msg.sender to be `address(this)` 405 | function stopPrank() external; 406 | // Sets an address' balance 407 | function deal(address account, uint256 newBalance) external; 408 | // Sets an address' code 409 | function etch(address target, bytes calldata newRuntimeBytecode) external; 410 | // Expects an error on next call 411 | function expectRevert(bytes calldata revertData) external; 412 | function expectRevert(bytes4 revertData) external; 413 | function expectRevert() external; 414 | 415 | // Prepare an expected log with all four checks enabled. 416 | // Call this function, then emit an event, then call a function. Internally after the call, we check if 417 | // logs were emitted in the expected order with the expected topics and data. 418 | // Second form also checks supplied address against emitting contract. 419 | function expectEmit() external; 420 | function expectEmit(address) external; 421 | 422 | // Prepare an expected log with (bool checkTopic1, bool checkTopic2, bool checkTopic3, bool checkData). 423 | // Call this function, then emit an event, then call a function. Internally after the call, we check if 424 | // logs were emitted in the expected order with the expected topics and data (as specified by the booleans). 425 | // Second form also checks supplied address against emitting contract. 426 | function expectEmit(bool checkTopic1, bool checkTopic2, bool checkTopic3, bool checkData) 427 | external; 428 | function expectEmit( 429 | bool checkTopic1, 430 | bool checkTopic2, 431 | bool checkTopic3, 432 | bool checkData, 433 | address emitter 434 | ) external; 435 | 436 | // Mocks a call to an address, returning specified data. 437 | // Calldata can either be strict or a partial match, e.g. if you only 438 | // pass a Solidity selector to the expected calldata, then the entire Solidity 439 | // function will be mocked. 440 | function mockCall(address callee, bytes calldata data, bytes calldata returnData) external; 441 | // Mocks a call to an address with a specific msg.value, returning specified data. 442 | // Calldata match takes precedence over msg.value in case of ambiguity. 443 | function mockCall( 444 | address callee, 445 | uint256 msgValue, 446 | bytes calldata data, 447 | bytes calldata returnData 448 | ) external; 449 | // Clears all mocked calls 450 | function clearMockedCalls() external; 451 | // Expects a call to an address with the specified calldata. 452 | // Calldata can either be a strict or a partial match 453 | function expectCall(address callee, bytes calldata data) external; 454 | // Expects a call to an address with the specified msg.value and calldata 455 | function expectCall(address callee, uint256 msgValue, bytes calldata data) external; 456 | // Expect a call to an address with the specified msg.value, gas, and calldata. 457 | function expectCall(address callee, uint256 msgValue, uint64 gas, bytes calldata data) 458 | external; 459 | // Expect a call to an address with the specified msg.value and calldata, and a *minimum* amount of gas. 460 | function expectCallMinGas(address callee, uint256 msgValue, uint64 minGas, bytes calldata data) 461 | external; 462 | // Sets block.coinbase 463 | function coinbase(address newCoinbase) external; 464 | // Snapshot the current state of the evm. 465 | // Returns the id of the snapshot that was created. 466 | // To revert a snapshot use `revertTo` 467 | function snapshot() external returns (uint256 snapshotId); 468 | // Revert the state of the EVM to a previous snapshot 469 | // Takes the snapshot id to revert to. 470 | // This deletes the snapshot and all snapshots taken after the given snapshot id. 471 | function revertTo(uint256 snapshotId) external returns (bool success); 472 | // Creates a new fork with the given endpoint and block and returns the identifier of the fork 473 | function createFork(string calldata urlOrAlias, uint256 blockNumber) 474 | external 475 | returns (uint256 forkId); 476 | // Creates a new fork with the given endpoint and the _latest_ block and returns the identifier of the fork 477 | function createFork(string calldata urlOrAlias) external returns (uint256 forkId); 478 | // Creates a new fork with the given endpoint and at the block the given transaction was mined in, replays all transaction mined in the block before the transaction, 479 | // and returns the identifier of the fork 480 | function createFork(string calldata urlOrAlias, bytes32 txHash) 481 | external 482 | returns (uint256 forkId); 483 | // Creates _and_ also selects a new fork with the given endpoint and block and returns the identifier of the fork 484 | function createSelectFork(string calldata urlOrAlias, uint256 blockNumber) 485 | external 486 | returns (uint256 forkId); 487 | // Creates _and_ also selects new fork with the given endpoint and at the block the given transaction was mined in, replays all transaction mined in the block before 488 | // the transaction, returns the identifier of the fork 489 | function createSelectFork(string calldata urlOrAlias, bytes32 txHash) 490 | external 491 | returns (uint256 forkId); 492 | // Creates _and_ also selects a new fork with the given endpoint and the latest block and returns the identifier of the fork 493 | function createSelectFork(string calldata urlOrAlias) external returns (uint256 forkId); 494 | // Takes a fork identifier created by `createFork` and sets the corresponding forked state as active. 495 | function selectFork(uint256 forkId) external; 496 | /// Returns the identifier of the currently active fork. Reverts if no fork is currently active. 497 | function activeFork() external view returns (uint256 forkId); 498 | // Updates the currently active fork to given block number 499 | // This is similar to `roll` but for the currently active fork 500 | function rollFork(uint256 blockNumber) external; 501 | // Updates the currently active fork to given transaction 502 | // this will `rollFork` with the number of the block the transaction was mined in and replays all transaction mined before it in the block 503 | function rollFork(bytes32 txHash) external; 504 | // Updates the given fork to given block number 505 | function rollFork(uint256 forkId, uint256 blockNumber) external; 506 | // Updates the given fork to block number of the given transaction and replays all transaction mined before it in the block 507 | function rollFork(uint256 forkId, bytes32 txHash) external; 508 | // Marks that the account(s) should use persistent storage across fork swaps in a multifork setup 509 | // Meaning, changes made to the state of this account will be kept when switching forks 510 | function makePersistent(address account) external; 511 | function makePersistent(address account0, address account1) external; 512 | function makePersistent(address account0, address account1, address account2) external; 513 | function makePersistent(address[] calldata accounts) external; 514 | // Revokes persistent status from the address, previously added via `makePersistent` 515 | function revokePersistent(address account) external; 516 | function revokePersistent(address[] calldata accounts) external; 517 | // Returns true if the account is marked as persistent 518 | function isPersistent(address account) external view returns (bool persistent); 519 | // In forking mode, explicitly grant the given address cheatcode access 520 | function allowCheatcodes(address account) external; 521 | // Fetches the given transaction from the active fork and executes it on the current state 522 | function transact(bytes32 txHash) external; 523 | // Fetches the given transaction from the given fork and executes it on the current state 524 | function transact(uint256 forkId, bytes32 txHash) external; 525 | } 526 | -------------------------------------------------------------------------------- /test/utils/forge-std/ds-test/test.sol: -------------------------------------------------------------------------------- 1 | // SPDX-License-Identifier: GPL-3.0-or-later 2 | 3 | // This program is free software: you can redistribute it and/or modify 4 | // it under the terms of the GNU General Public License as published by 5 | // the Free Software Foundation, either version 3 of the License, or 6 | // (at your option) any later version. 7 | 8 | // This program is distributed in the hope that it will be useful, 9 | // but WITHOUT ANY WARRANTY; without even the implied warranty of 10 | // MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the 11 | // GNU General Public License for more details. 12 | 13 | // You should have received a copy of the GNU General Public License 14 | // along with this program. If not, see . 15 | 16 | // Copied from: https://github.com/dapphub/ds-test/blob/9310e879db8ba3ea6d5c6489a579118fd264a3f5/src/test.sol 17 | 18 | pragma solidity >=0.5.0; 19 | 20 | contract DSTest { 21 | event log(string); 22 | event logs(bytes); 23 | 24 | event log_address(address); 25 | event log_bytes32(bytes32); 26 | event log_int(int256); 27 | event log_uint(uint256); 28 | event log_bytes(bytes); 29 | event log_string(string); 30 | 31 | event log_named_address(string key, address val); 32 | event log_named_bytes32(string key, bytes32 val); 33 | event log_named_decimal_int(string key, int256 val, uint256 decimals); 34 | event log_named_decimal_uint(string key, uint256 val, uint256 decimals); 35 | event log_named_int(string key, int256 val); 36 | event log_named_uint(string key, uint256 val); 37 | event log_named_bytes(string key, bytes val); 38 | event log_named_string(string key, string val); 39 | 40 | bool public IS_TEST = true; 41 | bool private _failed; 42 | 43 | address constant HEVM_ADDRESS = address(bytes20(uint160(uint256(keccak256("hevm cheat code"))))); 44 | 45 | modifier mayRevert() { 46 | _; 47 | } 48 | 49 | modifier testopts(string memory) { 50 | _; 51 | } 52 | 53 | function failed() public returns (bool) { 54 | if (_failed) { 55 | return _failed; 56 | } else { 57 | bool globalFailed = false; 58 | if (hasHEVMContext()) { 59 | (, bytes memory retdata) = HEVM_ADDRESS.call( 60 | abi.encodePacked( 61 | bytes4(keccak256("load(address,bytes32)")), 62 | abi.encode(HEVM_ADDRESS, bytes32("failed")) 63 | ) 64 | ); 65 | globalFailed = abi.decode(retdata, (bool)); 66 | } 67 | return globalFailed; 68 | } 69 | } 70 | 71 | function fail() internal { 72 | if (hasHEVMContext()) { 73 | (bool status,) = HEVM_ADDRESS.call( 74 | abi.encodePacked( 75 | bytes4(keccak256("store(address,bytes32,bytes32)")), 76 | abi.encode(HEVM_ADDRESS, bytes32("failed"), bytes32(uint256(0x01))) 77 | ) 78 | ); 79 | status; // Silence compiler warnings 80 | } 81 | _failed = true; 82 | } 83 | 84 | function hasHEVMContext() internal view returns (bool) { 85 | uint256 hevmCodeSize = 0; 86 | assembly { 87 | hevmCodeSize := extcodesize(0x7109709ECfa91a80626fF3989D68f67F5b1DD12D) 88 | } 89 | return hevmCodeSize > 0; 90 | } 91 | 92 | modifier logs_gas() { 93 | uint256 startGas = gasleft(); 94 | _; 95 | uint256 endGas = gasleft(); 96 | emit log_named_uint("gas", startGas - endGas); 97 | } 98 | 99 | function assertTrue(bool condition) internal { 100 | if (!condition) { 101 | emit log("Error: Assertion Failed"); 102 | fail(); 103 | } 104 | } 105 | 106 | function assertTrue(bool condition, string memory err) internal { 107 | if (!condition) { 108 | emit log_named_string("Error", err); 109 | assertTrue(condition); 110 | } 111 | } 112 | 113 | function assertEq(address a, address b) internal { 114 | if (a != b) { 115 | emit log("Error: a == b not satisfied [address]"); 116 | emit log_named_address(" Expected", b); 117 | emit log_named_address(" Actual", a); 118 | fail(); 119 | } 120 | } 121 | 122 | function assertEq(address a, address b, string memory err) internal { 123 | if (a != b) { 124 | emit log_named_string("Error", err); 125 | assertEq(a, b); 126 | } 127 | } 128 | 129 | function assertEq(bytes32 a, bytes32 b) internal { 130 | if (a != b) { 131 | emit log("Error: a == b not satisfied [bytes32]"); 132 | emit log_named_bytes32(" Expected", b); 133 | emit log_named_bytes32(" Actual", a); 134 | fail(); 135 | } 136 | } 137 | 138 | function assertEq(bytes32 a, bytes32 b, string memory err) internal { 139 | if (a != b) { 140 | emit log_named_string("Error", err); 141 | assertEq(a, b); 142 | } 143 | } 144 | 145 | function assertEq32(bytes32 a, bytes32 b) internal { 146 | assertEq(a, b); 147 | } 148 | 149 | function assertEq32(bytes32 a, bytes32 b, string memory err) internal { 150 | assertEq(a, b, err); 151 | } 152 | 153 | function assertEq(int256 a, int256 b) internal { 154 | if (a != b) { 155 | emit log("Error: a == b not satisfied [int]"); 156 | emit log_named_int(" Expected", b); 157 | emit log_named_int(" Actual", a); 158 | fail(); 159 | } 160 | } 161 | 162 | function assertEq(int256 a, int256 b, string memory err) internal { 163 | if (a != b) { 164 | emit log_named_string("Error", err); 165 | assertEq(a, b); 166 | } 167 | } 168 | 169 | function assertEq(uint256 a, uint256 b) internal { 170 | if (a != b) { 171 | emit log("Error: a == b not satisfied [uint]"); 172 | emit log_named_uint(" Expected", b); 173 | emit log_named_uint(" Actual", a); 174 | fail(); 175 | } 176 | } 177 | 178 | function assertEq(uint256 a, uint256 b, string memory err) internal { 179 | if (a != b) { 180 | emit log_named_string("Error", err); 181 | assertEq(a, b); 182 | } 183 | } 184 | 185 | function assertEqDecimal(int256 a, int256 b, uint256 decimals) internal { 186 | if (a != b) { 187 | emit log("Error: a == b not satisfied [decimal int]"); 188 | emit log_named_decimal_int(" Expected", b, decimals); 189 | emit log_named_decimal_int(" Actual", a, decimals); 190 | fail(); 191 | } 192 | } 193 | 194 | function assertEqDecimal(int256 a, int256 b, uint256 decimals, string memory err) internal { 195 | if (a != b) { 196 | emit log_named_string("Error", err); 197 | assertEqDecimal(a, b, decimals); 198 | } 199 | } 200 | 201 | function assertEqDecimal(uint256 a, uint256 b, uint256 decimals) internal { 202 | if (a != b) { 203 | emit log("Error: a == b not satisfied [decimal uint]"); 204 | emit log_named_decimal_uint(" Expected", b, decimals); 205 | emit log_named_decimal_uint(" Actual", a, decimals); 206 | fail(); 207 | } 208 | } 209 | 210 | function assertEqDecimal(uint256 a, uint256 b, uint256 decimals, string memory err) internal { 211 | if (a != b) { 212 | emit log_named_string("Error", err); 213 | assertEqDecimal(a, b, decimals); 214 | } 215 | } 216 | 217 | function assertGt(uint256 a, uint256 b) internal { 218 | if (a <= b) { 219 | emit log("Error: a > b not satisfied [uint]"); 220 | emit log_named_uint(" Value a", a); 221 | emit log_named_uint(" Value b", b); 222 | fail(); 223 | } 224 | } 225 | 226 | function assertGt(uint256 a, uint256 b, string memory err) internal { 227 | if (a <= b) { 228 | emit log_named_string("Error", err); 229 | assertGt(a, b); 230 | } 231 | } 232 | 233 | function assertGt(int256 a, int256 b) internal { 234 | if (a <= b) { 235 | emit log("Error: a > b not satisfied [int]"); 236 | emit log_named_int(" Value a", a); 237 | emit log_named_int(" Value b", b); 238 | fail(); 239 | } 240 | } 241 | 242 | function assertGt(int256 a, int256 b, string memory err) internal { 243 | if (a <= b) { 244 | emit log_named_string("Error", err); 245 | assertGt(a, b); 246 | } 247 | } 248 | 249 | function assertGtDecimal(int256 a, int256 b, uint256 decimals) internal { 250 | if (a <= b) { 251 | emit log("Error: a > b not satisfied [decimal int]"); 252 | emit log_named_decimal_int(" Value a", a, decimals); 253 | emit log_named_decimal_int(" Value b", b, decimals); 254 | fail(); 255 | } 256 | } 257 | 258 | function assertGtDecimal(int256 a, int256 b, uint256 decimals, string memory err) internal { 259 | if (a <= b) { 260 | emit log_named_string("Error", err); 261 | assertGtDecimal(a, b, decimals); 262 | } 263 | } 264 | 265 | function assertGtDecimal(uint256 a, uint256 b, uint256 decimals) internal { 266 | if (a <= b) { 267 | emit log("Error: a > b not satisfied [decimal uint]"); 268 | emit log_named_decimal_uint(" Value a", a, decimals); 269 | emit log_named_decimal_uint(" Value b", b, decimals); 270 | fail(); 271 | } 272 | } 273 | 274 | function assertGtDecimal(uint256 a, uint256 b, uint256 decimals, string memory err) internal { 275 | if (a <= b) { 276 | emit log_named_string("Error", err); 277 | assertGtDecimal(a, b, decimals); 278 | } 279 | } 280 | 281 | function assertGe(uint256 a, uint256 b) internal { 282 | if (a < b) { 283 | emit log("Error: a >= b not satisfied [uint]"); 284 | emit log_named_uint(" Value a", a); 285 | emit log_named_uint(" Value b", b); 286 | fail(); 287 | } 288 | } 289 | 290 | function assertGe(uint256 a, uint256 b, string memory err) internal { 291 | if (a < b) { 292 | emit log_named_string("Error", err); 293 | assertGe(a, b); 294 | } 295 | } 296 | 297 | function assertGe(int256 a, int256 b) internal { 298 | if (a < b) { 299 | emit log("Error: a >= b not satisfied [int]"); 300 | emit log_named_int(" Value a", a); 301 | emit log_named_int(" Value b", b); 302 | fail(); 303 | } 304 | } 305 | 306 | function assertGe(int256 a, int256 b, string memory err) internal { 307 | if (a < b) { 308 | emit log_named_string("Error", err); 309 | assertGe(a, b); 310 | } 311 | } 312 | 313 | function assertGeDecimal(int256 a, int256 b, uint256 decimals) internal { 314 | if (a < b) { 315 | emit log("Error: a >= b not satisfied [decimal int]"); 316 | emit log_named_decimal_int(" Value a", a, decimals); 317 | emit log_named_decimal_int(" Value b", b, decimals); 318 | fail(); 319 | } 320 | } 321 | 322 | function assertGeDecimal(int256 a, int256 b, uint256 decimals, string memory err) internal { 323 | if (a < b) { 324 | emit log_named_string("Error", err); 325 | assertGeDecimal(a, b, decimals); 326 | } 327 | } 328 | 329 | function assertGeDecimal(uint256 a, uint256 b, uint256 decimals) internal { 330 | if (a < b) { 331 | emit log("Error: a >= b not satisfied [decimal uint]"); 332 | emit log_named_decimal_uint(" Value a", a, decimals); 333 | emit log_named_decimal_uint(" Value b", b, decimals); 334 | fail(); 335 | } 336 | } 337 | 338 | function assertGeDecimal(uint256 a, uint256 b, uint256 decimals, string memory err) internal { 339 | if (a < b) { 340 | emit log_named_string("Error", err); 341 | assertGeDecimal(a, b, decimals); 342 | } 343 | } 344 | 345 | function assertLt(uint256 a, uint256 b) internal { 346 | if (a >= b) { 347 | emit log("Error: a < b not satisfied [uint]"); 348 | emit log_named_uint(" Value a", a); 349 | emit log_named_uint(" Value b", b); 350 | fail(); 351 | } 352 | } 353 | 354 | function assertLt(uint256 a, uint256 b, string memory err) internal { 355 | if (a >= b) { 356 | emit log_named_string("Error", err); 357 | assertLt(a, b); 358 | } 359 | } 360 | 361 | function assertLt(int256 a, int256 b) internal { 362 | if (a >= b) { 363 | emit log("Error: a < b not satisfied [int]"); 364 | emit log_named_int(" Value a", a); 365 | emit log_named_int(" Value b", b); 366 | fail(); 367 | } 368 | } 369 | 370 | function assertLt(int256 a, int256 b, string memory err) internal { 371 | if (a >= b) { 372 | emit log_named_string("Error", err); 373 | assertLt(a, b); 374 | } 375 | } 376 | 377 | function assertLtDecimal(int256 a, int256 b, uint256 decimals) internal { 378 | if (a >= b) { 379 | emit log("Error: a < b not satisfied [decimal int]"); 380 | emit log_named_decimal_int(" Value a", a, decimals); 381 | emit log_named_decimal_int(" Value b", b, decimals); 382 | fail(); 383 | } 384 | } 385 | 386 | function assertLtDecimal(int256 a, int256 b, uint256 decimals, string memory err) internal { 387 | if (a >= b) { 388 | emit log_named_string("Error", err); 389 | assertLtDecimal(a, b, decimals); 390 | } 391 | } 392 | 393 | function assertLtDecimal(uint256 a, uint256 b, uint256 decimals) internal { 394 | if (a >= b) { 395 | emit log("Error: a < b not satisfied [decimal uint]"); 396 | emit log_named_decimal_uint(" Value a", a, decimals); 397 | emit log_named_decimal_uint(" Value b", b, decimals); 398 | fail(); 399 | } 400 | } 401 | 402 | function assertLtDecimal(uint256 a, uint256 b, uint256 decimals, string memory err) internal { 403 | if (a >= b) { 404 | emit log_named_string("Error", err); 405 | assertLtDecimal(a, b, decimals); 406 | } 407 | } 408 | 409 | function assertLe(uint256 a, uint256 b) internal { 410 | if (a > b) { 411 | emit log("Error: a <= b not satisfied [uint]"); 412 | emit log_named_uint(" Value a", a); 413 | emit log_named_uint(" Value b", b); 414 | fail(); 415 | } 416 | } 417 | 418 | function assertLe(uint256 a, uint256 b, string memory err) internal { 419 | if (a > b) { 420 | emit log_named_string("Error", err); 421 | assertLe(a, b); 422 | } 423 | } 424 | 425 | function assertLe(int256 a, int256 b) internal { 426 | if (a > b) { 427 | emit log("Error: a <= b not satisfied [int]"); 428 | emit log_named_int(" Value a", a); 429 | emit log_named_int(" Value b", b); 430 | fail(); 431 | } 432 | } 433 | 434 | function assertLe(int256 a, int256 b, string memory err) internal { 435 | if (a > b) { 436 | emit log_named_string("Error", err); 437 | assertLe(a, b); 438 | } 439 | } 440 | 441 | function assertLeDecimal(int256 a, int256 b, uint256 decimals) internal { 442 | if (a > b) { 443 | emit log("Error: a <= b not satisfied [decimal int]"); 444 | emit log_named_decimal_int(" Value a", a, decimals); 445 | emit log_named_decimal_int(" Value b", b, decimals); 446 | fail(); 447 | } 448 | } 449 | 450 | function assertLeDecimal(int256 a, int256 b, uint256 decimals, string memory err) internal { 451 | if (a > b) { 452 | emit log_named_string("Error", err); 453 | assertLeDecimal(a, b, decimals); 454 | } 455 | } 456 | 457 | function assertLeDecimal(uint256 a, uint256 b, uint256 decimals) internal { 458 | if (a > b) { 459 | emit log("Error: a <= b not satisfied [decimal uint]"); 460 | emit log_named_decimal_uint(" Value a", a, decimals); 461 | emit log_named_decimal_uint(" Value b", b, decimals); 462 | fail(); 463 | } 464 | } 465 | 466 | function assertLeDecimal(uint256 a, uint256 b, uint256 decimals, string memory err) internal { 467 | if (a > b) { 468 | emit log_named_string("Error", err); 469 | assertGeDecimal(a, b, decimals); 470 | } 471 | } 472 | 473 | function assertEq(string memory a, string memory b) internal { 474 | if (keccak256(abi.encodePacked(a)) != keccak256(abi.encodePacked(b))) { 475 | emit log("Error: a == b not satisfied [string]"); 476 | emit log_named_string(" Expected", b); 477 | emit log_named_string(" Actual", a); 478 | fail(); 479 | } 480 | } 481 | 482 | function assertEq(string memory a, string memory b, string memory err) internal { 483 | if (keccak256(abi.encodePacked(a)) != keccak256(abi.encodePacked(b))) { 484 | emit log_named_string("Error", err); 485 | assertEq(a, b); 486 | } 487 | } 488 | 489 | function checkEq0(bytes memory a, bytes memory b) internal pure returns (bool ok) { 490 | ok = true; 491 | if (a.length == b.length) { 492 | for (uint256 i = 0; i < a.length; i++) { 493 | if (a[i] != b[i]) { 494 | ok = false; 495 | } 496 | } 497 | } else { 498 | ok = false; 499 | } 500 | } 501 | 502 | function assertEq0(bytes memory a, bytes memory b) internal { 503 | if (!checkEq0(a, b)) { 504 | emit log("Error: a == b not satisfied [bytes]"); 505 | emit log_named_bytes(" Expected", b); 506 | emit log_named_bytes(" Actual", a); 507 | fail(); 508 | } 509 | } 510 | 511 | function assertEq0(bytes memory a, bytes memory b, string memory err) internal { 512 | if (!checkEq0(a, b)) { 513 | emit log_named_string("Error", err); 514 | assertEq0(a, b); 515 | } 516 | } 517 | } 518 | -------------------------------------------------------------------------------- /test/utils/mocks/MockReentrancyGuard.sol: -------------------------------------------------------------------------------- 1 | // SPDX-License-Identifier: MIT 2 | pragma solidity ^0.8.24; 3 | 4 | import {ReentrancyGuard} from "../../../src/utils/ReentrancyGuard.sol"; 5 | 6 | /// @dev WARNING! This mock is strictly intended for testing purposes only. 7 | /// Do NOT copy anything here into production code unless you really know what you are doing. 8 | contract MockReentrancyGuard is ReentrancyGuard { 9 | /// @dev SEE: `ReentrancyGuard`. 10 | uint256 public constant _REENTRANCY_GUARD_SLOT = 0x929eee149b4bd21268; 11 | 12 | uint256 public enterTimes; 13 | 14 | // Mocks 15 | 16 | function isReentrancyGuardLocked() public view returns (bool locked) { 17 | /// @solidity memory-safe-assembly 18 | assembly { 19 | if tload(_REENTRANCY_GUARD_SLOT) { locked := true } 20 | } 21 | } 22 | 23 | function callUnguardedToGuarded() public { 24 | callbackTargetGuarded(); 25 | } 26 | 27 | function callUnguardedToUnguarded() public { 28 | callbackTargetUnguarded(); 29 | } 30 | 31 | function callGuardedToGuarded() public nonReentrant { 32 | callbackTargetGuarded(); 33 | } 34 | 35 | function callGuardedToUnguarded() public nonReentrant { 36 | callbackTargetUnguarded(); 37 | } 38 | 39 | function callGuardedToReadGuarded() public nonReentrant { 40 | readCallbackTargetGuarded(); 41 | } 42 | 43 | function callUnguardedToReadGuarded() public { 44 | readCallbackTargetGuarded(); 45 | } 46 | 47 | // Targets 48 | 49 | /// @dev Callback target without a reentrancy guard. 50 | function callbackTargetUnguarded() public { 51 | enterTimes++; 52 | } 53 | 54 | /// @dev Callback target with a reentrancy guard. 55 | function callbackTargetGuarded() public nonReentrant { 56 | enterTimes++; 57 | } 58 | 59 | /// @dev Callback target with a non-read reentrancy guard. 60 | function readCallbackTargetGuarded() public nonReadReentrant { 61 | enterTimes++; 62 | } 63 | 64 | // Recursion 65 | 66 | function countUnguardedDirectRecursive(uint256 recursion) public { 67 | _recurseDirect(false, recursion); 68 | } 69 | 70 | function countGuardedDirectRecursive(uint256 recursion) public nonReentrant { 71 | _recurseDirect(true, recursion); 72 | } 73 | 74 | function countUnguardedIndirectRecursive(uint256 recursion) public { 75 | _recurseIndirect(false, recursion); 76 | } 77 | 78 | function countGuardedIndirectRecursive(uint256 recursion) public nonReentrant { 79 | _recurseIndirect(true, recursion); 80 | } 81 | 82 | function countAndCall(ReentrancyAttack attacker) public nonReentrant { 83 | enterTimes++; 84 | attacker.callSender(bytes4(keccak256("callbackTargetGuarded()"))); 85 | } 86 | 87 | // Helpers 88 | 89 | function _recurseDirect(bool guarded, uint256 recursion) private { 90 | if (recursion > 0) { 91 | enterTimes++; 92 | 93 | if (guarded) { 94 | countGuardedDirectRecursive(recursion - 1); 95 | } else { 96 | countUnguardedDirectRecursive(recursion - 1); 97 | } 98 | } 99 | } 100 | 101 | function _recurseIndirect(bool guarded, uint256 recursion) private { 102 | if (recursion > 0) { 103 | enterTimes++; 104 | 105 | (bool success, bytes memory data) = address(this).call( 106 | abi.encodeWithSignature( 107 | guarded 108 | ? "countGuardedIndirectRecursive(uint256)" 109 | : "countUnguardedIndirectRecursive(uint256)", 110 | recursion - 1 111 | ) 112 | ); 113 | 114 | if (!success) { 115 | /// @solidity memory-safe-assembly 116 | assembly { 117 | revert(add(32, data), mload(data)) 118 | } 119 | } 120 | } 121 | } 122 | } 123 | 124 | /// @dev WARNING! This mock is strictly intended for testing purposes only. 125 | /// Do NOT copy anything here into production code unless you really know what you are doing. 126 | contract ReentrancyAttack { 127 | /// @dev Reverts on a failed reentrancy attack. 128 | error ReentrancyAttackFailed(); 129 | 130 | /// @dev Call the msg.sender with the given data to perform a reentrancy attack. 131 | function callSender(bytes4 data) external { 132 | (bool success,) = msg.sender.call(abi.encodeWithSelector(data)); 133 | 134 | if (!success) revert ReentrancyAttackFailed(); 135 | } 136 | } 137 | --------------------------------------------------------------------------------