├── .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 | #
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 |
--------------------------------------------------------------------------------
/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 |
--------------------------------------------------------------------------------