From e58647b689276d20ed396b28a45bdc6193163b74 Mon Sep 17 00:00:00 2001 From: TimeLord <836444684@qq.com> Date: Tue, 27 Aug 2024 23:51:52 -0400 Subject: [PATCH] "mutiple pols" --- lib/solidity-bytes-utils/.gitattribute | 1 + lib/solidity-bytes-utils/.gitattributes | 1 + lib/solidity-bytes-utils/.gitignore | 16 + lib/solidity-bytes-utils/.npmignore | 12 + lib/solidity-bytes-utils/.soliumignore | 4 + lib/solidity-bytes-utils/.soliumrc.json | 19 + lib/solidity-bytes-utils/LICENSE | 24 + lib/solidity-bytes-utils/README.md | 288 +++++++ .../contracts/AssertBytes.sol | 297 +++++++ .../contracts/BytesLib.sol | 576 +++++++++++++ lib/solidity-bytes-utils/ethpm.json | 17 + lib/solidity-bytes-utils/package-lock.json | 38 + lib/solidity-bytes-utils/package.json | 32 + lib/solidity-bytes-utils/remappings.txt | 2 + .../test/TestBytesLib1.sol | 787 ++++++++++++++++++ .../test/TestBytesLib2.sol | 449 ++++++++++ src/UniswapV3Factory.sol | 60 ++ src/UniswapV3Pool.sol | 368 +++++--- src/UniswapV3Quote.sol | 92 +- src/interfaces/IUniswapV3PoolDeployer.sol | 20 + src/lib/Path.sol | 54 ++ src/lib/PoolAddress.sol | 31 + test/UniswapV3Pool.t.sol | 54 +- 23 files changed, 3073 insertions(+), 169 deletions(-) create mode 100644 lib/solidity-bytes-utils/.gitattribute create mode 100644 lib/solidity-bytes-utils/.gitattributes create mode 100644 lib/solidity-bytes-utils/.gitignore create mode 100644 lib/solidity-bytes-utils/.npmignore create mode 100644 lib/solidity-bytes-utils/.soliumignore create mode 100644 lib/solidity-bytes-utils/.soliumrc.json create mode 100644 lib/solidity-bytes-utils/LICENSE create mode 100644 lib/solidity-bytes-utils/README.md create mode 100644 lib/solidity-bytes-utils/contracts/AssertBytes.sol create mode 100644 lib/solidity-bytes-utils/contracts/BytesLib.sol create mode 100644 lib/solidity-bytes-utils/ethpm.json create mode 100644 lib/solidity-bytes-utils/package-lock.json create mode 100644 lib/solidity-bytes-utils/package.json create mode 100644 lib/solidity-bytes-utils/remappings.txt create mode 100644 lib/solidity-bytes-utils/test/TestBytesLib1.sol create mode 100644 lib/solidity-bytes-utils/test/TestBytesLib2.sol create mode 100644 src/UniswapV3Factory.sol create mode 100644 src/interfaces/IUniswapV3PoolDeployer.sol create mode 100644 src/lib/Path.sol create mode 100644 src/lib/PoolAddress.sol diff --git a/lib/solidity-bytes-utils/.gitattribute b/lib/solidity-bytes-utils/.gitattribute new file mode 100644 index 0000000..7cc88f0 --- /dev/null +++ b/lib/solidity-bytes-utils/.gitattribute @@ -0,0 +1 @@ +*.sol linguist-language=Solidity \ No newline at end of file diff --git a/lib/solidity-bytes-utils/.gitattributes b/lib/solidity-bytes-utils/.gitattributes new file mode 100644 index 0000000..52031de --- /dev/null +++ b/lib/solidity-bytes-utils/.gitattributes @@ -0,0 +1 @@ +*.sol linguist-language=Solidity diff --git a/lib/solidity-bytes-utils/.gitignore b/lib/solidity-bytes-utils/.gitignore new file mode 100644 index 0000000..522e14d --- /dev/null +++ b/lib/solidity-bytes-utils/.gitignore @@ -0,0 +1,16 @@ +# System Cruft +# ================================================ +.DS_Store +*.swp +*.swo + +# secret stuff +# ================================================ +secrets.json + +# Generated stuff +# ================================================ +build/* +node_modules/* +cache/* +out/* \ No newline at end of file diff --git a/lib/solidity-bytes-utils/.npmignore b/lib/solidity-bytes-utils/.npmignore new file mode 100644 index 0000000..8655172 --- /dev/null +++ b/lib/solidity-bytes-utils/.npmignore @@ -0,0 +1,12 @@ +# secret stuff +# ================================================ +secrets.json + +# Generated stuff +# ================================================ +build/* +node_modules/* + +# Unneeded files for NPM deployment +# ================================================ +truffle.js diff --git a/lib/solidity-bytes-utils/.soliumignore b/lib/solidity-bytes-utils/.soliumignore new file mode 100644 index 0000000..c83e871 --- /dev/null +++ b/lib/solidity-bytes-utils/.soliumignore @@ -0,0 +1,4 @@ +node_modules +coverageEnv +.git +test diff --git a/lib/solidity-bytes-utils/.soliumrc.json b/lib/solidity-bytes-utils/.soliumrc.json new file mode 100644 index 0000000..d3cbe2d --- /dev/null +++ b/lib/solidity-bytes-utils/.soliumrc.json @@ -0,0 +1,19 @@ +{ + "extends": "solium:all", + "plugins": ["security"], + "rules": { + "error-reason": "off", + "indentation": ["error", 4], + "lbrace": "off", + "linebreak-style": ["error", "unix"], + "no-constant": ["error"], + "no-empty-blocks": "warning", + "quotes": ["error", "double"], + "uppercase": "off", + "imports-on-top": "error", + "visibility-first": "error", + "security/enforce-explicit-visibility": ["error"], + "security/no-block-members": ["off"], + "security/no-inline-assembly": ["off"] + } +} diff --git a/lib/solidity-bytes-utils/LICENSE b/lib/solidity-bytes-utils/LICENSE new file mode 100644 index 0000000..fdddb29 --- /dev/null +++ b/lib/solidity-bytes-utils/LICENSE @@ -0,0 +1,24 @@ +This is free and unencumbered software released into the public domain. + +Anyone is free to copy, modify, publish, use, compile, sell, or +distribute this software, either in source code form or as a compiled +binary, for any purpose, commercial or non-commercial, and by any +means. + +In jurisdictions that recognize copyright laws, the author or authors +of this software dedicate any and all copyright interest in the +software to the public domain. We make this dedication for the benefit +of the public at large and to the detriment of our heirs and +successors. We intend this dedication to be an overt act of +relinquishment in perpetuity of all present and future rights to this +software under copyright law. + +THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, +EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF +MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. +IN NO EVENT SHALL THE AUTHORS BE LIABLE FOR ANY CLAIM, DAMAGES OR +OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, +ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR +OTHER DEALINGS IN THE SOFTWARE. + +For more information, please refer to diff --git a/lib/solidity-bytes-utils/README.md b/lib/solidity-bytes-utils/README.md new file mode 100644 index 0000000..0e5bd34 --- /dev/null +++ b/lib/solidity-bytes-utils/README.md @@ -0,0 +1,288 @@ +# Solidity Bytes Arrays Utils + +Bytes tightly packed arrays' utility library for ethereum contracts written in Solidity. + +The library lets you concatenate, slice and type cast bytes arrays both in memory and storage. + +Given this library has an all-internal collection of methods it doesn't make sense to have it reside in the mainnet. Instead it will only be available on EPM as an installable package. + +## Important Fixes Changelog + +_**2021-01-07**_ + +A bug regarding zero-length slices was disclosed by @MrChico following an audit to the Optimism codebase. + +The exact bug happened under the following conditions: if memory slots higher then the current free-memory pointer were tainted before calling the `slice` method with a desired length of `0`, the returned bytes array, instead of being a zero-length slice was an array of arbitrary length based on the values that previously populated that memory region. + +Overall, the usage of zero-length slices should be pretty unusual and, as such, hopefully, this bug does not have far-reaching implications. Nonetheless, *please update the library to the new version if you're using it in production*. + +**TL;DR: if you're using the `slice` method with a length parameter of '0' in your codebase, please update to version 0.1.2 of the bytes library ASAP!** + +_**2020-11-01**_ + +There was a **critical bug** in the `slice` method, reported on an audit to a DXDao codebase. + +Previously, no checks were being made on overflows of the `_start` and `_length` parameters since previous reviews of the codebase deemed this overflow "unexploitable" because of an inordinate expansion of memory (i.e., reading an immensely large memory offset causing huge memory expansion) resulting in an out-of-gas exception. + +However, as noted in the review mentioned above, this is not the case. The `slice` method in versions `<=0.9.0` actually allows for arbitrary _kind of_ (i.e., it allows memory writes to very specific values) arbitrary memory writes _in the specific case where these parameters are user-supplied inputs and not hardcoded values (which is uncommon). + +This made me realize that in permissioned blockchains where gas is also not a limiting factor this could become problematic in other methods and so I updated all typecasting-related methods to include new bound checks as well. + +**TL;DR: if you're using the `slice` method with user-supplied inputs in your codebase please update the bytes library immediately!** + +## _Version Notes_: + +* Version `v0.9.0` has a new feature: a new "equal_nonAligned" method that allows for comparing two bytes arrays that are not aligned to 32 bytes. +This is useful for comparing bytes arrays that were created with assembly/Yul or other, non-Solidity compilers that don't pad bytes arrays to 32 bytes. + +* Starting from version `v0.8.0` the versioning will change to follow compatible Solidity's compiler versions. +This means that now the library will only compile on Solidity versions `>=0.8.0` so, if you need `<0.8.0` support for your project just use `v0.1.2` of the library with: + +``` +$ truffle install bytes@0.8.0 +``` +or +``` +$ npm install solidity-bytes-utils@0.8.0 +``` + +* Version `v0.1.2` has a major bug fix. + +* Version `v0.1.1` has a critical bug fix. + +* Version `v0.9.0` now compiles with Solidity compilers `0.5.x` and `0.6.x`. + +* Since version `v0.0.7` the library will only compile on Solidity versions `>0.4.22` so, if you need `v0.4.x` support for your project just use `v0.0.6` of the library with: + +``` +$ truffle install bytes@0.0.6 +``` +or +``` +$ npm install solidity-bytes-utils@0.0.6 +``` + +## Usage + +You can use the library here present by direct download and importing with: +``` +import "BytesLib.sol"; +``` + +or, if you have installed it from EPM (see below), with Truffle's specific paths: +``` +import "bytes/BytesLib.sol"; +``` + +Usage examples and API are more thoroughly explained below. + +Also there's an extra library in there called `AssertBytes` (inside the same named file) which is compatible with Truffle's Solidity testing library `Assert.sol` event firing and so lets you now test bytes equalities/inequalities in your Solidity tests by just importing it in your `.sol` test files: +``` +import "bytes/AssertBytes.sol"; +``` + +and use the library `AssertBytes` much like they use `Assert` in Truffle's [example](http://truffleframework.com/docs/getting_started/solidity-tests). + +## EthPM + +This library is published in EPM under the alias `bytes` + +**Installing it with Truffle** + +``` +$ truffle install bytes +``` + +## NPM + +This library is published in NPM under the alias `solidity-bytes-utils` + +**Installing it with NPM** + +``` +$ npm install solidity-bytes-utils +``` + +**Importing it in your Solidity contract** + +``` +import "solidity-bytes-utils/contracts/BytesLib.sol"; +``` + +## Contributing + +Contributions are more than welcome in any way shape or form! 😄 + +TODOs: +* Two storage bytes arrays concatenation +* Slicing directly from storage +* Implement inline assembly functions for better readability + +### Testing + +This project uses Truffle for tests. Truffle's version of `solc` needs to be at least 0.4.19 for the contracts to compile. If you encounter compilation errors, try: + + $ cd /usr/local/lib/node_modules/truffle + $ npm install solc@latest + +To run the tests, start a `testrpc` instance, then run `truffle test`. + +## API + +* `function concat(bytes memory _preBytes, bytes memory _postBytes) internal pure returns (bytes)` + +Concatenates two `bytes` arrays in memory and returns the concatenation result as another `bytes` array in memory. + + +* `function concatStorage(bytes storage _preBytes, bytes memory _postBytes) internal pure` + +Concatenates a `bytes` array present in memory directly into the given storage location addressed by the `_preBytes` storage pointer. + + +* `function slice(bytes _bytes, uint _start, uint _length) internal pure returns (bytes)` + +Takes a slice from a `bytes` array in memory of given `length` starting from `_start`th byte counting from the left-most one (0-based). + + +* `function toAddress(bytes _bytes, uint _start) internal pure returns (address)` + +Takes a 20-byte-long sequence present in a `bytes` array in memory and returns that as an address (also checks for sufficient length). + + +* `function toUint(bytes _bytes, uint _start) internal pure returns (uint256)` + +Takes a 32-byte-long sequence present in a `bytes` array in memory and returns that as an unsigned integer (also checks for sufficient length). + + +* `function equal(bytes memory _preBytes, bytes memory _postBytes) internal view returns (bool)` + +Compares two `bytes` arrays in memory and returns the comparison result as a `bool` variable. + + +* `function equalStorage(bytes storage _preBytes, bytes memory _postBytes) internal view returns (bool)` + +Compares a `bytes` array in storage against another `bytes` array in memory and returns the comparison result as a `bool` variable. + + +## Examples + +Ordered to mimic the above `API` section ordering: + +```javascript +contract MyContract { + using BytesLib for bytes; + + function myFunc() { + bytes memory _preBytes = hex"f00dfeed"; + bytes memory _postBytes = hex"f00dfeed"; + + bytes memory concatBytes = _preBytes.concat(_postBytes); + + // concatBytes == 0xf00dfeedf00dfeed + } +} +``` + + +```javascript +contract MyContract { + using BytesLib for bytes; + + bytes storageBytes = hex"f00dfeed"; + + function myFunc() { + bytes memory _postBytes = hex"f00dfeed"; + + storageBytes.concatStorage(_postBytes); + + // storageBytes == 0xf00dfeedf00dfeed + } +} +``` + + +```javascript +contract MyContract { + using BytesLib for bytes; + + function myFunc() { + bytes memory memBytes = hex"f00dfeedaabbccddeeff"; + + bytes memory slice1 = memBytes.slice(0, 2); + bytes memory slice2 = memBytes.slice(2, 2); + + // slice1 == 0xf00d + // slice2 == 0xfeed + } +} +``` + + +```javascript +contract MyContract { + using BytesLib for bytes; + + function myFunc() { + bytes memory memBytes = hex"f00dfeed383Fa3B60f9B4AB7fBf6835d3c26C3765cD2B2e2f00dfeed"; + + address addrFromBytes = memBytes.toAddress(4); + + // addrFromBytes == 0x383Fa3B60f9B4AB7fBf6835d3c26C3765cD2B2e2 + } +} +``` + + +```javascript +contract MyContract { + using BytesLib for bytes; + + function myFunc() { + bytes memory memBytes = hex"f00d0000000000000000000000000000000000000000000000000000000000000042feed"; + + uint256 uintFromBytes = memBytes.toUint(2); + + // uintFromBytes == 42 + } +} +``` + + +```javascript +contract MyContract { + using BytesLib for bytes; + + function myFunc() { + bytes memory memBytes = hex"f00dfeed"; + bytes memory checkBytesTrue = hex"f00dfeed"; + bytes memory checkBytesFalse = hex"00000000"; + + bool check1 = memBytes.equal(checkBytesTrue); + bool check2 = memBytes.equal(checkBytesFalse); + + // check1 == true + // check2 == false + } +} +``` + + +```javascript + +contract MyContract { + using BytesLib for bytes; + + bytes storageBytes = hex"f00dfeed"; + + function myFunc() { + bytes memory checkBytesTrue = hex"f00dfeed"; + bytes memory checkBytesFalse = hex"00000000"; + + bool check1 = storageBytes.equalStorage(checkBytesTrue); + bool check2 = storageBytes.equalStorage(checkBytesFalse); + + // check1 == true + // check2 == false + } +} +``` diff --git a/lib/solidity-bytes-utils/contracts/AssertBytes.sol b/lib/solidity-bytes-utils/contracts/AssertBytes.sol new file mode 100644 index 0000000..3751ac2 --- /dev/null +++ b/lib/solidity-bytes-utils/contracts/AssertBytes.sol @@ -0,0 +1,297 @@ +// SPDX-License-Identifier: Unlicense +/* + * @title Solidity Bytes Assertion Library + * @author Gonçalo Sá + * + * @dev A Solidity library built to complete assertions in Solidity unit tests. + * This library is compliant with the test event convention that the Truffle suite uses. + */ + +pragma solidity >=0.8.0 <0.9.0; + + +library AssertBytes { + // Event to maintain compatibility with Truffle's Assertion Lib + event TestEvent(bool indexed result, string message); + + /* + Function: equal(bytes memory, bytes memory) + + Assert that two tightly packed bytes arrays are equal. + + Params: + A (bytes) - The first bytes. + B (bytes) - The second bytes. + message (string) - A message that is sent if the assertion fails. + + Returns: + result (bool) - The result. + */ + function _equal(bytes memory _a, bytes memory _b) internal pure returns (bool) { + bool returnBool = true; + + assembly { + let length := mload(_a) + + // if lengths don't match the arrays are not equal + switch eq(length, mload(_b)) + case 1 { + // cb is a circuit breaker in the for loop since there's + // no said feature for inline assembly loops + // cb = 1 - don't breaker + // cb = 0 - break + let cb := 1 + + let mc := add(_a, 0x20) + let end := add(mc, length) + + for { + let cc := add(_b, 0x20) + // the next line is the loop condition: + // while(uint256(mc < end) + cb == 2) + } eq(add(lt(mc, end), cb), 2) { + mc := add(mc, 0x20) + cc := add(cc, 0x20) + } { + // if any of these checks fails then arrays are not equal + if iszero(eq(mload(mc), mload(cc))) { + // unsuccess: + returnBool := 0 + cb := 0 + } + } + } + default { + // unsuccess: + returnBool := 0 + } + } + + return returnBool; + } + + function equal(bytes memory _a, bytes memory _b, string memory message) internal returns (bool) { + bool returnBool = _equal(_a, _b); + + _report(returnBool, message); + + return returnBool; + } + + function notEqual(bytes memory _a, bytes memory _b, string memory message) internal returns (bool) { + bool returnBool = _equal(_a, _b); + + _report(!returnBool, message); + + return !returnBool; + } + + /* + Function: equal_nonAligned(bytes memory, bytes memory) + + Assert that two tightly packed bytes arrays that are not aligned to 32 bytes are equal. + + Params: + A (bytes) - The first bytes. + B (bytes) - The second bytes. + message (string) - A message that is sent if the assertion fails. + + Returns: + result (bool) - The result. + */ + + + function _equal_nonAligned(bytes memory _preBytes, bytes memory _postBytes) internal pure returns (bool) { + bool success = true; + + assembly { + let length := mload(_preBytes) + + // if lengths don't match the arrays are not equal + switch eq(length, mload(_postBytes)) + case 1 { + // cb is a circuit breaker in the for loop since there's + // no said feature for inline assembly loops + // cb = 1 - don't breaker + // cb = 0 - break + let cb := 1 + + let endMinusWord := add(_preBytes, length) + let mc := add(_preBytes, 0x20) + let cc := add(_postBytes, 0x20) + + for { + // the next line is the loop condition: + // while(uint256(mc < endWord) + cb == 2) + } eq(add(lt(mc, endMinusWord), cb), 2) { + mc := add(mc, 0x20) + cc := add(cc, 0x20) + } { + // if any of these checks fails then arrays are not equal + if iszero(eq(mload(mc), mload(cc))) { + // unsuccess: + success := 0 + cb := 0 + } + } + + // Only if still successful + // For <1 word tail bytes + if gt(success, 0) { + // Get the remainder of length/32 + // length % 32 = AND(length, 32 - 1) + let numTailBytes := and(length, 0x1f) + let mcRem := mload(mc) + let ccRem := mload(cc) + for { + let i := 0 + // the next line is the loop condition: + // while(uint256(i < numTailBytes) + cb == 2) + } eq(add(lt(i, numTailBytes), cb), 2) { + i := add(i, 1) + } { + if iszero(eq(byte(i, mcRem), byte(i, ccRem))) { + // unsuccess: + success := 0 + cb := 0 + } + } + } + } + default { + // unsuccess: + success := 0 + } + } + + return success; + } + + function equal_nonAligned(bytes memory _a, bytes memory _b, string memory message) internal returns (bool) { + bool returnBool = _equal_nonAligned(_a, _b); + + _report(returnBool, message); + + return returnBool; + } + + function notEqual_nonAligned(bytes memory _a, bytes memory _b, string memory message) internal returns (bool) { + bool returnBool = _equal_nonAligned(_a, _b); + + _report(!returnBool, message); + + return !returnBool; + } + + /* + Function: equal(bytes storage, bytes memory) + + Assert that two tightly packed bytes arrays are equal. + + Params: + A (bytes) - The first bytes. + B (bytes) - The second bytes. + message (string) - A message that is sent if the assertion fails. + + Returns: + result (bool) - The result. + */ + function _equalStorage(bytes storage _a, bytes memory _b) internal view returns (bool) { + bool returnBool = true; + + assembly { + // we know _a_offset is 0 + let fslot := sload(_a.slot) + let slength := div(and(fslot, sub(mul(0x100, iszero(and(fslot, 1))), 1)), 2) + let mlength := mload(_b) + + // if lengths don't match the arrays are not equal + switch eq(slength, mlength) + case 1 { + // slength can contain both the length and contents of the array + // if length < 32 bytes so let's prepare for that + // v. http://solidity.readthedocs.io/en/latest/miscellaneous.html#layout-of-state-variables-in-storage + if iszero(iszero(slength)) { + switch lt(slength, 32) + case 1 { + // blank the last byte which is the length + fslot := mul(div(fslot, 0x100), 0x100) + + if iszero(eq(fslot, mload(add(_b, 0x20)))) { + // unsuccess: + returnBool := 0 + } + } + default { + // cb is a circuit breaker in the for loop since there's + // no said feature for inline assembly loops + // cb = 1 - don't breaker + // cb = 0 - break + let cb := 1 + + // get the keccak hash to get the contents of the array + mstore(0x0, _a.slot) + let sc := keccak256(0x0, 0x20) + + let mc := add(_b, 0x20) + let end := add(mc, mlength) + + // the next line is the loop condition: + // while(uint256(mc < end) + cb == 2) + for {} eq(add(lt(mc, end), cb), 2) { + sc := add(sc, 1) + mc := add(mc, 0x20) + } { + if iszero(eq(sload(sc), mload(mc))) { + // unsuccess: + returnBool := 0 + cb := 0 + } + } + } + } + } + default { + // unsuccess: + returnBool := 0 + } + } + + return returnBool; + } + + function equalStorage(bytes storage _a, bytes memory _b, string memory message) internal returns (bool) { + bool returnBool = _equalStorage(_a, _b); + + _report(returnBool, message); + + return returnBool; + } + + function notEqualStorage(bytes storage _a, bytes memory _b, string memory message) internal returns (bool) { + bool returnBool = _equalStorage(_a, _b); + + _report(!returnBool, message); + + return !returnBool; + } + + /********** Maintaining compatibility with Truffle's Assert.sol ***********/ + /******************************** internal ********************************/ + + /* + Function: _report + + Internal function for triggering . + + Params: + result (bool) - The test result (true or false). + message (string) - The message that is sent if the assertion fails. + */ + function _report(bool result, string memory message) internal { + if (result) + emit TestEvent(true, ""); + else + emit TestEvent(false, message); + } +} diff --git a/lib/solidity-bytes-utils/contracts/BytesLib.sol b/lib/solidity-bytes-utils/contracts/BytesLib.sol new file mode 100644 index 0000000..13e018c --- /dev/null +++ b/lib/solidity-bytes-utils/contracts/BytesLib.sol @@ -0,0 +1,576 @@ +// SPDX-License-Identifier: Unlicense +/* + * @title Solidity Bytes Arrays Utils + * @author Gonçalo Sá + * + * @dev Bytes tightly packed arrays utility library for ethereum contracts written in Solidity. + * The library lets you concatenate, slice and type cast bytes arrays both in memory and storage. + */ +pragma solidity >=0.8.0 <0.9.0; + + +library BytesLib { + function concat( + bytes memory _preBytes, + bytes memory _postBytes + ) + internal + pure + returns (bytes memory) + { + bytes memory tempBytes; + + assembly { + // Get a location of some free memory and store it in tempBytes as + // Solidity does for memory variables. + tempBytes := mload(0x40) + + // Store the length of the first bytes array at the beginning of + // the memory for tempBytes. + let length := mload(_preBytes) + mstore(tempBytes, length) + + // Maintain a memory counter for the current write location in the + // temp bytes array by adding the 32 bytes for the array length to + // the starting location. + let mc := add(tempBytes, 0x20) + // Stop copying when the memory counter reaches the length of the + // first bytes array. + let end := add(mc, length) + + for { + // Initialize a copy counter to the start of the _preBytes data, + // 32 bytes into its memory. + let cc := add(_preBytes, 0x20) + } lt(mc, end) { + // Increase both counters by 32 bytes each iteration. + mc := add(mc, 0x20) + cc := add(cc, 0x20) + } { + // Write the _preBytes data into the tempBytes memory 32 bytes + // at a time. + mstore(mc, mload(cc)) + } + + // Add the length of _postBytes to the current length of tempBytes + // and store it as the new length in the first 32 bytes of the + // tempBytes memory. + length := mload(_postBytes) + mstore(tempBytes, add(length, mload(tempBytes))) + + // Move the memory counter back from a multiple of 0x20 to the + // actual end of the _preBytes data. + mc := end + // Stop copying when the memory counter reaches the new combined + // length of the arrays. + end := add(mc, length) + + for { + let cc := add(_postBytes, 0x20) + } lt(mc, end) { + mc := add(mc, 0x20) + cc := add(cc, 0x20) + } { + mstore(mc, mload(cc)) + } + + // Update the free-memory pointer by padding our last write location + // to 32 bytes: add 31 bytes to the end of tempBytes to move to the + // next 32 byte block, then round down to the nearest multiple of + // 32. If the sum of the length of the two arrays is zero then add + // one before rounding down to leave a blank 32 bytes (the length block with 0). + mstore(0x40, and( + add(add(end, iszero(add(length, mload(_preBytes)))), 31), + not(31) // Round down to the nearest 32 bytes. + )) + } + + return tempBytes; + } + + function concatStorage(bytes storage _preBytes, bytes memory _postBytes) internal { + assembly { + // Read the first 32 bytes of _preBytes storage, which is the length + // of the array. (We don't need to use the offset into the slot + // because arrays use the entire slot.) + let fslot := sload(_preBytes.slot) + // Arrays of 31 bytes or less have an even value in their slot, + // while longer arrays have an odd value. The actual length is + // the slot divided by two for odd values, and the lowest order + // byte divided by two for even values. + // If the slot is even, bitwise and the slot with 255 and divide by + // two to get the length. If the slot is odd, bitwise and the slot + // with -1 and divide by two. + let slength := div(and(fslot, sub(mul(0x100, iszero(and(fslot, 1))), 1)), 2) + let mlength := mload(_postBytes) + let newlength := add(slength, mlength) + // slength can contain both the length and contents of the array + // if length < 32 bytes so let's prepare for that + // v. http://solidity.readthedocs.io/en/latest/miscellaneous.html#layout-of-state-variables-in-storage + switch add(lt(slength, 32), lt(newlength, 32)) + case 2 { + // Since the new array still fits in the slot, we just need to + // update the contents of the slot. + // uint256(bytes_storage) = uint256(bytes_storage) + uint256(bytes_memory) + new_length + sstore( + _preBytes.slot, + // all the modifications to the slot are inside this + // next block + add( + // we can just add to the slot contents because the + // bytes we want to change are the LSBs + fslot, + add( + mul( + div( + // load the bytes from memory + mload(add(_postBytes, 0x20)), + // zero all bytes to the right + exp(0x100, sub(32, mlength)) + ), + // and now shift left the number of bytes to + // leave space for the length in the slot + exp(0x100, sub(32, newlength)) + ), + // increase length by the double of the memory + // bytes length + mul(mlength, 2) + ) + ) + ) + } + case 1 { + // The stored value fits in the slot, but the combined value + // will exceed it. + // get the keccak hash to get the contents of the array + mstore(0x0, _preBytes.slot) + let sc := add(keccak256(0x0, 0x20), div(slength, 32)) + + // save new length + sstore(_preBytes.slot, add(mul(newlength, 2), 1)) + + // The contents of the _postBytes array start 32 bytes into + // the structure. Our first read should obtain the `submod` + // bytes that can fit into the unused space in the last word + // of the stored array. To get this, we read 32 bytes starting + // from `submod`, so the data we read overlaps with the array + // contents by `submod` bytes. Masking the lowest-order + // `submod` bytes allows us to add that value directly to the + // stored value. + + let submod := sub(32, slength) + let mc := add(_postBytes, submod) + let end := add(_postBytes, mlength) + let mask := sub(exp(0x100, submod), 1) + + sstore( + sc, + add( + and( + fslot, + 0xffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff00 + ), + and(mload(mc), mask) + ) + ) + + for { + mc := add(mc, 0x20) + sc := add(sc, 1) + } lt(mc, end) { + sc := add(sc, 1) + mc := add(mc, 0x20) + } { + sstore(sc, mload(mc)) + } + + mask := exp(0x100, sub(mc, end)) + + sstore(sc, mul(div(mload(mc), mask), mask)) + } + default { + // get the keccak hash to get the contents of the array + mstore(0x0, _preBytes.slot) + // Start copying to the last used word of the stored array. + let sc := add(keccak256(0x0, 0x20), div(slength, 32)) + + // save new length + sstore(_preBytes.slot, add(mul(newlength, 2), 1)) + + // Copy over the first `submod` bytes of the new data as in + // case 1 above. + let slengthmod := mod(slength, 32) + let mlengthmod := mod(mlength, 32) + let submod := sub(32, slengthmod) + let mc := add(_postBytes, submod) + let end := add(_postBytes, mlength) + let mask := sub(exp(0x100, submod), 1) + + sstore(sc, add(sload(sc), and(mload(mc), mask))) + + for { + sc := add(sc, 1) + mc := add(mc, 0x20) + } lt(mc, end) { + sc := add(sc, 1) + mc := add(mc, 0x20) + } { + sstore(sc, mload(mc)) + } + + mask := exp(0x100, sub(mc, end)) + + sstore(sc, mul(div(mload(mc), mask), mask)) + } + } + } + + function slice( + bytes memory _bytes, + uint256 _start, + uint256 _length + ) + internal + pure + returns (bytes memory) + { + require(_length + 31 >= _length, "slice_overflow"); + require(_bytes.length >= _start + _length, "slice_outOfBounds"); + + bytes memory tempBytes; + + assembly { + switch iszero(_length) + case 0 { + // Get a location of some free memory and store it in tempBytes as + // Solidity does for memory variables. + tempBytes := mload(0x40) + + // The first word of the slice result is potentially a partial + // word read from the original array. To read it, we calculate + // the length of that partial word and start copying that many + // bytes into the array. The first word we copy will start with + // data we don't care about, but the last `lengthmod` bytes will + // land at the beginning of the contents of the new array. When + // we're done copying, we overwrite the full first word with + // the actual length of the slice. + let lengthmod := and(_length, 31) + + // The multiplication in the next line is necessary + // because when slicing multiples of 32 bytes (lengthmod == 0) + // the following copy loop was copying the origin's length + // and then ending prematurely not copying everything it should. + let mc := add(add(tempBytes, lengthmod), mul(0x20, iszero(lengthmod))) + let end := add(mc, _length) + + for { + // The multiplication in the next line has the same exact purpose + // as the one above. + let cc := add(add(add(_bytes, lengthmod), mul(0x20, iszero(lengthmod))), _start) + } lt(mc, end) { + mc := add(mc, 0x20) + cc := add(cc, 0x20) + } { + mstore(mc, mload(cc)) + } + + mstore(tempBytes, _length) + + //update free-memory pointer + //allocating the array padded to 32 bytes like the compiler does now + mstore(0x40, and(add(mc, 31), not(31))) + } + //if we want a zero-length slice let's just return a zero-length array + default { + tempBytes := mload(0x40) + //zero out the 32 bytes slice we are about to return + //we need to do it because Solidity does not garbage collect + mstore(tempBytes, 0) + + mstore(0x40, add(tempBytes, 0x20)) + } + } + + return tempBytes; + } + + function toAddress(bytes memory _bytes, uint256 _start) internal pure returns (address) { + require(_bytes.length >= _start + 20, "toAddress_outOfBounds"); + address tempAddress; + + assembly { + tempAddress := div(mload(add(add(_bytes, 0x20), _start)), 0x1000000000000000000000000) + } + + return tempAddress; + } + + function toUint8(bytes memory _bytes, uint256 _start) internal pure returns (uint8) { + require(_bytes.length >= _start + 1 , "toUint8_outOfBounds"); + uint8 tempUint; + + assembly { + tempUint := mload(add(add(_bytes, 0x1), _start)) + } + + return tempUint; + } + + function toUint16(bytes memory _bytes, uint256 _start) internal pure returns (uint16) { + require(_bytes.length >= _start + 2, "toUint16_outOfBounds"); + uint16 tempUint; + + assembly { + tempUint := mload(add(add(_bytes, 0x2), _start)) + } + + return tempUint; + } + + function toUint32(bytes memory _bytes, uint256 _start) internal pure returns (uint32) { + require(_bytes.length >= _start + 4, "toUint32_outOfBounds"); + uint32 tempUint; + + assembly { + tempUint := mload(add(add(_bytes, 0x4), _start)) + } + + return tempUint; + } + + function toUint64(bytes memory _bytes, uint256 _start) internal pure returns (uint64) { + require(_bytes.length >= _start + 8, "toUint64_outOfBounds"); + uint64 tempUint; + + assembly { + tempUint := mload(add(add(_bytes, 0x8), _start)) + } + + return tempUint; + } + + function toUint96(bytes memory _bytes, uint256 _start) internal pure returns (uint96) { + require(_bytes.length >= _start + 12, "toUint96_outOfBounds"); + uint96 tempUint; + + assembly { + tempUint := mload(add(add(_bytes, 0xc), _start)) + } + + return tempUint; + } + + function toUint128(bytes memory _bytes, uint256 _start) internal pure returns (uint128) { + require(_bytes.length >= _start + 16, "toUint128_outOfBounds"); + uint128 tempUint; + + assembly { + tempUint := mload(add(add(_bytes, 0x10), _start)) + } + + return tempUint; + } + + function toUint256(bytes memory _bytes, uint256 _start) internal pure returns (uint256) { + require(_bytes.length >= _start + 32, "toUint256_outOfBounds"); + uint256 tempUint; + + assembly { + tempUint := mload(add(add(_bytes, 0x20), _start)) + } + + return tempUint; + } + + function toBytes32(bytes memory _bytes, uint256 _start) internal pure returns (bytes32) { + require(_bytes.length >= _start + 32, "toBytes32_outOfBounds"); + bytes32 tempBytes32; + + assembly { + tempBytes32 := mload(add(add(_bytes, 0x20), _start)) + } + + return tempBytes32; + } + + function equal(bytes memory _preBytes, bytes memory _postBytes) internal pure returns (bool) { + bool success = true; + + assembly { + let length := mload(_preBytes) + + // if lengths don't match the arrays are not equal + switch eq(length, mload(_postBytes)) + case 1 { + // cb is a circuit breaker in the for loop since there's + // no said feature for inline assembly loops + // cb = 1 - don't breaker + // cb = 0 - break + let cb := 1 + + let mc := add(_preBytes, 0x20) + let end := add(mc, length) + + for { + let cc := add(_postBytes, 0x20) + // the next line is the loop condition: + // while(uint256(mc < end) + cb == 2) + } eq(add(lt(mc, end), cb), 2) { + mc := add(mc, 0x20) + cc := add(cc, 0x20) + } { + // if any of these checks fails then arrays are not equal + if iszero(eq(mload(mc), mload(cc))) { + // unsuccess: + success := 0 + cb := 0 + } + } + } + default { + // unsuccess: + success := 0 + } + } + + return success; + } + + function equal_nonAligned(bytes memory _preBytes, bytes memory _postBytes) internal pure returns (bool) { + bool success = true; + + assembly { + let length := mload(_preBytes) + + // if lengths don't match the arrays are not equal + switch eq(length, mload(_postBytes)) + case 1 { + // cb is a circuit breaker in the for loop since there's + // no said feature for inline assembly loops + // cb = 1 - don't breaker + // cb = 0 - break + let cb := 1 + + let endMinusWord := add(_preBytes, length) + let mc := add(_preBytes, 0x20) + let cc := add(_postBytes, 0x20) + + for { + // the next line is the loop condition: + // while(uint256(mc < endWord) + cb == 2) + } eq(add(lt(mc, endMinusWord), cb), 2) { + mc := add(mc, 0x20) + cc := add(cc, 0x20) + } { + // if any of these checks fails then arrays are not equal + if iszero(eq(mload(mc), mload(cc))) { + // unsuccess: + success := 0 + cb := 0 + } + } + + // Only if still successful + // For <1 word tail bytes + if gt(success, 0) { + // Get the remainder of length/32 + // length % 32 = AND(length, 32 - 1) + let numTailBytes := and(length, 0x1f) + let mcRem := mload(mc) + let ccRem := mload(cc) + for { + let i := 0 + // the next line is the loop condition: + // while(uint256(i < numTailBytes) + cb == 2) + } eq(add(lt(i, numTailBytes), cb), 2) { + i := add(i, 1) + } { + if iszero(eq(byte(i, mcRem), byte(i, ccRem))) { + // unsuccess: + success := 0 + cb := 0 + } + } + } + } + default { + // unsuccess: + success := 0 + } + } + + return success; + } + + function equalStorage( + bytes storage _preBytes, + bytes memory _postBytes + ) + internal + view + returns (bool) + { + bool success = true; + + assembly { + // we know _preBytes_offset is 0 + let fslot := sload(_preBytes.slot) + // Decode the length of the stored array like in concatStorage(). + let slength := div(and(fslot, sub(mul(0x100, iszero(and(fslot, 1))), 1)), 2) + let mlength := mload(_postBytes) + + // if lengths don't match the arrays are not equal + switch eq(slength, mlength) + case 1 { + // slength can contain both the length and contents of the array + // if length < 32 bytes so let's prepare for that + // v. http://solidity.readthedocs.io/en/latest/miscellaneous.html#layout-of-state-variables-in-storage + if iszero(iszero(slength)) { + switch lt(slength, 32) + case 1 { + // blank the last byte which is the length + fslot := mul(div(fslot, 0x100), 0x100) + + if iszero(eq(fslot, mload(add(_postBytes, 0x20)))) { + // unsuccess: + success := 0 + } + } + default { + // cb is a circuit breaker in the for loop since there's + // no said feature for inline assembly loops + // cb = 1 - don't breaker + // cb = 0 - break + let cb := 1 + + // get the keccak hash to get the contents of the array + mstore(0x0, _preBytes.slot) + let sc := keccak256(0x0, 0x20) + + let mc := add(_postBytes, 0x20) + let end := add(mc, mlength) + + // the next line is the loop condition: + // while(uint256(mc < end) + cb == 2) + for {} eq(add(lt(mc, end), cb), 2) { + sc := add(sc, 1) + mc := add(mc, 0x20) + } { + if iszero(eq(sload(sc), mload(mc))) { + // unsuccess: + success := 0 + cb := 0 + } + } + } + } + } + default { + // unsuccess: + success := 0 + } + } + + return success; + } +} diff --git a/lib/solidity-bytes-utils/ethpm.json b/lib/solidity-bytes-utils/ethpm.json new file mode 100644 index 0000000..daabb78 --- /dev/null +++ b/lib/solidity-bytes-utils/ethpm.json @@ -0,0 +1,17 @@ +{ + "package_name": "bytes", + "version": "0.8.0", + "description": "Solidity bytes tightly packed arrays utility library.", + "authors": [ + "Gonçalo Sá " + ], + "keywords": [ + "bytes", + "utils", + "solidity", + "library" + ], + "dependencies": { + }, + "license": "MIT" +} \ No newline at end of file diff --git a/lib/solidity-bytes-utils/package-lock.json b/lib/solidity-bytes-utils/package-lock.json new file mode 100644 index 0000000..01bb404 --- /dev/null +++ b/lib/solidity-bytes-utils/package-lock.json @@ -0,0 +1,38 @@ +{ + "name": "solidity-bytes-utils", + "version": "0.8.2", + "lockfileVersion": 2, + "requires": true, + "packages": { + "": { + "name": "solidity-bytes-utils", + "version": "0.8.2", + "license": "MIT", + "dependencies": { + "ds-test": "github:dapphub/ds-test", + "forge-std": "^1.1.2" + } + }, + "node_modules/ds-test": { + "version": "1.0.0", + "resolved": "git+ssh://git@github.com/dapphub/ds-test.git#e282159d5170298eb2455a6c05280ab5a73a4ef0", + "license": "GPL-3.0" + }, + "node_modules/forge-std": { + "version": "1.1.2", + "resolved": "https://registry.npmjs.org/forge-std/-/forge-std-1.1.2.tgz", + "integrity": "sha512-Wfb0iAS9PcfjMKtGpWQw9mXzJxrWD62kJCUqqLcyuI0+VRtJ3j20XembjF3kS20qELYdXft1vD/SPFVWVKMFOw==" + } + }, + "dependencies": { + "ds-test": { + "version": "git+ssh://git@github.com/dapphub/ds-test.git#e282159d5170298eb2455a6c05280ab5a73a4ef0", + "from": "ds-test@github:dapphub/ds-test" + }, + "forge-std": { + "version": "1.1.2", + "resolved": "https://registry.npmjs.org/forge-std/-/forge-std-1.1.2.tgz", + "integrity": "sha512-Wfb0iAS9PcfjMKtGpWQw9mXzJxrWD62kJCUqqLcyuI0+VRtJ3j20XembjF3kS20qELYdXft1vD/SPFVWVKMFOw==" + } + } +} diff --git a/lib/solidity-bytes-utils/package.json b/lib/solidity-bytes-utils/package.json new file mode 100644 index 0000000..ad61cd9 --- /dev/null +++ b/lib/solidity-bytes-utils/package.json @@ -0,0 +1,32 @@ +{ + "name": "solidity-bytes-utils", + "version": "0.8.2", + "description": "Solidity bytes tightly packed arrays utility library.", + "main": "truffle.js", + "repository": { + "type": "git", + "url": "git@github.com:GNSPS/solidity-bytes-utils.git" + }, + "files": [ + "contracts", + "test" + ], + "keywords": [ + "ethereum", + "bytes", + "solidity", + "library" + ], + "authors": [ + "Gonçalo Sá " + ], + "license": "MIT", + "bugs": { + "url": "https://github.com/GNSPS/solidity-bytes-utils/issues" + }, + "homepage": "https://github.com/GNSPS/solidity-bytes-utils#readme", + "dependencies": { + "ds-test": "github:dapphub/ds-test", + "forge-std": "^1.1.2" + } +} diff --git a/lib/solidity-bytes-utils/remappings.txt b/lib/solidity-bytes-utils/remappings.txt new file mode 100644 index 0000000..99e3a3f --- /dev/null +++ b/lib/solidity-bytes-utils/remappings.txt @@ -0,0 +1,2 @@ +forge-std/=node_modules/forge-std/ +ds-test/=node_modules/ds-test/src/ \ No newline at end of file diff --git a/lib/solidity-bytes-utils/test/TestBytesLib1.sol b/lib/solidity-bytes-utils/test/TestBytesLib1.sol new file mode 100644 index 0000000..0714d1a --- /dev/null +++ b/lib/solidity-bytes-utils/test/TestBytesLib1.sol @@ -0,0 +1,787 @@ +// SPDX-License-Identifier: Unlicense +pragma solidity >=0.8.0 <0.9.0; + +import "forge-std/Test.sol"; +import "../contracts/AssertBytes.sol"; +import "../contracts/BytesLib.sol"; + + +contract TestBytesLib1 is Test { + using BytesLib for bytes; + + bytes storageCheckBytes = hex"aabbccddeeff"; + bytes storageCheckBytesZeroLength = hex""; + + bytes storageBytes4 = hex"f00dfeed"; + bytes storageBytes31 = hex"f00d000000000000000000000000000000000000000000000000000000feed"; + bytes storageBytes32 = hex"f00d00000000000000000000000000000000000000000000000000000000feed"; + bytes storageBytes33 = hex"f00d0000000000000000000000000000000000000000000000000000000000feed"; + bytes storageBytes63 = hex"f00d0000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000feed"; + bytes storageBytes64 = hex"f00d000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000feed"; + bytes storageBytes65 = hex"f00d00000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000feed"; + bytes storageBytes70 = hex"f00d000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000feed"; + + /** + * Sanity Checks + */ + + function testSanityCheck() public { + // Assert library sanity checks + // + // Please don't change the ordering of the var definitions + // the order is purposeful for testing zero-length arrays + bytes memory checkBytes = hex"aabbccddeeff"; + bytes memory checkBytesZeroLength = hex""; + + bytes memory checkBytesRight = hex"aabbccddeeff"; + bytes memory checkBytesZeroLengthRight = hex""; + bytes memory checkBytesWrongLength = hex"aa0000"; + bytes memory checkBytesWrongContent = hex"aabbccddee00"; + + assertEq(checkBytes, checkBytesRight, "Sanity check should be checking equal bytes arrays out."); + assertTrue(keccak256(checkBytes) != keccak256(checkBytesWrongLength), "Sanity check should be checking different length bytes arrays out."); + assertTrue(keccak256(checkBytes) != keccak256(checkBytesWrongContent), "Sanity check should be checking different content bytes arrays out."); + AssertBytes.equal(checkBytes, checkBytesRight, "Sanity check should be checking equal bytes arrays out."); + AssertBytes.notEqual(checkBytes, checkBytesWrongLength, "Sanity check should be checking different length bytes arrays out."); + AssertBytes.notEqual(checkBytes, checkBytesWrongContent, "Sanity check should be checking different content bytes arrays out."); + + assertEq(storageCheckBytes, checkBytesRight, "Sanity check should be checking equal bytes arrays out. (Storage)"); + assertTrue(keccak256(storageCheckBytes) != keccak256(checkBytesWrongLength), "Sanity check should be checking different length bytes arrays out. (Storage)"); + assertTrue(keccak256(storageCheckBytes) != keccak256(checkBytesWrongContent), "Sanity check should be checking different content bytes arrays out. (Storage)"); + AssertBytes.equalStorage(storageCheckBytes, checkBytesRight, "Sanity check should be checking equal bytes arrays out. (Storage)"); + AssertBytes.notEqualStorage(storageCheckBytes, checkBytesWrongLength, "Sanity check should be checking different length bytes arrays out. (Storage)"); + AssertBytes.notEqualStorage(storageCheckBytes, checkBytesWrongContent, "Sanity check should be checking different content bytes arrays out. (Storage)"); + + // Zero-length checks + assertEq(checkBytesZeroLength, checkBytesZeroLengthRight, "Sanity check should be checking equal zero-length bytes arrays out."); + assertTrue(keccak256(checkBytesZeroLength) != keccak256(checkBytes), "Sanity check should be checking different length bytes arrays out."); + AssertBytes.equal(checkBytesZeroLength, checkBytesZeroLengthRight, "Sanity check should be checking equal zero-length bytes arrays out."); + AssertBytes.notEqual(checkBytesZeroLength, checkBytes, "Sanity check should be checking different length bytes arrays out."); + + assertEq(storageCheckBytesZeroLength, checkBytesZeroLengthRight, "Sanity check should be checking equal zero-length bytes arrays out. (Storage)"); + assertTrue(keccak256(storageCheckBytesZeroLength) != keccak256(checkBytes), "Sanity check should be checking different length bytes arrays out. (Storage)"); + AssertBytes.equalStorage(storageCheckBytesZeroLength, checkBytesZeroLengthRight, "Sanity check should be checking equal zero-length bytes arrays out. (Storage)"); + AssertBytes.notEqualStorage(storageCheckBytesZeroLength, checkBytes, "Sanity check should be checking different length bytes arrays out. (Storage)"); + } + + /** + * Memory Integrity Checks + */ + + function testMemoryIntegrityCheck4Bytes() public { + bytes memory preBytes4 = hex"f00dfeed"; + bytes memory postBytes4 = hex"f00dfeed"; + bytes memory postBytes31 = hex"f00d000000000000000000000000000000000000000000000000000000feed"; + bytes memory postBytes32 = hex"f00d00000000000000000000000000000000000000000000000000000000feed"; + bytes memory postBytes33 = hex"f00d0000000000000000000000000000000000000000000000000000000000feed"; + + bytes memory testBytes; + bytes memory resultBytes; + + resultBytes = preBytes4.concat(postBytes4); + + // Now we should make sure that all the other previously initialized arrays stayed the same + testBytes = hex"f00dfeed"; + assertEq(preBytes4, testBytes, "After a postBytes4 concat the preBytes4 integrity check failed."); + assertEq(postBytes4, testBytes, "After a postBytes4 concat the postBytes4 integrity check failed."); + AssertBytes.equal(preBytes4, testBytes, "After a postBytes4 concat the preBytes4 integrity check failed."); + AssertBytes.equal(postBytes4, testBytes, "After a postBytes4 concat the postBytes4 integrity check failed."); + testBytes = hex"f00d000000000000000000000000000000000000000000000000000000feed"; + assertEq(postBytes31, testBytes, "After a postBytes4 concat the postBytes31 integrity check failed."); + AssertBytes.equal(postBytes31, testBytes, "After a postBytes4 concat the postBytes31 integrity check failed."); + testBytes = hex"f00d00000000000000000000000000000000000000000000000000000000feed"; + assertEq(postBytes32, testBytes, "After a postBytes4 concat the postBytes32 integrity check failed."); + AssertBytes.equal(postBytes32, testBytes, "After a postBytes4 concat the postBytes32 integrity check failed."); + testBytes = hex"f00d0000000000000000000000000000000000000000000000000000000000feed"; + assertEq(postBytes33, testBytes, "After a postBytes4 concat the postBytes33 integrity check failed."); + AssertBytes.equal(postBytes33, testBytes, "After a postBytes4 concat the postBytes33 integrity check failed."); + } + + function testMemoryIntegrityCheck31Bytes() public { + bytes memory preBytes4 = hex"f00dfeed"; + bytes memory postBytes4 = hex"f00dfeed"; + bytes memory postBytes31 = hex"f00d000000000000000000000000000000000000000000000000000000feed"; + bytes memory postBytes32 = hex"f00d00000000000000000000000000000000000000000000000000000000feed"; + bytes memory postBytes33 = hex"f00d0000000000000000000000000000000000000000000000000000000000feed"; + + bytes memory testBytes; + bytes memory resultBytes; + + resultBytes = preBytes4.concat(postBytes31); + + // Now we should make sure that all the other previously initialized arrays stayed the same + testBytes = hex"f00dfeed"; + assertEq(preBytes4, testBytes, "After a postBytes31 concat the preBytes4 integrity check failed."); + assertEq(postBytes4, testBytes, "After a postBytes31 concat the postBytes4 integrity check failed."); + AssertBytes.equal(preBytes4, testBytes, "After a postBytes31 concat the preBytes4 integrity check failed."); + AssertBytes.equal(postBytes4, testBytes, "After a postBytes31 concat the postBytes4 integrity check failed."); + testBytes = hex"f00d000000000000000000000000000000000000000000000000000000feed"; + assertEq(postBytes31, testBytes, "After a postBytes31 concat the postBytes31 integrity check failed."); + AssertBytes.equal(postBytes31, testBytes, "After a postBytes31 concat the postBytes31 integrity check failed."); + testBytes = hex"f00d00000000000000000000000000000000000000000000000000000000feed"; + assertEq(postBytes32, testBytes, "After a postBytes31 concat the postBytes32 integrity check failed."); + AssertBytes.equal(postBytes32, testBytes, "After a postBytes31 concat the postBytes32 integrity check failed."); + testBytes = hex"f00d0000000000000000000000000000000000000000000000000000000000feed"; + assertEq(postBytes33, testBytes, "After a postBytes31 concat the postBytes33 integrity check failed."); + AssertBytes.equal(postBytes33, testBytes, "After a postBytes31 concat the postBytes33 integrity check failed."); + } + + function testMemoryIntegrityCheck32Bytes() public { + bytes memory preBytes4 = hex"f00dfeed"; + bytes memory postBytes4 = hex"f00dfeed"; + bytes memory postBytes31 = hex"f00d000000000000000000000000000000000000000000000000000000feed"; + bytes memory postBytes32 = hex"f00d00000000000000000000000000000000000000000000000000000000feed"; + bytes memory postBytes33 = hex"f00d0000000000000000000000000000000000000000000000000000000000feed"; + + bytes memory testBytes; + bytes memory resultBytes; + + resultBytes = preBytes4.concat(postBytes32); + + // Now we should make sure that all the other previously initialized arrays stayed the same + testBytes = hex"f00dfeed"; + assertEq(preBytes4, testBytes, "After a postBytes32 concat the preBytes4 integrity check failed."); + assertEq(postBytes4, testBytes, "After a postBytes32 concat the postBytes4 integrity check failed."); + AssertBytes.equal(preBytes4, testBytes, "After a postBytes32 concat the preBytes4 integrity check failed."); + AssertBytes.equal(postBytes4, testBytes, "After a postBytes32 concat the postBytes4 integrity check failed."); + testBytes = hex"f00d000000000000000000000000000000000000000000000000000000feed"; + assertEq(postBytes31, testBytes, "After a postBytes32 concat the postBytes31 integrity check failed."); + AssertBytes.equal(postBytes31, testBytes, "After a postBytes32 concat the postBytes31 integrity check failed."); + testBytes = hex"f00d00000000000000000000000000000000000000000000000000000000feed"; + assertEq(postBytes32, testBytes, "After a postBytes32 concat the postBytes32 integrity check failed."); + AssertBytes.equal(postBytes32, testBytes, "After a postBytes32 concat the postBytes32 integrity check failed."); + testBytes = hex"f00d0000000000000000000000000000000000000000000000000000000000feed"; + assertEq(postBytes33, testBytes, "After a postBytes32 concat the postBytes33 integrity check failed."); + AssertBytes.equal(postBytes33, testBytes, "After a postBytes32 concat the postBytes33 integrity check failed."); +} + + function testMemoryIntegrityCheck33Bytes() public { + bytes memory preBytes4 = hex"f00dfeed"; + bytes memory postBytes4 = hex"f00dfeed"; + bytes memory postBytes31 = hex"f00d000000000000000000000000000000000000000000000000000000feed"; + bytes memory postBytes32 = hex"f00d00000000000000000000000000000000000000000000000000000000feed"; + bytes memory postBytes33 = hex"f00d0000000000000000000000000000000000000000000000000000000000feed"; + + bytes memory testBytes; + bytes memory resultBytes; + + resultBytes = preBytes4.concat(postBytes33); + + // Now we should make sure that all the other previously initialized arrays stayed the same + testBytes = hex"f00dfeed"; + assertEq(preBytes4, testBytes, "After a postBytes33 concat the preBytes4 integrity check failed."); + assertEq(postBytes4, testBytes, "After a postBytes33 concat the postBytes4 integrity check failed."); + AssertBytes.equal(preBytes4, testBytes, "After a postBytes33 concat the preBytes4 integrity check failed."); + AssertBytes.equal(postBytes4, testBytes, "After a postBytes33 concat the postBytes4 integrity check failed."); + testBytes = hex"f00d000000000000000000000000000000000000000000000000000000feed"; + assertEq(postBytes31, testBytes, "After a postBytes33 concat the postBytes31 integrity check failed."); + AssertBytes.equal(postBytes31, testBytes, "After a postBytes33 concat the postBytes31 integrity check failed."); + testBytes = hex"f00d00000000000000000000000000000000000000000000000000000000feed"; + assertEq(postBytes32, testBytes, "After a postBytes33 concat the postBytes32 integrity check failed."); + AssertBytes.equal(postBytes32, testBytes, "After a postBytes33 concat the postBytes32 integrity check failed."); + testBytes = hex"f00d0000000000000000000000000000000000000000000000000000000000feed"; + assertEq(postBytes33, testBytes, "After a postBytes33 concat the postBytes33 integrity check failed."); + AssertBytes.equal(postBytes33, testBytes, "After a postBytes33 concat the postBytes33 integrity check failed."); + } + + /** + * Memory Concatenation Tests + */ + + function testConcatMemory4Bytes() public { + // Initialize `bytes` variables in memory with different critical sizes + bytes memory preBytes4 = hex"f00dfeed"; + bytes memory preBytes31 = hex"f00d000000000000000000000000000000000000000000000000000000feed"; + bytes memory preBytes32 = hex"f00d00000000000000000000000000000000000000000000000000000000feed"; + bytes memory preBytes33 = hex"f00d0000000000000000000000000000000000000000000000000000000000feed"; + + bytes memory postBytes4 = hex"f00dfeed"; + + bytes memory testBytes; + bytes memory resultBytes; + + resultBytes = preBytes4.concat(postBytes4); + testBytes = hex"f00dfeedf00dfeed"; + assertEq(resultBytes, testBytes, "preBytes4 + postBytes4 concatenation failed."); + AssertBytes.equal(resultBytes, testBytes, "preBytes4 + postBytes4 concatenation failed."); + + resultBytes = preBytes31.concat(postBytes4); + testBytes = hex"f00d000000000000000000000000000000000000000000000000000000feedf00dfeed"; + assertEq(resultBytes, testBytes, "preBytes31 + postBytes4 concatenation failed."); + AssertBytes.equal(resultBytes, testBytes, "preBytes31 + postBytes4 concatenation failed."); + + resultBytes = preBytes32.concat(postBytes4); + testBytes = hex"f00d00000000000000000000000000000000000000000000000000000000feedf00dfeed"; + assertEq(resultBytes, testBytes, "preBytes32 + postBytes4 concatenation failed."); + AssertBytes.equal(resultBytes, testBytes, "preBytes32 + postBytes4 concatenation failed."); + + resultBytes = preBytes33.concat(postBytes4); + testBytes = hex"f00d0000000000000000000000000000000000000000000000000000000000feedf00dfeed"; + assertEq(resultBytes, testBytes, "preBytes33 + postBytes4 concatenation failed."); + AssertBytes.equal(resultBytes, testBytes, "preBytes33 + postBytes4 concatenation failed."); + } + + function testConcatMemory31Bytes() public { + // Initialize `bytes` variables in memory with different critical sizes + bytes memory preBytes4 = hex"f00dfeed"; + bytes memory preBytes31 = hex"f00d000000000000000000000000000000000000000000000000000000feed"; + bytes memory preBytes32 = hex"f00d00000000000000000000000000000000000000000000000000000000feed"; + bytes memory preBytes33 = hex"f00d0000000000000000000000000000000000000000000000000000000000feed"; + + bytes memory postBytes31 = hex"f00d000000000000000000000000000000000000000000000000000000feed"; + + bytes memory testBytes; + bytes memory resultBytes; + + resultBytes = preBytes4.concat(postBytes31); + testBytes = hex"f00dfeedf00d000000000000000000000000000000000000000000000000000000feed"; + assertEq(resultBytes, testBytes, "preBytes4 + postBytes31 concatenation failed."); + AssertBytes.equal(resultBytes, testBytes, "preBytes4 + postBytes31 concatenation failed."); + + resultBytes = preBytes31.concat(postBytes31); + testBytes = hex"f00d000000000000000000000000000000000000000000000000000000feedf00d000000000000000000000000000000000000000000000000000000feed"; + assertEq(resultBytes, testBytes, "preBytes31 + postBytes31 concatenation failed."); + AssertBytes.equal(resultBytes, testBytes, "preBytes31 + postBytes31 concatenation failed."); + + resultBytes = preBytes32.concat(postBytes31); + testBytes = hex"f00d00000000000000000000000000000000000000000000000000000000feedf00d000000000000000000000000000000000000000000000000000000feed"; + assertEq(resultBytes, testBytes, "preBytes32 + postBytes31 concatenation failed."); + AssertBytes.equal(resultBytes, testBytes, "preBytes32 + postBytes31 concatenation failed."); + + resultBytes = preBytes33.concat(postBytes31); + testBytes = hex"f00d0000000000000000000000000000000000000000000000000000000000feedf00d000000000000000000000000000000000000000000000000000000feed"; + assertEq(resultBytes, testBytes, "preBytes33 + postBytes31 concatenation failed."); + AssertBytes.equal(resultBytes, testBytes, "preBytes33 + postBytes31 concatenation failed."); + } + + function testConcatMemory32Bytes() public { + // Initialize `bytes` variables in memory with different critical sizes + bytes memory preBytes4 = hex"f00dfeed"; + bytes memory preBytes31 = hex"f00d000000000000000000000000000000000000000000000000000000feed"; + bytes memory preBytes32 = hex"f00d00000000000000000000000000000000000000000000000000000000feed"; + bytes memory preBytes33 = hex"f00d0000000000000000000000000000000000000000000000000000000000feed"; + + bytes memory postBytes32 = hex"f00d00000000000000000000000000000000000000000000000000000000feed"; + + bytes memory testBytes; + bytes memory resultBytes; + + resultBytes = preBytes4.concat(postBytes32); + testBytes = hex"f00dfeedf00d00000000000000000000000000000000000000000000000000000000feed"; + assertEq(resultBytes, testBytes, "preBytes4 + postBytes32 concatenation failed."); + AssertBytes.equal(resultBytes, testBytes, "preBytes4 + postBytes32 concatenation failed."); + + resultBytes = preBytes31.concat(postBytes32); + testBytes = hex"f00d000000000000000000000000000000000000000000000000000000feedf00d00000000000000000000000000000000000000000000000000000000feed"; + assertEq(resultBytes, testBytes, "preBytes31 + postBytes32 concatenation failed."); + AssertBytes.equal(resultBytes, testBytes, "preBytes31 + postBytes32 concatenation failed."); + + resultBytes = preBytes32.concat(postBytes32); + testBytes = hex"f00d00000000000000000000000000000000000000000000000000000000feedf00d00000000000000000000000000000000000000000000000000000000feed"; + assertEq(resultBytes, testBytes, "preBytes32 + postBytes32 concatenation failed."); + AssertBytes.equal(resultBytes, testBytes, "preBytes32 + postBytes32 concatenation failed."); + + resultBytes = preBytes33.concat(postBytes32); + testBytes = hex"f00d0000000000000000000000000000000000000000000000000000000000feedf00d00000000000000000000000000000000000000000000000000000000feed"; + assertEq(resultBytes, testBytes, "preBytes33 + postBytes32 concatenation failed."); + AssertBytes.equal(resultBytes, testBytes, "preBytes33 + postBytes32 concatenation failed."); + } + + function testConcatMemory33Bytes() public { + // Initialize `bytes` variables in memory with different critical sizes + bytes memory preBytes4 = hex"f00dfeed"; + bytes memory preBytes31 = hex"f00d000000000000000000000000000000000000000000000000000000feed"; + bytes memory preBytes32 = hex"f00d00000000000000000000000000000000000000000000000000000000feed"; + bytes memory preBytes33 = hex"f00d0000000000000000000000000000000000000000000000000000000000feed"; + + bytes memory postBytes33 = hex"f00d0000000000000000000000000000000000000000000000000000000000feed"; + + bytes memory testBytes; + bytes memory resultBytes; + + resultBytes = preBytes4.concat(postBytes33); + testBytes = hex"f00dfeedf00d0000000000000000000000000000000000000000000000000000000000feed"; + assertEq(resultBytes, testBytes, "preBytes4 + postBytes33 concatenation failed."); + AssertBytes.equal(resultBytes, testBytes, "preBytes4 + postBytes33 concatenation failed."); + + resultBytes = preBytes31.concat(postBytes33); + testBytes = hex"f00d000000000000000000000000000000000000000000000000000000feedf00d0000000000000000000000000000000000000000000000000000000000feed"; + assertEq(resultBytes, testBytes, "preBytes31 + postBytes33 concatenation failed."); + AssertBytes.equal(resultBytes, testBytes, "preBytes31 + postBytes33 concatenation failed."); + + resultBytes = preBytes32.concat(postBytes33); + testBytes = hex"f00d00000000000000000000000000000000000000000000000000000000feedf00d0000000000000000000000000000000000000000000000000000000000feed"; + assertEq(resultBytes, testBytes, "preBytes32 + postBytes33 concatenation failed."); + AssertBytes.equal(resultBytes, testBytes, "preBytes32 + postBytes33 concatenation failed."); + + resultBytes = preBytes33.concat(postBytes33); + testBytes = hex"f00d0000000000000000000000000000000000000000000000000000000000feedf00d0000000000000000000000000000000000000000000000000000000000feed"; + assertEq(resultBytes, testBytes, "preBytes33 + postBytes33 concatenation failed."); + AssertBytes.equal(resultBytes, testBytes, "preBytes33 + postBytes33 concatenation failed."); + } + + /** + * Storage Concatenation Tests + */ + + function testConcatStorage4Bytes() public { + // Initialize `bytes` variables in memory + bytes memory memBytes4 = hex"f00dfeed"; + + // this next assembly block is to guarantee that the block of memory next to the end of the bytes + // array is not zero'ed out + assembly { + let mc := mload(0x40) + mstore(mc, 0xffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff) + mstore(0x40, add(mc, 0x20)) + } + + bytes memory testBytes; + + storageBytes4.concatStorage(memBytes4); + testBytes = hex"f00dfeedf00dfeed"; + assertEq(storageBytes4, testBytes, "storageBytes4 + memBytes4 concatenation failed."); + AssertBytes.equalStorage(storageBytes4, testBytes, "storageBytes4 + memBytes4 concatenation failed."); + + storageBytes31.concatStorage(memBytes4); + testBytes = hex"f00d000000000000000000000000000000000000000000000000000000feedf00dfeed"; + assertEq(storageBytes31, testBytes, "storageBytes31 + memBytes4 concatenation failed."); + AssertBytes.equalStorage(storageBytes31, testBytes, "storageBytes31 + memBytes4 concatenation failed."); + + storageBytes32.concatStorage(memBytes4); + testBytes = hex"f00d00000000000000000000000000000000000000000000000000000000feedf00dfeed"; + assertEq(storageBytes32, testBytes, "storageBytes32 + memBytes4 concatenation failed."); + AssertBytes.equalStorage(storageBytes32, testBytes, "storageBytes32 + memBytes4 concatenation failed."); + + storageBytes33.concatStorage(memBytes4); + testBytes = hex"f00d0000000000000000000000000000000000000000000000000000000000feedf00dfeed"; + assertEq(storageBytes33, testBytes, "storageBytes33 + memBytes4 concatenation failed."); + AssertBytes.equalStorage(storageBytes33, testBytes, "storageBytes33 + memBytes4 concatenation failed."); + + storageBytes63.concatStorage(memBytes4); + testBytes = hex"f00d0000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000feedf00dfeed"; + assertEq(storageBytes63, testBytes, "storageBytes63 + memBytes4 concatenation failed."); + AssertBytes.equalStorage(storageBytes63, testBytes, "storageBytes63 + memBytes4 concatenation failed."); + + storageBytes64.concatStorage(memBytes4); + testBytes = hex"f00d000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000feedf00dfeed"; + assertEq(storageBytes64, testBytes, "storageBytes64 + memBytes4 concatenation failed."); + AssertBytes.equalStorage(storageBytes64, testBytes, "storageBytes64 + memBytes4 concatenation failed."); + + storageBytes65.concatStorage(memBytes4); + testBytes = hex"f00d00000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000feedf00dfeed"; + assertEq(storageBytes65, testBytes, "storageBytes65 + memBytes4 concatenation failed."); + AssertBytes.equalStorage(storageBytes65, testBytes, "storageBytes65 + memBytes4 concatenation failed."); + + storageBytes70.concatStorage(memBytes4); + testBytes = hex"f00d000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000feedf00dfeed"; + assertEq(storageBytes70, testBytes, "storageBytes70 + memBytes4 concatenation failed."); + AssertBytes.equalStorage(storageBytes70, testBytes, "storageBytes70 + memBytes4 concatenation failed."); + + resetStorage(); + } + + function testConcatStorage31Bytes() public { + // Initialize `bytes` variables in memory + bytes memory memBytes31 = hex"f00d000000000000000000000000000000000000000000000000000000feed"; + + // this next assembly block is to guarantee that the block of memory next to the end of the bytes + // array is not zero'ed out + assembly { + let mc := mload(0x40) + mstore(mc, 0xffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff) + mstore(0x40, add(mc, 0x20)) + } + + bytes memory testBytes; + + storageBytes4.concatStorage(memBytes31); + testBytes = hex"f00dfeedf00d000000000000000000000000000000000000000000000000000000feed"; + assertEq(storageBytes4, testBytes, "storageBytes4 + memBytes31 concatenation failed."); + AssertBytes.equalStorage(storageBytes4, testBytes, "storageBytes4 + memBytes31 concatenation failed."); + + storageBytes31.concatStorage(memBytes31); + testBytes = hex"f00d000000000000000000000000000000000000000000000000000000feedf00d000000000000000000000000000000000000000000000000000000feed"; + assertEq(storageBytes31, testBytes, "storageBytes31 + memBytes31 concatenation failed."); + AssertBytes.equalStorage(storageBytes31, testBytes, "storageBytes31 + memBytes31 concatenation failed."); + + storageBytes32.concatStorage(memBytes31); + testBytes = hex"f00d00000000000000000000000000000000000000000000000000000000feedf00d000000000000000000000000000000000000000000000000000000feed"; + assertEq(storageBytes32, testBytes, "storageBytes32 + memBytes31 concatenation failed."); + AssertBytes.equalStorage(storageBytes32, testBytes, "storageBytes32 + memBytes31 concatenation failed."); + + storageBytes33.concatStorage(memBytes31); + testBytes = hex"f00d0000000000000000000000000000000000000000000000000000000000feedf00d000000000000000000000000000000000000000000000000000000feed"; + assertEq(storageBytes33, testBytes, "storageBytes33 + memBytes31 concatenation failed."); + AssertBytes.equalStorage(storageBytes33, testBytes, "storageBytes33 + memBytes31 concatenation failed."); + + storageBytes63.concatStorage(memBytes31); + testBytes = hex"f00d0000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000feedf00d000000000000000000000000000000000000000000000000000000feed"; + assertEq(storageBytes63, testBytes, "storageBytes63 + memBytes31 concatenation failed."); + AssertBytes.equalStorage(storageBytes63, testBytes, "storageBytes63 + memBytes31 concatenation failed."); + + storageBytes64.concatStorage(memBytes31); + testBytes = hex"f00d000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000feedf00d000000000000000000000000000000000000000000000000000000feed"; + assertEq(storageBytes64, testBytes, "storageBytes64 + memBytes31 concatenation failed."); + AssertBytes.equalStorage(storageBytes64, testBytes, "storageBytes64 + memBytes31 concatenation failed."); + + storageBytes65.concatStorage(memBytes31); + testBytes = hex"f00d00000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000feedf00d000000000000000000000000000000000000000000000000000000feed"; + assertEq(storageBytes65, testBytes, "storageBytes65 + memBytes31 concatenation failed."); + AssertBytes.equalStorage(storageBytes65, testBytes, "storageBytes65 + memBytes31 concatenation failed."); + + storageBytes70.concatStorage(memBytes31); + testBytes = hex"f00d000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000feedf00d000000000000000000000000000000000000000000000000000000feed"; + assertEq(storageBytes70, testBytes, "storageBytes70 + memBytes31 concatenation failed."); + AssertBytes.equalStorage(storageBytes70, testBytes, "storageBytes70 + memBytes31 concatenation failed."); + + resetStorage(); + } + + function testConcatStorage32Bytes() public { + // Initialize `bytes` variables in memory + bytes memory memBytes32 = hex"f00d00000000000000000000000000000000000000000000000000000000feed"; + + // this next assembly block is to guarantee that the block of memory next to the end of the bytes + // array is not zero'ed out + assembly { + let mc := mload(0x40) + mstore(mc, 0xffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff) + mstore(0x40, add(mc, 0x20)) + } + + bytes memory testBytes; + + storageBytes4.concatStorage(memBytes32); + testBytes = hex"f00dfeedf00d00000000000000000000000000000000000000000000000000000000feed"; + assertEq(storageBytes4, testBytes, "storageBytes4 + memBytes32 concatenation failed."); + AssertBytes.equalStorage(storageBytes4, testBytes, "storageBytes4 + memBytes32 concatenation failed."); + + storageBytes31.concatStorage(memBytes32); + testBytes = hex"f00d000000000000000000000000000000000000000000000000000000feedf00d00000000000000000000000000000000000000000000000000000000feed"; + assertEq(storageBytes31, testBytes, "storageBytes31 + memBytes32 concatenation failed."); + AssertBytes.equalStorage(storageBytes31, testBytes, "storageBytes31 + memBytes32 concatenation failed."); + + storageBytes32.concatStorage(memBytes32); + testBytes = hex"f00d00000000000000000000000000000000000000000000000000000000feedf00d00000000000000000000000000000000000000000000000000000000feed"; + assertEq(storageBytes32, testBytes, "storageBytes32 + memBytes32 concatenation failed."); + AssertBytes.equalStorage(storageBytes32, testBytes, "storageBytes32 + memBytes32 concatenation failed."); + + storageBytes33.concatStorage(memBytes32); + testBytes = hex"f00d0000000000000000000000000000000000000000000000000000000000feedf00d00000000000000000000000000000000000000000000000000000000feed"; + assertEq(storageBytes33, testBytes, "storageBytes33 + memBytes32 concatenation failed."); + AssertBytes.equalStorage(storageBytes33, testBytes, "storageBytes33 + memBytes32 concatenation failed."); + + storageBytes63.concatStorage(memBytes32); + testBytes = hex"f00d0000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000feedf00d00000000000000000000000000000000000000000000000000000000feed"; + assertEq(storageBytes63, testBytes, "storageBytes63 + memBytes32 concatenation failed."); + AssertBytes.equalStorage(storageBytes63, testBytes, "storageBytes63 + memBytes32 concatenation failed."); + + storageBytes64.concatStorage(memBytes32); + testBytes = hex"f00d000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000feedf00d00000000000000000000000000000000000000000000000000000000feed"; + assertEq(storageBytes64, testBytes, "storageBytes64 + memBytes32 concatenation failed."); + AssertBytes.equalStorage(storageBytes64, testBytes, "storageBytes64 + memBytes32 concatenation failed."); + + storageBytes65.concatStorage(memBytes32); + testBytes = hex"f00d00000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000feedf00d00000000000000000000000000000000000000000000000000000000feed"; + assertEq(storageBytes65, testBytes, "storageBytes65 + memBytes32 concatenation failed."); + AssertBytes.equalStorage(storageBytes65, testBytes, "storageBytes65 + memBytes32 concatenation failed."); + + storageBytes70.concatStorage(memBytes32); + testBytes = hex"f00d000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000feedf00d00000000000000000000000000000000000000000000000000000000feed"; + assertEq(storageBytes70, testBytes, "storageBytes70 + memBytes32 concatenation failed."); + AssertBytes.equalStorage(storageBytes70, testBytes, "storageBytes70 + memBytes32 concatenation failed."); + + resetStorage(); + } + + function testConcatStorage33Bytes() public { + // Initialize `bytes` variables in memory + bytes memory memBytes33 = hex"f00d0000000000000000000000000000000000000000000000000000000000feed"; + + // this next assembly block is to guarantee that the block of memory next to the end of the bytes + // array is not zero'ed out + assembly { + let mc := mload(0x40) + mstore(mc, 0xffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff) + mstore(0x40, add(mc, 0x20)) + } + + bytes memory testBytes; + + storageBytes4.concatStorage(memBytes33); + testBytes = hex"f00dfeedf00d0000000000000000000000000000000000000000000000000000000000feed"; + assertEq(storageBytes4, testBytes, "storageBytes4 + memBytes33 concatenation failed."); + AssertBytes.equalStorage(storageBytes4, testBytes, "storageBytes4 + memBytes33 concatenation failed."); + + storageBytes31.concatStorage(memBytes33); + testBytes = hex"f00d000000000000000000000000000000000000000000000000000000feedf00d0000000000000000000000000000000000000000000000000000000000feed"; + assertEq(storageBytes31, testBytes, "storageBytes31 + memBytes33 concatenation failed."); + AssertBytes.equalStorage(storageBytes31, testBytes, "storageBytes31 + memBytes33 concatenation failed."); + + storageBytes32.concatStorage(memBytes33); + testBytes = hex"f00d00000000000000000000000000000000000000000000000000000000feedf00d0000000000000000000000000000000000000000000000000000000000feed"; + assertEq(storageBytes32, testBytes, "storageBytes32 + memBytes33 concatenation failed."); + AssertBytes.equalStorage(storageBytes32, testBytes, "storageBytes32 + memBytes33 concatenation failed."); + + storageBytes33.concatStorage(memBytes33); + testBytes = hex"f00d0000000000000000000000000000000000000000000000000000000000feedf00d0000000000000000000000000000000000000000000000000000000000feed"; + assertEq(storageBytes33, testBytes, "storageBytes33 + memBytes33 concatenation failed."); + AssertBytes.equalStorage(storageBytes33, testBytes, "storageBytes33 + memBytes33 concatenation failed."); + + storageBytes63.concatStorage(memBytes33); + testBytes = hex"f00d0000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000feedf00d0000000000000000000000000000000000000000000000000000000000feed"; + assertEq(storageBytes63, testBytes, "storageBytes63 + memBytes33 concatenation failed."); + AssertBytes.equalStorage(storageBytes63, testBytes, "storageBytes63 + memBytes33 concatenation failed."); + + storageBytes64.concatStorage(memBytes33); + testBytes = hex"f00d000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000feedf00d0000000000000000000000000000000000000000000000000000000000feed"; + assertEq(storageBytes64, testBytes, "storageBytes64 + memBytes33 concatenation failed."); + AssertBytes.equalStorage(storageBytes64, testBytes, "storageBytes64 + memBytes33 concatenation failed."); + + storageBytes65.concatStorage(memBytes33); + testBytes = hex"f00d00000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000feedf00d0000000000000000000000000000000000000000000000000000000000feed"; + assertEq(storageBytes65, testBytes, "storageBytes65 + memBytes33 concatenation failed."); + AssertBytes.equalStorage(storageBytes65, testBytes, "storageBytes65 + memBytes33 concatenation failed."); + + storageBytes70.concatStorage(memBytes33); + testBytes = hex"f00d000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000feedf00d0000000000000000000000000000000000000000000000000000000000feed"; + assertEq(storageBytes70, testBytes, "storageBytes70 + memBytes33 concatenation failed."); + AssertBytes.equalStorage(storageBytes70, testBytes, "storageBytes70 + memBytes33 concatenation failed."); + + resetStorage(); + } + + /** + * Equality Non-aligned Tests + */ + + function testEqualNonAligned4Bytes() public { + bytes memory memBytes1; // hex"f00dfeed" + bytes memory memBytes2; // hex"f00dfeed" + + // We need to make sure that the bytes are not aligned to a 32 byte boundary + // so we need to use assembly to allocate the bytes in contiguous memory + // Solidity will not let us do this normally, this equality method exists + // to test the edge case of non-aligned bytes created in assembly + assembly { + // Fetch free memory pointer + let freePointer := mload(0x40) + + // We first store the length of the byte array (4 bytes) + // And then we write a byte at a time + memBytes1 := freePointer + mstore(freePointer, 0x04) + freePointer := add(freePointer, 0x20) + mstore8(freePointer, 0xf0) + freePointer := add(freePointer, 0x1) + mstore8(freePointer, 0x0d) + freePointer := add(freePointer, 0x1) + mstore8(freePointer, 0xfe) + freePointer := add(freePointer, 0x1) + mstore8(freePointer, 0xed) + freePointer := add(freePointer, 0x1) + + // We do the same for memBytes2 in contiguous memory + memBytes2 := freePointer + mstore(freePointer, 0x04) + freePointer := add(freePointer, 0x20) + mstore8(freePointer, 0xf0) + freePointer := add(freePointer, 0x1) + mstore8(freePointer, 0x0d) + freePointer := add(freePointer, 0x1) + mstore8(freePointer, 0xfe) + freePointer := add(freePointer, 0x1) + mstore8(freePointer, 0xed) + freePointer := add(freePointer, 0x1) + + // We add some garbage bytes in contiguous memory + mstore8(freePointer, 0xde) + freePointer := add(freePointer, 0x1) + mstore8(freePointer, 0xad) + freePointer := add(freePointer, 0x1) + + // now, just for completeness sake we'll update the free-memory pointer accordingly + mstore(0x40, freePointer) + } + + AssertBytes.equal_nonAligned(memBytes1, memBytes2, "The equality check for the non-aligned equality 4-bytes-long test failed."); + // The equality check for aligned byte arrays should fail for non-aligned bytes + AssertBytes.notEqual(memBytes1, memBytes2, "The equality check for the non-aligned equality 4-bytes-long test failed."); + } + + function testEqualNonAligned4BytesFail() public { + bytes memory memBytes1; // hex"f00dfeed" + bytes memory memBytes2; // hex"feedf00d" + + // We need to make sure that the bytes are not aligned to a 32 byte boundary + // so we need to use assembly to allocate the bytes in contiguous memory + // Solidity will not let us do this normally, this equality method exists + // to test the edge case of non-aligned bytes created in assembly + assembly { + // Fetch free memory pointer + let freePointer := mload(0x40) + + // We first store the length of the byte array (4 bytes) + // And then we write a byte at a time + memBytes1 := freePointer + mstore(freePointer, 0x04) + freePointer := add(freePointer, 0x20) + mstore8(freePointer, 0xf0) + freePointer := add(freePointer, 0x1) + mstore8(freePointer, 0x0d) + freePointer := add(freePointer, 0x1) + mstore8(freePointer, 0xfe) + freePointer := add(freePointer, 0x1) + mstore8(freePointer, 0xed) + freePointer := add(freePointer, 0x1) + + // We do the same for memBytes2 in contiguous memory + memBytes2 := freePointer + mstore(freePointer, 0x04) + freePointer := add(freePointer, 0x20) + mstore8(freePointer, 0xfe) + freePointer := add(freePointer, 0x1) + mstore8(freePointer, 0xed) + freePointer := add(freePointer, 0x1) + mstore8(freePointer, 0xf0) + freePointer := add(freePointer, 0x1) + mstore8(freePointer, 0x0d) + freePointer := add(freePointer, 0x1) + + // We add some garbage bytes in contiguous memory + mstore8(freePointer, 0xde) + freePointer := add(freePointer, 0x1) + mstore8(freePointer, 0xad) + freePointer := add(freePointer, 0x1) + + // now, just for completeness sake we'll update the free-memory pointer accordingly + mstore(0x40, freePointer) + } + + AssertBytes.notEqual_nonAligned(memBytes1, memBytes2, "The non equality check for the non-aligned equality 4-bytes-long test failed."); + } + + function testEqualNonAligned33Bytes() public { + bytes memory memBytes1; // hex"f00d00000000000000000000000000000000000000000000000000000000feedcc"; + bytes memory memBytes2; // hex"f00d00000000000000000000000000000000000000000000000000000000feedcc"; + + // We need to make sure that the bytes are not aligned to a 32 byte boundary + // so we need to use assembly to allocate the bytes in contiguous memory + // Solidity will not let us do this normally, this equality method exists + // to test the edge case of non-aligned bytes created in assembly + assembly { + // Fetch free memory pointer + let freePointer := mload(0x40) + + // We first store the length of the byte array (33 bytes) + // And then we write a word and then a byte + memBytes1 := freePointer + mstore(freePointer, 0x21) + freePointer := add(freePointer, 0x20) + mstore(freePointer, 0xf00d00000000000000000000000000000000000000000000000000000000feed) + freePointer := add(freePointer, 0x20) + mstore8(freePointer, 0xcc) + freePointer := add(freePointer, 0x1) + + // We do the same for memBytes2 in contiguous memory + memBytes2 := freePointer + mstore(freePointer, 0x21) + freePointer := add(freePointer, 0x20) + mstore(freePointer, 0xf00d00000000000000000000000000000000000000000000000000000000feed) + freePointer := add(freePointer, 0x20) + mstore8(freePointer, 0xcc) + freePointer := add(freePointer, 0x1) + + // We add some garbage bytes in contiguous memory + mstore8(freePointer, 0xde) + freePointer := add(freePointer, 0x1) + mstore8(freePointer, 0xad) + freePointer := add(freePointer, 0x1) + + // now, just for completeness sake we'll update the free-memory pointer accordingly + mstore(0x40, freePointer) + } + + AssertBytes.equal_nonAligned(memBytes1, memBytes2, "The equality check for the non-aligned equality 33-bytes-long test failed."); + // The equality check for aligned byte arrays should fail for non-aligned bytes + AssertBytes.notEqual(memBytes1, memBytes2, "The equality check for the non-aligned equality 4-bytes-long test failed."); + } + + function testEqualNonAligned33BytesFail() public { + bytes memory memBytes1; // hex"f00d00000000000000000000000000000000000000000000000000000000feedcc"; + bytes memory memBytes2; // hex"f00d00000000000000000000000000000000000000000000000000000000feedee"; + + // We need to make sure that the bytes are not aligned to a 32 byte boundary + // so we need to use assembly to allocate the bytes in contiguous memory + // Solidity will not let us do this normally, this equality method exists + // to test the edge case of non-aligned bytes created in assembly + assembly { + // Fetch free memory pointer + let freePointer := mload(0x40) + + // We first store the length of the byte array (33 bytes) + // And then we write a word and then a byte + memBytes1 := freePointer + mstore(freePointer, 0x21) + freePointer := add(freePointer, 0x20) + mstore(freePointer, 0xf00d00000000000000000000000000000000000000000000000000000000feed) + freePointer := add(freePointer, 0x20) + mstore8(freePointer, 0xcc) + freePointer := add(freePointer, 0x1) + + // We do the same for memBytes2 in contiguous memory + memBytes2 := freePointer + mstore(freePointer, 0x21) + freePointer := add(freePointer, 0x20) + mstore(freePointer, 0xf00d00000000000000000000000000000000000000000000000000000000feed) + freePointer := add(freePointer, 0x20) + mstore8(freePointer, 0xee) + freePointer := add(freePointer, 0x1) + + // We add some garbage bytes in contiguous memory + mstore8(freePointer, 0xde) + freePointer := add(freePointer, 0x1) + mstore8(freePointer, 0xad) + freePointer := add(freePointer, 0x1) + + // now, just for completeness sake we'll update the free-memory pointer accordingly + mstore(0x40, freePointer) + } + + AssertBytes.notEqual_nonAligned(memBytes1, memBytes2, "The non equality check for the non-aligned equality 33-bytes-long test failed."); + } + + /** + * Edge Cases + */ + + function testConcatMemoryZeroLength() public { + // Initialize `bytes` variables in memory with different critical sizes + bytes memory preZeroLength = hex""; + bytes memory postZeroLength = hex""; + + bytes memory testBytes; + bytes memory resultBytes; + + resultBytes = preZeroLength.concat(postZeroLength); + testBytes = hex""; + assertEq(resultBytes, testBytes, "Zero Length concatenation failed."); + AssertBytes.equal(resultBytes, testBytes, "Zero Length concatenation failed."); + } + + /** + * Helper Functions + */ + + function resetStorage() internal { + storageBytes4 = hex"f00dfeed"; + storageBytes31 = hex"f00d000000000000000000000000000000000000000000000000000000feed"; + storageBytes32 = hex"f00d00000000000000000000000000000000000000000000000000000000feed"; + storageBytes33 = hex"f00d0000000000000000000000000000000000000000000000000000000000feed"; + storageBytes63 = hex"f00d0000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000feed"; + storageBytes64 = hex"f00d000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000feed"; + storageBytes65 = hex"f00d00000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000feed"; + storageBytes70 = hex"f00d000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000feed"; + } +} diff --git a/lib/solidity-bytes-utils/test/TestBytesLib2.sol b/lib/solidity-bytes-utils/test/TestBytesLib2.sol new file mode 100644 index 0000000..cb54ca3 --- /dev/null +++ b/lib/solidity-bytes-utils/test/TestBytesLib2.sol @@ -0,0 +1,449 @@ +// SPDX-License-Identifier: Unlicense +pragma solidity >=0.8.0 <0.9.0; + +import "forge-std/Test.sol"; +import "../contracts/AssertBytes.sol"; +import "../contracts/BytesLib.sol"; + + +contract TestBytesLib2 is Test { + using BytesLib for bytes; + + bytes storageCheckBytes = hex"aabbccddeeff"; + bytes storageCheckBytesZeroLength = hex""; + + uint256 constant MAX_UINT = type(uint256).max; + + /** + * Sanity Checks + */ + + function testSanityCheck() public { + // Assert library sanity checks + // + // Please don't change the ordering of the var definitions + // the order is purposeful for testing zero-length arrays + bytes memory checkBytes = hex"aabbccddeeff"; + bytes memory checkBytesZeroLength = hex""; + + bytes memory checkBytesRight = hex"aabbccddeeff"; + bytes memory checkBytesZeroLengthRight = hex""; + bytes memory checkBytesWrongLength = hex"aa0000"; + bytes memory checkBytesWrongContent = hex"aabbccddee00"; + + assertEq(checkBytes, checkBytesRight, "Sanity check should be checking equal bytes arrays out."); + assertTrue(keccak256(checkBytes) != keccak256(checkBytesWrongLength), "Sanity check should be checking different length bytes arrays out."); + assertTrue(keccak256(checkBytes) != keccak256(checkBytesWrongContent), "Sanity check should be checking different content bytes arrays out."); + AssertBytes.equal(checkBytes, checkBytesRight, "Sanity check should be checking equal bytes arrays out."); + AssertBytes.notEqual(checkBytes, checkBytesWrongLength, "Sanity check should be checking different length bytes arrays out."); + AssertBytes.notEqual(checkBytes, checkBytesWrongContent, "Sanity check should be checking different content bytes arrays out."); + + assertEq(storageCheckBytes, checkBytesRight, "Sanity check should be checking equal bytes arrays out. (Storage)"); + assertTrue(keccak256(storageCheckBytes) != keccak256(checkBytesWrongLength), "Sanity check should be checking different length bytes arrays out. (Storage)"); + assertTrue(keccak256(storageCheckBytes) != keccak256(checkBytesWrongContent), "Sanity check should be checking different content bytes arrays out. (Storage)"); +AssertBytes.equalStorage(storageCheckBytes, checkBytesRight, "Sanity check should be checking equal bytes arrays out. (Storage)"); + AssertBytes.notEqualStorage(storageCheckBytes, checkBytesWrongLength, "Sanity check should be checking different length bytes arrays out. (Storage)"); + AssertBytes.notEqualStorage(storageCheckBytes, checkBytesWrongContent, "Sanity check should be checking different content bytes arrays out. (Storage)"); + + // Zero-length checks + assertEq(checkBytesZeroLength, checkBytesZeroLengthRight, "Sanity check should be checking equal zero-length bytes arrays out."); + assertTrue(keccak256(checkBytesZeroLength) != keccak256(checkBytes), "Sanity check should be checking different length bytes arrays out."); +AssertBytes.equal(checkBytesZeroLength, checkBytesZeroLengthRight, "Sanity check should be checking equal zero-length bytes arrays out."); + AssertBytes.notEqual(checkBytesZeroLength, checkBytes, "Sanity check should be checking different length bytes arrays out."); + + AssertBytes.equalStorage(storageCheckBytesZeroLength, checkBytesZeroLengthRight, "Sanity check should be checking equal zero-length bytes arrays out. (Storage)"); + AssertBytes.notEqualStorage(storageCheckBytesZeroLength, checkBytes, "Sanity check should be checking different length bytes arrays out. (Storage)"); + } + + /** + * Slice Tests + */ + + function testSlice() public { + bytes memory memBytes = hex"f00d0000000000000000000000000000000000000000000000000000000000feedf00d00000000000000000000000000000000000000000000000000000000feed"; + + bytes memory testBytes; + bytes memory resultBytes; + + testBytes = hex"f00d"; + resultBytes = memBytes.slice(0,2); + assertEq(resultBytes, testBytes); + AssertBytes.equal(resultBytes, testBytes, "Normal slicing array failed."); + + testBytes = hex""; + resultBytes = memBytes.slice(1,0); + assertEq(resultBytes, testBytes); + AssertBytes.equal(resultBytes, testBytes, "Slicing with zero-length failed."); + + testBytes = hex""; + resultBytes = memBytes.slice(0,0); + assertEq(resultBytes, testBytes); + AssertBytes.equal(resultBytes, testBytes, "Slicing with zero-length on index 0 failed."); + + testBytes = hex"feed"; + resultBytes = memBytes.slice(31,2); + assertEq(resultBytes, testBytes); + AssertBytes.equal(resultBytes, testBytes, "Slicing across the 32-byte slot boundary failed."); + + testBytes = hex"f00d0000000000000000000000000000000000000000000000000000000000feed"; + resultBytes = memBytes.slice(0,33); + assertEq(resultBytes, testBytes); + AssertBytes.equal(resultBytes, testBytes, "Full length slice failed."); + + testBytes = hex"f00d0000000000000000000000000000000000000000000000000000000000fe"; + resultBytes = memBytes.slice(0,32); + assertEq(resultBytes, testBytes); + AssertBytes.equal(resultBytes, testBytes, "Multiple of 32 bytes slice failed."); + + testBytes = hex"f00d0000000000000000000000000000000000000000000000000000000000feedf00d00000000000000000000000000000000000000000000000000000000fe"; + resultBytes = memBytes.slice(0,64); + assertEq(resultBytes, testBytes); + AssertBytes.equal(resultBytes, testBytes, "Multiple (*2) of 32 bytes slice failed."); + + // With v0.5.x we can now entirely replace the ThrowProxy patterns that was creating issues with the js-vm + // and use an external call to our own contract with the function selector, since Solidity now gives us + // access to those + bool r; + + // We're basically calling our contract externally with a raw call, forwarding all available gas, with + // msg.data equal to the throwing function selector that we want to be sure throws and using only the boolean + // value associated with the message call's success + (r, ) = address(this).call(abi.encodePacked(this.sliceIndexThrow.selector)); + assertFalse(r); + + (r, ) = address(this).call(abi.encodePacked(this.sliceOverflowLength0Throw.selector)); + assertFalse(r); + + (r, ) = address(this).call(abi.encodePacked(this.sliceOverflowLength1Throw.selector)); + assertFalse(r); + + (r, ) = address(this).call(abi.encodePacked(this.sliceOverflowLength33Throw.selector)); + assertFalse(r); + + (r, ) = address(this).call(abi.encodePacked(this.sliceOverflowLengthMinus32Throw.selector)); + assertFalse(r); + + (r, ) = address(this).call(abi.encodePacked(this.sliceOverflowStart0Throw.selector)); + assertFalse(r); + + (r, ) = address(this).call(abi.encodePacked(this.sliceOverflowStart1Throw.selector)); + assertFalse(r); + + (r, ) = address(this).call(abi.encodePacked(this.sliceOverflowStart33Throw.selector)); + assertFalse(r); + + (r, ) = address(this).call(abi.encodePacked(this.sliceLengthThrow.selector)); + assertFalse(r); + } + + function sliceIndexThrow() public pure { + bytes memory memBytes33 = hex"f00d0000000000000000000000000000000000000000000000000000000000feed"; + + bytes memory testBytes; + bytes memory resultBytes; + + testBytes = hex"f00d"; + resultBytes = memBytes33.slice(34,2); + // This should throw; + } + + function sliceLengthThrow() public pure { + bytes memory memBytes33 = hex"f00d0000000000000000000000000000000000000000000000000000000000feed"; + + bytes memory testBytes; + bytes memory resultBytes; + + testBytes = hex"f00d"; + resultBytes = memBytes33.slice(0,34); + // This should throw; + } + + function sliceOverflowLength0Throw() public pure { + bytes memory memBytes33 = hex"f00d0000000000000000000000000000000000000000000000000000000000feed"; + + bytes memory testBytes; + bytes memory resultBytes; + + testBytes = hex"f00d"; + resultBytes = memBytes33.slice(0, MAX_UINT); + // This should throw; + } + + function sliceOverflowLength1Throw() public pure { + bytes memory memBytes33 = hex"f00d0000000000000000000000000000000000000000000000000000000000feed"; + + bytes memory testBytes; + bytes memory resultBytes; + + testBytes = hex"f00d"; + resultBytes = memBytes33.slice(1, MAX_UINT); + // This should throw; + } + + function sliceOverflowLength33Throw() public pure { + bytes memory memBytes33 = hex"f00d0000000000000000000000000000000000000000000000000000000000feed"; + + bytes memory testBytes; + bytes memory resultBytes; + + testBytes = hex"f00d"; + resultBytes = memBytes33.slice(33, MAX_UINT); + // This should throw; + } + + function sliceOverflowLengthMinus32Throw() public pure { + bytes memory memBytes33 = hex"f00d0000000000000000000000000000000000000000000000000000000000feed"; + + bytes memory testBytes; + bytes memory resultBytes; + + testBytes = hex"f00d"; + resultBytes = memBytes33.slice(1, MAX_UINT - 32); + // This should throw; + } + + function sliceOverflowStart0Throw() public pure { + bytes memory memBytes33 = hex"f00d0000000000000000000000000000000000000000000000000000000000feed"; + + bytes memory testBytes; + bytes memory resultBytes; + + testBytes = hex"f00d"; + resultBytes = memBytes33.slice(MAX_UINT, 0); + // This should throw; + } + + function sliceOverflowStart1Throw() public pure { + bytes memory memBytes33 = hex"f00d0000000000000000000000000000000000000000000000000000000000feed"; + + bytes memory testBytes; + bytes memory resultBytes; + + testBytes = hex"f00d"; + resultBytes = memBytes33.slice(MAX_UINT, 1); + // This should throw; + } + + function sliceOverflowStart33Throw() public pure { + bytes memory memBytes33 = hex"f00d0000000000000000000000000000000000000000000000000000000000feed"; + + bytes memory testBytes; + bytes memory resultBytes; + + testBytes = hex"f00d"; + resultBytes = memBytes33.slice(MAX_UINT, 1); + // This should throw; + } + + /** + * Memory Integrity Checks + */ + + function testMemoryIntegrityCheckZeroLengthSlice() public { + // Let's taint memory first + bytes memory taintBytes4 = hex"f00dfeed"; + + // Now declare arrays to be sliced + bytes memory testBytes4 = hex"f00dfeed"; + bytes memory emptyBytes = hex""; + + bytes memory resultBytes; + + // Try a zero-length slice from a non-zero-length array + resultBytes = testBytes4.slice(0,0); + + assertEq(hex"", resultBytes); + AssertBytes.equal(hex"", resultBytes, "The result of a zero-length slice is not a zero-length array."); + + // Try a zero-length slice from a zero-length array + resultBytes = emptyBytes.slice(0,0); + + assertEq(hex"", resultBytes); + AssertBytes.equal(hex"", resultBytes, "The result of a zero-length slice is not a zero-length array."); + } + + /** + * Type casting Checks + */ + + function testToUint8() public { + bytes memory memBytes = hex"f00d20feed"; + + uint8 testUint8 = 32; // 0x20 == 32 + uint8 resultUint8; + + resultUint8 = memBytes.toUint8(2); + assertEq(uint256(resultUint8), uint256(testUint8)); + + // Testing for the throw conditions below + (bool r, ) = address(this).call(abi.encodePacked(this.toUint8Throw.selector)); + assertFalse(r); + } + + function toUint8Throw() public pure { + bytes memory memBytes = hex"f00d42feed"; + + uint8 resultUint8; + + resultUint8 = memBytes.toUint8(35); + // This should throw; + } + + function testToUint16() public { + bytes memory memBytes = hex"f00d0020feed"; + + uint16 testUint16 = 32; // 0x20 == 32 + uint16 resultUint16; + + resultUint16 = memBytes.toUint16(2); + assertEq(uint256(resultUint16), uint256(testUint16)); + + // Testing for the throw conditions below + (bool r, ) = address(this).call(abi.encodePacked(this.toUint16Throw.selector)); + assertFalse(r); + } + + function toUint16Throw() public pure { + bytes memory memBytes = hex"f00d0042feed"; + + uint16 resultUint16; + + resultUint16 = memBytes.toUint16(35); + // This should throw; + } + + function testToUint32() public { + bytes memory memBytes = hex"f00d00000020feed"; + + uint32 testUint32 = 32; // 0x20 == 32 + uint32 resultUint32; + + resultUint32 = memBytes.toUint32(2); + assertEq(uint256(resultUint32), uint256(testUint32)); + + // Testing for the throw conditions below + (bool r, ) = address(this).call(abi.encodePacked(this.toUint32Throw.selector)); + assertFalse(r); + } + + function toUint32Throw() public pure { + bytes memory memBytes = hex"f00d00000042feed"; + + uint32 resultUint32; + + resultUint32 = memBytes.toUint32(35); + // This should throw; + } + + function testToUint64() public { + bytes memory memBytes = hex"f00d0000000000000020feed"; + + uint64 testUint64 = 32; // 0x20 == 32 + uint64 resultUint64; + + resultUint64 = memBytes.toUint64(2); + assertEq(uint256(resultUint64), uint256(testUint64)); + + // Testing for the throw conditions below + (bool r, ) = address(this).call(abi.encodePacked(this.toUint64Throw.selector)); + assertFalse(r); + } + + function toUint64Throw() public pure { + bytes memory memBytes = hex"f00d42feed"; + + uint64 resultUint64; + + resultUint64 = memBytes.toUint64(35); // This should throw; + } + + function testToUint96() public { + bytes memory memBytes = hex"f00d000000000000000000000020feed"; + + uint96 testUint96 = 32; // 0x20 == 32 + uint96 resultUint96; + + resultUint96 = memBytes.toUint96(2); + assertEq(uint256(resultUint96), uint256(testUint96)); + + // Testing for the throw conditions below + (bool r, ) = address(this).call(abi.encodePacked(this.toUint64Throw.selector)); + assertFalse(r); + } + + function toUint96Throw() public pure { + bytes memory memBytes = hex"f00d42feed"; + + uint96 resultUint96; + + resultUint96 = memBytes.toUint96(35); // This should throw; + } + + function testToUint128() public { + bytes memory memBytes = hex"f00d00000000000000000000000000000020feed"; + + uint128 testUint128 = 32; // 0x20 == 32 + uint128 resultUint128; + + resultUint128 = memBytes.toUint128(2); + assertEq(uint256(resultUint128), uint256(testUint128)); + + // Testing for the throw conditions below + (bool r, ) = address(this).call(abi.encodePacked(this.toUint128Throw.selector)); + assertFalse(r); + } + + function toUint128Throw() public pure { + bytes memory memBytes = hex"f00d42feed"; + + uint128 resultUint128; + + resultUint128 = memBytes.toUint128(35); // This should throw; + } + + function testToUint() public { + bytes memory memBytes = hex"f00d0000000000000000000000000000000000000000000000000000000000000020feed"; + + uint256 testUint = 32; // 0x20 == 32 + uint256 resultUint; + + resultUint = memBytes.toUint256(2); + assertEq(resultUint, testUint); + + // Testing for the throw conditions below + (bool r, ) = address(this).call(abi.encodePacked(this.toUintThrow.selector)); + assertFalse(r); + } + + function toUintThrow() public pure { + bytes memory memBytes = hex"f00d0000000000000000000000000000000000000000000000000000000000000042feed"; + + uint256 resultUint; + + resultUint = memBytes.toUint256(35); + // This should throw; + } + + function testToAddress() public { + bytes memory memBytes = hex"f00dfeed383Fa3B60f9B4AB7fBf6835d3c26C3765cD2B2e2f00dfeed"; + + address testAddress = 0x383Fa3B60f9B4AB7fBf6835d3c26C3765cD2B2e2; + address resultAddress; + + resultAddress = memBytes.toAddress(4); + assertEq(resultAddress, testAddress); + + // Testing for the throw conditions below + (bool r, ) = address(this).call(abi.encodePacked(this.toAddressThrow.selector)); + assertFalse(r); + } + + function toAddressThrow() public pure { + bytes memory memBytes = hex"f00dfeed383FA3b60F9b4ab7FBF6835D3c26C3765Cd2B2E2f00dfeed"; + + address resultAddress; + + resultAddress = memBytes.toAddress(35); + // This should throw; + } +} diff --git a/src/UniswapV3Factory.sol b/src/UniswapV3Factory.sol new file mode 100644 index 0000000..284a08f --- /dev/null +++ b/src/UniswapV3Factory.sol @@ -0,0 +1,60 @@ +// SPDX-License-Identifier: MIT +pragma solidity ^0.8.14; +import "./interfaces/IUniswapV3PoolDeployer.sol"; +import "./UniswapV3Pool.sol"; + +contract UniswapV3Factory is IUniswapV3PoolDeployer{ + error TokensMustBeDifferent(); + error UnsupportedTickSpacing(); + error ZeroAddressNotAllowed(); + error PoolAlreadyExists(); + mapping(uint24=>bool) public tickSpacings; + PoolParameters public parameters; + mapping(address=> mapping(address=>mapping(uint24=>address))) public pools; + + event PoolCreated( + address indexed token0, + address indexed token1, + uint24 indexed tickSpacing, + address pool + ); + + constructor() { + tickSpacings[10] = true; + tickSpacings[60] = true; + } + + function createPool( + address tokenX, + address tokenY, + uint24 tickSpacing + )public returns(address pool){ + if(tokenX == tokenY) revert TokensMustBeDifferent(); + if(!tickSpacings[tickSpacing]) revert UnsupportedTickSpacing(); + + (tokenX, tokenY) = tokenX < tokenY + ? (tokenX, tokenY) + :(tokenY, tokenX); + if(tokenX == address(0)) revert ZeroAddressNotAllowed(); + if(pools[tokenX][tokenY][tickSpacing] != address(0)) revert PoolAlreadyExists(); + + parameters = PoolParameters({ + factory: address(this), + token0: tokenX, + token1: tokenY, + tickSpacing: tickSpacing + }); + pool = address( + new UniswapV3Pool{ + salt: keccak256(abi.encodePacked(tokenX, tokenY, tickSpacing)) + }() + ); + + delete parameters; + + pools[tokenX][tokenY][tickSpacing] = pool; + pools[tokenY][tokenX][tickSpacing] = pool; + + emit PoolCreated(tokenX, tokenY, tickSpacing, pool); + } +} \ No newline at end of file diff --git a/src/UniswapV3Pool.sol b/src/UniswapV3Pool.sol index 55fa2ad..f515b46 100644 --- a/src/UniswapV3Pool.sol +++ b/src/UniswapV3Pool.sol @@ -6,6 +6,7 @@ import "./interfaces/IUniswapV3FlashCallback.sol"; import "./interfaces/IUniswapV3MintCallback.sol"; import "./interfaces/IUniswapV3Pool.sol"; import "./interfaces/IUniswapV3SwapCallback.sol"; +import "./interfaces/IUniswapV3PoolDeployer.sol"; import "./lib/LiquidityMath.sol"; import "./lib/Math.sol"; @@ -26,6 +27,7 @@ contract UniswapV3Pool is IUniswapV3Pool { error InvalidTickRange(); error NotEnoughLiquidity(); error ZeroLiquidity(); + error AlreadyInitialized(); event Flash(address indexed recipient, uint256 amount0, uint256 amount1); @@ -49,9 +51,11 @@ contract UniswapV3Pool is IUniswapV3Pool { int24 tick ); - // Pool tokens, immutable + // Pool parameters + address public immutable factory; address public immutable token0; address public immutable token1; + uint24 public immutable tickSpacing; // First slot will contain essential data struct Slot0 { @@ -77,7 +81,24 @@ contract UniswapV3Pool is IUniswapV3Pool { uint256 amountIn; uint256 amountOut; } + struct SwapSingleParams { + address tokenIn; + address tokenOut; + uint24 tickSpacing; + uint256 amountIn; + uint160 sqrtPriceLimitX96; + } + struct SwapParams { + bytes path; + address recipient; + uint256 amountIn; + uint256 minAmountOut; + } + struct SwapCallbackData { + bytes path; + address payer; + } Slot0 public slot0; // Amount of liquidity, L. @@ -87,14 +108,16 @@ contract UniswapV3Pool is IUniswapV3Pool { mapping(int16 => uint256) public tickBitmap; mapping(bytes32 => Position.Info) public positions; - constructor( - address token0_, - address token1_, - uint160 sqrtPriceX96, - int24 tick - ) { - token0 = token0_; - token1 = token1_; + constructor() { + (factory, token0, token1, tickSpacing) = IUniswapV3PoolDeployer( + msg.sender + ).parameters(); + } + //initialize tick and slot + function initialize(uint160 sqrtPriceX96) public { + if (slot0.sqrtPriceX96 != 0) revert AlreadyInitialized(); + + int24 tick = TickMath.getTickAtSqrtRatio(sqrtPriceX96); slot0 = Slot0({sqrtPriceX96: sqrtPriceX96, tick: tick}); } @@ -153,7 +176,7 @@ contract UniswapV3Pool is IUniswapV3Pool { amount ); - liquidity = LiquidityMath.addLiquidity(liquidity, int128(amount)); + liquidity = LiquidityMath.addLiquidity(liquidity, int128(amount)); } else { amount1 = Math.calcAmount1Delta( TickMath.getSqrtRatioAtTick(lowerTick), @@ -187,136 +210,219 @@ contract UniswapV3Pool is IUniswapV3Pool { ); } - function swap( + function _swap( + uint256 amountIn, address recipient, - bool zeroForOne, - uint256 amountSpecified, uint160 sqrtPriceLimitX96, - bytes calldata data - ) public returns (int256 amount0, int256 amount1) { - // Caching for gas saving - Slot0 memory slot0_ = slot0; - uint128 liquidity_ = liquidity; - - if ( - zeroForOne - ? sqrtPriceLimitX96 > slot0_.sqrtPriceX96 || - sqrtPriceLimitX96 < TickMath.MIN_SQRT_RATIO - : sqrtPriceLimitX96 < slot0_.sqrtPriceX96 || - sqrtPriceLimitX96 > TickMath.MAX_SQRT_RATIO - ) revert InvalidPriceLimit(); - - SwapState memory state = SwapState({ - amountSpecifiedRemaining: amountSpecified, - amountCalculated: 0, - sqrtPriceX96: slot0_.sqrtPriceX96, - tick: slot0_.tick, - liquidity: liquidity_ - }); - - while ( - state.amountSpecifiedRemaining > 0 && - state.sqrtPriceX96 != sqrtPriceLimitX96 - ) { - StepState memory step; - - step.sqrtPriceStartX96 = state.sqrtPriceX96; - - (step.nextTick, step.initialized) = tickBitmap.nextInitializedTickWithinOneWord( - state.tick, - 1, - zeroForOne - ); - - step.sqrtPriceNextX96 = TickMath.getSqrtRatioAtTick(step.nextTick); - - (state.sqrtPriceX96, step.amountIn, step.amountOut) = SwapMath - .computeSwapStep( - state.sqrtPriceX96, - ( + SwapCallbackData memory data + ) internal returns (uint256 amountOut) { + (address tokenIn, address tokenOut, uint24 tickSpacing) = data + .path + .decodeFirstPool(); + bool zeroForOne = tokenIn < tokenOut; + + (int256 amount0, int256 amount1) = getPool( + tokenIn, + tokenOUt, + tickSpacing + ).swap( + recipient, + zeroForOne, + amountIn, + sqrtPriceLimitX96 == 0 + ? ( zeroForOne - ? step.sqrtPriceNextX96 < sqrtPriceLimitX96 - : step.sqrtPriceNextX96 > sqrtPriceLimitX96 + ? TickMath.MIN_SQRT_RATIO + 1 + : TickMath.MAX_SQRT_RATIO - 1 ) - ? sqrtPriceLimitX96 - : step.sqrtPriceNextX96, - state.liquidity, - state.amountSpecifiedRemaining - ); - - state.amountSpecifiedRemaining -= step.amountIn; - state.amountCalculated += step.amountOut; - - if (state.sqrtPriceX96 == step.sqrtPriceNextX96) { - if (step.initialized) { - int128 liquidityDelta = ticks.cross(step.nextTick); - - if (zeroForOne) liquidityDelta = -liquidityDelta; - - state.liquidity = LiquidityMath.addLiquidity( - state.liquidity, - liquidityDelta - ); - - if (state.liquidity == 0) revert NotEnoughLiquidity(); - } - - state.tick = zeroForOne ? step.nextTick - 1 : step.nextTick; - } else if (state.sqrtPriceX96 != step.sqrtPriceStartX96) { - state.tick = TickMath.getTickAtSqrtRatio(state.sqrtPriceX96); - } - } - - if (state.tick != slot0_.tick) { - (slot0.sqrtPriceX96, slot0.tick) = (state.sqrtPriceX96, state.tick); - } - - if (liquidity_ != state.liquidity) liquidity = state.liquidity; - - (amount0, amount1) = zeroForOne - ? ( - int256(amountSpecified - state.amountSpecifiedRemaining), - -int256(state.amountCalculated) - ) - : ( - -int256(state.amountCalculated), - int256(amountSpecified - state.amountSpecifiedRemaining) + : sqrtPriceLimitX96, + abi.encode(data) ); + amountOut = uint256(-(zeroForOne ? amount1 : amount0)); + } + function swapSingle( + SwapSingleParams calldata params + ) public returns (uint256 amountOut) { + amountOut = _swap( + params.amountIn, + msg.sender, + params.sqrtPriceLimitX96, + SwapCallbackData({ + path: abi.encodePacked( + params.tokenIn, + params.tickSpacing, + params.tokenOut + ), + payer: msg.sender + }) + ); + } + function getPool( + address token0, + address token1, + uint24 tickSpacing + ) internal view returns (IUniswapV3Pool pool) { + (token0, token1) = token0 < token1 + ? (token0, token1) + : (token1, token0); + pool = IUniswapV3Pool( + PoolAddress.computeAddress(factory, token0, token1, tickSpacing) + ); + } - if (zeroForOne) { - IERC20(token1).transfer(recipient, uint256(-amount1)); - - uint256 balance0Before = balance0(); - IUniswapV3SwapCallback(msg.sender).uniswapV3SwapCallback( - amount0, - amount1, - data + function swap(SwapParams memory params) public returns (uint256 amountOut) { + address payer = msg.sender; + bool hasMutilplePools; + while (true) { + hasMutiplePools = params.path.hasMultiplePools(); + + params.amountIn = _swap( + params.amountIn, + hasMutilplePools ? address(this) : params.recipient, + 0, + SwapCallbackData({ + path: params.path.getFirstPool(), + payer: payer + }) ); - if (balance0Before + uint256(amount0) > balance0()) - revert InsufficientInputAmount(); + } + if (hasMultiplePools) { + payer = address(this); + params.path = params.path.skipToken(); } else { - IERC20(token0).transfer(recipient, uint256(-amount0)); - - uint256 balance1Before = balance1(); - IUniswapV3SwapCallback(msg.sender).uniswapV3SwapCallback( - amount0, - amount1, - data - ); - if (balance1Before + uint256(amount1) > balance1()) - revert InsufficientInputAmount(); + amountOut = params.amountIn; + break; } - - emit Swap( - msg.sender, - recipient, - amount0, - amount1, - slot0.sqrtPriceX96, - state.liquidity, - slot0.tick - ); + if (amountOut < params.minAmountOut) + revert TooLittleReceived(amountOut); } + // function swap( + // address recipient, + // bool zeroForOne, + // uint256 amountSpecified, + // uint160 sqrtPriceLimitX96, + // bytes calldata data + // ) public returns (int256 amount0, int256 amount1) { + // // Caching for gas saving + // Slot0 memory slot0_ = slot0; + // uint128 liquidity_ = liquidity; + + // if ( + // zeroForOne + // ? sqrtPriceLimitX96 > slot0_.sqrtPriceX96 || + // sqrtPriceLimitX96 < TickMath.MIN_SQRT_RATIO + // : sqrtPriceLimitX96 < slot0_.sqrtPriceX96 || + // sqrtPriceLimitX96 > TickMath.MAX_SQRT_RATIO + // ) revert InvalidPriceLimit(); + + // SwapState memory state = SwapState({ + // amountSpecifiedRemaining: amountSpecified, + // amountCalculated: 0, + // sqrtPriceX96: slot0_.sqrtPriceX96, + // tick: slot0_.tick, + // liquidity: liquidity_ + // }); + + // while ( + // state.amountSpecifiedRemaining > 0 && + // state.sqrtPriceX96 != sqrtPriceLimitX96 + // ) { + // StepState memory step; + + // step.sqrtPriceStartX96 = state.sqrtPriceX96; + + // (step.nextTick, step.initialized) = tickBitmap + // .nextInitializedTickWithinOneWord(state.tick, 1, zeroForOne); + + // step.sqrtPriceNextX96 = TickMath.getSqrtRatioAtTick(step.nextTick); + + // (state.sqrtPriceX96, step.amountIn, step.amountOut) = SwapMath + // .computeSwapStep( + // state.sqrtPriceX96, + // ( + // zeroForOne + // ? step.sqrtPriceNextX96 < sqrtPriceLimitX96 + // : step.sqrtPriceNextX96 > sqrtPriceLimitX96 + // ) + // ? sqrtPriceLimitX96 + // : step.sqrtPriceNextX96, + // state.liquidity, + // state.amountSpecifiedRemaining + // ); + + // state.amountSpecifiedRemaining -= step.amountIn; + // state.amountCalculated += step.amountOut; + + // if (state.sqrtPriceX96 == step.sqrtPriceNextX96) { + // if (step.initialized) { + // int128 liquidityDelta = ticks.cross(step.nextTick); + + // if (zeroForOne) liquidityDelta = -liquidityDelta; + + // state.liquidity = LiquidityMath.addLiquidity( + // state.liquidity, + // liquidityDelta + // ); + + // if (state.liquidity == 0) revert NotEnoughLiquidity(); + // } + + // state.tick = zeroForOne ? step.nextTick - 1 : step.nextTick; + // } else if (state.sqrtPriceX96 != step.sqrtPriceStartX96) { + // state.tick = TickMath.getTickAtSqrtRatio(state.sqrtPriceX96); + // } + // } + + // if (state.tick != slot0_.tick) { + // (slot0.sqrtPriceX96, slot0.tick) = (state.sqrtPriceX96, state.tick); + // } + + // if (liquidity_ != state.liquidity) liquidity = state.liquidity; + + // (amount0, amount1) = zeroForOne + // ? ( + // int256(amountSpecified - state.amountSpecifiedRemaining), + // -int256(state.amountCalculated) + // ) + // : ( + // -int256(state.amountCalculated), + // int256(amountSpecified - state.amountSpecifiedRemaining) + // ); + + // if (zeroForOne) { + // IERC20(token1).transfer(recipient, uint256(-amount1)); + + // uint256 balance0Before = balance0(); + // IUniswapV3SwapCallback(msg.sender).uniswapV3SwapCallback( + // amount0, + // amount1, + // data + // ); + // if (balance0Before + uint256(amount0) > balance0()) + // revert InsufficientInputAmount(); + // } else { + // IERC20(token0).transfer(recipient, uint256(-amount0)); + + // uint256 balance1Before = balance1(); + // IUniswapV3SwapCallback(msg.sender).uniswapV3SwapCallback( + // amount0, + // amount1, + // data + // ); + // if (balance1Before + uint256(amount1) > balance1()) + // revert InsufficientInputAmount(); + // } + + // emit Swap( + // msg.sender, + // recipient, + // amount0, + // amount1, + // slot0.sqrtPriceX96, + // state.liquidity, + // slot0.tick + // ); + // } function flash( uint256 amount0, @@ -344,4 +450,4 @@ contract UniswapV3Pool is IUniswapV3Pool { function balance1() internal returns (uint256 balance) { balance = IERC20(token1).balanceOf(address(this)); } -} \ No newline at end of file +} diff --git a/src/UniswapV3Quote.sol b/src/UniswapV3Quote.sol index ba69f6b..d89e72e 100644 --- a/src/UniswapV3Quote.sol +++ b/src/UniswapV3Quote.sol @@ -2,17 +2,72 @@ pragma solidity ^0.8.14; import "./interfaces/IUniswapV3Pool.sol"; +import "./lib/Path.sol"; +import "./lib/PoolAddress.sol"; import "./lib/TickMath.sol"; contract UniswapV3Quoter { - struct QuoteParams { - address pool; + using Path for bytes; + + struct QuoteSingleParams { + address tokenIn; + address tokenOut; + uint24 tickSpacing; uint256 amountIn; uint160 sqrtPriceLimitX96; - bool zeroForOne; } - function quote(QuoteParams memory params) + address public immutable factory; + + constructor(address factory_) { + factory = factory_; + } + + function quote(bytes memory path, uint256 amountIn) + public + returns ( + uint256 amountOut, + uint160[] memory sqrtPriceX96AfterList, + int24[] memory tickAfterList + ) + { + sqrtPriceX96AfterList = new uint160[](path.numPools()); + tickAfterList = new int24[](path.numPools()); + + uint256 i = 0; + while (true) { + (address tokenIn, address tokenOut, uint24 tickSpacing) = path + .decodeFirstPool(); + + ( + uint256 amountOut_, + uint160 sqrtPriceX96After, + int24 tickAfter + ) = quoteSingle( + QuoteSingleParams({ + tokenIn: tokenIn, + tokenOut: tokenOut, + tickSpacing: tickSpacing, + amountIn: amountIn, + sqrtPriceLimitX96: 0 + }) + ); + + sqrtPriceX96AfterList[i] = sqrtPriceX96After; + tickAfterList[i] = tickAfter; + amountIn = amountOut_; + i++; + + if (path.hasMultiplePools()) { + path = path.skipToken(); + } else { + amountOut = amountIn; + break; + } + } + } + + function quoteSingle(QuoteSingleParams memory params) public returns ( uint256 amountOut, @@ -20,19 +75,27 @@ contract UniswapV3Quoter { int24 tickAfter ) { + IUniswapV3Pool pool = getPool( + params.tokenIn, + params.tokenOut, + params.tickSpacing + ); + + bool zeroForOne = params.tokenIn < params.tokenOut; + try - IUniswapV3Pool(params.pool).swap( + pool.swap( address(this), - params.zeroForOne, + zeroForOne, params.amountIn, params.sqrtPriceLimitX96 == 0 ? ( - params.zeroForOne + zeroForOne ? TickMath.MIN_SQRT_RATIO + 1 : TickMath.MAX_SQRT_RATIO - 1 ) : params.sqrtPriceLimitX96, - abi.encode(params.pool) + abi.encode(address(pool)) ) {} catch (bytes memory reason) { return abi.decode(reason, (uint256, uint160, int24)); @@ -61,4 +124,17 @@ contract UniswapV3Quoter { revert(ptr, 96) } } + + function getPool( + address token0, + address token1, + uint24 tickSpacing + ) internal view returns (IUniswapV3Pool pool) { + (token0, token1) = token0 < token1 + ? (token0, token1) + : (token1, token0); + pool = IUniswapV3Pool( + PoolAddress.computeAddress(factory, token0, token1, tickSpacing) + ); + } } \ No newline at end of file diff --git a/src/interfaces/IUniswapV3PoolDeployer.sol b/src/interfaces/IUniswapV3PoolDeployer.sol new file mode 100644 index 0000000..86485ec --- /dev/null +++ b/src/interfaces/IUniswapV3PoolDeployer.sol @@ -0,0 +1,20 @@ +// SPDX-License-Identifier: MIT +pragma solidity ^0.8.14; + +interface IUniswapV3PoolDeployer { + struct PoolParameters { + address factory; + address token0; + address token1; + uint24 tickSpacing; + } + + function parameters() + external + returns ( + address factory, + address token0, + address token1, + uint24 tickSpacing + ); +} \ No newline at end of file diff --git a/src/lib/Path.sol b/src/lib/Path.sol new file mode 100644 index 0000000..b80a80a --- /dev/null +++ b/src/lib/Path.sol @@ -0,0 +1,54 @@ +// SPDX-License-Identifier: MIT +pragma solidity ^0.8.14; + +import "../UniswapV3Pool.sol"; +import "../../lib/solidity-bytes-utils/contracts/BytesLib.sol"; + +library BytesLibExt { + function toUint24(bytes memory _bytes, uint256 _start) + internal + pure + returns (uint24) + { + require(_bytes.length >= _start + 3, "toUint24_outOfBounds"); + uint24 tempUint; + + assembly { + tempUint := mload(add(add(_bytes, 0x3), _start)) + } + + return tempUint; + } +} + +library Path{ + using BytesLib for bytes; + using BytesLibExt for bytes; + uint256 private constant ADDR_SIZE = 20; + uint256 private constant TICKSPACING_SIZE = 3; + uint256 private constant NEXT_OFFSET = ADDR_SIZE + TICKSPACING_SIZE; + uint256 private constant POP_OFFSET = NEXT_OFFSET + ADDR_SIZE; + uint256 private constant MULTIPLE_POOLS_MIN_LENGTH = POP_OFFSET + NEXT_OFFSET; + + function numPools(bytes memory path) internal pure returns (uint256) { + return (path.length - ADDR_SIZE) / NEXT_OFFSET; + } + + function hasMultiplePools(bytes memory path) internal pure returns (bool){ + return path.length >= MULTIPLE_POOLS_MIN_LENGTH; + } + + function getFirstPool(bytes memory path) internal pure returns(bytes memory){ + return path.slice(0, POP_OFFSET); + } + + function skipToken(bytes memory path) internal pure returns (bytes memory) { + return path.slice(NEXT_OFFSET, path.length - NEXT_OFFSET); + } + + function decodeFirstPool(bytes memory path) internal pure returns(address tokenIn, address tokenOut, uint24 tickSpacing){ + tokenIn = path.toAddress(0); + tickSpacing = path.toUnit24(ADDR_SIZE); + tokenOut = path.toAddress(NEXT_OFFSET); + } +} \ No newline at end of file diff --git a/src/lib/PoolAddress.sol b/src/lib/PoolAddress.sol new file mode 100644 index 0000000..5f39758 --- /dev/null +++ b/src/lib/PoolAddress.sol @@ -0,0 +1,31 @@ +// SPDX-License-Identifier: MIT +pragma solidity ^0.8.14; + +import "../UniswapV3Pool.sol"; + +library PoolAddress{ + function computeAddress( + address factory, + address token0, + address token1, + uint24 tickSpacing + ) internal pure returns (address pool){ + require(token0 < token1); + pool = address( + uint160( + uint256( + keccak256( + abi.encodePacked( + hex"ff", + factory, + keccak256( + abi.encodePacked(token0, token1, tickSpacing) + ), + keccak256(type(UniswapV3Pool).creationCode) + ) + ) + ) + ) + ); + } +} \ No newline at end of file diff --git a/test/UniswapV3Pool.t.sol b/test/UniswapV3Pool.t.sol index b5b014f..47e5ec1 100644 --- a/test/UniswapV3Pool.t.sol +++ b/test/UniswapV3Pool.t.sol @@ -1,7 +1,7 @@ -// SPDX-License-Identifier: UNLICENSED +// SPDX-License-Identifier: MIT pragma solidity ^0.8.14; -import "forge-std/Test.sol"; +import "../lib/forge-std/src/Test.sol"; import "./ERC20Mintable.sol"; import "./UniswapV3Pool.Utils.t.sol"; @@ -10,7 +10,7 @@ import "../src/lib/LiquidityMath.sol"; import "../src/lib/TickMath.sol"; import "../src/UniswapV3Pool.sol"; -import "forge-std/console.sol"; +import "../lib/forge-std/src/console.sol"; contract UniswapV3PoolSwapsTest is Test, UniswapV3PoolUtils { ERC20Mintable token0; @@ -778,34 +778,28 @@ contract UniswapV3PoolSwapsTest is Test, UniswapV3PoolUtils { // CALLBACKS // //////////////////////////////////////////////////////////////////////////// - function uniswapV3SwapCallback( - int256 amount0, - int256 amount1, - bytes calldata data - ) public { - if (transferInSwapCallback) { - IUniswapV3Pool.CallbackData memory cbData = abi.decode( - data, - (IUniswapV3Pool.CallbackData) - ); - - if (amount0 > 0) { - IERC20(cbData.token0).transferFrom( - cbData.payer, - msg.sender, - uint256(amount0) - ); - } - - if (amount1 > 0) { - IERC20(cbData.token1).transferFrom( - cbData.payer, - msg.sender, - uint256(amount1) - ); - } - } +function uniswapV3SwapCallback( + int256 amount0, + int256 amount1, + bytes calldata data_ +) public { + SwapCallbackData memory data = abi.decode(data_, (SwapCallbackData)); + (address tokenIn, address tokenOut, ) = data.path.decodeFirstPool(); + + bool zeroForOne = tokenIn < tokenOut; + + int256 amount = zeroForOne ? amount0 : amount1; + + if (data.payer == address(this)) { + IERC20(tokenIn).transfer(msg.sender, uint256(amount)); + } else { + IERC20(tokenIn).transferFrom( + data.payer, + msg.sender, + uint256(amount) + ); } +} function uniswapV3MintCallback( uint256 amount0,