├── .gitattributes ├── .github └── workflows │ └── tests.yml ├── .gitignore ├── .prettierrc ├── .solcover.js ├── .solcover.pre.js ├── CONTRIBUTING.md ├── DEPLOYMENT.md ├── LICENSE ├── README.md ├── audits └── certik │ └── CertiK-Audit-for-Gridex.pdf ├── contracts ├── Grid.sol ├── GridDeployer.sol ├── GridFactory.sol ├── LICENSE ├── PriceOracle.sol ├── interfaces │ ├── IGrid.sol │ ├── IGridDeployer.sol │ ├── IGridEvents.sol │ ├── IGridFactory.sol │ ├── IGridParameters.sol │ ├── IGridStructs.sol │ ├── IPriceOracle.sol │ ├── IWETHMinimum.sol │ ├── LICENSE │ └── callback │ │ ├── IGridFlashCallback.sol │ │ ├── IGridPlaceMakerOrderCallback.sol │ │ ├── IGridSwapCallback.sol │ │ └── LICENSE ├── libraries │ ├── BitMath.sol │ ├── BoundaryBitmap.sol │ ├── BoundaryMath.sol │ ├── BundleMath.sol │ ├── CallbackValidator.sol │ ├── FixedPointX128.sol │ ├── FixedPointX192.sol │ ├── FixedPointX96.sol │ ├── GridAddress.sol │ ├── LICENSE │ ├── SwapMath.sol │ ├── Uint128Math.sol │ └── Uint160Math.sol └── test │ ├── AbstractPayFacade.sol │ ├── BoundaryBitmapTest.sol │ ├── BoundaryMathTest.sol │ ├── BundleMathTest.sol │ ├── ERC20Test.sol │ ├── FlashTest.sol │ ├── GridAddressTest.sol │ ├── GridTestHelper.sol │ ├── PriceOracleTestHelper.sol │ ├── SwapMathTest.sol │ ├── SwapTest.sol │ ├── Uint128MathTest.sol │ ├── Uint160MathTest.sol │ └── UnsafeERC20.sol ├── coverage.sh ├── hardhat.config.ts ├── package-lock.json ├── package.json ├── test ├── BoundaryBitmap.test.ts ├── BoundaryMath.gas.test.ts ├── BoundaryMath.test.ts ├── BundleMath.test.ts ├── Grid.gas.test.ts ├── Grid.test.ts ├── GridAddress.test.ts ├── GridFactory.test.ts ├── PriceOracle.test.ts ├── SwapMath.test.ts ├── Uint128Math.test.ts ├── Uint160Math.test.ts ├── __snapshots__ │ ├── BoundaryMath.gas.test.ts.snap │ ├── Grid.gas.test.ts.snap │ └── GridAddress.test.ts.snap ├── contracts │ └── WETH9.json └── shared │ ├── GridAddress.ts │ ├── deployer.ts │ ├── expect.ts │ ├── math.ts │ └── util.ts └── tsconfig.json /.gitattributes: -------------------------------------------------------------------------------- 1 | *.ts linguist-detectable=false 2 | -------------------------------------------------------------------------------- /.github/workflows/tests.yml: -------------------------------------------------------------------------------- 1 | name: Tests 2 | 3 | on: 4 | push: 5 | branches: [ "main" ] 6 | pull_request: 7 | branches: [ "main" ] 8 | workflow_dispatch: 9 | 10 | jobs: 11 | build: 12 | runs-on: ubuntu-latest 13 | 14 | strategy: 15 | matrix: 16 | node-version: [ 18.x ] 17 | steps: 18 | - uses: actions/checkout@v3 19 | 20 | - name: Use Node.js ${{ matrix.node-version }} 21 | uses: actions/setup-node@v3.4.1 22 | with: 23 | node-version: ${{ matrix.node-version }} 24 | cache: 'npm' 25 | 26 | - name: Install dependencies 27 | run: npm ci 28 | 29 | - name: Compile 30 | run: PRIVATE_KEY=0000000000000000000000000000000000000000000000000000000000000000 npx hardhat compile 31 | 32 | - name: Run tests 33 | run: PRIVATE_KEY=0000000000000000000000000000000000000000000000000000000000000000 npx hardhat test 34 | -------------------------------------------------------------------------------- /.gitignore: -------------------------------------------------------------------------------- 1 | node_modules 2 | .env 3 | coverage 4 | coverage.json 5 | typechain 6 | typechain-types 7 | 8 | #Hardhat files 9 | cache 10 | artifacts 11 | 12 | *.log 13 | .vscode 14 | .idea 15 | .DS_Store 16 | 17 | #openzeppelin 18 | .openzeppelin 19 | 20 | .npmrc 21 | -------------------------------------------------------------------------------- /.prettierrc: -------------------------------------------------------------------------------- 1 | { 2 | "printWidth": 120, 3 | "tabWidth": 4, 4 | "useTabs": false, 5 | "singleQuote": false, 6 | "bracketSpacing": false 7 | } 8 | -------------------------------------------------------------------------------- /.solcover.js: -------------------------------------------------------------------------------- 1 | module.exports = { 2 | mocha: { 3 | grep: "bytecode size", 4 | invert: true, 5 | }, 6 | }; 7 | -------------------------------------------------------------------------------- /.solcover.pre.js: -------------------------------------------------------------------------------- 1 | module.exports = { 2 | silent: true, 3 | mocha: { 4 | grep: "skip all tests", 5 | }, 6 | }; 7 | -------------------------------------------------------------------------------- /CONTRIBUTING.md: -------------------------------------------------------------------------------- 1 | # Contributing 2 | 3 | ## Development 4 | 5 | 1. The latest state of the code is on the `main` branch. 6 | 2. Create a new branch for each feature or issue you are working on. 7 | 3. Do the work, write [good commit messages](https://chris.beams.io/posts/git-commit/), and read 8 | the [style guide](https://solidity.readthedocs.io/en/v0.8.6/style-guide.html). 9 | 4. Submit a pull request. 10 | 5. Pull requests should pass all CI tests before being merged. 11 | 6. Pull requests should be reviewed by at least one other developer. 12 | 7. Pull requests are merged into `main` by a maintainer after being reviewed. 13 | 8. If you are a maintainer, please use "Squash and merge" to merge the pull request. 14 | 9. Delete the branch after the pull request is merged. 15 | 16 | ## How to compile or test the contracts 17 | 18 | ### Pre Requisites 19 | 20 | Before running any command, you need to create a `.env` file and set a `PRIVATE_KEY`, for example: 21 | 22 | ``` 23 | PRIVATE_KEY=1234567890abcdef1234567890abcdef1234567890abcdef1234567890abcdef 24 | ``` 25 | 26 | ### Deploy 27 | 28 | Deploy the contracts to Hardhat Network: 29 | 30 | ```sh 31 | $ npx hardhat run scripts/deploy.ts --network goerli 32 | ``` 33 | 34 | ### Verify 35 | 36 | Verify the contracts to XXXScan like etherscan: 37 | 38 | ```sh 39 | $ npx hardhat verify --network polygon --contract contracts/gridex.sol:Gridex 40 | ``` 41 | 42 | ### Compile 43 | 44 | Compile the smart contracts with Hardhat: 45 | 46 | ```sh 47 | $ npx hardhat compile 48 | ``` 49 | 50 | ### Lint Solidity 51 | 52 | Lint the Solidity code: 53 | 54 | ```sh 55 | $ npx hardhat check 56 | ``` 57 | 58 | ### Test 59 | 60 | Run the Mocha tests: 61 | 62 | ```sh 63 | $ npx hardhat test 64 | ``` 65 | 66 | ### Coverage 67 | 68 | Generate the code coverage report: 69 | 70 | ```sh 71 | $ ./coverage.sh 72 | ``` 73 | 74 | ### Report Gas 75 | 76 | See the gas usage per unit test and average gas per method call: 77 | 78 | ```sh 79 | $ REPORT_GAS=true npx hardhat test 80 | ``` 81 | 82 | ### Report Size 83 | 84 | Output Solidity contract size with hardhat: 85 | 86 | ```shell 87 | $ REPORT_SIZE=true npx hardhat compile 88 | ``` 89 | 90 | ### Clean 91 | 92 | Delete the smart contract artifacts, the coverage reports and the Hardhat cache: 93 | 94 | ```sh 95 | $ npx hardhat clean 96 | ``` 97 | -------------------------------------------------------------------------------- /DEPLOYMENT.md: -------------------------------------------------------------------------------- 1 | The Gridex Protocol has already deployed on Arbitrum, and here are the contract addresses: 2 | 3 | | Contract | Arbitrum | 4 | |------------------------------------------|----------------------------------------------| 5 | | [GridFactory](contracts/GridFactory.sol) | `0x32d1F0Dce675902f89D72251DB4AB1d728efa19c` | 6 | | [PriceOracle](contracts/PriceOracle.sol) | `0x9Da10a86F57e810A8A1D72F4A874419eB69BF16A` | 7 | -------------------------------------------------------------------------------- /LICENSE: -------------------------------------------------------------------------------- 1 | ----------------------------------------------------------------------------- 2 | 3 | Business Source License 1.1 4 | 5 | License text copyright (c) 2017 MariaDB Corporation Ab, All Rights Reserved. 6 | "Business Source License" is a trademark of MariaDB Corporation Ab. 7 | 8 | ----------------------------------------------------------------------------- 9 | 10 | Parameters 11 | 12 | Licensor: Gridex 13 | 14 | Licensed Work: Core 15 | The Licensed Work is (c) 2023 Gridex 16 | 17 | Additional Use Grant: Any uses listed and defined at 18 | core-license-grants.gridex.eth 19 | 20 | Change Date: The earlier of 2026-03-01 or a date specified at 21 | core-license-date.gridex.eth 22 | 23 | Change License: GNU General Public License v2.0 or later 24 | 25 | ----------------------------------------------------------------------------- 26 | 27 | Terms 28 | 29 | The Licensor hereby grants you the right to copy, modify, create derivative 30 | works, redistribute, and make non-production use of the Licensed Work. The 31 | Licensor may make an Additional Use Grant, above, permitting limited 32 | production use. 33 | 34 | Effective on the Change Date, or the fourth anniversary of the first publicly 35 | available distribution of a specific version of the Licensed Work under this 36 | License, whichever comes first, the Licensor hereby grants you rights under 37 | the terms of the Change License, and the rights granted in the paragraph 38 | above terminate. 39 | 40 | If your use of the Licensed Work does not comply with the requirements 41 | currently in effect as described in this License, you must purchase a 42 | commercial license from the Licensor, its affiliated entities, or authorized 43 | resellers, or you must refrain from using the Licensed Work. 44 | 45 | All copies of the original and modified Licensed Work, and derivative works 46 | of the Licensed Work, are subject to this License. This License applies 47 | separately for each version of the Licensed Work and the Change Date may vary 48 | for each version of the Licensed Work released by Licensor. 49 | 50 | You must conspicuously display this License on each original or modified copy 51 | of the Licensed Work. If you receive the Licensed Work in original or 52 | modified form from a third party, the terms and conditions set forth in this 53 | License apply to your use of that work. 54 | 55 | Any use of the Licensed Work in violation of this License will automatically 56 | terminate your rights under this License for the current and all other 57 | versions of the Licensed Work. 58 | 59 | This License does not grant you any right in any trademark or logo of 60 | Licensor or its affiliates (provided that you may use a trademark or logo of 61 | Licensor as expressly required by this License). 62 | 63 | TO THE EXTENT PERMITTED BY APPLICABLE LAW, THE LICENSED WORK IS PROVIDED ON 64 | AN "AS IS" BASIS. LICENSOR HEREBY DISCLAIMS ALL WARRANTIES AND CONDITIONS, 65 | EXPRESS OR IMPLIED, INCLUDING (WITHOUT LIMITATION) WARRANTIES OF 66 | MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE, NON-INFRINGEMENT, AND 67 | TITLE. 68 | 69 | ----------------------------------------------------------------------------- 70 | 71 | MariaDB hereby grants you permission to use this License’s text to license 72 | your works, and to refer to it using the trademark "Business Source License", 73 | as long as you comply with the Covenants of Licensor below. 74 | 75 | Covenants of Licensor 76 | 77 | In consideration of the right to use this License’s text and the "Business 78 | Source License" name and trademark, Licensor covenants to MariaDB, and to all 79 | other recipients of the licensed work to be provided by Licensor: 80 | 81 | 1. To specify as the Change License the GPL Version 2.0 or any later version, 82 | or a license that is compatible with GPL Version 2.0 or a later version, 83 | where "compatible" means that software provided under the Change License can 84 | be included in a program with software provided under GPL Version 2.0 or a 85 | later version. Licensor may specify additional Change Licenses without 86 | limitation. 87 | 88 | 2. To either: (a) specify an additional grant of rights to use that does not 89 | impose any additional restriction on the right granted in this License, as 90 | the Additional Use Grant; or (b) insert the text "None". 91 | 92 | 3. To specify a Change Date. 93 | 94 | 4. Not to modify this License in any other way. 95 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | # Core 2 | 3 | This repository contains the core smart contracts for the Gridex Protocol. 4 | 5 | The Gridex Protocol is a permissionless and non-custodial trading protocol consisting of a set of persistent, 6 | non-upgradable smart contracts on the Ethereum blockchain. 7 | 8 | ## License 9 | 10 | Gridex Protocol is licensed under the [BUSL-1.1](LICENSE). 11 | However, some files are licensed under the `GPL-2.0-or-later` license: 12 | 13 | - All files in the [contracts/interfaces](contracts/interfaces) may be used under the terms of 14 | the `GPL-2.0-or-later` license, see [LICENSE](contracts/interfaces/LICENSE) for more details. 15 | - All files in the [contracts/libraries](contracts/libraries) may be used under the terms of 16 | the `GPL-2.0-or-later` license, see [LICENSE](contracts/libraries/LICENSE) for more details. 17 | - All files in the [contracts/interfaces/callback](contracts/interfaces/callback) may be used under 18 | the terms of the `GPL-2.0-or-later` license, see [LICENSE](contracts/interfaces/callback/LICENSE) 19 | for more details. 20 | 21 | ### Other Exceptions 22 | 23 | - All files in the [contracts/test](contracts/test) directory are remain unlicensed(as indicated in 24 | their SPDX headers). 25 | 26 | ## Contract Deployments 27 | 28 | The Gridex Protocol has already deployed on Arbitrum, see [DEPLOYMENT](DEPLOYMENT.md) for more details. 29 | 30 | ## Contributing 31 | 32 | Please read [CONTRIBUTING.md](CONTRIBUTING.md) for details on our code of conduct, and the process 33 | for submitting pull requests to us. 34 | -------------------------------------------------------------------------------- /audits/certik/CertiK-Audit-for-Gridex.pdf: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/GridexProtocol/core/d2bf72bf7354251b70042515d7f6df5b55326bb0/audits/certik/CertiK-Audit-for-Gridex.pdf -------------------------------------------------------------------------------- /contracts/GridDeployer.sol: -------------------------------------------------------------------------------- 1 | // SPDX-License-Identifier: BUSL-1.1 2 | pragma solidity =0.8.9; 3 | 4 | import "@openzeppelin/contracts/utils/Create2.sol"; 5 | import "@openzeppelin/contracts/utils/Address.sol"; 6 | import "./interfaces/IGridDeployer.sol"; 7 | 8 | contract GridDeployer is IGridDeployer { 9 | bytes public override gridCreationCode; 10 | Parameters public override parameters; 11 | 12 | /// @dev Split the creationCode of the Grid contract into two parts, so that the Gas Limit of particular networks can be met when deploying. 13 | /// @param _gridPrefixCreationCode This parameter is the first half of the creationCode of the Grid contract. 14 | function _setGridPrefixCreationCode(bytes memory _gridPrefixCreationCode) internal { 15 | gridCreationCode = _gridPrefixCreationCode; 16 | } 17 | 18 | /// @dev Split the creationCode of the Grid contract into two parts, so that the Gas Limit of particular networks can be met when deploying. 19 | /// @param _gridSuffixCreationCode This parameter is the second half of the creationCode of the Grid contract. 20 | function _concatGridSuffixCreationCode(bytes memory _gridSuffixCreationCode) internal { 21 | gridCreationCode = bytes.concat(gridCreationCode, _gridSuffixCreationCode); 22 | } 23 | 24 | /// @dev Deploys a grid with desired parameters and clears these parameters after the deployment is complete 25 | /// @param token0 The first token in the grid, after sorting by address 26 | /// @param token1 The second token in the grid, after sorting by address 27 | /// @param resolution The step size in initialized boundaries for a grid created with a given fee 28 | /// @param takerFee The taker fee, denominated in hundredths of a bip (i.e. 1e-6) 29 | /// @param priceOracle The address of the price oracle contract 30 | /// @param weth9 The address of the WETH9 contract 31 | /// @return grid The address of the deployed grid 32 | function deploy( 33 | address token0, 34 | address token1, 35 | int24 resolution, 36 | int24 takerFee, 37 | address priceOracle, 38 | address weth9 39 | ) internal returns (address grid) { 40 | parameters = Parameters(token0, token1, resolution, takerFee, priceOracle, weth9); 41 | grid = Create2.deploy(0, keccak256(abi.encode(token0, token1, resolution)), gridCreationCode); 42 | delete parameters; 43 | } 44 | } 45 | -------------------------------------------------------------------------------- /contracts/GridFactory.sol: -------------------------------------------------------------------------------- 1 | // SPDX-License-Identifier: BUSL-1.1 2 | pragma solidity =0.8.9; 3 | 4 | import "@openzeppelin/contracts/utils/Context.sol"; 5 | import "@openzeppelin/contracts/access/Ownable.sol"; 6 | import "./interfaces/IGridFactory.sol"; 7 | import "./GridDeployer.sol"; 8 | import "./PriceOracle.sol"; 9 | 10 | /// @title The implementation of a Gridex grid factory 11 | contract GridFactory is IGridFactory, Context, GridDeployer, Ownable { 12 | address public immutable override priceOracle; 13 | address public immutable weth9; 14 | mapping(int24 => int24) public override resolutions; 15 | /// @notice The first key and the second key are token addresses, and the third key is the resolution, 16 | /// and the value is the grid address 17 | /// @dev For tokenA/tokenB and the specified resolution, both the combination of tokenA/tokenB 18 | /// and the combination of tokenB/tokenA with resolution will be stored in the mapping 19 | mapping(address => mapping(address => mapping(int24 => address))) public override grids; 20 | 21 | constructor(address _weth9, bytes memory _gridPrefixCreationCode) { 22 | // GF_NC: not contract 23 | require(Address.isContract(_weth9), "GF_NC"); 24 | 25 | priceOracle = address(new PriceOracle()); 26 | weth9 = _weth9; 27 | 28 | _enableResolutions(); 29 | 30 | _setGridPrefixCreationCode(_gridPrefixCreationCode); 31 | } 32 | 33 | function _enableResolutions() internal { 34 | resolutions[1] = 100; 35 | emit ResolutionEnabled(1, 100); 36 | 37 | resolutions[5] = 500; 38 | emit ResolutionEnabled(5, 500); 39 | 40 | resolutions[30] = 3000; 41 | emit ResolutionEnabled(30, 3000); 42 | } 43 | 44 | /// @inheritdoc IGridFactory 45 | function concatGridSuffixCreationCode(bytes memory gridSuffixCreationCode) external override onlyOwner { 46 | _concatGridSuffixCreationCode(gridSuffixCreationCode); 47 | renounceOwnership(); 48 | } 49 | 50 | /// @inheritdoc IGridFactory 51 | function createGrid(address tokenA, address tokenB, int24 resolution) external override returns (address grid) { 52 | // GF_NI: not initialized 53 | require(owner() == address(0), "GF_NI"); 54 | 55 | // GF_NC: not contract 56 | require(Address.isContract(tokenA), "GF_NC"); 57 | require(Address.isContract(tokenB), "GF_NC"); 58 | 59 | // GF_TAD: token address must be different 60 | require(tokenA != tokenB, "GF_TAD"); 61 | // GF_PAE: grid already exists 62 | require(grids[tokenA][tokenB][resolution] == address(0), "GF_PAE"); 63 | 64 | int24 takerFee = resolutions[resolution]; 65 | // GF_RNE: resolution not enabled 66 | require(takerFee > 0, "GF_RNE"); 67 | 68 | (address token0, address token1) = tokenA < tokenB ? (tokenA, tokenB) : (tokenB, tokenA); 69 | grid = deploy(token0, token1, resolution, takerFee, priceOracle, weth9); 70 | grids[tokenA][tokenB][resolution] = grid; 71 | grids[tokenB][tokenA][resolution] = grid; 72 | emit GridCreated(token0, token1, resolution, grid); 73 | } 74 | } 75 | -------------------------------------------------------------------------------- /contracts/PriceOracle.sol: -------------------------------------------------------------------------------- 1 | // SPDX-License-Identifier: BUSL-1.1 2 | pragma solidity =0.8.9; 3 | 4 | import "./interfaces/IGrid.sol"; 5 | import "./interfaces/IPriceOracle.sol"; 6 | import "./libraries/CallbackValidator.sol"; 7 | import "./libraries/GridAddress.sol"; 8 | 9 | contract PriceOracle is IPriceOracle { 10 | address public immutable gridFactory; 11 | 12 | mapping(address => GridOracleState) public override gridOracleStates; 13 | mapping(address => GridPriceData[65535]) public override gridPriceData; 14 | 15 | constructor() { 16 | gridFactory = msg.sender; 17 | } 18 | 19 | /// @inheritdoc IPriceOracle 20 | function register(address tokenA, address tokenB, int24 resolution) external override { 21 | address grid = GridAddress.computeAddress(gridFactory, GridAddress.gridKey(tokenA, tokenB, resolution)); 22 | // PO_IC: invalid caller 23 | require(grid == msg.sender, "PO_IC"); 24 | 25 | _register(grid); 26 | } 27 | 28 | function _register(address grid) internal { 29 | // PO_AR: already registered 30 | require(gridOracleStates[grid].capacity == 0, "PO_AR"); 31 | 32 | gridOracleStates[grid].capacity = 1; 33 | gridOracleStates[grid].capacityNext = 1; 34 | gridPriceData[grid][0] = GridPriceData({ 35 | blockTimestamp: uint32(block.timestamp), 36 | boundaryCumulative: 0, 37 | initialized: true 38 | }); 39 | } 40 | 41 | /// @inheritdoc IPriceOracle 42 | function update(int24 boundary, uint32 blockTimestamp) external override { 43 | _update(msg.sender, boundary, blockTimestamp); 44 | } 45 | 46 | function _update(address grid, int24 boundary, uint32 blockTimestamp) internal { 47 | GridOracleState memory stateCache = gridOracleStates[grid]; 48 | // PO_UR: unregistered grid 49 | require(stateCache.capacity >= 1, "PO_UR"); 50 | 51 | GridPriceData storage lastData = gridPriceData[grid][stateCache.index]; 52 | 53 | // safe for 0 or 1 overflows 54 | unchecked { 55 | uint32 delta = blockTimestamp - lastData.blockTimestamp; 56 | 57 | uint16 indexNext = (stateCache.index + 1) % stateCache.capacityNext; 58 | gridPriceData[grid][indexNext] = GridPriceData({ 59 | blockTimestamp: blockTimestamp, 60 | boundaryCumulative: lastData.boundaryCumulative + int56(boundary) * int56(uint56(delta)), 61 | initialized: true 62 | }); 63 | 64 | // In the interest of gas-efficiency, the capacity is set to be the same as capacityNext 65 | if (indexNext == stateCache.capacity) gridOracleStates[grid].capacity = stateCache.capacityNext; 66 | 67 | gridOracleStates[grid].index = indexNext; 68 | } 69 | } 70 | 71 | /// @inheritdoc IPriceOracle 72 | function increaseCapacity(address grid, uint16 capacityNext) external override { 73 | GridOracleState storage state = gridOracleStates[grid]; 74 | // PO_UR: unregistered grid 75 | require(state.capacity >= 1, "PO_UR"); 76 | 77 | uint16 capacityOld = state.capacityNext; 78 | if (capacityOld >= capacityNext) return; 79 | 80 | for (uint16 i = capacityOld; i < capacityNext; i++) { 81 | // In the interest of gas-efficiency the array is initialized at the specified index here 82 | // when updating the oracle price 83 | // Note: this data will not be used, because the initialized property is still false 84 | gridPriceData[grid][i].blockTimestamp = 1; 85 | } 86 | 87 | state.capacityNext = capacityNext; 88 | 89 | emit IncreaseCapacity(grid, capacityOld, capacityNext); 90 | } 91 | 92 | /// @inheritdoc IPriceOracle 93 | function getBoundaryCumulative( 94 | address grid, 95 | uint32 secondsAgo 96 | ) external view override returns (int56 boundaryCumulative) { 97 | GridOracleState memory state = gridOracleStates[grid]; 98 | // PO_UR: unregistered grid 99 | require(state.capacity >= 1, "PO_UR"); 100 | 101 | (, int24 boundary, , ) = IGrid(grid).slot0(); 102 | 103 | return _getBoundaryCumulative(state, gridPriceData[grid], boundary, uint32(block.timestamp), secondsAgo); 104 | } 105 | 106 | /// @inheritdoc IPriceOracle 107 | function getBoundaryCumulatives( 108 | address grid, 109 | uint32[] calldata secondsAgos 110 | ) external view override returns (int56[] memory boundaryCumulatives) { 111 | GridOracleState memory state = gridOracleStates[grid]; 112 | // PO_UR: unregistered grid 113 | require(state.capacity >= 1, "PO_UR"); 114 | 115 | boundaryCumulatives = new int56[](secondsAgos.length); 116 | (, int24 boundary, , ) = IGrid(grid).slot0(); 117 | uint32 blockTimestamp = uint32(block.timestamp); 118 | GridPriceData[65535] storage targetGridPriceData = gridPriceData[grid]; 119 | for (uint256 i = 0; i < secondsAgos.length; i++) { 120 | boundaryCumulatives[i] = _getBoundaryCumulative( 121 | state, 122 | targetGridPriceData, 123 | boundary, 124 | blockTimestamp, 125 | secondsAgos[i] 126 | ); 127 | } 128 | } 129 | 130 | /// @notice Get the time-cumulative boundary at a given time in the past 131 | /// @param blockTimestamp The timestamp of the current block 132 | /// @param secondsAgo The time elapsed (in seconds) in the past to get the price for 133 | /// @return boundaryCumulative The time-cumulative boundary at the given time 134 | function _getBoundaryCumulative( 135 | GridOracleState memory state, 136 | GridPriceData[65535] storage priceData, 137 | int24 boundary, 138 | uint32 blockTimestamp, 139 | uint32 secondsAgo 140 | ) internal view returns (int56 boundaryCumulative) { 141 | if (secondsAgo == 0) { 142 | GridPriceData memory last = priceData[state.index]; 143 | if (last.blockTimestamp == blockTimestamp) return last.boundaryCumulative; 144 | 145 | unchecked { 146 | return last.boundaryCumulative + int56(boundary) * int56(uint56(blockTimestamp - last.blockTimestamp)); 147 | } 148 | } 149 | 150 | uint32 targetTimestamp; 151 | unchecked { 152 | targetTimestamp = blockTimestamp - secondsAgo; 153 | } 154 | 155 | (GridPriceData memory beforePriceData, GridPriceData memory afterPriceData) = _getSurroundingPriceData( 156 | state, 157 | priceData, 158 | boundary, 159 | blockTimestamp, 160 | targetTimestamp 161 | ); 162 | 163 | if (targetTimestamp == beforePriceData.blockTimestamp) { 164 | return beforePriceData.boundaryCumulative; 165 | } else if (targetTimestamp == afterPriceData.blockTimestamp) { 166 | return afterPriceData.boundaryCumulative; 167 | } else { 168 | // p = p_b + (p_a - p_b) / (t_a - t_b) * (t - t_b) 169 | unchecked { 170 | uint32 timestampDelta = targetTimestamp - beforePriceData.blockTimestamp; 171 | int88 boundaryCumulativeDelta = (int88(uint88(timestampDelta)) * 172 | (afterPriceData.boundaryCumulative - beforePriceData.boundaryCumulative)) / 173 | int32(afterPriceData.blockTimestamp - beforePriceData.blockTimestamp); 174 | return beforePriceData.boundaryCumulative + int56(boundaryCumulativeDelta); 175 | } 176 | } 177 | } 178 | 179 | /// @notice Get the surrounding price data for a given timestamp 180 | /// @param boundary The boundary of the grid at the current block 181 | /// @param blockTimestamp The timestamp of the current block 182 | /// @param targetTimestamp The timestamp to search for 183 | /// @return beforeOrAtPriceData The price data with the largest timestamp 184 | /// less than or equal to the target timestamp 185 | /// @return afterOrAtPriceData The price data with the smallest timestamp 186 | /// greater than or equal to the target timestamp 187 | function _getSurroundingPriceData( 188 | GridOracleState memory state, 189 | GridPriceData[65535] storage priceData, 190 | int24 boundary, 191 | uint32 blockTimestamp, 192 | uint32 targetTimestamp 193 | ) private view returns (GridPriceData memory beforeOrAtPriceData, GridPriceData memory afterOrAtPriceData) { 194 | beforeOrAtPriceData = priceData[state.index]; 195 | 196 | if (_overflowSafeLTE(blockTimestamp, beforeOrAtPriceData.blockTimestamp, targetTimestamp)) { 197 | if (beforeOrAtPriceData.blockTimestamp != targetTimestamp) { 198 | // When the target time is greater than or equal to the last update time, it only needs to 199 | // calculate the time-cumulative price for the given time 200 | unchecked { 201 | beforeOrAtPriceData = GridPriceData({ 202 | blockTimestamp: targetTimestamp, 203 | boundaryCumulative: beforeOrAtPriceData.boundaryCumulative + 204 | int56(boundary) * 205 | (int56(uint56(targetTimestamp - beforeOrAtPriceData.blockTimestamp))), 206 | initialized: false 207 | }); 208 | } 209 | } 210 | return (beforeOrAtPriceData, afterOrAtPriceData); 211 | } 212 | 213 | GridPriceData storage oldestPriceData = priceData[(state.index + 1) % state.capacity]; 214 | if (!oldestPriceData.initialized) oldestPriceData = priceData[0]; 215 | 216 | // PO_STL: secondsAgo is too large 217 | require(_overflowSafeLTE(blockTimestamp, oldestPriceData.blockTimestamp, targetTimestamp), "PO_STL"); 218 | 219 | return _binarySearch(state, priceData, blockTimestamp, targetTimestamp); 220 | } 221 | 222 | /// @notice Binary search for the surrounding price data for a given timestamp 223 | /// @param blockTimestamp The timestamp of the current block 224 | /// @param targetTimestamp The timestamp to search for 225 | /// @return beforeOrAtPriceData The price data with the largest timestamp 226 | /// less than or equal to the target timestamp 227 | /// @return afterOrAtPriceData The price data with the smallest timestamp 228 | /// greater than or equal to the target timestamp 229 | function _binarySearch( 230 | GridOracleState memory state, 231 | GridPriceData[65535] storage priceData, 232 | uint32 blockTimestamp, 233 | uint32 targetTimestamp 234 | ) private view returns (GridPriceData memory beforeOrAtPriceData, GridPriceData memory afterOrAtPriceData) { 235 | uint256 left = (state.index + 1) % state.capacity; 236 | uint256 right = left + state.capacity - 1; 237 | uint256 mid; 238 | while (true) { 239 | mid = (left + right) / 2; 240 | 241 | beforeOrAtPriceData = priceData[mid % state.capacity]; 242 | if (!beforeOrAtPriceData.initialized) { 243 | left = mid + 1; 244 | continue; 245 | } 246 | 247 | afterOrAtPriceData = priceData[(mid + 1) % state.capacity]; 248 | 249 | bool targetAfterOrAt = _overflowSafeLTE( 250 | blockTimestamp, 251 | beforeOrAtPriceData.blockTimestamp, 252 | targetTimestamp 253 | ); 254 | if ( 255 | targetAfterOrAt && _overflowSafeLTE(blockTimestamp, targetTimestamp, afterOrAtPriceData.blockTimestamp) 256 | ) { 257 | return (beforeOrAtPriceData, afterOrAtPriceData); 258 | } 259 | 260 | if (!targetAfterOrAt) right = mid - 1; 261 | else left = mid + 1; 262 | } 263 | } 264 | 265 | /// @notice Compare the order of timestamps 266 | /// @dev blockTimestamp The timestamp of the current block 267 | /// @dev a First timestamp (in the past) to check 268 | /// @dev b Second timestamp (in the past) to check 269 | /// @return lte Result of a <= b 270 | function _overflowSafeLTE(uint32 blockTimestamp, uint32 a, uint32 b) private pure returns (bool lte) { 271 | if (a <= blockTimestamp && b <= blockTimestamp) return a <= b; 272 | unchecked { 273 | uint256 aAdjusted = a > blockTimestamp ? a : a + 2 ** 32; 274 | uint256 bAdjusted = b > blockTimestamp ? b : b + 2 ** 32; 275 | return aAdjusted <= bAdjusted; 276 | } 277 | } 278 | } 279 | -------------------------------------------------------------------------------- /contracts/interfaces/IGrid.sol: -------------------------------------------------------------------------------- 1 | // SPDX-License-Identifier: GPL-2.0-or-later 2 | pragma solidity ^0.8.0; 3 | 4 | import "./IGridStructs.sol"; 5 | import "./IGridParameters.sol"; 6 | 7 | /// @title The interface for Gridex grid 8 | interface IGrid { 9 | ///==================================== Grid States ==================================== 10 | 11 | /// @notice The first token in the grid, after sorting by address 12 | function token0() external view returns (address); 13 | 14 | /// @notice The second token in the grid, after sorting by address 15 | function token1() external view returns (address); 16 | 17 | /// @notice The step size in initialized boundaries for a grid created with a given fee 18 | function resolution() external view returns (int24); 19 | 20 | /// @notice The fee paid to the grid denominated in hundredths of a bip, i.e. 1e-6 21 | function takerFee() external view returns (int24); 22 | 23 | /// @notice The 0th slot of the grid holds a lot of values that can be gas-efficiently accessed 24 | /// externally as a single method 25 | /// @return priceX96 The current price of the grid, as a Q64.96 26 | /// @return boundary The current boundary of the grid 27 | /// @return blockTimestamp The time the oracle was last updated 28 | /// @return unlocked Whether the grid is unlocked or not 29 | function slot0() external view returns (uint160 priceX96, int24 boundary, uint32 blockTimestamp, bool unlocked); 30 | 31 | /// @notice Returns the boundary information of token0 32 | /// @param boundary The boundary of the grid 33 | /// @return bundle0Id The unique identifier of bundle0 34 | /// @return bundle1Id The unique identifier of bundle1 35 | /// @return makerAmountRemaining The remaining amount of token0 that can be swapped out, 36 | /// which is the sum of bundle0 and bundle1 37 | function boundaries0( 38 | int24 boundary 39 | ) external view returns (uint64 bundle0Id, uint64 bundle1Id, uint128 makerAmountRemaining); 40 | 41 | /// @notice Returns the boundary information of token1 42 | /// @param boundary The boundary of the grid 43 | /// @return bundle0Id The unique identifier of bundle0 44 | /// @return bundle1Id The unique identifier of bundle1 45 | /// @return makerAmountRemaining The remaining amount of token1 that can be swapped out, 46 | /// which is the sum of bundle0 and bundle1 47 | function boundaries1( 48 | int24 boundary 49 | ) external view returns (uint64 bundle0Id, uint64 bundle1Id, uint128 makerAmountRemaining); 50 | 51 | /// @notice Returns 256 packed boundary initialized boolean values for token0 52 | function boundaryBitmaps0(int16 wordPos) external view returns (uint256 word); 53 | 54 | /// @notice Returns 256 packed boundary initialized boolean values for token1 55 | function boundaryBitmaps1(int16 wordPos) external view returns (uint256 word); 56 | 57 | /// @notice Returns the amount owed for token0 and token1 58 | /// @param owner The address of owner 59 | /// @return token0 The amount of token0 owed 60 | /// @return token1 The amount of token1 owed 61 | function tokensOweds(address owner) external view returns (uint128 token0, uint128 token1); 62 | 63 | /// @notice Returns the information of a given bundle 64 | /// @param bundleId The unique identifier of the bundle 65 | /// @return boundaryLower The lower boundary of the bundle 66 | /// @return zero When zero is true, it represents token0, otherwise it represents token1 67 | /// @return makerAmountTotal The total amount of token0 or token1 that the maker added 68 | /// @return makerAmountRemaining The remaining amount of token0 or token1 that can be swapped out from the makers 69 | /// @return takerAmountRemaining The remaining amount of token0 or token1 that have been swapped in from the takers 70 | /// @return takerFeeAmountRemaining The remaining amount of fees that takers have paid in 71 | function bundles( 72 | uint64 bundleId 73 | ) 74 | external 75 | view 76 | returns ( 77 | int24 boundaryLower, 78 | bool zero, 79 | uint128 makerAmountTotal, 80 | uint128 makerAmountRemaining, 81 | uint128 takerAmountRemaining, 82 | uint128 takerFeeAmountRemaining 83 | ); 84 | 85 | /// @notice Returns the information of a given order 86 | /// @param orderId The unique identifier of the order 87 | /// @return bundleId The unique identifier of the bundle -- represents which bundle this order belongs to 88 | /// @return owner The address of the owner of the order 89 | /// @return amount The amount of token0 or token1 to add 90 | function orders(uint256 orderId) external view returns (uint64 bundleId, address owner, uint128 amount); 91 | 92 | ///==================================== Grid Actions ==================================== 93 | 94 | /// @notice Initializes the grid with the given parameters 95 | /// @dev The caller of this method receives a callback in the form of 96 | /// IGridPlaceMakerOrderCallback#gridexPlaceMakerOrderCallback. 97 | /// When initializing the grid, token0 and token1's liquidity must be added simultaneously. 98 | /// @param parameters The parameters used to initialize the grid 99 | /// @param data Any data to be passed through to the callback 100 | /// @return orderIds0 The unique identifiers of the orders for token0 101 | /// @return orderIds1 The unique identifiers of the orders for token1 102 | function initialize( 103 | IGridParameters.InitializeParameters memory parameters, 104 | bytes calldata data 105 | ) external returns (uint256[] memory orderIds0, uint256[] memory orderIds1); 106 | 107 | /// @notice Swaps token0 for token1, or vice versa 108 | /// @dev The caller of this method receives a callback in the form of IGridSwapCallback#gridexSwapCallback 109 | /// @param recipient The address to receive the output of the swap 110 | /// @param zeroForOne The swap direction, true for token0 to token1 and false otherwise 111 | /// @param amountSpecified The amount of the swap, configured as an exactInput (positive) 112 | /// or an exactOutput (negative) 113 | /// @param priceLimitX96 Swap price limit: if zeroForOne, the price will not be less than this value after swap, 114 | /// if oneForZero, it will not be greater than this value after swap, as a Q64.96 115 | /// @param data Any data to be passed through to the callback 116 | /// @return amount0 The balance change of the grid's token0. When negative, it will reduce the balance 117 | /// by the exact amount. When positive, it will increase by at least this amount 118 | /// @return amount1 The balance change of the grid's token1. When negative, it will reduce the balance 119 | /// by the exact amount. When positive, it will increase by at least this amount. 120 | function swap( 121 | address recipient, 122 | bool zeroForOne, 123 | int256 amountSpecified, 124 | uint160 priceLimitX96, 125 | bytes calldata data 126 | ) external returns (int256 amount0, int256 amount1); 127 | 128 | /// @notice Places a maker order on the grid 129 | /// @dev The caller of this method receives a callback in the form of 130 | /// IGridPlaceMakerOrderCallback#gridexPlaceMakerOrderCallback 131 | /// @param parameters The parameters used to place the maker order 132 | /// @param data Any data to be passed through to the callback 133 | /// @return orderId The unique identifier of the order 134 | function placeMakerOrder( 135 | IGridParameters.PlaceOrderParameters memory parameters, 136 | bytes calldata data 137 | ) external returns (uint256 orderId); 138 | 139 | /// @notice Places maker orders on the grid 140 | /// @dev The caller of this method receives a callback in the form of 141 | /// IGridPlaceMakerOrderCallback#gridexPlaceMakerOrderCallback 142 | /// @param parameters The parameters used to place the maker orders 143 | /// @param data Any data to be passed through to the callback 144 | /// @return orderIds The unique identifiers of the orders 145 | function placeMakerOrderInBatch( 146 | IGridParameters.PlaceOrderInBatchParameters memory parameters, 147 | bytes calldata data 148 | ) external returns (uint256[] memory orderIds); 149 | 150 | /// @notice Settles a maker order 151 | /// @param orderId The unique identifier of the order 152 | /// @return amount0 The amount of token0 that the maker received 153 | /// @return amount1 The amount of token1 that the maker received 154 | function settleMakerOrder(uint256 orderId) external returns (uint128 amount0, uint128 amount1); 155 | 156 | /// @notice Settle maker order and collect 157 | /// @param recipient The address to receive the output of the settlement 158 | /// @param orderId The unique identifier of the order 159 | /// @param unwrapWETH9 Whether to unwrap WETH9 to ETH 160 | /// @return amount0 The amount of token0 that the maker received 161 | /// @return amount1 The amount of token1 that the maker received 162 | function settleMakerOrderAndCollect( 163 | address recipient, 164 | uint256 orderId, 165 | bool unwrapWETH9 166 | ) external returns (uint128 amount0, uint128 amount1); 167 | 168 | /// @notice Settles maker orders and collects in a batch 169 | /// @param recipient The address to receive the output of the settlement 170 | /// @param orderIds The unique identifiers of the orders 171 | /// @param unwrapWETH9 Whether to unwrap WETH9 to ETH 172 | /// @return amount0Total The total amount of token0 that the maker received 173 | /// @return amount1Total The total amount of token1 that the maker received 174 | function settleMakerOrderAndCollectInBatch( 175 | address recipient, 176 | uint256[] memory orderIds, 177 | bool unwrapWETH9 178 | ) external returns (uint128 amount0Total, uint128 amount1Total); 179 | 180 | /// @notice For flash swaps. The caller borrows assets and returns them in the callback of the function, 181 | /// in addition to a fee 182 | /// @dev The caller of this function receives a callback in the form of IGridFlashCallback#gridexFlashCallback 183 | /// @param recipient The address which will receive the token0 and token1 184 | /// @param amount0 The amount of token0 to receive 185 | /// @param amount1 The amount of token1 to receive 186 | /// @param data Any data to be passed through to the callback 187 | function flash(address recipient, uint256 amount0, uint256 amount1, bytes calldata data) external; 188 | 189 | /// @notice Collects tokens owed 190 | /// @param recipient The address to receive the collected fees 191 | /// @param amount0Requested The maximum amount of token0 to send. 192 | /// Set to 0 if fees should only be collected in token1. 193 | /// @param amount1Requested The maximum amount of token1 to send. 194 | /// Set to 0 if fees should only be collected in token0. 195 | /// @return amount0 The amount of fees collected in token0 196 | /// @return amount1 The amount of fees collected in token1 197 | function collect( 198 | address recipient, 199 | uint128 amount0Requested, 200 | uint128 amount1Requested 201 | ) external returns (uint128 amount0, uint128 amount1); 202 | } 203 | -------------------------------------------------------------------------------- /contracts/interfaces/IGridDeployer.sol: -------------------------------------------------------------------------------- 1 | // SPDX-License-Identifier: GPL-2.0-or-later 2 | pragma solidity ^0.8.0; 3 | 4 | /// @title A contract interface for deploying grids 5 | /// @notice A grid constructor must use the interface to pass arguments to the grid 6 | /// @dev This is necessary to ensure there are no constructor arguments in the grid contract. 7 | /// This keeps the grid init code hash constant, allowing a CREATE2 address to be computed on-chain gas-efficiently. 8 | interface IGridDeployer { 9 | struct Parameters { 10 | address token0; 11 | address token1; 12 | int24 resolution; 13 | int24 takerFee; 14 | address priceOracle; 15 | address weth9; 16 | } 17 | 18 | /// @notice Returns the grid creation code 19 | function gridCreationCode() external view returns (bytes memory); 20 | 21 | /// @notice Getter for the arguments used in constructing the grid. These are set locally during grid creation 22 | /// @dev Retrieves grid parameters, after being called by the grid constructor 23 | /// @return token0 The first token in the grid, after sorting by address 24 | /// @return token1 The second token in the grid, after sorting by address 25 | /// @return resolution The step size in initialized boundaries for a grid created with a given fee 26 | /// @return takerFee The taker fee, denominated in hundredths of a bip (i.e. 1e-6) 27 | /// @return priceOracle The address of the price oracle contract 28 | /// @return weth9 The address of the WETH9 contract 29 | function parameters() 30 | external 31 | view 32 | returns ( 33 | address token0, 34 | address token1, 35 | int24 resolution, 36 | int24 takerFee, 37 | address priceOracle, 38 | address weth9 39 | ); 40 | } 41 | -------------------------------------------------------------------------------- /contracts/interfaces/IGridEvents.sol: -------------------------------------------------------------------------------- 1 | // SPDX-License-Identifier: GPL-2.0-or-later 2 | pragma solidity ^0.8.0; 3 | 4 | import "./IGridStructs.sol"; 5 | 6 | /// @title Events emitted by the grid contract 7 | interface IGridEvents { 8 | /// @notice Emitted exactly once by a grid when #initialize is first called on the grid 9 | /// @param priceX96 The initial price of the grid, as a Q64.96 10 | /// @param boundary The initial boundary of the grid 11 | event Initialize(uint160 priceX96, int24 boundary); 12 | 13 | /// @notice Emitted when the maker places an order to add liquidity for token0 or token1 14 | /// @param orderId The unique identifier of the order 15 | /// @param recipient The address that received the order 16 | /// @param bundleId The unique identifier of the bundle -- represents which bundle this order belongs to 17 | /// @param zero When zero is true, it represents token0, otherwise it represents token1 18 | /// @param boundaryLower The lower boundary of the order 19 | /// @param amount The amount of token0 or token1 to add 20 | event PlaceMakerOrder( 21 | uint256 indexed orderId, 22 | address indexed recipient, 23 | uint64 indexed bundleId, 24 | bool zero, 25 | int24 boundaryLower, 26 | uint128 amount 27 | ); 28 | 29 | /// @notice Emitted when settling a single range order 30 | /// @param orderId The unique identifier of the order 31 | /// @param makerAmountOut The amount of token0 or token1 that the maker has removed 32 | /// @param takerAmountOut The amount of token0 or token1 that the taker has submitted 33 | /// @param takerFeeAmountOut The amount of token0 or token1 fees that the taker has paid 34 | event SettleMakerOrder( 35 | uint256 indexed orderId, 36 | uint128 makerAmountOut, 37 | uint128 takerAmountOut, 38 | uint128 takerFeeAmountOut 39 | ); 40 | 41 | /// @notice Emitted when a maker settles an order 42 | /// @dev When either of the bundle's total maker amount or the remaining maker amount becomes 0, 43 | /// the bundle is closed 44 | /// @param bundleId The unique identifier of the bundle 45 | /// @param makerAmountTotal The change in the total maker amount in the bundle 46 | /// @param makerAmountRemaining The change in the remaining maker amount in the bundle 47 | event ChangeBundleForSettleOrder(uint64 indexed bundleId, int256 makerAmountTotal, int256 makerAmountRemaining); 48 | 49 | /// @notice Emitted when a taker is swapping 50 | /// @dev When the bundle's remaining maker amount becomes 0, the bundle is closed 51 | /// @param bundleId The unique identifier of the bundle 52 | /// @param makerAmountRemaining The change in the remaining maker amount in the bundle 53 | /// @param amountIn The change in the remaining taker amount in the bundle 54 | /// @param takerFeeAmountIn The change in the remaining taker fee amount in the bundle 55 | event ChangeBundleForSwap( 56 | uint64 indexed bundleId, 57 | int256 makerAmountRemaining, 58 | uint256 amountIn, 59 | uint128 takerFeeAmountIn 60 | ); 61 | 62 | /// @notice Emitted by the grid for any swaps between token0 and token1 63 | /// @param sender The address that initiated the swap call, and that received the callback 64 | /// @param recipient The address that received the output of the swap 65 | /// @param amount0 The delta of the token0 balance of the grid 66 | /// @param amount1 The delta of the token1 balance of the grid 67 | /// @param priceX96 The price of the grid after the swap, as a Q64.96 68 | /// @param boundary The log base 1.0001 of the price of the grid after the swap 69 | event Swap( 70 | address indexed sender, 71 | address indexed recipient, 72 | int256 amount0, 73 | int256 amount1, 74 | uint160 priceX96, 75 | int24 boundary 76 | ); 77 | 78 | /// @notice Emitted by the grid for any flashes of token0/token1 79 | /// @param sender The address that initiated the flash call, and that received the callback 80 | /// @param recipient The address that received the tokens from the flash 81 | /// @param amount0 The amount of token0 that was flashed 82 | /// @param amount1 The amount of token1 that was flashed 83 | /// @param paid0 The amount of token0 paid for the flash, which can exceed the amount0 plus the fee 84 | /// @param paid1 The amount of token1 paid for the flash, which can exceed the amount1 plus the fee 85 | event Flash( 86 | address indexed sender, 87 | address indexed recipient, 88 | uint256 amount0, 89 | uint256 amount1, 90 | uint128 paid0, 91 | uint128 paid1 92 | ); 93 | 94 | /// @notice Emitted when the collected owed fees are withdrawn by the sender 95 | /// @param sender The address that collects the fees 96 | /// @param recipient The address that receives the fees 97 | /// @param amount0 The amount of token0 fees that is withdrawn 98 | /// @param amount1 The amount of token1 fees that is withdrawn 99 | event Collect(address indexed sender, address indexed recipient, uint128 amount0, uint128 amount1); 100 | } 101 | -------------------------------------------------------------------------------- /contracts/interfaces/IGridFactory.sol: -------------------------------------------------------------------------------- 1 | // SPDX-License-Identifier: GPL-2.0-or-later 2 | pragma solidity ^0.8.0; 3 | 4 | /// @title The interface for Gridex grid factory 5 | interface IGridFactory { 6 | /// @notice Emitted when a new resolution is enabled for grid creation via the grid factory 7 | /// @param resolution The step size in initialized boundaries for a grid created with a given fee 8 | /// @param takerFee The taker fee, denominated in hundredths of a bip (i.e. 1e-6) 9 | event ResolutionEnabled(int24 indexed resolution, int24 indexed takerFee); 10 | 11 | /// @notice Emitted upon grid creation 12 | /// @param token0 The first token in the grid, after sorting by address 13 | /// @param token1 The first token in the grid, after sorting by address 14 | /// @param resolution The step size in initialized boundaries for a grid created with a given fee 15 | /// @param grid The address of the deployed grid 16 | event GridCreated(address indexed token0, address indexed token1, int24 indexed resolution, address grid); 17 | 18 | /// @notice Returns the taker fee for the given resolution if enabled. Else, returns 0. 19 | /// @dev A resolution can never be removed, so this value should be hard coded or cached in the calling context 20 | /// @param resolution The enabled resolution 21 | /// @return takerFee The taker fee, denominated in hundredths of a bip (i.e. 1e-6) 22 | function resolutions(int24 resolution) external view returns (int24 takerFee); 23 | 24 | /// @notice The implementation address of the price oracle 25 | function priceOracle() external view returns (address); 26 | 27 | /// @notice Returns the grid address for a given token pair and a resolution. Returns 0 if the pair does not exist. 28 | /// @dev tokenA and tokenB may be passed in, in the order of either token0/token1 or token1/token0 29 | /// @param tokenA The contract address of either token0 or token1 30 | /// @param tokenB The contract address of the other token 31 | /// @param resolution The step size in initialized boundaries for a grid created with a given fee 32 | /// @return grid The grid address 33 | function grids(address tokenA, address tokenB, int24 resolution) external view returns (address grid); 34 | 35 | /// @notice Concat grid creation code bytes 36 | /// @dev Split the creationCode of the Grid contract into two parts, so that the Gas Limit of particular networks can be met when deploying. 37 | /// @param gridSuffixCreationCode This parameter is the second half of the creationCode of the Grid contract. 38 | function concatGridSuffixCreationCode(bytes memory gridSuffixCreationCode) external; 39 | 40 | /// @notice Creates a grid for a given pair of tokens and resolution 41 | /// @dev tokenA and tokenB may be passed in either order: token0/token1 or token1/token0. 42 | /// @param tokenA One token of the grid token pair 43 | /// @param tokenB The other token of the grid token pair 44 | /// @param resolution The step size in initialized boundaries for a grid created with a given fee 45 | /// @return grid The address of the deployed grid 46 | function createGrid(address tokenA, address tokenB, int24 resolution) external returns (address grid); 47 | } 48 | -------------------------------------------------------------------------------- /contracts/interfaces/IGridParameters.sol: -------------------------------------------------------------------------------- 1 | // SPDX-License-Identifier: GPL-2.0-or-later 2 | pragma solidity ^0.8.0; 3 | 4 | interface IGridParameters { 5 | /// @dev Parameters for initializing the grid 6 | struct InitializeParameters { 7 | /// @dev The initial price of the grid, as a Q64.96. 8 | /// Price is represented as an amountToken1/amountToken0 Q64.96 value. 9 | uint160 priceX96; 10 | /// @dev The address to receive orders 11 | address recipient; 12 | /// @dev Represents the order parameters for token0 13 | BoundaryLowerWithAmountParameters[] orders0; 14 | /// @dev Represents the order parameters for token1 15 | BoundaryLowerWithAmountParameters[] orders1; 16 | } 17 | 18 | /// @dev Parameters for placing an order 19 | struct PlaceOrderParameters { 20 | /// @dev The address to receive the order 21 | address recipient; 22 | /// @dev When zero is true, it represents token0, otherwise it represents token1 23 | bool zero; 24 | /// @dev The lower boundary of the order 25 | int24 boundaryLower; 26 | /// @dev The amount of token0 or token1 to add 27 | uint128 amount; 28 | } 29 | 30 | struct PlaceOrderInBatchParameters { 31 | /// @dev The address to receive the order 32 | address recipient; 33 | /// @dev When zero is true, it represents token0, otherwise it represents token1 34 | bool zero; 35 | BoundaryLowerWithAmountParameters[] orders; 36 | } 37 | 38 | struct BoundaryLowerWithAmountParameters { 39 | /// @dev The lower boundary of the order 40 | int24 boundaryLower; 41 | /// @dev The amount of token0 or token1 to add 42 | uint128 amount; 43 | } 44 | 45 | /// @dev Status during swap 46 | struct SwapState { 47 | /// @dev When true, token0 is swapped for token1, otherwise token1 is swapped for token0 48 | bool zeroForOne; 49 | /// @dev The remaining amount of the swap, which implicitly configures 50 | /// the swap as exact input (positive), or exact output (negative) 51 | int256 amountSpecifiedRemaining; 52 | /// @dev The calculated amount to be inputted 53 | uint256 amountInputCalculated; 54 | /// @dev The calculated amount of fee to be inputted 55 | uint256 feeAmountInputCalculated; 56 | /// @dev The calculated amount to be outputted 57 | uint256 amountOutputCalculated; 58 | /// @dev The price of the grid, as a Q64.96 59 | uint160 priceX96; 60 | uint160 priceLimitX96; 61 | /// @dev The boundary of the grid 62 | int24 boundary; 63 | /// @dev The lower boundary of the grid 64 | int24 boundaryLower; 65 | uint160 initializedBoundaryLowerPriceX96; 66 | uint160 initializedBoundaryUpperPriceX96; 67 | /// @dev Whether the swap has been completed 68 | bool stopSwap; 69 | } 70 | 71 | struct SwapForBoundaryState { 72 | /// @dev The price indicated by the lower boundary, as a Q64.96 73 | uint160 boundaryLowerPriceX96; 74 | /// @dev The price indicated by the upper boundary, as a Q64.96 75 | uint160 boundaryUpperPriceX96; 76 | /// @dev The price indicated by the lower or upper boundary, as a Q64.96. 77 | /// When using token0 to exchange token1, it is equal to boundaryLowerPriceX96, 78 | /// otherwise it is equal to boundaryUpperPriceX96 79 | uint160 boundaryPriceX96; 80 | /// @dev The price of the grid, as a Q64.96 81 | uint160 priceX96; 82 | } 83 | 84 | struct UpdateBundleForTakerParameters { 85 | /// @dev The amount to be swapped in to bundle0 86 | uint256 amountInUsed; 87 | /// @dev The remaining amount to be swapped in to bundle1 88 | uint256 amountInRemaining; 89 | /// @dev The amount to be swapped out to bundle0 90 | uint128 amountOutUsed; 91 | /// @dev The remaining amount to be swapped out to bundle1 92 | uint128 amountOutRemaining; 93 | /// @dev The amount to be paid to bundle0 94 | uint128 takerFeeForMakerAmountUsed; 95 | /// @dev The amount to be paid to bundle1 96 | uint128 takerFeeForMakerAmountRemaining; 97 | } 98 | } 99 | -------------------------------------------------------------------------------- /contracts/interfaces/IGridStructs.sol: -------------------------------------------------------------------------------- 1 | // SPDX-License-Identifier: GPL-2.0-or-later 2 | pragma solidity ^0.8.0; 3 | 4 | interface IGridStructs { 5 | struct Bundle { 6 | int24 boundaryLower; 7 | bool zero; 8 | uint128 makerAmountTotal; 9 | uint128 makerAmountRemaining; 10 | uint128 takerAmountRemaining; 11 | uint128 takerFeeAmountRemaining; 12 | } 13 | 14 | struct Boundary { 15 | uint64 bundle0Id; 16 | uint64 bundle1Id; 17 | uint128 makerAmountRemaining; 18 | } 19 | 20 | struct Order { 21 | uint64 bundleId; 22 | address owner; 23 | uint128 amount; 24 | } 25 | 26 | struct TokensOwed { 27 | uint128 token0; 28 | uint128 token1; 29 | } 30 | 31 | struct Slot0 { 32 | uint160 priceX96; 33 | int24 boundary; 34 | uint32 blockTimestamp; 35 | bool unlocked; 36 | } 37 | } 38 | -------------------------------------------------------------------------------- /contracts/interfaces/IPriceOracle.sol: -------------------------------------------------------------------------------- 1 | // SPDX-License-Identifier: GPL-2.0-or-later 2 | pragma solidity ^0.8.0; 3 | 4 | /// @title The interface for the price oracle 5 | interface IPriceOracle { 6 | /// @notice Emitted when the capacity of the array in which the oracle can store prices has increased. 7 | /// @param grid The grid address whose capacity has been increased 8 | /// @param capacityOld Array capacity before the increase in capacity 9 | /// @param capacityNew Array capacity after the increase in capacity 10 | event IncreaseCapacity(address indexed grid, uint16 capacityOld, uint16 capacityNew); 11 | 12 | struct GridPriceData { 13 | /// @dev The block timestamp of the price data 14 | uint32 blockTimestamp; 15 | /// @dev The time-cumulative boundary 16 | int56 boundaryCumulative; 17 | /// @dev Whether or not the price data is initialized 18 | bool initialized; 19 | } 20 | 21 | struct GridOracleState { 22 | /// @dev The index of the last updated price 23 | uint16 index; 24 | /// @dev The array capacity used by the oracle 25 | uint16 capacity; 26 | /// @dev The capacity of the array that the oracle can use 27 | uint16 capacityNext; 28 | } 29 | 30 | /// @notice Returns the state of the oracle for a given grid 31 | /// @param grid The grid to retrieve the state of 32 | /// @return index The index of the last updated price 33 | /// @return capacity The array capacity used by the oracle 34 | /// @return capacityNext The capacity of the array that the oracle can use 35 | function gridOracleStates(address grid) external view returns (uint16 index, uint16 capacity, uint16 capacityNext); 36 | 37 | /// @notice Returns the price data of the oracle for a given grid and index 38 | /// @param grid The grid to get the price data of 39 | /// @param index The index of the price data to get 40 | /// @return blockTimestamp The block timestamp of the price data 41 | /// @return boundaryCumulative The time-cumulative boundary 42 | /// @return initialized Whether or not the price data is initialized 43 | function gridPriceData( 44 | address grid, 45 | uint256 index 46 | ) external view returns (uint32 blockTimestamp, int56 boundaryCumulative, bool initialized); 47 | 48 | /// @notice Register a grid to the oracle using a given token pair and resolution 49 | /// @param tokenA The contract address of either token0 or token1 50 | /// @param tokenB The contract address of the other token 51 | /// @param resolution The step size in initialized boundaries for a grid created with a given fee 52 | function register(address tokenA, address tokenB, int24 resolution) external; 53 | 54 | /// @notice Update the oracle price 55 | /// @param boundary The new boundary to write to the oracle 56 | /// @param blockTimestamp The timestamp of the oracle price to write 57 | function update(int24 boundary, uint32 blockTimestamp) external; 58 | 59 | /// @notice Increase the storage capacity of the oracle 60 | /// @param grid The grid whose capacity is to be increased 61 | /// @param capacityNext Array capacity after increase in capacity 62 | function increaseCapacity(address grid, uint16 capacityNext) external; 63 | 64 | /// @notice Get the time-cumulative price for a given time 65 | /// @param grid Get the price of a grid address 66 | /// @param secondsAgo The time elapsed (in seconds) to get the boundary for 67 | /// @return boundaryCumulative The time-cumulative boundary for the given time 68 | function getBoundaryCumulative(address grid, uint32 secondsAgo) external view returns (int56 boundaryCumulative); 69 | 70 | /// @notice Get a list of time-cumulative boundaries for given times 71 | /// @param grid The grid address to get the boundaries of 72 | /// @param secondsAgos A list of times elapsed (in seconds) to get the boundaries for 73 | /// @return boundaryCumulatives The list of time-cumulative boundaries for the given times 74 | function getBoundaryCumulatives( 75 | address grid, 76 | uint32[] calldata secondsAgos 77 | ) external view returns (int56[] memory boundaryCumulatives); 78 | } 79 | -------------------------------------------------------------------------------- /contracts/interfaces/IWETHMinimum.sol: -------------------------------------------------------------------------------- 1 | // SPDX-License-Identifier: GPL-2.0-or-later 2 | pragma solidity ^0.8.0; 3 | 4 | interface IWETHMinimum { 5 | function deposit() external payable; 6 | 7 | function transfer(address dst, uint256 wad) external returns (bool); 8 | 9 | function withdraw(uint256) external; 10 | 11 | function approve(address guy, uint256 wad) external returns (bool); 12 | 13 | function balanceOf(address dst) external view returns (uint256); 14 | } 15 | -------------------------------------------------------------------------------- /contracts/interfaces/callback/IGridFlashCallback.sol: -------------------------------------------------------------------------------- 1 | // SPDX-License-Identifier: GPL-2.0-or-later 2 | pragma solidity ^0.8.0; 3 | 4 | /// @title Callback for IGrid#flash 5 | /// @notice Any contract that calls IGrid#flash must implement this interface 6 | interface IGridFlashCallback { 7 | /// @notice Called to `msg.sender` after executing a flash via IGrid#flash 8 | /// @dev In this implementation, you are required to repay the grid the tokens owed for the flash. 9 | /// The caller of the method must be a grid deployed by the canonical GridFactory. 10 | /// @param data Any data passed through by the caller via the IGrid@flash call 11 | function gridexFlashCallback(bytes calldata data) external; 12 | } 13 | -------------------------------------------------------------------------------- /contracts/interfaces/callback/IGridPlaceMakerOrderCallback.sol: -------------------------------------------------------------------------------- 1 | // SPDX-License-Identifier: GPL-2.0-or-later 2 | pragma solidity ^0.8.0; 3 | 4 | /// @title Callback for IGrid#placeMakerOrder 5 | /// @notice Any contract that calls IGrid#placeMakerOrder must implement this interface 6 | interface IGridPlaceMakerOrderCallback { 7 | /// @notice Called to `msg.sender` after executing a place maker order via IGrid#placeMakerOrder 8 | /// @dev In this implementation, you are required to pay the grid tokens owed for the maker order. 9 | /// The caller of the method must be a grid deployed by the canonical GridFactory. 10 | /// At most one of amount0 and amount1 is a positive number 11 | /// @param amount0 The grid will receive the amount of token0 upon placement of the maker order. 12 | /// In the receiving case, the callback must send this amount of token0 to the grid 13 | /// @param amount1 The grid will receive the amount of token1 upon placement of the maker order. 14 | /// In the receiving case, the callback must send this amount of token1 to the grid 15 | /// @param data Any data passed through by the caller via the IGrid#placeMakerOrder call 16 | function gridexPlaceMakerOrderCallback(uint256 amount0, uint256 amount1, bytes calldata data) external; 17 | } 18 | -------------------------------------------------------------------------------- /contracts/interfaces/callback/IGridSwapCallback.sol: -------------------------------------------------------------------------------- 1 | // SPDX-License-Identifier: GPL-2.0-or-later 2 | pragma solidity ^0.8.0; 3 | 4 | /// @title Callback for IGrid#swap 5 | /// @notice Any contract that calls IGrid#swap must implement this interface 6 | interface IGridSwapCallback { 7 | /// @notice Called to `msg.sender` after executing a swap via IGrid#swap 8 | /// @dev In this implementation, you are required to pay the grid tokens owed for the swap. 9 | /// The caller of the method must be a grid deployed by the canonical GridFactory. 10 | /// If there is no token swap, both amount0Delta and amount1Delta are 0 11 | /// @param amount0Delta The grid will send or receive the amount of token0 upon completion of the swap. 12 | /// In the receiving case, the callback must send this amount of token0 to the grid 13 | /// @param amount1Delta The grid will send or receive the quantity of token1 upon completion of the swap. 14 | /// In the receiving case, the callback must send this amount of token1 to the grid 15 | /// @param data Any data passed through by the caller via the IGrid#swap call 16 | function gridexSwapCallback(int256 amount0Delta, int256 amount1Delta, bytes calldata data) external; 17 | } 18 | -------------------------------------------------------------------------------- /contracts/libraries/BitMath.sol: -------------------------------------------------------------------------------- 1 | // SPDX-License-Identifier: GPL-2.0-or-later 2 | pragma solidity ^0.8.0; 3 | 4 | /// @title BitMath 5 | /// @dev Library for computing the bit properties of unsigned integer 6 | library BitMath { 7 | /// @notice Returns the index of the most significant bit of the number, 8 | /// where the least significant bit is at index 0 and the most significant bit is at index 255 9 | /// @dev The function satisfies the property: 10 | /// x >= 2**mostSignificantBit(x) and x < 2**(mostSignificantBit(x)+1) 11 | /// @param x the value for which to compute the most significant bit, must be greater than 0 12 | /// @return r the index of the most significant bit 13 | function mostSignificantBit(uint256 x) internal pure returns (uint8 r) { 14 | require(x > 0); 15 | 16 | unchecked { 17 | if (x >= 0x100000000000000000000000000000000) { 18 | x >>= 128; 19 | r += 128; 20 | } 21 | if (x >= 0x10000000000000000) { 22 | x >>= 64; 23 | r += 64; 24 | } 25 | if (x >= 0x100000000) { 26 | x >>= 32; 27 | r += 32; 28 | } 29 | if (x >= 0x10000) { 30 | x >>= 16; 31 | r += 16; 32 | } 33 | if (x >= 0x100) { 34 | x >>= 8; 35 | r += 8; 36 | } 37 | if (x >= 0x10) { 38 | x >>= 4; 39 | r += 4; 40 | } 41 | if (x >= 0x4) { 42 | x >>= 2; 43 | r += 2; 44 | } 45 | if (x >= 0x2) { 46 | r += 1; 47 | } 48 | } 49 | } 50 | 51 | /// @notice Returns the index of the least significant bit of the number, 52 | /// where the least significant bit is at index 0 and the most significant bit is at index 255 53 | /// @dev The function satisfies the property: 54 | /// (x & 2**leastSignificantBit(x)) != 0 and (x & (2**(leastSignificantBit(x)) - 1)) == 0) 55 | /// @param x the value for which to compute the least significant bit, must be greater than 0 56 | /// @return r the index of the least significant bit 57 | function leastSignificantBit(uint256 x) internal pure returns (uint8 r) { 58 | require(x > 0); 59 | 60 | r = 255; 61 | unchecked { 62 | if (x & type(uint128).max > 0) { 63 | r -= 128; 64 | } else { 65 | x >>= 128; 66 | } 67 | if (x & type(uint64).max > 0) { 68 | r -= 64; 69 | } else { 70 | x >>= 64; 71 | } 72 | if (x & type(uint32).max > 0) { 73 | r -= 32; 74 | } else { 75 | x >>= 32; 76 | } 77 | if (x & type(uint16).max > 0) { 78 | r -= 16; 79 | } else { 80 | x >>= 16; 81 | } 82 | if (x & type(uint8).max > 0) { 83 | r -= 8; 84 | } else { 85 | x >>= 8; 86 | } 87 | if (x & 0xf > 0) { 88 | r -= 4; 89 | } else { 90 | x >>= 4; 91 | } 92 | if (x & 0x3 > 0) { 93 | r -= 2; 94 | } else { 95 | x >>= 2; 96 | } 97 | if (x & 0x1 > 0) { 98 | r -= 1; 99 | } 100 | } 101 | } 102 | } 103 | -------------------------------------------------------------------------------- /contracts/libraries/BoundaryBitmap.sol: -------------------------------------------------------------------------------- 1 | // SPDX-License-Identifier: GPL-2.0-or-later 2 | pragma solidity ^0.8.0; 3 | 4 | import "./BitMath.sol"; 5 | import "./BoundaryMath.sol"; 6 | 7 | library BoundaryBitmap { 8 | /// @notice Calculates the position of the bit in the bitmap for a given boundary 9 | /// @param boundary The boundary for calculating the bit position 10 | /// @return wordPos The key within the mapping that contains the word storing the bit 11 | /// @return bitPos The bit position in the word where the flag is stored 12 | function position(int24 boundary) internal pure returns (int16 wordPos, uint8 bitPos) { 13 | wordPos = int16(boundary >> 8); 14 | bitPos = uint8(uint24(boundary)); 15 | } 16 | 17 | /// @notice Flips the boolean value of the initialization state of the given boundary 18 | /// @param self The mapping that stores the initial state of the boundary 19 | /// @param boundary The boundary to flip 20 | /// @param resolution The step size in initialized boundaries for a grid created with a given fee 21 | function flipBoundary(mapping(int16 => uint256) storage self, int24 boundary, int24 resolution) internal { 22 | require(boundary % resolution == 0); 23 | (int16 wordPos, uint8 bitPos) = position(boundary / resolution); 24 | uint256 mask = 1 << bitPos; 25 | self[wordPos] ^= mask; 26 | } 27 | 28 | /// @notice Returns the next initialized boundary as the left boundary (less than) 29 | /// or the right boundary (more than) 30 | /// @param self The mapping that stores the initial state of the boundary 31 | /// @param boundary The starting boundary 32 | /// @param priceX96 Price of the initial boundary, as a Q64.96 33 | /// @param currentBoundaryInitialized Whether the starting boundary is initialized or not 34 | /// @param resolution The step size in initialized boundaries for a grid created with a given fee 35 | /// @param boundaryLower The starting lower boundary of the grid 36 | /// @param lte Whether or not to search for the next initialized boundary 37 | /// to the left (less than or equal to the starting boundary) 38 | /// @return next The next boundary, regardless of initialization state 39 | /// @return initialized Whether or not the next boundary is initialized 40 | /// @return initializedBoundaryLowerPriceX96 If the current boundary has been initialized and can be swapped, 41 | /// return the current left boundary price, otherwise return 0, as a Q64.96 42 | /// @return initializedBoundaryUpperPriceX96 If the current boundary has been initialized and can be swapped, 43 | /// return the current right boundary price, otherwise return 0, as a Q64.96 44 | function nextInitializedBoundary( 45 | mapping(int16 => uint256) storage self, 46 | int24 boundary, 47 | uint160 priceX96, 48 | bool currentBoundaryInitialized, 49 | int24 resolution, 50 | int24 boundaryLower, 51 | bool lte 52 | ) 53 | internal 54 | view 55 | returns ( 56 | int24 next, 57 | bool initialized, 58 | uint160 initializedBoundaryLowerPriceX96, 59 | uint160 initializedBoundaryUpperPriceX96 60 | ) 61 | { 62 | int24 boundaryUpper = boundaryLower + resolution; 63 | if (currentBoundaryInitialized) { 64 | if (lte) { 65 | uint160 boundaryLowerPriceX96 = BoundaryMath.getPriceX96AtBoundary(boundaryLower); 66 | if (boundaryLowerPriceX96 < priceX96) { 67 | return ( 68 | boundaryLower, 69 | true, 70 | boundaryLowerPriceX96, 71 | BoundaryMath.getPriceX96AtBoundary(boundaryUpper) 72 | ); 73 | } 74 | } else { 75 | uint160 boundaryUpperPriceX96 = BoundaryMath.getPriceX96AtBoundary(boundaryUpper); 76 | if (boundaryUpperPriceX96 > priceX96) { 77 | return ( 78 | boundaryLower, 79 | true, 80 | BoundaryMath.getPriceX96AtBoundary(boundaryLower), 81 | boundaryUpperPriceX96 82 | ); 83 | } 84 | } 85 | } 86 | 87 | // When the price is rising and the current boundary coincides with the upper boundary, start searching 88 | // from the lower boundary. Otherwise, start searching from the current boundary 89 | boundary = !lte && boundaryUpper == boundary ? boundaryLower : boundary; 90 | while (BoundaryMath.isInRange(boundary)) { 91 | (next, initialized) = nextInitializedBoundaryWithinOneWord(self, boundary, resolution, lte); 92 | if (initialized) { 93 | unchecked { 94 | return ( 95 | next, 96 | true, 97 | BoundaryMath.getPriceX96AtBoundary(next), 98 | BoundaryMath.getPriceX96AtBoundary(next + resolution) 99 | ); 100 | } 101 | } 102 | boundary = next; 103 | } 104 | } 105 | 106 | /// @notice Returns the next initialized boundary contained in the same (or adjacent) word 107 | /// as the boundary that is either to the left (less than) or right (greater than) 108 | /// of the given boundary 109 | /// @param self The mapping that stores the initial state of the boundary 110 | /// @param boundary The starting boundary 111 | /// @param resolution The step size in initialized boundaries for a grid created with a given fee 112 | /// @param lte Whether or not to search to the left (less than or equal to the start boundary) 113 | /// for the next initialization 114 | /// @return next The next boundary, regardless of initialization state 115 | /// @return initialized Whether or not the next boundary is initialized 116 | function nextInitializedBoundaryWithinOneWord( 117 | mapping(int16 => uint256) storage self, 118 | int24 boundary, 119 | int24 resolution, 120 | bool lte 121 | ) internal view returns (int24 next, bool initialized) { 122 | int24 compressed = boundary / resolution; 123 | 124 | if (lte) { 125 | // Begin from the word of the next boundary, since the current boundary state is immaterial 126 | (int16 wordPos, uint8 bitPos) = position(compressed - 1); 127 | // all the 1s at or to the right of the current bitPos 128 | uint256 mask = ~uint256(0) >> (type(uint8).max - bitPos); 129 | uint256 masked = self[wordPos] & mask; 130 | 131 | // If no initialized boundaries exist to the right of the current boundary, 132 | // return the rightmost boundary in the word 133 | initialized = masked != 0; 134 | // Overflow/underflow is possible. The resolution and the boundary should be limited 135 | // when calling externally to prevent this 136 | next = initialized 137 | ? (compressed - 1 - int24(uint24(bitPos - BitMath.mostSignificantBit(masked)))) * resolution 138 | : (compressed - 1 - int24(uint24(bitPos))) * resolution; 139 | } else { 140 | if (boundary < 0 && boundary % resolution != 0) { 141 | // round towards negative infinity 142 | --compressed; 143 | } 144 | 145 | // Begin from the word of the next boundary, since the current boundary state is immaterial 146 | (int16 wordPos, uint8 bitPos) = position(compressed + 1); 147 | // all the 1s at or to the left of the bitPos 148 | uint256 mask = ~uint256(0) << bitPos; 149 | uint256 masked = self[wordPos] & mask; 150 | 151 | // If no initialized boundaries exist to the left of the current boundary, 152 | // return the leftmost boundary in the word 153 | initialized = masked != 0; 154 | // Overflow/underflow is possible. The resolution and the boundary should be limited 155 | // when calling externally to prevent this 156 | next = initialized 157 | ? (compressed + 1 + int24(uint24(BitMath.leastSignificantBit(masked) - bitPos))) * resolution 158 | : (compressed + 1 + int24(uint24(type(uint8).max - bitPos))) * resolution; 159 | } 160 | } 161 | } 162 | -------------------------------------------------------------------------------- /contracts/libraries/BoundaryMath.sol: -------------------------------------------------------------------------------- 1 | // SPDX-License-Identifier: GPL-2.0-or-later 2 | pragma solidity ^0.8.0; 3 | 4 | library BoundaryMath { 5 | int24 public constant MIN_BOUNDARY = -527400; 6 | int24 public constant MAX_BOUNDARY = 443635; 7 | 8 | /// @dev The minimum value that can be returned from #getPriceX96AtBoundary. Equivalent to getPriceX96AtBoundary(MIN_BOUNDARY) 9 | uint160 internal constant MIN_RATIO = 989314; 10 | /// @dev The maximum value that can be returned from #getPriceX96AtBoundary. Equivalent to getPriceX96AtBoundary(MAX_BOUNDARY) 11 | uint160 internal constant MAX_RATIO = 1461300573427867316570072651998408279850435624081; 12 | 13 | /// @dev Checks if a boundary is divisible by a resolution 14 | /// @param boundary The boundary to check 15 | /// @param resolution The step size in initialized boundaries for a grid created with a given fee 16 | /// @return isValid Whether or not the boundary is valid 17 | function isValidBoundary(int24 boundary, int24 resolution) internal pure returns (bool isValid) { 18 | return boundary % resolution == 0; 19 | } 20 | 21 | /// @dev Checks if a boundary is within the valid range 22 | /// @param boundary The boundary to check 23 | /// @return inRange Whether or not the boundary is in range 24 | function isInRange(int24 boundary) internal pure returns (bool inRange) { 25 | return boundary >= MIN_BOUNDARY && boundary <= MAX_BOUNDARY; 26 | } 27 | 28 | /// @dev Checks if a price is within the valid range 29 | /// @param priceX96 The price to check, as a Q64.96 30 | /// @return inRange Whether or not the price is in range 31 | function isPriceX96InRange(uint160 priceX96) internal pure returns (bool inRange) { 32 | return priceX96 >= MIN_RATIO && priceX96 <= MAX_RATIO; 33 | } 34 | 35 | /// @notice Calculates the price at a given boundary 36 | /// @dev priceX96 = pow(1.0001, boundary) * 2**96 37 | /// @param boundary The boundary to calculate the price at 38 | /// @return priceX96 The price at the boundary, as a Q64.96 39 | function getPriceX96AtBoundary(int24 boundary) internal pure returns (uint160 priceX96) { 40 | unchecked { 41 | uint256 absBoundary = boundary < 0 ? uint256(-int256(boundary)) : uint24(boundary); 42 | 43 | uint256 ratio = absBoundary & 0x1 != 0 44 | ? 0xfff97272373d413259a46990580e213a 45 | : 0x100000000000000000000000000000000; 46 | if (absBoundary & 0x2 != 0) ratio = (ratio * 0xfff2e50f5f656932ef12357cf3c7fdcc) >> 128; 47 | if (absBoundary & 0x4 != 0) ratio = (ratio * 0xffe5caca7e10e4e61c3624eaa0941cd0) >> 128; 48 | if (absBoundary & 0x8 != 0) ratio = (ratio * 0xffcb9843d60f6159c9db58835c926644) >> 128; 49 | if (absBoundary & 0x10 != 0) ratio = (ratio * 0xff973b41fa98c081472e6896dfb254c0) >> 128; 50 | if (absBoundary & 0x20 != 0) ratio = (ratio * 0xff2ea16466c96a3843ec78b326b52861) >> 128; 51 | if (absBoundary & 0x40 != 0) ratio = (ratio * 0xfe5dee046a99a2a811c461f1969c3053) >> 128; 52 | if (absBoundary & 0x80 != 0) ratio = (ratio * 0xfcbe86c7900a88aedcffc83b479aa3a4) >> 128; 53 | if (absBoundary & 0x100 != 0) ratio = (ratio * 0xf987a7253ac413176f2b074cf7815e54) >> 128; 54 | if (absBoundary & 0x200 != 0) ratio = (ratio * 0xf3392b0822b70005940c7a398e4b70f3) >> 128; 55 | if (absBoundary & 0x400 != 0) ratio = (ratio * 0xe7159475a2c29b7443b29c7fa6e889d9) >> 128; 56 | if (absBoundary & 0x800 != 0) ratio = (ratio * 0xd097f3bdfd2022b8845ad8f792aa5825) >> 128; 57 | if (absBoundary & 0x1000 != 0) ratio = (ratio * 0xa9f746462d870fdf8a65dc1f90e061e5) >> 128; 58 | if (absBoundary & 0x2000 != 0) ratio = (ratio * 0x70d869a156d2a1b890bb3df62baf32f7) >> 128; 59 | if (absBoundary & 0x4000 != 0) ratio = (ratio * 0x31be135f97d08fd981231505542fcfa6) >> 128; 60 | if (absBoundary & 0x8000 != 0) ratio = (ratio * 0x9aa508b5b7a84e1c677de54f3e99bc9) >> 128; 61 | if (absBoundary & 0x10000 != 0) ratio = (ratio * 0x5d6af8dedb81196699c329225ee604) >> 128; 62 | if (absBoundary & 0x20000 != 0) ratio = (ratio * 0x2216e584f5fa1ea926041bedfe98) >> 128; 63 | if (absBoundary & 0x40000 != 0) ratio = (ratio * 0x48a170391f7dc42444e8fa2) >> 128; 64 | if (absBoundary & 0x80000 != 0) ratio = (ratio * 0x149b34ee7ac263) >> 128; 65 | 66 | if (boundary > 0) ratio = type(uint256).max / ratio; 67 | 68 | // this divides by 1<<32 and rounds up to go from a Q128.128 to a Q128.96. 69 | // due to out boundary input limitations, we then proceed to downcast as the 70 | // result will always fit within 160 bits. 71 | // we round up in the division so that getBoundaryAtPriceX96 of the output price is always consistent 72 | priceX96 = uint160((ratio + 0xffffffff) >> 32); 73 | } 74 | } 75 | 76 | /// @notice Calculates the boundary at a given price 77 | /// @param priceX96 The price to calculate the boundary at, as a Q64.96 78 | /// @return boundary The boundary at the price 79 | function getBoundaryAtPriceX96(uint160 priceX96) internal pure returns (int24 boundary) { 80 | unchecked { 81 | uint256 ratio = uint256(priceX96) << 32; 82 | 83 | uint256 r = ratio; 84 | uint256 msb = 0; 85 | 86 | assembly { 87 | let f := shl(7, gt(r, 0xFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFF)) 88 | msb := or(msb, f) 89 | r := shr(f, r) 90 | } 91 | assembly { 92 | let f := shl(6, gt(r, 0xFFFFFFFFFFFFFFFF)) 93 | msb := or(msb, f) 94 | r := shr(f, r) 95 | } 96 | assembly { 97 | let f := shl(5, gt(r, 0xFFFFFFFF)) 98 | msb := or(msb, f) 99 | r := shr(f, r) 100 | } 101 | assembly { 102 | let f := shl(4, gt(r, 0xFFFF)) 103 | msb := or(msb, f) 104 | r := shr(f, r) 105 | } 106 | assembly { 107 | let f := shl(3, gt(r, 0xFF)) 108 | msb := or(msb, f) 109 | r := shr(f, r) 110 | } 111 | assembly { 112 | let f := shl(2, gt(r, 0xF)) 113 | msb := or(msb, f) 114 | r := shr(f, r) 115 | } 116 | assembly { 117 | let f := shl(1, gt(r, 0x3)) 118 | msb := or(msb, f) 119 | r := shr(f, r) 120 | } 121 | assembly { 122 | let f := gt(r, 0x1) 123 | msb := or(msb, f) 124 | } 125 | 126 | if (msb >= 128) r = ratio >> (msb - 127); 127 | else r = ratio << (127 - msb); 128 | 129 | int256 log_2 = (int256(msb) - 128) << 64; 130 | 131 | assembly { 132 | r := shr(127, mul(r, r)) 133 | let f := shr(128, r) 134 | log_2 := or(log_2, shl(63, f)) 135 | r := shr(f, r) 136 | } 137 | assembly { 138 | r := shr(127, mul(r, r)) 139 | let f := shr(128, r) 140 | log_2 := or(log_2, shl(62, f)) 141 | r := shr(f, r) 142 | } 143 | assembly { 144 | r := shr(127, mul(r, r)) 145 | let f := shr(128, r) 146 | log_2 := or(log_2, shl(61, f)) 147 | r := shr(f, r) 148 | } 149 | assembly { 150 | r := shr(127, mul(r, r)) 151 | let f := shr(128, r) 152 | log_2 := or(log_2, shl(60, f)) 153 | r := shr(f, r) 154 | } 155 | assembly { 156 | r := shr(127, mul(r, r)) 157 | let f := shr(128, r) 158 | log_2 := or(log_2, shl(59, f)) 159 | r := shr(f, r) 160 | } 161 | assembly { 162 | r := shr(127, mul(r, r)) 163 | let f := shr(128, r) 164 | log_2 := or(log_2, shl(58, f)) 165 | r := shr(f, r) 166 | } 167 | assembly { 168 | r := shr(127, mul(r, r)) 169 | let f := shr(128, r) 170 | log_2 := or(log_2, shl(57, f)) 171 | r := shr(f, r) 172 | } 173 | assembly { 174 | r := shr(127, mul(r, r)) 175 | let f := shr(128, r) 176 | log_2 := or(log_2, shl(56, f)) 177 | r := shr(f, r) 178 | } 179 | assembly { 180 | r := shr(127, mul(r, r)) 181 | let f := shr(128, r) 182 | log_2 := or(log_2, shl(55, f)) 183 | r := shr(f, r) 184 | } 185 | assembly { 186 | r := shr(127, mul(r, r)) 187 | let f := shr(128, r) 188 | log_2 := or(log_2, shl(54, f)) 189 | r := shr(f, r) 190 | } 191 | assembly { 192 | r := shr(127, mul(r, r)) 193 | let f := shr(128, r) 194 | log_2 := or(log_2, shl(53, f)) 195 | r := shr(f, r) 196 | } 197 | assembly { 198 | r := shr(127, mul(r, r)) 199 | let f := shr(128, r) 200 | log_2 := or(log_2, shl(52, f)) 201 | r := shr(f, r) 202 | } 203 | assembly { 204 | r := shr(127, mul(r, r)) 205 | let f := shr(128, r) 206 | log_2 := or(log_2, shl(51, f)) 207 | r := shr(f, r) 208 | } 209 | assembly { 210 | r := shr(127, mul(r, r)) 211 | let f := shr(128, r) 212 | log_2 := or(log_2, shl(50, f)) 213 | } 214 | 215 | int256 log10001 = log_2 * 127869479499801913173570; 216 | // 128.128 number 217 | 218 | int24 boundaryLow = int24((log10001 - 1701496478404566090792001455681771637) >> 128); 219 | int24 boundaryHi = int24((log10001 + 289637967442836604689790891002483458648) >> 128); 220 | 221 | boundary = boundaryLow == boundaryHi ? boundaryLow : getPriceX96AtBoundary(boundaryHi) <= priceX96 222 | ? boundaryHi 223 | : boundaryLow; 224 | } 225 | } 226 | 227 | /// @dev Returns the lower boundary for the given boundary and resolution. 228 | /// The lower boundary may not be valid (if out of the boundary range) 229 | /// @param boundary The boundary to get the lower boundary for 230 | /// @param resolution The step size in initialized boundaries for a grid created with a given fee 231 | /// @return boundaryLower The lower boundary for the given boundary and resolution 232 | function getBoundaryLowerAtBoundary(int24 boundary, int24 resolution) internal pure returns (int24 boundaryLower) { 233 | unchecked { 234 | return boundary - (((boundary % resolution) + resolution) % resolution); 235 | } 236 | } 237 | 238 | /// @dev Rewrite the lower boundary that is not in the range to a valid value 239 | /// @param boundaryLower The lower boundary to rewrite 240 | /// @param resolution The step size in initialized boundaries for a grid created with a given fee 241 | /// @return validBoundaryLower The valid lower boundary 242 | function rewriteToValidBoundaryLower( 243 | int24 boundaryLower, 244 | int24 resolution 245 | ) internal pure returns (int24 validBoundaryLower) { 246 | unchecked { 247 | if (boundaryLower < MIN_BOUNDARY) return boundaryLower + resolution; 248 | else if (boundaryLower + resolution > MAX_BOUNDARY) return boundaryLower - resolution; 249 | else return boundaryLower; 250 | } 251 | } 252 | } 253 | -------------------------------------------------------------------------------- /contracts/libraries/BundleMath.sol: -------------------------------------------------------------------------------- 1 | // SPDX-License-Identifier: BUSL-1.1 2 | pragma solidity ^0.8.0; 3 | 4 | import "@openzeppelin/contracts/utils/math/Math.sol"; 5 | import "@openzeppelin/contracts/utils/math/SafeCast.sol"; 6 | import "../interfaces/IGridStructs.sol"; 7 | import "../interfaces/IGridParameters.sol"; 8 | import "./FixedPointX128.sol"; 9 | 10 | library BundleMath { 11 | using SafeCast for uint256; 12 | 13 | /// @dev Updates for a taker 14 | /// @param self The bundle 15 | /// @param amountIn The amount of swapped in token by the taker 16 | /// @param amountOut The amount of swapped out token by the taker. If amountOut is greater than bundle balance, the difference is transferred to bundle1 17 | /// @param takerFeeForMakerAmount The fee paid by the taker(excluding the protocol fee). If amountOut is greater than bundle balance, the difference is transferred to bundle1 18 | function updateForTaker( 19 | IGridStructs.Bundle storage self, 20 | uint256 amountIn, 21 | uint128 amountOut, 22 | uint128 takerFeeForMakerAmount 23 | ) internal returns (IGridParameters.UpdateBundleForTakerParameters memory parameters) { 24 | uint128 makerAmountRemaining = self.makerAmountRemaining; 25 | // the amount out actually paid to the taker 26 | parameters.amountOutUsed = amountOut <= makerAmountRemaining ? amountOut : makerAmountRemaining; 27 | 28 | if (parameters.amountOutUsed == amountOut) { 29 | parameters.amountInUsed = amountIn; 30 | 31 | parameters.takerFeeForMakerAmountUsed = takerFeeForMakerAmount; 32 | } else { 33 | parameters.amountInUsed = parameters.amountOutUsed * amountIn / amountOut; // amountOutUsed * amountIn may overflow here 34 | unchecked { 35 | parameters.amountInRemaining = amountIn - parameters.amountInUsed; 36 | 37 | parameters.amountOutRemaining = amountOut - parameters.amountOutUsed; 38 | 39 | parameters.takerFeeForMakerAmountUsed = uint128( 40 | (uint256(parameters.amountOutUsed) * takerFeeForMakerAmount) / amountOut 41 | ); 42 | parameters.takerFeeForMakerAmountRemaining = 43 | takerFeeForMakerAmount - 44 | parameters.takerFeeForMakerAmountUsed; 45 | } 46 | } 47 | 48 | // updates maker amount remaining 49 | unchecked { 50 | self.makerAmountRemaining = makerAmountRemaining - parameters.amountOutUsed; 51 | } 52 | 53 | self.takerAmountRemaining = self.takerAmountRemaining + (parameters.amountInUsed).toUint128(); 54 | 55 | self.takerFeeAmountRemaining = self.takerFeeAmountRemaining + parameters.takerFeeForMakerAmountUsed; 56 | } 57 | 58 | /// @notice Maker adds liquidity to the bundle 59 | /// @param self The bundle to be updated 60 | /// @param makerAmount The amount of token to be added to the bundle 61 | function addLiquidity(IGridStructs.Bundle storage self, uint128 makerAmount) internal { 62 | self.makerAmountTotal = self.makerAmountTotal + makerAmount; 63 | unchecked { 64 | self.makerAmountRemaining = self.makerAmountRemaining + makerAmount; 65 | } 66 | } 67 | 68 | /// @notice Maker adds liquidity to the bundle 69 | /// @param self The bundle to be updated 70 | /// @param makerAmountTotal The total amount of token that the maker has added to the bundle 71 | /// @param makerAmountRemaining The amount of token that the maker has not yet swapped 72 | /// @param makerAmount The amount of token to be added to the bundle 73 | function addLiquidityWithAmount( 74 | IGridStructs.Bundle storage self, 75 | uint128 makerAmountTotal, 76 | uint128 makerAmountRemaining, 77 | uint128 makerAmount 78 | ) internal { 79 | self.makerAmountTotal = makerAmountTotal + makerAmount; 80 | unchecked { 81 | self.makerAmountRemaining = makerAmountRemaining + makerAmount; 82 | } 83 | } 84 | 85 | /// @notice Maker removes liquidity from the bundle 86 | /// @param self The bundle to be updated 87 | /// @param makerAmountRaw The amount of liquidity added by the maker when placing an order 88 | /// @return makerAmountOut The amount of token0 or token1 that the maker will receive 89 | /// @return takerAmountOut The amount of token1 or token0 that the maker will receive 90 | /// @return takerFeeAmountOut The amount of fees that the maker will receive 91 | /// @return makerAmountTotalNew The remaining amount of liquidity added by the maker 92 | function removeLiquidity( 93 | IGridStructs.Bundle storage self, 94 | uint128 makerAmountRaw 95 | ) 96 | internal 97 | returns (uint128 makerAmountOut, uint128 takerAmountOut, uint128 takerFeeAmountOut, uint128 makerAmountTotalNew) 98 | { 99 | uint128 makerAmountTotal = self.makerAmountTotal; 100 | uint128 makerAmountRemaining = self.makerAmountRemaining; 101 | uint128 takerAmountRemaining = self.takerAmountRemaining; 102 | uint128 takerFeeAmountRemaining = self.takerFeeAmountRemaining; 103 | 104 | unchecked { 105 | makerAmountTotalNew = makerAmountTotal - makerAmountRaw; 106 | self.makerAmountTotal = makerAmountTotalNew; 107 | 108 | // This calculation won't overflow because makerAmountRaw divided by 109 | // makerAmountTotal will always have a value between 0 and 1 (excluding 0), and 110 | // multiplying that by a uint128 value won't result in an overflow. So the 111 | // calculation is designed to work within the constraints of the data types being used, 112 | // without exceeding their maximum values. 113 | makerAmountOut = uint128((uint256(makerAmountRaw) * makerAmountRemaining) / makerAmountTotal); 114 | self.makerAmountRemaining = makerAmountRemaining - makerAmountOut; 115 | 116 | takerAmountOut = uint128((uint256(makerAmountRaw) * takerAmountRemaining) / makerAmountTotal); 117 | self.takerAmountRemaining = takerAmountRemaining - takerAmountOut; 118 | 119 | takerFeeAmountOut = uint128((uint256(makerAmountRaw) * takerFeeAmountRemaining) / makerAmountTotal); 120 | self.takerFeeAmountRemaining = takerFeeAmountRemaining - takerFeeAmountOut; 121 | } 122 | } 123 | } 124 | -------------------------------------------------------------------------------- /contracts/libraries/CallbackValidator.sol: -------------------------------------------------------------------------------- 1 | // SPDX-License-Identifier: GPL-2.0-or-later 2 | pragma solidity ^0.8.0; 3 | 4 | import "./GridAddress.sol"; 5 | 6 | library CallbackValidator { 7 | /// @dev Validates the `msg.sender` is the canonical grid address for the given parameters 8 | /// @param gridFactory The address of the grid factory 9 | /// @param gridKey The grid key to compute the canonical address for the grid 10 | function validate(address gridFactory, GridAddress.GridKey memory gridKey) internal view { 11 | // CV_IC: invalid caller 12 | require(GridAddress.computeAddress(gridFactory, gridKey) == msg.sender, "CV_IC"); 13 | } 14 | } 15 | -------------------------------------------------------------------------------- /contracts/libraries/FixedPointX128.sol: -------------------------------------------------------------------------------- 1 | // SPDX-License-Identifier: GPL-2.0-or-later 2 | pragma solidity ^0.8.0; 3 | 4 | library FixedPointX128 { 5 | uint160 internal constant RESOLUTION = 1 << 128; 6 | uint160 internal constant Q = 0x100000000000000000000000000000000; 7 | } 8 | -------------------------------------------------------------------------------- /contracts/libraries/FixedPointX192.sol: -------------------------------------------------------------------------------- 1 | // SPDX-License-Identifier: GPL-2.0-or-later 2 | pragma solidity ^0.8.0; 3 | 4 | library FixedPointX192 { 5 | uint256 internal constant RESOLUTION = 1 << 192; 6 | uint256 internal constant Q = 0x1000000000000000000000000000000000000000000000000; 7 | } 8 | -------------------------------------------------------------------------------- /contracts/libraries/FixedPointX96.sol: -------------------------------------------------------------------------------- 1 | // SPDX-License-Identifier: GPL-2.0-or-later 2 | pragma solidity ^0.8.0; 3 | 4 | library FixedPointX96 { 5 | uint160 internal constant RESOLUTION = 1 << 96; 6 | uint160 internal constant Q = 0x1000000000000000000000000; 7 | uint160 internal constant Q_2 = 0x2000000000000000000000000; 8 | } 9 | -------------------------------------------------------------------------------- /contracts/libraries/GridAddress.sol: -------------------------------------------------------------------------------- 1 | // SPDX-License-Identifier: GPL-2.0-or-later 2 | pragma solidity ^0.8.0; 3 | 4 | import "@openzeppelin/contracts/utils/Create2.sol"; 5 | 6 | library GridAddress { 7 | bytes32 internal constant GRID_BYTES_CODE_HASH = 0x884a6891a166f885bf6f0a3b330a25e41d1761a5aa091110a229d9a0e34b2c36; 8 | 9 | struct GridKey { 10 | address token0; 11 | address token1; 12 | int24 resolution; 13 | } 14 | 15 | /// @notice Constructs the grid key for the given parameters 16 | /// @dev tokenA and tokenB may be passed in, in the order of either token0/token1 or token1/token0 17 | /// @param tokenA The contract address of either token0 or token1 18 | /// @param tokenB The contract address of the other token 19 | /// @param resolution The step size in initialized boundaries for a grid created with a given fee 20 | /// @return key The grid key to compute the canonical address for the grid 21 | function gridKey(address tokenA, address tokenB, int24 resolution) internal pure returns (GridKey memory key) { 22 | if (tokenA > tokenB) (tokenA, tokenB) = (tokenB, tokenA); 23 | 24 | return GridKey(tokenA, tokenB, resolution); 25 | } 26 | 27 | /// @dev Computes the CREATE2 address for a grid with the given parameters 28 | /// @param gridFactory The address of the grid factory 29 | /// @param key The grid key to compute the canonical address for the grid 30 | /// @return grid The computed address 31 | function computeAddress(address gridFactory, GridKey memory key) internal pure returns (address grid) { 32 | require(key.token0 < key.token1); 33 | return 34 | Create2.computeAddress( 35 | keccak256(abi.encode(key.token0, key.token1, key.resolution)), 36 | GRID_BYTES_CODE_HASH, 37 | gridFactory 38 | ); 39 | } 40 | } 41 | -------------------------------------------------------------------------------- /contracts/libraries/SwapMath.sol: -------------------------------------------------------------------------------- 1 | // SPDX-License-Identifier: BUSL-1.1 2 | pragma solidity ^0.8.0; 3 | 4 | import "@openzeppelin/contracts/utils/math/Math.sol"; 5 | import "@openzeppelin/contracts/utils/math/SafeCast.sol"; 6 | import "./Uint128Math.sol"; 7 | import "./FixedPointX96.sol"; 8 | import "./FixedPointX192.sol"; 9 | 10 | library SwapMath { 11 | using SafeCast for uint256; 12 | 13 | struct ComputeSwapStep { 14 | /// @dev The price after swapping the amount in/out 15 | uint160 priceNextX96; 16 | /// @dev The amount to be swapped in, of either token0 or token1, based on the direction of the swap 17 | uint256 amountIn; 18 | /// @dev The amount to be swapped out, of either token0 or token1, based on the direction of the swap 19 | uint128 amountOut; 20 | /// @dev The amount of fees paid by the taker 21 | uint128 feeAmount; 22 | } 23 | 24 | /// @notice Calculates the result of the swap through the given boundary parameters 25 | /// @param priceCurrentX96 The current price of the grid, as a Q64.96 26 | /// @param boundaryPriceX96 It is the upper boundary price when using token1 to exchange for token0. 27 | /// Otherwise, it is the lower boundary price, as a Q64.96 28 | /// @param priceLimitX96 The price limit of the swap, as a Q64.96 29 | /// @param amountRemaining The remaining amount to be swapped in (positive) or swapped out (negative) 30 | /// @param makerAmount The remaining amount of token0 or token1 that can be swapped out from the makers 31 | /// @param takerFeePips The taker fee, denominated in hundredths of a bip (i.e. 1e-6) 32 | /// @return step The result of the swap step 33 | function computeSwapStep( 34 | uint160 priceCurrentX96, 35 | uint160 boundaryPriceX96, 36 | uint160 priceLimitX96, 37 | int256 amountRemaining, 38 | uint128 makerAmount, 39 | int24 takerFeePips 40 | ) internal pure returns (ComputeSwapStep memory step) { 41 | if (amountRemaining > 0) { 42 | return 43 | computeSwapStepForExactIn( 44 | priceCurrentX96, 45 | boundaryPriceX96, 46 | priceLimitX96, 47 | uint256(amountRemaining), 48 | makerAmount, 49 | takerFeePips 50 | ); 51 | } else { 52 | uint256 absAmountRemaining; 53 | unchecked { 54 | absAmountRemaining = uint256(-amountRemaining); 55 | } 56 | return 57 | computeSwapStepForExactOut( 58 | priceCurrentX96, 59 | boundaryPriceX96, 60 | priceLimitX96, 61 | // The converted value will not overflow. The maximum amount of liquidity 62 | // allowed in each boundary is less than or equal to uint128. 63 | absAmountRemaining > makerAmount ? makerAmount : uint128(absAmountRemaining), 64 | makerAmount, 65 | takerFeePips 66 | ); 67 | } 68 | } 69 | 70 | function computeSwapStepForExactIn( 71 | uint160 priceCurrentX96, 72 | uint160 boundaryPriceX96, 73 | uint160 priceLimitX96, 74 | uint256 takerAmountInRemaining, 75 | uint128 makerAmount, 76 | int24 takerFeePips 77 | ) internal pure returns (ComputeSwapStep memory step) { 78 | if (!_priceInRange(priceCurrentX96, boundaryPriceX96, priceLimitX96)) { 79 | return 80 | _computeSwapStepForExactIn( 81 | priceCurrentX96, 82 | boundaryPriceX96, 83 | takerAmountInRemaining, 84 | makerAmount, 85 | takerFeePips 86 | ); 87 | } else { 88 | step.amountOut = _computeAmountOutForPriceLimit( 89 | priceCurrentX96, 90 | boundaryPriceX96, 91 | priceLimitX96, 92 | makerAmount 93 | ); 94 | 95 | step = _computeSwapStepForExactOut( 96 | priceCurrentX96, 97 | boundaryPriceX96, 98 | step.amountOut, 99 | makerAmount, 100 | takerFeePips 101 | ); 102 | return 103 | step.amountIn + step.feeAmount > takerAmountInRemaining // the remaining amount in is not enough to reach the limit price 104 | ? _computeSwapStepForExactIn( 105 | priceCurrentX96, 106 | boundaryPriceX96, 107 | takerAmountInRemaining, 108 | makerAmount, 109 | takerFeePips 110 | ) 111 | : step; 112 | } 113 | } 114 | 115 | function _computeSwapStepForExactIn( 116 | uint160 priceCurrentX96, 117 | uint160 boundaryPriceX96, 118 | uint256 takerAmountInRemaining, 119 | uint128 makerAmount, 120 | int24 takerFeePips 121 | ) private pure returns (ComputeSwapStep memory step) { 122 | bool zeroForOne = priceCurrentX96 >= boundaryPriceX96; 123 | 124 | uint256 takerAmountInWithoutFee = Math.mulDiv(takerAmountInRemaining, 1e6 - uint256(uint24(takerFeePips)), 1e6); 125 | 126 | uint160 priceDeltaX96; 127 | unchecked { 128 | priceDeltaX96 = zeroForOne ? priceCurrentX96 - boundaryPriceX96 : boundaryPriceX96 - priceCurrentX96; 129 | } 130 | 131 | uint256 amountOut; 132 | if (zeroForOne) { 133 | // (2 * takerAmountIn * priceCurrent) / (2 - (priceMax - priceCurrent) * takerAmountIn / makerAmount) 134 | uint256 numerator = 2 * takerAmountInWithoutFee * priceCurrentX96; 135 | 136 | uint256 denominator = Math.mulDiv( 137 | priceDeltaX96, 138 | takerAmountInWithoutFee, 139 | makerAmount, 140 | Math.Rounding.Up // round up 141 | ); 142 | 143 | amountOut = numerator / (FixedPointX96.Q_2 + denominator); 144 | } else { 145 | // ((2 * takerAmountIn * (1/priceCurrent) / (2 - (1/priceMax - 1/priceCurrent) * takerAmountIn / makerAmount)) 146 | // Specifically divide first, then multiply to ensure that the amountOut is smaller 147 | uint256 numerator = 2 * takerAmountInWithoutFee * (FixedPointX192.Q / priceCurrentX96); 148 | 149 | uint256 reversePriceDeltaX96 = Math.ceilDiv( 150 | FixedPointX192.Q, 151 | priceCurrentX96 // round up 152 | ) - (FixedPointX192.Q / boundaryPriceX96); 153 | uint256 denominator = Math.mulDiv( 154 | reversePriceDeltaX96, 155 | takerAmountInWithoutFee, 156 | makerAmount, 157 | Math.Rounding.Up // round up 158 | ); 159 | amountOut = numerator / (FixedPointX96.Q_2 + denominator); 160 | } 161 | 162 | if (amountOut > makerAmount) { 163 | step.priceNextX96 = boundaryPriceX96; 164 | step.amountOut = makerAmount; 165 | (step.amountIn, step.feeAmount) = _computeAmountInAndFeeAmount( 166 | zeroForOne, 167 | priceCurrentX96, 168 | boundaryPriceX96, 169 | makerAmount, 170 | Math.Rounding.Down, 171 | takerFeePips 172 | ); 173 | } else { 174 | step.amountOut = amountOut.toUint128(); 175 | step.priceNextX96 = _computePriceNextX96( 176 | zeroForOne, 177 | priceCurrentX96, 178 | priceDeltaX96, 179 | step.amountOut, 180 | makerAmount 181 | ); 182 | step.amountIn = takerAmountInWithoutFee; 183 | unchecked { 184 | step.feeAmount = (takerAmountInRemaining - takerAmountInWithoutFee).toUint128(); 185 | } 186 | } 187 | } 188 | 189 | function computeSwapStepForExactOut( 190 | uint160 priceCurrentX96, 191 | uint160 boundaryPriceX96, 192 | uint160 priceLimitX96, 193 | uint128 takerAmountOutRemaining, 194 | uint128 makerAmount, 195 | int24 takerFeePips 196 | ) internal pure returns (ComputeSwapStep memory step) { 197 | // if the limit price is not within the range, it will be calculated directly 198 | if (!_priceInRange(priceCurrentX96, boundaryPriceX96, priceLimitX96)) { 199 | return 200 | _computeSwapStepForExactOut( 201 | priceCurrentX96, 202 | boundaryPriceX96, 203 | takerAmountOutRemaining, 204 | makerAmount, 205 | takerFeePips 206 | ); 207 | } 208 | 209 | // otherwise calculate the new takerAmountRemaining value 210 | uint128 availableAmountOut = _computeAmountOutForPriceLimit( 211 | priceCurrentX96, 212 | boundaryPriceX96, 213 | priceLimitX96, 214 | makerAmount 215 | ); 216 | 217 | return 218 | _computeSwapStepForExactOut( 219 | priceCurrentX96, 220 | boundaryPriceX96, 221 | Uint128Math.minUint128(availableAmountOut, takerAmountOutRemaining), 222 | makerAmount, 223 | takerFeePips 224 | ); 225 | } 226 | 227 | /// @dev Checks if the price limit is within the range 228 | /// @param priceCurrentX96 The current price of the grid, as a Q64.96 229 | /// @param boundaryPriceX96 It is the upper boundary price when using token1 to exchange for token0. 230 | /// Otherwise, it is the lower boundary price, as a Q64.96 231 | /// @param priceLimitX96 The price limit of the swap, as a Q64.96 232 | /// @return True if the price limit is within the range 233 | function _priceInRange( 234 | uint160 priceCurrentX96, 235 | uint160 boundaryPriceX96, 236 | uint160 priceLimitX96 237 | ) private pure returns (bool) { 238 | return 239 | priceCurrentX96 >= boundaryPriceX96 240 | ? (priceLimitX96 > boundaryPriceX96 && priceLimitX96 <= priceCurrentX96) 241 | : (priceLimitX96 >= priceCurrentX96 && priceLimitX96 < boundaryPriceX96); 242 | } 243 | 244 | function _computeSwapStepForExactOut( 245 | uint160 priceCurrentX96, 246 | uint160 boundaryPriceX96, 247 | uint128 takerAmountOutRemaining, 248 | uint128 makerAmount, 249 | int24 takerFeePips 250 | ) private pure returns (ComputeSwapStep memory step) { 251 | bool zeroForOne = priceCurrentX96 >= boundaryPriceX96; 252 | 253 | uint160 priceDeltaX96; 254 | Math.Rounding priceNextRounding; 255 | unchecked { 256 | (priceDeltaX96, priceNextRounding) = zeroForOne 257 | ? (priceCurrentX96 - boundaryPriceX96, Math.Rounding.Down) 258 | : (boundaryPriceX96 - priceCurrentX96, Math.Rounding.Up); 259 | } 260 | 261 | step.priceNextX96 = _computePriceNextX96( 262 | zeroForOne, 263 | priceCurrentX96, 264 | priceDeltaX96, 265 | takerAmountOutRemaining, 266 | makerAmount 267 | ); 268 | 269 | (step.amountIn, step.feeAmount) = _computeAmountInAndFeeAmount( 270 | zeroForOne, 271 | priceCurrentX96, 272 | step.priceNextX96, 273 | takerAmountOutRemaining, 274 | priceNextRounding, 275 | takerFeePips 276 | ); 277 | step.amountOut = takerAmountOutRemaining; 278 | } 279 | 280 | function _computePriceNextX96( 281 | bool zeroForOne, 282 | uint160 priceCurrentX96, 283 | uint160 priceDeltaX96, 284 | uint160 takerAmountOut, 285 | uint128 makerAmount 286 | ) private pure returns (uint160) { 287 | uint256 priceDeltaX96WithRate = Math.mulDiv(priceDeltaX96, takerAmountOut, makerAmount, Math.Rounding.Up); 288 | unchecked { 289 | return 290 | zeroForOne 291 | ? (priceCurrentX96 - priceDeltaX96WithRate).toUint160() 292 | : (priceCurrentX96 + priceDeltaX96WithRate).toUint160(); 293 | } 294 | } 295 | 296 | function _computeAmountInAndFeeAmount( 297 | bool zeroForOne, 298 | uint160 priceCurrentX96, 299 | uint160 priceNextX96, 300 | uint128 amountOut, 301 | Math.Rounding priceNextRounding, 302 | int24 takerFeePips 303 | ) private pure returns (uint256 amountIn, uint128 feeAmount) { 304 | uint160 priceAvgX96; 305 | unchecked { 306 | uint256 priceAccumulateX96 = uint256(priceCurrentX96) + priceNextX96; 307 | priceAccumulateX96 = priceNextRounding == Math.Rounding.Up ? priceAccumulateX96 + 1 : priceAccumulateX96; 308 | priceAvgX96 = uint160(priceAccumulateX96 >> 1); 309 | } 310 | 311 | amountIn = zeroForOne 312 | ? Math.mulDiv(amountOut, FixedPointX96.Q, priceAvgX96, Math.Rounding.Up) 313 | : Math.mulDiv(priceAvgX96, amountOut, FixedPointX96.Q, Math.Rounding.Up); 314 | 315 | // feeAmount = amountIn * takerFeePips / (1e6 - takerFeePips) 316 | feeAmount = Math 317 | .mulDiv(uint24(takerFeePips), amountIn, 1e6 - uint24(takerFeePips), Math.Rounding.Up) 318 | .toUint128(); 319 | } 320 | 321 | function _computeAmountOutForPriceLimit( 322 | uint160 priceCurrentX96, 323 | uint160 boundaryPriceX96, 324 | uint160 priceLimitX96, 325 | uint128 makerAmount 326 | ) private pure returns (uint128 availableAmountOut) { 327 | uint160 priceLimitDeltaX96; 328 | uint160 priceMaxDeltaX96; 329 | unchecked { 330 | (priceLimitDeltaX96, priceMaxDeltaX96) = priceLimitX96 >= priceCurrentX96 331 | ? (priceLimitX96 - priceCurrentX96, boundaryPriceX96 - priceCurrentX96) 332 | : (priceCurrentX96 - priceLimitX96, priceCurrentX96 - boundaryPriceX96); 333 | } 334 | 335 | uint256 tempX96 = _divUpForPriceX96(priceLimitDeltaX96, priceMaxDeltaX96); 336 | availableAmountOut = Math.mulDiv(tempX96, makerAmount, FixedPointX96.Q, Math.Rounding.Up).toUint128(); 337 | } 338 | 339 | function _divUpForPriceX96(uint160 aX96, uint160 bX96) private pure returns (uint256) { 340 | if (aX96 == 0) { 341 | return 0; 342 | } 343 | unchecked { 344 | // never overflows 345 | uint256 tempX96 = uint256(aX96) * FixedPointX96.Q; 346 | // (a + b - 1) / b can overflow on addition, so we distribute 347 | return (tempX96 - 1) / bX96 + 1; 348 | } 349 | } 350 | } 351 | -------------------------------------------------------------------------------- /contracts/libraries/Uint128Math.sol: -------------------------------------------------------------------------------- 1 | // SPDX-License-Identifier: GPL-2.0-or-later 2 | pragma solidity ^0.8.0; 3 | 4 | library Uint128Math { 5 | /// @dev Returns the minimum of the two values 6 | /// @param a The first value 7 | /// @param b The second value 8 | /// @return min The minimum of the two values 9 | function minUint128(uint128 a, uint128 b) internal pure returns (uint128 min) { 10 | return a < b ? a : b; 11 | } 12 | 13 | /// @dev Returns the maximum of the two values 14 | /// @param a The first value 15 | /// @param b The second value 16 | /// @return max The maximum of the two values 17 | function maxUint128(uint128 a, uint128 b) internal pure returns (uint128 max) { 18 | return a > b ? a : b; 19 | } 20 | } 21 | -------------------------------------------------------------------------------- /contracts/libraries/Uint160Math.sol: -------------------------------------------------------------------------------- 1 | // SPDX-License-Identifier: GPL-2.0-or-later 2 | pragma solidity ^0.8.0; 3 | 4 | library Uint160Math { 5 | /// @dev Returns the minimum of the two values 6 | /// @param a The first value 7 | /// @param b The second value 8 | /// @return min The minimum of the two values 9 | function minUint160(uint160 a, uint160 b) internal pure returns (uint160 min) { 10 | return a < b ? a : b; 11 | } 12 | 13 | /// @dev Returns the maximum of the two values 14 | /// @param a The first value 15 | /// @param b The second value 16 | /// @return max The maximum of the two values 17 | function maxUint160(uint160 a, uint160 b) internal pure returns (uint160 max) { 18 | return a > b ? a : b; 19 | } 20 | } 21 | -------------------------------------------------------------------------------- /contracts/test/AbstractPayFacade.sol: -------------------------------------------------------------------------------- 1 | // SPDX-License-Identifier: UNLICENSED 2 | pragma solidity ^0.8.0; 3 | 4 | import "@openzeppelin/contracts/token/ERC20/IERC20.sol"; 5 | import "@openzeppelin/contracts/token/ERC20/utils/SafeERC20.sol"; 6 | import "../interfaces/IWETHMinimum.sol"; 7 | 8 | abstract contract AbstractPayFacade { 9 | address public immutable gridFactory; 10 | address public immutable weth9; 11 | 12 | constructor(address _factory, address _weth9) { 13 | gridFactory = _factory; 14 | weth9 = _weth9; 15 | } 16 | 17 | /// @dev pay token to recipient 18 | /// @param token The token to pay 19 | /// @param payer The address of the payment token 20 | /// @param recipient The address that receive payment 21 | /// @param amount The amount to pay 22 | function pay(address token, address payer, address recipient, uint256 amount) internal { 23 | if (token == weth9 && address(this).balance >= amount) { 24 | // pay with WETH9 25 | Address.sendValue(payable(weth9), amount); 26 | IWETHMinimum(weth9).transfer(recipient, amount); 27 | } else if (payer == address(this)) { 28 | SafeERC20.safeTransfer(IERC20(token), recipient, amount); 29 | } else { 30 | SafeERC20.safeTransferFrom(IERC20(token), payer, recipient, amount); 31 | } 32 | } 33 | } 34 | -------------------------------------------------------------------------------- /contracts/test/BoundaryBitmapTest.sol: -------------------------------------------------------------------------------- 1 | // SPDX-License-Identifier: UNLICENSED 2 | pragma solidity ^0.8.0; 3 | 4 | import "../libraries/BoundaryMath.sol"; 5 | import "../libraries/BoundaryBitmap.sol"; 6 | 7 | contract BoundaryBitmapTest { 8 | using BoundaryBitmap for mapping(int16 => uint256); 9 | 10 | mapping(int16 => uint256) private boundaryBitmap; 11 | int24 private immutable resolution; 12 | 13 | constructor(int24 _resolution) { 14 | require(_resolution > 0); 15 | resolution = _resolution; 16 | } 17 | 18 | function position(int24 boundary) external pure returns (int16 wordPos, uint8 bitPos) { 19 | return BoundaryBitmap.position(boundary); 20 | } 21 | 22 | function getWord(int24 boundary) external view returns (uint256 word) { 23 | (int16 wordPos, ) = this.position(boundary); 24 | return boundaryBitmap[wordPos]; 25 | } 26 | 27 | function flipBoundary(int24 boundary) external { 28 | require(BoundaryMath.isValidBoundary(boundary, resolution)); 29 | boundaryBitmap.flipBoundary(boundary, resolution); 30 | } 31 | 32 | function nextInitializedBoundaryWithinOneWord( 33 | int24 boundary, 34 | bool lte 35 | ) external view returns (int24 next, bool initialized) { 36 | return boundaryBitmap.nextInitializedBoundaryWithinOneWord(boundary, resolution, lte); 37 | } 38 | 39 | function nextInitializedBoundary( 40 | int24 boundary, 41 | uint160 priceX96, 42 | bool currentBoundaryInitialized, 43 | int24 boundaryLower, 44 | bool lte 45 | ) 46 | external 47 | view 48 | returns ( 49 | int24 next, 50 | bool initialized, 51 | uint160 initializedBoundaryLowerPriceX96, 52 | uint160 initializedBoundaryUpperPriceX96 53 | ) 54 | { 55 | return 56 | boundaryBitmap.nextInitializedBoundary( 57 | boundary, 58 | priceX96, 59 | currentBoundaryInitialized, 60 | resolution, 61 | boundaryLower, 62 | lte 63 | ); 64 | } 65 | } 66 | -------------------------------------------------------------------------------- /contracts/test/BoundaryMathTest.sol: -------------------------------------------------------------------------------- 1 | // SPDX-License-Identifier: UNLICENSED 2 | pragma solidity ^0.8.0; 3 | 4 | import "../libraries/BoundaryMath.sol"; 5 | 6 | contract BoundaryMathTest { 7 | function isValidBoundary(int24 boundary, int24 resolution) external pure returns (bool) { 8 | return BoundaryMath.isValidBoundary(boundary, resolution); 9 | } 10 | 11 | function isInRange(int24 boundary) external pure returns (bool) { 12 | return BoundaryMath.isInRange(boundary); 13 | } 14 | 15 | function isPriceX96InRange(uint160 priceX96) external pure returns (bool inRange) { 16 | return BoundaryMath.isPriceX96InRange(priceX96); 17 | } 18 | 19 | function getPriceX96AtBoundary(int24 boundary) external pure returns (uint256 priceX96) { 20 | return BoundaryMath.getPriceX96AtBoundary(boundary); 21 | } 22 | 23 | function getPriceX96AtBoundaryWithGasUsed( 24 | int24 boundary 25 | ) external view returns (uint256 priceX96, uint256 gasUsed) { 26 | uint256 gasBefore = gasleft(); 27 | priceX96 = BoundaryMath.getPriceX96AtBoundary(boundary); 28 | gasUsed = gasBefore - gasleft(); 29 | } 30 | 31 | function getBoundaryAtPriceX96(uint160 priceX96) external pure returns (int24 boundary) { 32 | return BoundaryMath.getBoundaryAtPriceX96(priceX96); 33 | } 34 | 35 | function getBoundaryAtPriceX96WithGasUsed( 36 | uint160 priceX96 37 | ) external view returns (int24 boundary, uint256 gasUsed) { 38 | uint256 gasBefore = gasleft(); 39 | boundary = BoundaryMath.getBoundaryAtPriceX96(priceX96); 40 | gasUsed = gasBefore - gasleft(); 41 | } 42 | 43 | function getBoundaryLowerAtBoundary(int24 boundary, int24 resolution) external pure returns (int24 boundaryLower) { 44 | return BoundaryMath.getBoundaryLowerAtBoundary(boundary, resolution); 45 | } 46 | 47 | function getBoundaryLowerAtBoundaryWithGasUsed( 48 | int24 boundary, 49 | int24 resolution 50 | ) external view returns (int24 boundaryLower, uint256 gasUsed) { 51 | uint256 gasBefore = gasleft(); 52 | boundaryLower = BoundaryMath.getBoundaryLowerAtBoundary(boundary, resolution); 53 | gasUsed = gasBefore - gasleft(); 54 | } 55 | 56 | function rewriteToValidBoundaryLower( 57 | int24 boundaryLower, 58 | int24 resolution 59 | ) external pure returns (int24 validBoundaryLower) { 60 | return BoundaryMath.rewriteToValidBoundaryLower(boundaryLower, resolution); 61 | } 62 | } 63 | -------------------------------------------------------------------------------- /contracts/test/BundleMathTest.sol: -------------------------------------------------------------------------------- 1 | // SPDX-License-Identifier: UNLICENSED 2 | pragma solidity ^0.8.0; 3 | 4 | import "../interfaces/IGridStructs.sol"; 5 | import "../interfaces/IGridParameters.sol"; 6 | import "../libraries/BundleMath.sol"; 7 | 8 | contract BundleMathTest { 9 | IGridStructs.Bundle public bundle; 10 | 11 | // remove liquidity 12 | uint128 public makerAmountOut; 13 | uint128 public takerAmountOut; 14 | uint128 public takerFeeAmountOut; 15 | 16 | // update for taker 17 | IGridParameters.UpdateBundleForTakerParameters public parameters; 18 | 19 | function setBundle(IGridStructs.Bundle calldata _bundle) external { 20 | bundle = IGridStructs.Bundle({ 21 | boundaryLower: _bundle.boundaryLower, 22 | zero: _bundle.zero, 23 | makerAmountTotal: _bundle.makerAmountTotal, 24 | makerAmountRemaining: _bundle.makerAmountRemaining, 25 | takerAmountRemaining: _bundle.takerAmountRemaining, 26 | takerFeeAmountRemaining: _bundle.takerFeeAmountRemaining 27 | }); 28 | } 29 | 30 | function addLiquidity(uint128 makerAmount) external { 31 | BundleMath.addLiquidity(bundle, makerAmount); 32 | } 33 | 34 | function removeLiquidity(uint128 makerAmountRaw) external { 35 | (makerAmountOut, takerAmountOut, takerFeeAmountOut, ) = BundleMath.removeLiquidity(bundle, makerAmountRaw); 36 | } 37 | 38 | function updateForTaker(uint256 amountIn, uint128 amountOut, uint128 takerFeeForMakerAmount) external { 39 | IGridParameters.UpdateBundleForTakerParameters memory _parameters = BundleMath.updateForTaker( 40 | bundle, 41 | amountIn, 42 | amountOut, 43 | takerFeeForMakerAmount 44 | ); 45 | parameters = IGridParameters.UpdateBundleForTakerParameters({ 46 | amountInUsed: _parameters.amountInUsed, 47 | amountInRemaining: _parameters.amountInRemaining, 48 | amountOutUsed: _parameters.amountOutUsed, 49 | amountOutRemaining: _parameters.amountOutRemaining, 50 | takerFeeForMakerAmountUsed: _parameters.takerFeeForMakerAmountUsed, 51 | takerFeeForMakerAmountRemaining: _parameters.takerFeeForMakerAmountRemaining 52 | }); 53 | } 54 | } 55 | -------------------------------------------------------------------------------- /contracts/test/ERC20Test.sol: -------------------------------------------------------------------------------- 1 | // SPDX-License-Identifier: UNLICENSED 2 | pragma solidity ^0.8.0; 3 | 4 | import "@openzeppelin/contracts/token/ERC20/ERC20.sol"; 5 | 6 | contract ERC20Test is ERC20 { 7 | uint8 private _decimals; 8 | 9 | receive() external payable {} 10 | 11 | constructor( 12 | string memory name_, 13 | string memory symbol_, 14 | uint8 decimals_, 15 | uint256 initialSupply 16 | ) ERC20(name_, symbol_) { 17 | _decimals = decimals_; 18 | 19 | _mint(_msgSender(), initialSupply); 20 | } 21 | 22 | function decimals() public view override returns (uint8) { 23 | return _decimals; 24 | } 25 | } 26 | 27 | contract NonStandardERC20 is ERC20 { 28 | constructor() ERC20("NonStandardERC20", "NonStandardERC20") { 29 | _mint(_msgSender(), 1e8 << 18); 30 | } 31 | 32 | function transferFrom(address from, address to, uint256 amount) public override returns (bool) { 33 | uint256 tax = 1; 34 | _transfer(from, to, amount - tax); 35 | return true; 36 | } 37 | } 38 | -------------------------------------------------------------------------------- /contracts/test/FlashTest.sol: -------------------------------------------------------------------------------- 1 | // SPDX-License-Identifier: UNLICENSED 2 | pragma solidity ^0.8.0; 3 | pragma abicoder v2; 4 | 5 | import "@openzeppelin/contracts/utils/Context.sol"; 6 | import "./AbstractPayFacade.sol"; 7 | import "../interfaces/IGrid.sol"; 8 | import "../interfaces/callback/IGridFlashCallback.sol"; 9 | import "../libraries/GridAddress.sol"; 10 | import "../libraries/CallbackValidator.sol"; 11 | 12 | contract FlashTest is IGridFlashCallback, AbstractPayFacade, Context { 13 | struct FlashCalldata { 14 | address tokenA; 15 | address tokenB; 16 | int24 resolution; 17 | address recipient; 18 | address payer; 19 | uint256 amount0; 20 | uint256 amount1; 21 | bool payAmount0; 22 | bool payAmount1; 23 | bool payMore; 24 | } 25 | 26 | uint256 public gasUsed; 27 | 28 | constructor(address _factory, address _weth9) AbstractPayFacade(_factory, _weth9) {} 29 | 30 | function gridexFlashCallback(bytes calldata data) external override { 31 | FlashCalldata memory decodeData = abi.decode(data, (FlashCalldata)); 32 | GridAddress.GridKey memory gridKey = GridAddress.gridKey( 33 | decodeData.tokenA, 34 | decodeData.tokenB, 35 | decodeData.resolution 36 | ); 37 | CallbackValidator.validate(gridFactory, gridKey); 38 | 39 | if (decodeData.payAmount0 && decodeData.amount0 > 0) { 40 | pay(gridKey.token0, decodeData.payer, _msgSender(), decodeData.amount0); 41 | } 42 | 43 | if (decodeData.payMore && decodeData.payAmount0) { 44 | pay(gridKey.token0, decodeData.payer, _msgSender(), 1e18); 45 | } 46 | 47 | if (decodeData.payAmount1 && decodeData.amount1 > 0) { 48 | pay(gridKey.token1, decodeData.payer, _msgSender(), decodeData.amount1); 49 | } 50 | 51 | if (decodeData.payMore && decodeData.payAmount1) { 52 | pay(gridKey.token1, decodeData.payer, _msgSender(), 1e18); 53 | } 54 | } 55 | 56 | function flash(FlashCalldata calldata data) external payable { 57 | GridAddress.GridKey memory gridKey = GridAddress.gridKey(data.tokenA, data.tokenB, data.resolution); 58 | uint256 gasBefore = gasleft(); 59 | IGrid(GridAddress.computeAddress(gridFactory, gridKey)).flash( 60 | data.recipient, 61 | data.amount0, 62 | data.amount1, 63 | abi.encode(data) 64 | ); 65 | gasUsed = gasBefore - gasleft(); 66 | } 67 | } 68 | -------------------------------------------------------------------------------- /contracts/test/GridAddressTest.sol: -------------------------------------------------------------------------------- 1 | // SPDX-License-Identifier: UNLICENSED 2 | pragma solidity ^0.8.0; 3 | 4 | import "../libraries/GridAddress.sol"; 5 | 6 | contract GridAddressTest { 7 | function GRID_BYTES_CODE_HASH() external pure returns (bytes32) { 8 | return GridAddress.GRID_BYTES_CODE_HASH; 9 | } 10 | 11 | function gridKey( 12 | address tokenA, 13 | address tokenB, 14 | int24 resolution 15 | ) external view returns (GridAddress.GridKey memory key, uint256 gasUsed) { 16 | uint256 gasBefore = gasleft(); 17 | key = GridAddress.gridKey(tokenA, tokenB, resolution); 18 | gasUsed = gasBefore - gasleft(); 19 | } 20 | 21 | function computeAddress(address gridFactory, GridAddress.GridKey memory key) external pure returns (address) { 22 | return GridAddress.computeAddress(gridFactory, key); 23 | } 24 | } 25 | -------------------------------------------------------------------------------- /contracts/test/GridTestHelper.sol: -------------------------------------------------------------------------------- 1 | // SPDX-License-Identifier: UNLICENSED 2 | pragma solidity ^0.8.0; 3 | 4 | import "@openzeppelin/contracts/utils/Context.sol"; 5 | import "../interfaces/callback/IGridPlaceMakerOrderCallback.sol"; 6 | import "../interfaces/callback/IGridSwapCallback.sol"; 7 | import "../interfaces/IGrid.sol"; 8 | import "../interfaces/IGridParameters.sol"; 9 | import "./AbstractPayFacade.sol"; 10 | import "../libraries/GridAddress.sol"; 11 | import "../libraries/CallbackValidator.sol"; 12 | import "../libraries/BoundaryMath.sol"; 13 | 14 | contract GridTestHelper is IGridPlaceMakerOrderCallback, IGridSwapCallback, AbstractPayFacade, Context { 15 | uint256 public gasUsed; 16 | 17 | constructor(address _gridFactory, address _weth9) AbstractPayFacade(_gridFactory, _weth9) {} 18 | 19 | struct PlaceMakerOrderCalldata { 20 | GridAddress.GridKey gridKey; 21 | address payer; 22 | } 23 | 24 | function gridexPlaceMakerOrderCallback(uint256 amount0, uint256 amount1, bytes calldata data) external { 25 | PlaceMakerOrderCalldata memory decodeData = abi.decode(data, (PlaceMakerOrderCalldata)); 26 | CallbackValidator.validate(gridFactory, decodeData.gridKey); 27 | 28 | if (amount0 > 0) { 29 | pay(decodeData.gridKey.token0, decodeData.payer, _msgSender(), amount0); 30 | } 31 | 32 | if (amount1 > 0) { 33 | pay(decodeData.gridKey.token1, decodeData.payer, _msgSender(), amount1); 34 | } 35 | } 36 | 37 | struct InitializeParameters { 38 | address tokenA; 39 | address tokenB; 40 | int24 resolution; 41 | uint160 priceX96; 42 | address recipient; 43 | IGridParameters.BoundaryLowerWithAmountParameters[] orders0; 44 | IGridParameters.BoundaryLowerWithAmountParameters[] orders1; 45 | } 46 | 47 | function initialize(InitializeParameters calldata parameters) external payable { 48 | uint256 gasBefore = gasleft(); 49 | 50 | GridAddress.GridKey memory gridKey = GridAddress.gridKey( 51 | parameters.tokenA, 52 | parameters.tokenB, 53 | parameters.resolution 54 | ); 55 | 56 | IGrid grid = IGrid(GridAddress.computeAddress(gridFactory, gridKey)); 57 | grid.initialize( 58 | IGridParameters.InitializeParameters({ 59 | priceX96: parameters.priceX96, 60 | recipient: parameters.recipient, 61 | orders0: parameters.orders0, 62 | orders1: parameters.orders1 63 | }), 64 | abi.encode(PlaceMakerOrderCalldata(gridKey, _msgSender())) 65 | ); 66 | 67 | gasUsed = gasBefore - gasleft(); 68 | } 69 | 70 | struct PlaceMakerOrderParameters { 71 | address tokenA; 72 | address tokenB; 73 | int24 resolution; 74 | address recipient; 75 | bool zero; 76 | int24 boundaryLower; 77 | uint128 amount; 78 | } 79 | 80 | function placeMakerOrder(PlaceMakerOrderParameters calldata parameters) external payable { 81 | uint256 gasBefore = gasleft(); 82 | 83 | GridAddress.GridKey memory gridKey = GridAddress.gridKey( 84 | parameters.tokenA, 85 | parameters.tokenB, 86 | parameters.resolution 87 | ); 88 | 89 | IGrid grid = IGrid(GridAddress.computeAddress(gridFactory, gridKey)); 90 | address recipient = parameters.recipient == address(0) ? _msgSender() : parameters.recipient; 91 | grid.placeMakerOrder( 92 | IGridParameters.PlaceOrderParameters({ 93 | recipient: recipient, 94 | zero: parameters.zero, 95 | boundaryLower: parameters.boundaryLower, 96 | amount: parameters.amount 97 | }), 98 | abi.encode(PlaceMakerOrderCalldata(gridKey, _msgSender())) 99 | ); 100 | 101 | gasUsed = gasBefore - gasleft(); 102 | } 103 | 104 | struct PlaceOrderInBatchParameters { 105 | address tokenA; 106 | address tokenB; 107 | int24 resolution; 108 | address recipient; 109 | bool zero; 110 | IGridParameters.BoundaryLowerWithAmountParameters[] orders; 111 | } 112 | 113 | function placeMakerOrderInBatch(PlaceOrderInBatchParameters calldata parameters) external payable { 114 | uint256 gasBefore = gasleft(); 115 | 116 | GridAddress.GridKey memory gridKey = GridAddress.gridKey( 117 | parameters.tokenA, 118 | parameters.tokenB, 119 | parameters.resolution 120 | ); 121 | 122 | IGrid grid = IGrid(GridAddress.computeAddress(gridFactory, gridKey)); 123 | address recipient = parameters.recipient == address(0) ? _msgSender() : parameters.recipient; 124 | grid.placeMakerOrderInBatch( 125 | IGridParameters.PlaceOrderInBatchParameters({ 126 | recipient: recipient, 127 | zero: parameters.zero, 128 | orders: parameters.orders 129 | }), 130 | abi.encode(PlaceMakerOrderCalldata(gridKey, _msgSender())) 131 | ); 132 | 133 | gasUsed = gasBefore - gasleft(); 134 | } 135 | 136 | struct SwapCalldata { 137 | GridAddress.GridKey gridKey; 138 | address payer; 139 | } 140 | 141 | function gridexSwapCallback(int256 amount0Delta, int256 amount1Delta, bytes calldata data) external { 142 | require(amount0Delta > 0 || amount1Delta > 0, "amount0Delta or amount1Delta must be positive"); 143 | 144 | SwapCalldata memory decodeData = abi.decode(data, (SwapCalldata)); 145 | CallbackValidator.validate(gridFactory, decodeData.gridKey); 146 | 147 | if (amount0Delta > 0) { 148 | pay(decodeData.gridKey.token0, decodeData.payer, _msgSender(), uint256(amount0Delta)); 149 | } 150 | if (amount1Delta > 0) { 151 | pay(decodeData.gridKey.token1, decodeData.payer, _msgSender(), uint256(amount1Delta)); 152 | } 153 | } 154 | 155 | struct ExactInputParameters { 156 | address tokenIn; 157 | address tokenOut; 158 | int24 resolution; 159 | address recipient; 160 | uint256 amountIn; 161 | uint256 amountOutMinimum; 162 | uint160 priceLimitX96; 163 | } 164 | 165 | function exactInput(ExactInputParameters calldata parameters) external payable { 166 | uint256 gasBefore = gasleft(); 167 | 168 | GridAddress.GridKey memory gridKey = GridAddress.gridKey( 169 | parameters.tokenIn, 170 | parameters.tokenOut, 171 | parameters.resolution 172 | ); 173 | 174 | IGrid grid = IGrid(GridAddress.computeAddress(gridFactory, gridKey)); 175 | bool zeroForOne = parameters.tokenIn < parameters.tokenOut; 176 | // allow swapping to the router address with address 0 177 | address recipient = parameters.recipient == address(0) ? address(this) : parameters.recipient; 178 | (int256 amount0, int256 amount1) = grid.swap( 179 | recipient, 180 | zeroForOne, 181 | int256(parameters.amountIn), 182 | parameters.priceLimitX96 == 0 183 | ? (zeroForOne ? BoundaryMath.MIN_RATIO : BoundaryMath.MAX_RATIO) 184 | : parameters.priceLimitX96, 185 | abi.encode(SwapCalldata(gridKey, _msgSender())) 186 | ); 187 | 188 | if (zeroForOne) { 189 | require(amount1 * -1 >= int256(parameters.amountOutMinimum), "amountOutMinimum not reached"); 190 | } else { 191 | require(amount0 * -1 >= int256(parameters.amountOutMinimum), "amountOutMinimum not reached"); 192 | } 193 | 194 | gasUsed = gasBefore - gasleft(); 195 | } 196 | 197 | struct ExactOutputParameters { 198 | address tokenIn; 199 | address tokenOut; 200 | int24 resolution; 201 | address recipient; 202 | uint256 amountOut; 203 | uint256 amountInMaximum; 204 | uint160 priceLimitX96; 205 | } 206 | 207 | function exactOutput(ExactOutputParameters calldata parameters) external payable { 208 | uint256 gasBefore = gasleft(); 209 | 210 | GridAddress.GridKey memory gridKey = GridAddress.gridKey( 211 | parameters.tokenIn, 212 | parameters.tokenOut, 213 | parameters.resolution 214 | ); 215 | 216 | IGrid grid = IGrid(GridAddress.computeAddress(gridFactory, gridKey)); 217 | bool zeroForOne = parameters.tokenIn < parameters.tokenOut; 218 | // allow swapping to the router address with address 0 219 | address recipient = parameters.recipient == address(0) ? address(this) : parameters.recipient; 220 | (int256 amount0, int256 amount1) = grid.swap( 221 | recipient, 222 | zeroForOne, 223 | int256(parameters.amountOut) * -1, 224 | parameters.priceLimitX96 == 0 225 | ? (zeroForOne ? BoundaryMath.MIN_RATIO : BoundaryMath.MAX_RATIO) 226 | : parameters.priceLimitX96, 227 | abi.encode(SwapCalldata(gridKey, _msgSender())) 228 | ); 229 | 230 | if (zeroForOne) { 231 | require(amount0 <= int256(parameters.amountInMaximum), "amountInMaximum exceeded"); 232 | } else { 233 | require(amount1 <= int256(parameters.amountInMaximum), "amountInMaximum exceeded"); 234 | } 235 | 236 | gasUsed = gasBefore - gasleft(); 237 | } 238 | } 239 | -------------------------------------------------------------------------------- /contracts/test/PriceOracleTestHelper.sol: -------------------------------------------------------------------------------- 1 | // SPDX-License-Identifier: UNLICENSED 2 | pragma solidity ^0.8.0; 3 | 4 | import "../PriceOracle.sol"; 5 | 6 | contract PriceOracleTestHelper is PriceOracle { 7 | function register(address grid) external { 8 | super._register(grid); 9 | } 10 | 11 | function update(address grid, int24 boundary) external { 12 | super._update(grid, boundary, uint32(block.timestamp)); 13 | } 14 | 15 | function update(address grid, int24 boundary, uint256 blockTimestamp) external { 16 | super._update(grid, boundary, uint32(blockTimestamp)); 17 | } 18 | 19 | function getBoundaryCumulative( 20 | address grid, 21 | int24 boundary, 22 | uint256 blockTimestamp, 23 | uint32 secondsAgo 24 | ) external view returns (int56 boundaryCumulative) { 25 | GridOracleState memory state = gridOracleStates[grid]; 26 | // PO_UR: unregistered grid 27 | require(state.capacity >= 1, "PO_UR"); 28 | 29 | return super._getBoundaryCumulative(state, gridPriceData[grid], boundary, uint32(blockTimestamp), secondsAgo); 30 | } 31 | } 32 | -------------------------------------------------------------------------------- /contracts/test/SwapMathTest.sol: -------------------------------------------------------------------------------- 1 | // SPDX-License-Identifier: UNLICENSED 2 | pragma solidity ^0.8.0; 3 | 4 | import "../libraries/SwapMath.sol"; 5 | 6 | contract SwapMathTest { 7 | function computeSwapStep( 8 | uint160 priceCurrentX96, 9 | uint160 boundaryPriceX96, 10 | uint160 priceLimitX96, 11 | int256 amountRemaining, 12 | uint128 makerAmount, 13 | int24 takerFeePips 14 | ) external pure returns (SwapMath.ComputeSwapStep memory step) { 15 | return 16 | SwapMath.computeSwapStep( 17 | priceCurrentX96, 18 | boundaryPriceX96, 19 | priceLimitX96, 20 | amountRemaining, 21 | makerAmount, 22 | takerFeePips 23 | ); 24 | } 25 | } 26 | -------------------------------------------------------------------------------- /contracts/test/SwapTest.sol: -------------------------------------------------------------------------------- 1 | // SPDX-License-Identifier: UNLICENSED 2 | pragma solidity ^0.8.0; 3 | 4 | import "@openzeppelin/contracts/utils/Context.sol"; 5 | import "./AbstractPayFacade.sol"; 6 | import "../interfaces/IGrid.sol"; 7 | import "../interfaces/callback/IGridSwapCallback.sol"; 8 | import "../libraries/GridAddress.sol"; 9 | import "../libraries/CallbackValidator.sol"; 10 | 11 | contract SwapTest is IGridSwapCallback, AbstractPayFacade, Context { 12 | struct SwapCalldata { 13 | address tokenA; 14 | address tokenB; 15 | int24 resolution; 16 | address recipient; 17 | bool zeroForOne; 18 | int256 amountSpecified; 19 | uint160 priceLimitX96; 20 | address payer; 21 | } 22 | 23 | constructor(address _factory, address _weth9) AbstractPayFacade(_factory, _weth9) {} 24 | 25 | /// @inheritdoc IGridSwapCallback 26 | function gridexSwapCallback(int256 amount0Delta, int256 amount1Delta, bytes calldata _data) external override { 27 | SwapCalldata memory decodeData = abi.decode(_data, (SwapCalldata)); 28 | GridAddress.GridKey memory gridKey = GridAddress.gridKey( 29 | decodeData.tokenA, 30 | decodeData.tokenB, 31 | decodeData.resolution 32 | ); 33 | CallbackValidator.validate(gridFactory, gridKey); 34 | if (amount0Delta > 0) { 35 | pay(gridKey.token0, decodeData.payer, _msgSender(), uint256(amount0Delta)); 36 | } 37 | 38 | if (amount1Delta > 0) { 39 | pay(gridKey.token1, decodeData.payer, _msgSender(), uint256(amount1Delta)); 40 | } 41 | } 42 | 43 | function output(SwapCalldata calldata data, uint256 times) external payable { 44 | GridAddress.GridKey memory gridKey = GridAddress.gridKey(data.tokenA, data.tokenB, data.resolution); 45 | IGrid grid = IGrid(GridAddress.computeAddress(gridFactory, gridKey)); 46 | for (uint256 i = 0; i < times; ++i) { 47 | grid.swap( 48 | data.recipient, 49 | data.zeroForOne, 50 | data.amountSpecified, 51 | data.priceLimitX96, 52 | abi.encode(data) 53 | ); 54 | } 55 | } 56 | 57 | function input(SwapCalldata calldata data) external payable { 58 | GridAddress.GridKey memory gridKey = GridAddress.gridKey(data.tokenA, data.tokenB, data.resolution); 59 | IGrid grid = IGrid(GridAddress.computeAddress(gridFactory, gridKey)); 60 | grid.swap( 61 | data.recipient, 62 | data.zeroForOne, 63 | data.amountSpecified, 64 | data.priceLimitX96, 65 | abi.encode(data) 66 | ); 67 | } 68 | } 69 | -------------------------------------------------------------------------------- /contracts/test/Uint128MathTest.sol: -------------------------------------------------------------------------------- 1 | // SPDX-License-Identifier: UNLICENSED 2 | pragma solidity ^0.8.0; 3 | 4 | import "../libraries/Uint128Math.sol"; 5 | 6 | contract Uint128MathTest { 7 | function minUint128(uint128 a, uint128 b) external pure returns (uint160) { 8 | return Uint128Math.minUint128(a, b); 9 | } 10 | 11 | function maxUint128(uint128 a, uint128 b) external pure returns (uint160) { 12 | return Uint128Math.maxUint128(a, b); 13 | } 14 | } 15 | -------------------------------------------------------------------------------- /contracts/test/Uint160MathTest.sol: -------------------------------------------------------------------------------- 1 | // SPDX-License-Identifier: UNLICENSED 2 | pragma solidity ^0.8.0; 3 | 4 | import "../libraries/Uint160Math.sol"; 5 | 6 | contract Uint160MathTest { 7 | function minUint160(uint160 a, uint160 b) external pure returns (uint160) { 8 | return Uint160Math.minUint160(a, b); 9 | } 10 | 11 | function maxUint160(uint160 a, uint160 b) external pure returns (uint160) { 12 | return Uint160Math.maxUint160(a, b); 13 | } 14 | } 15 | -------------------------------------------------------------------------------- /contracts/test/UnsafeERC20.sol: -------------------------------------------------------------------------------- 1 | // SPDX-License-Identifier: UNLICENSED 2 | pragma solidity ^0.8.0; 3 | 4 | import "@openzeppelin/contracts/token/ERC20/ERC20.sol"; 5 | 6 | contract UnsafeERC20 is ERC20 { 7 | uint8 private _decimals; 8 | 9 | constructor(string memory name_, string memory symbol_, uint8 decimals_) ERC20(name_, symbol_) { 10 | _decimals = decimals_; 11 | } 12 | 13 | function decimals() public view override returns (uint8) { 14 | return _decimals; 15 | } 16 | 17 | function unsafeMint(address recipient, uint256 humanReadableAmount) public { 18 | _mint(recipient, humanReadableAmount * 10 ** _decimals); 19 | } 20 | } 21 | -------------------------------------------------------------------------------- /coverage.sh: -------------------------------------------------------------------------------- 1 | #!/bin/bash 2 | # This script is used to generate coverage reports for the project. 3 | # Because our GridAddress.sol contract relies on the compiled bytecode hash of the Grid.sol contract, 4 | # and the coverage tool will automatically weave some code, so we need to re-modify the GRID_BYTES_CODE_HASH 5 | # of GridAddress.sol after weaving 6 | 7 | echo "pre instrument..." 8 | npx hardhat coverage --solcoverjs .solcover.pre.js >> /dev/null 9 | 10 | hash=`echo "const bytecode = require(\"./artifacts/contracts/Grid.sol/Grid.json\").bytecode; \ 11 | const hash = require(\"ethers\").utils.keccak256(bytecode); \ 12 | console.log(hash);" |node` 13 | 14 | echo "recomputed byte code hash:${hash}" 15 | 16 | originalCode=`cat ./contracts/libraries/GridAddress.sol` 17 | 18 | name=`uname` 19 | if [ "${name}" = "Darwin" ]; then 20 | sed -i "" "s/0x.*;/${hash};/" ./contracts/libraries/GridAddress.sol 21 | else 22 | sed -i "s/0x.*;/${hash};/" ./contracts/libraries/GridAddress.sol 23 | fi 24 | 25 | echo "replace GridAddress.sol success" 26 | 27 | echo "execute coverage..." 28 | npx hardhat coverage 29 | 30 | echo "${originalCode}" > ./contracts/libraries/GridAddress.sol 31 | 32 | echo "recover GridAddress.sol success" 33 | -------------------------------------------------------------------------------- /hardhat.config.ts: -------------------------------------------------------------------------------- 1 | import "dotenv/config"; 2 | import {HardhatUserConfig} from "hardhat/config"; 3 | import "@nomicfoundation/hardhat-toolbox"; 4 | import "@nomiclabs/hardhat-solhint"; 5 | import "@nomiclabs/hardhat-etherscan"; 6 | import "hardhat-contract-sizer"; 7 | 8 | const config: HardhatUserConfig = { 9 | defaultNetwork: "hardhat", 10 | solidity: { 11 | compilers: [ 12 | { 13 | version: "0.8.9", 14 | settings: { 15 | optimizer: { 16 | enabled: true, 17 | runs: 1e8, 18 | }, 19 | }, 20 | }, 21 | ], 22 | overrides: { 23 | "contracts/Grid.sol": { 24 | version: "0.8.9", 25 | settings: { 26 | optimizer: { 27 | enabled: true, 28 | runs: 8500, 29 | }, 30 | }, 31 | }, 32 | }, 33 | }, 34 | networks: { 35 | goerli: { 36 | url: `https://eth-goerli.g.alchemy.com/v2/${process.env.ALCHEMY_GOERLI_KEY}`, 37 | accounts: [`${process.env.PRIVATE_KEY}`], 38 | }, 39 | sepolia: { 40 | url: `https://sepolia.infura.io/v3/${process.env.INFURA_KEY}`, 41 | accounts: [`${process.env.PRIVATE_KEY}`], 42 | }, 43 | mainnet: { 44 | url: `https://eth-mainnet.g.alchemy.com/v2/${process.env.ALCHEMY_MAINNET_KEY}`, 45 | accounts: [`${process.env.PRIVATE_KEY}`], 46 | }, 47 | arbitrum: { 48 | url: `https://arb-mainnet.g.alchemy.com/v2/${process.env.ALCHEMY_ARBITRUM_KEY}`, 49 | accounts: [`${process.env.PRIVATE_KEY}`], 50 | }, 51 | "base-goerli": { 52 | url: `https://goerli.base.org`, 53 | accounts: [`${process.env.PRIVATE_KEY}`], 54 | }, 55 | }, 56 | etherscan: { 57 | apiKey: { 58 | mainnet: `${process.env.ETHERSCAN_API_KEY}`, 59 | goerli: `${process.env.ETHERSCAN_API_KEY}`, 60 | arbitrumOne: `${process.env.ARBISCAN_API_KEY}`, 61 | // Basescan doesn't require an API key, however 62 | // Hardhat still expects an arbitrary string to be provided. 63 | "base-goerli": "PLACEHOLDER_STRING", 64 | }, 65 | customChains: [ 66 | { 67 | network: "base-goerli", 68 | chainId: 84531, 69 | urls: { 70 | apiURL: "https://api-goerli.basescan.org/api", 71 | browserURL: "https://goerli.basescan.org", 72 | }, 73 | }, 74 | ], 75 | }, 76 | gasReporter: {}, 77 | contractSizer: { 78 | runOnCompile: `${process.env.REPORT_SIZE}` == "true", 79 | }, 80 | }; 81 | 82 | export default config; 83 | -------------------------------------------------------------------------------- /package.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "@gridexprotocol/core", 3 | "version": "1.0.3", 4 | "author": "Gridex", 5 | "description": "Core smart contracts of Gridex Protocol", 6 | "license": "BUSL-1.1", 7 | "homepage": "https://gdx.org", 8 | "keywords": [ 9 | "gridex", 10 | "core", 11 | "gridexswap", 12 | "gridexprotocol", 13 | "ethereum" 14 | ], 15 | "repository": { 16 | "type": "git", 17 | "url": "git@github.com:GridexProtocol/core.git" 18 | }, 19 | "files": [ 20 | "contracts/**/*.sol", 21 | "!contracts/test/*", 22 | "artifacts/contracts/**/*.json", 23 | "!artifacts/contracts/test/**/*" 24 | ], 25 | "bugs": { 26 | "url": "https://github.com/gridexprotocol/core/issues" 27 | }, 28 | "engines": { 29 | "node": ">=10" 30 | }, 31 | "scripts": { 32 | "build": "npm run compile", 33 | "compile": "hardhat compile", 34 | "test": "hardhat test", 35 | "coverage": "./coverage.sh" 36 | }, 37 | "devDependencies": { 38 | "@nomicfoundation/hardhat-chai-matchers": "^1.0.3", 39 | "@nomicfoundation/hardhat-toolbox": "^1.0.2", 40 | "@nomiclabs/hardhat-ethers": "^2.1.1", 41 | "@nomiclabs/hardhat-etherscan": "^3.1.2", 42 | "@nomiclabs/hardhat-solhint": "^2.0.1", 43 | "@types/mocha": "^9.1.1", 44 | "@types/node": "^18.7.6", 45 | "decimal.js": "^10.4.0", 46 | "dotenv": "^16.0.1", 47 | "ethers": "^5.7.1", 48 | "hardhat": "^2.10.2", 49 | "hardhat-contract-sizer": "^2.6.1", 50 | "hardhat-gas-reporter": "^1.0.8", 51 | "mocha-chai-jest-snapshot": "^1.1.4", 52 | "prettier": "^2.7.1", 53 | "prettier-plugin-solidity": "^1.0.0", 54 | "ts-node": "^10.9.1", 55 | "typescript": "^4.7.4" 56 | }, 57 | "dependencies": { 58 | "@openzeppelin/contracts": "^4.7.3" 59 | } 60 | } 61 | -------------------------------------------------------------------------------- /test/BoundaryMath.gas.test.ts: -------------------------------------------------------------------------------- 1 | import {ethers} from "hardhat"; 2 | import {MAX_BOUNDARY, MAX_RATIO, MIN_BOUNDARY, MIN_RATIO, Resolution} from "./shared/util"; 3 | import {loadFixture} from "@nomicfoundation/hardhat-network-helpers"; 4 | import {expect} from "./shared/expect"; 5 | 6 | describe("BoundaryMath", () => { 7 | const deployFixture = async function () { 8 | const boundaryMathFactory = await ethers.getContractFactory("BoundaryMathTest"); 9 | const boundaryMath = await boundaryMathFactory.deploy(); 10 | await boundaryMath.deployed(); 11 | return {boundaryMath}; 12 | }; 13 | 14 | describe("#getPriceX96AtBoundary", () => { 15 | const boundaries = [ 16 | MIN_BOUNDARY, 17 | -500000, 18 | -400000, 19 | -300000, 20 | -200000, 21 | -100000, 22 | 0, 23 | 100000, 24 | 200000, 25 | 300000, 26 | 400000, 27 | MAX_BOUNDARY, 28 | ]; 29 | 30 | for (const boundary of boundaries) { 31 | it(`boundary = ${boundary}`, async () => { 32 | const {boundaryMath} = await loadFixture(deployFixture); 33 | const {priceX96, gasUsed} = await boundaryMath.getPriceX96AtBoundaryWithGasUsed(boundary); 34 | const cmpObj = { 35 | priceX96: priceX96.toBigInt(), 36 | gasUsed: gasUsed.toNumber(), 37 | }; 38 | expect(cmpObj).toMatchSnapshot(); 39 | }); 40 | } 41 | }); 42 | 43 | describe("#getBoundaryAtPriceX96", () => { 44 | const priceX96s = [ 45 | MIN_RATIO, 46 | 15319379n, 47 | 337263108622n, 48 | 7425001144658883n, 49 | 163464786360687385626n, 50 | 3598751819609688046946419n, 51 | 79228162514264337593543950336n, 52 | 1744244129640337381386292603617838n, 53 | 38400329974042030913961448288742562464n, 54 | 845400776793423922697130608897531771147615n, 55 | 18611883644907511909590774894315720731532604461n, 56 | MAX_RATIO, 57 | ]; 58 | for (const priceX96 of priceX96s) { 59 | it(`priceX96 = ${priceX96}`, async () => { 60 | const {boundaryMath} = await loadFixture(deployFixture); 61 | const {boundary, gasUsed} = await boundaryMath.getBoundaryAtPriceX96WithGasUsed(priceX96); 62 | const cmpObj = { 63 | boundary: boundary, 64 | gasUsed: gasUsed.toNumber(), 65 | }; 66 | expect(cmpObj).toMatchSnapshot(); 67 | }); 68 | } 69 | }); 70 | 71 | describe("#getBoundaryLowerAtBoundary", () => { 72 | const tests = [ 73 | { 74 | boundary: MIN_BOUNDARY, 75 | resolution: Resolution.LOW, 76 | }, 77 | { 78 | boundary: 0, 79 | resolution: Resolution.LOW, 80 | }, 81 | { 82 | boundary: MAX_BOUNDARY, 83 | resolution: Resolution.LOW, 84 | }, 85 | 86 | { 87 | boundary: 3, 88 | resolution: Resolution.LOW, 89 | }, 90 | { 91 | boundary: 3, 92 | resolution: Resolution.MEDIUM, 93 | }, 94 | { 95 | boundary: 3, 96 | resolution: Resolution.HIGH, 97 | }, 98 | ]; 99 | 100 | for (const test of tests) { 101 | it(`boundary = ${test.boundary}, resolution = ${test.resolution}`, async () => { 102 | const {boundaryMath} = await loadFixture(deployFixture); 103 | const {boundaryLower, gasUsed} = await boundaryMath.getBoundaryLowerAtBoundaryWithGasUsed( 104 | test.boundary, 105 | test.resolution 106 | ); 107 | const cmpObj = { 108 | boundaryLower: boundaryLower, 109 | gasUsed: gasUsed.toNumber(), 110 | }; 111 | expect(cmpObj).toMatchSnapshot(); 112 | }); 113 | } 114 | }); 115 | }); 116 | -------------------------------------------------------------------------------- /test/BoundaryMath.test.ts: -------------------------------------------------------------------------------- 1 | import {loadFixture} from "@nomicfoundation/hardhat-network-helpers"; 2 | import {expect} from "chai"; 3 | import {ethers} from "hardhat"; 4 | import {encodePrice, MAX_BOUNDARY, MAX_RATIO, MIN_BOUNDARY, MIN_RATIO, Resolution, RESOLUTION_X96} from "./shared/util"; 5 | import "decimal.js"; 6 | import Decimal from "decimal.js"; 7 | import {BigNumber} from "ethers"; 8 | 9 | describe("BoundaryMath", () => { 10 | async function deployFixture() { 11 | const boundaryMathTestFactory = await ethers.getContractFactory("BoundaryMathTest"); 12 | const boundaryMathTest = await boundaryMathTestFactory.deploy(); 13 | return {boundaryMathTest}; 14 | } 15 | 16 | describe("#isValidBoundary", () => { 17 | it("should return false", async function () { 18 | const {boundaryMathTest} = await loadFixture(deployFixture); 19 | expect(await boundaryMathTest.isValidBoundary(-100, 30)).to.false; 20 | expect(await boundaryMathTest.isValidBoundary(100, 30)).to.false; 21 | }); 22 | it("should return true", async function () { 23 | const {boundaryMathTest} = await loadFixture(deployFixture); 24 | expect(await boundaryMathTest.isValidBoundary(-100, 10)).to.true; 25 | expect(await boundaryMathTest.isValidBoundary(100, 10)).to.true; 26 | }); 27 | }); 28 | 29 | describe("#isInRange", () => { 30 | it("should return false", async function () { 31 | const {boundaryMathTest} = await loadFixture(deployFixture); 32 | expect(await boundaryMathTest.isInRange(MIN_BOUNDARY - 1)).to.false; 33 | expect(await boundaryMathTest.isInRange(MAX_BOUNDARY + 1)).to.false; 34 | }); 35 | it("should return true", async function () { 36 | const {boundaryMathTest} = await loadFixture(deployFixture); 37 | expect(await boundaryMathTest.isInRange(MIN_BOUNDARY)).to.true; 38 | expect(await boundaryMathTest.isInRange(MIN_BOUNDARY + 1)).to.true; 39 | expect(await boundaryMathTest.isInRange(0)).to.true; 40 | expect(await boundaryMathTest.isInRange(MAX_BOUNDARY)).to.true; 41 | expect(await boundaryMathTest.isInRange(MAX_BOUNDARY - 1)).to.true; 42 | }); 43 | }); 44 | 45 | describe("#isPriceX96InRange", function () { 46 | it("should return false", async function () { 47 | const {boundaryMathTest} = await loadFixture(deployFixture); 48 | expect(await boundaryMathTest.isPriceX96InRange(MIN_RATIO - 1n)).to.false; 49 | expect(await boundaryMathTest.isPriceX96InRange(MAX_RATIO + 1n)).to.false; 50 | }); 51 | it("should return true", async function () { 52 | const {boundaryMathTest} = await loadFixture(deployFixture); 53 | expect(await boundaryMathTest.isPriceX96InRange(await boundaryMathTest.getPriceX96AtBoundary(MIN_BOUNDARY))) 54 | .to.true; 55 | expect(await boundaryMathTest.isPriceX96InRange(await boundaryMathTest.getPriceX96AtBoundary(MAX_BOUNDARY))) 56 | .to.true; 57 | }); 58 | }); 59 | 60 | describe("#getBoundaryAtPriceX96", () => { 61 | it("boundary is zero", async function () { 62 | const {boundaryMathTest} = await loadFixture(deployFixture); 63 | expect(await boundaryMathTest.getBoundaryAtPriceX96(RESOLUTION_X96)).to.equal(0); 64 | }); 65 | it("should get boundary success", async function () { 66 | const {boundaryMathTest} = await loadFixture(deployFixture); 67 | expect(await boundaryMathTest.getBoundaryAtPriceX96(MIN_RATIO)).to.equal(MIN_BOUNDARY); 68 | expect(await boundaryMathTest.getBoundaryAtPriceX96("989413")).to.equal(MIN_BOUNDARY + 1); 69 | expect(await boundaryMathTest.getBoundaryAtPriceX96(MAX_RATIO)).to.equal(MAX_BOUNDARY); 70 | expect( 71 | await boundaryMathTest.getBoundaryAtPriceX96("1461154457982069109660872690047936117497446526938") 72 | ).to.equal(MAX_BOUNDARY - 1); 73 | }); 74 | }); 75 | 76 | for (const ratio of [ 77 | MIN_RATIO, 78 | encodePrice(BigNumber.from(10).pow(12), 1), 79 | encodePrice(BigNumber.from(10).pow(6), 1), 80 | encodePrice(1, 64), 81 | encodePrice(1, 8), 82 | encodePrice(1, 2), 83 | encodePrice(1, 1), 84 | encodePrice(2, 1), 85 | encodePrice(8, 1), 86 | encodePrice(64, 1), 87 | encodePrice(1, BigNumber.from(10).pow(6)), 88 | encodePrice(1, BigNumber.from(10).pow(12)), 89 | MAX_RATIO, 90 | ]) { 91 | describe(`ratio ${ratio}`, () => { 92 | it("is at most off by 1", async () => { 93 | const {boundaryMathTest} = await loadFixture(deployFixture); 94 | const jsResult = new Decimal(ratio.toString()).div(new Decimal(2).pow(96)).log(1.0001).floor(); 95 | const result = await boundaryMathTest.getBoundaryAtPriceX96(ratio); 96 | const absDiff = new Decimal(result.toString()).sub(jsResult).abs(); 97 | expect(absDiff.toNumber()).to.be.lte(1); 98 | }); 99 | it("ratio is between the boundary and boundary+1", async () => { 100 | const {boundaryMathTest} = await loadFixture(deployFixture); 101 | const boundary = await boundaryMathTest.getBoundaryAtPriceX96(ratio); 102 | const ratioOfBoundary = await boundaryMathTest.getPriceX96AtBoundary(boundary); 103 | const ratioOfBoundaryPlusOne = await boundaryMathTest.getPriceX96AtBoundary( 104 | Math.min(boundary + 1, MAX_BOUNDARY) 105 | ); 106 | expect(ratio).to.be.gte(ratioOfBoundary); 107 | expect(ratio).to.be.lte(ratioOfBoundaryPlusOne); 108 | }); 109 | }); 110 | } 111 | 112 | describe("#getPriceX96AtBoundary", () => { 113 | it("boundary is zero", async () => { 114 | const {boundaryMathTest} = await loadFixture(deployFixture); 115 | let priceX96AtBoundary0 = await boundaryMathTest.getPriceX96AtBoundary(0); 116 | expect(priceX96AtBoundary0).to.equal(RESOLUTION_X96); 117 | expect(priceX96AtBoundary0.toHexString()).to.equal("0x01000000000000000000000000"); 118 | }); 119 | it("should get price success", async () => { 120 | const {boundaryMathTest} = await loadFixture(deployFixture); 121 | expect(await boundaryMathTest.getPriceX96AtBoundary(MIN_BOUNDARY)).to.equal(MIN_RATIO); 122 | expect(await boundaryMathTest.getPriceX96AtBoundary(MIN_BOUNDARY + 1)).to.equal("989413"); 123 | expect(await boundaryMathTest.getPriceX96AtBoundary(MAX_BOUNDARY - 1)).to.equal( 124 | "1461154457982069109660872690047936117497446526938" 125 | ); 126 | expect(await boundaryMathTest.getPriceX96AtBoundary(MAX_BOUNDARY)).to.equal(MAX_RATIO); 127 | }); 128 | const boundaries = [ 129 | 50, 130 | 100, 131 | 250, 132 | 500, 133 | 1_000, 134 | 2_500, 135 | 3_000, 136 | 4_000, 137 | 5_000, 138 | 50_000, 139 | 150_000, 140 | 250_000, 141 | 350_000, 142 | 440_000, 143 | MAX_BOUNDARY, 144 | ] 145 | .flatMap((t) => [-t, t]) 146 | .concat([-500_000, -510_000, -520_000, MIN_BOUNDARY]); 147 | for (const boundary of boundaries) { 148 | describe(`boundary: ${boundary}`, () => { 149 | it("is at most off by 1/100th of a bips", async function () { 150 | const {boundaryMathTest} = await loadFixture(deployFixture); 151 | const jsResult = new Decimal(1.0001).pow(boundary).mul(new Decimal(2).pow(96)); 152 | const result = await boundaryMathTest.getPriceX96AtBoundary(boundary); 153 | const absDiff = new Decimal(result.toString()).sub(jsResult).abs(); 154 | expect(absDiff.div(jsResult).toNumber()).to.be.lt(0.000001); 155 | }); 156 | }); 157 | } 158 | }); 159 | 160 | describe("#getBoundaryLowerAtBoundary", () => { 161 | const resolutions = [1, 10, 25, 50, 75, 100, 500]; 162 | const tests = [ 163 | { 164 | boundary: -659245, 165 | expectBoundaryLowers: [-659245, -659250, -659250, -659250, -659250, -659300, -659500], 166 | }, 167 | { 168 | boundary: -1, 169 | expectBoundaryLowers: [-1, -10, -25, -50, -75, -100, -500], 170 | }, 171 | { 172 | boundary: 0, 173 | expectBoundaryLowers: [0, 0, 0, 0, 0, 0, 0], 174 | }, 175 | { 176 | boundary: 554539, 177 | expectBoundaryLowers: [554539, 554530, 554525, 554500, 554475, 554500, 554500], 178 | }, 179 | ]; 180 | 181 | tests.forEach((test) => { 182 | it(`boundary: ${test.boundary}`, async () => { 183 | const {boundaryMathTest} = await loadFixture(deployFixture); 184 | 185 | for (let i = 0; i < resolutions.length; i++) { 186 | expect(await boundaryMathTest.getBoundaryLowerAtBoundary(test.boundary, resolutions[i])).to.equal( 187 | test.expectBoundaryLowers[i] 188 | ); 189 | } 190 | }); 191 | }); 192 | }); 193 | 194 | describe("#rewriteToValidBoundaryLower", () => { 195 | const tests = [ 196 | { 197 | name: "boundary lower is less than MIN_BOUNDARY", 198 | boundaryLower: MIN_BOUNDARY - 10, 199 | expectValidBoundaryLower: MIN_BOUNDARY - 10 + Resolution.HIGH, 200 | }, 201 | { 202 | name: "boundary lower is greater than MAX_BOUNDARY", 203 | boundaryLower: MAX_BOUNDARY + 10, 204 | expectValidBoundaryLower: MAX_BOUNDARY + 10 - Resolution.HIGH, 205 | }, 206 | { 207 | name: "boundary lower plus resolution is greater than MAX_BOUNDARY", 208 | boundaryLower: MAX_BOUNDARY, 209 | expectValidBoundaryLower: MAX_BOUNDARY - Resolution.HIGH, 210 | }, 211 | { 212 | name: "boundary lower is valid", 213 | boundaryLower: 0, 214 | expectValidBoundaryLower: 0, 215 | }, 216 | ]; 217 | tests.forEach((test) => { 218 | it(`${test.name}`, async () => { 219 | const {boundaryMathTest} = await loadFixture(deployFixture); 220 | expect( 221 | await boundaryMathTest.rewriteToValidBoundaryLower(test.boundaryLower, Resolution.HIGH) 222 | ).to.equal(test.expectValidBoundaryLower); 223 | }); 224 | }); 225 | }); 226 | }); 227 | -------------------------------------------------------------------------------- /test/BundleMath.test.ts: -------------------------------------------------------------------------------- 1 | import {ethers} from "hardhat"; 2 | import {loadFixture} from "@nomicfoundation/hardhat-network-helpers"; 3 | import {IGridStructs} from "../typechain-types/contracts/test/BundleMathTest"; 4 | import {expect} from "chai"; 5 | import {BigNumber} from "ethers"; 6 | import BundleStruct = IGridStructs.BundleStruct; 7 | 8 | describe("BundleMath", () => { 9 | async function deployFixture() { 10 | const contractFactory = await ethers.getContractFactory("BundleMathTest"); 11 | const bundleMath = await contractFactory.deploy(); 12 | await bundleMath.deployed(); 13 | await bundleMath.setBundle(createEmptyBundle(0, false)); 14 | return {bundleMath}; 15 | } 16 | 17 | function createEmptyBundle(boundaryLower: number, zero: boolean): BundleStruct { 18 | return { 19 | boundaryLower: boundaryLower, 20 | zero: zero, 21 | makerAmountTotal: 0, 22 | makerAmountRemaining: 0, 23 | takerAmountRemaining: 0, 24 | takerFeeAmountRemaining: 0, 25 | }; 26 | } 27 | 28 | describe("#addLiquidity", () => { 29 | it("first added", async () => { 30 | const {bundleMath} = await loadFixture(deployFixture); 31 | await bundleMath.addLiquidity(1); 32 | const {makerAmountTotal, makerAmountRemaining} = await bundleMath.bundle(); 33 | expect(makerAmountTotal).to.equal(makerAmountRemaining); 34 | expect(makerAmountTotal).to.equal(1); 35 | }); 36 | 37 | it("added multiple times", async () => { 38 | const {bundleMath} = await loadFixture(deployFixture); 39 | for (let i = 0; i < 10; i++) { 40 | await bundleMath.addLiquidity(1000); 41 | } 42 | const {makerAmountTotal, makerAmountRemaining} = await bundleMath.bundle(); 43 | expect(makerAmountTotal).to.equal(makerAmountRemaining); 44 | expect(makerAmountTotal).to.equal(10 * 1000); 45 | }); 46 | 47 | it("overflow", async () => { 48 | const {bundleMath} = await loadFixture(deployFixture); 49 | await bundleMath.addLiquidity(1000); 50 | await expect(bundleMath.addLiquidity(BigNumber.from(1).shl(128).sub(1))).to.revertedWithPanic(0x11); // Arithmetic operation underflowed or overflowed outside of an unchecked block 51 | }); 52 | }); 53 | 54 | describe("#removeLiquidity", () => { 55 | describe("used == false", () => { 56 | it("1 LP", async () => { 57 | const {bundleMath} = await loadFixture(deployFixture); 58 | await bundleMath.addLiquidity(1000); 59 | await bundleMath.removeLiquidity(1000); 60 | 61 | const {makerAmountTotal, makerAmountRemaining, takerAmountRemaining, takerFeeAmountRemaining} = 62 | await bundleMath.bundle(); 63 | expect(makerAmountTotal).to.equal(makerAmountRemaining); 64 | expect(makerAmountTotal).to.equal(0); 65 | expect(takerAmountRemaining).to.equal(0); 66 | expect(takerFeeAmountRemaining).to.equal(0); 67 | expect(await bundleMath.makerAmountOut()).to.equal(1000); 68 | expect(await bundleMath.takerAmountOut()).to.equal(0); 69 | expect(await bundleMath.takerFeeAmountOut()).to.equal(0); 70 | }); 71 | 72 | it("10 LP", async () => { 73 | const {bundleMath} = await loadFixture(deployFixture); 74 | for (let i = 0; i < 10; i++) { 75 | await bundleMath.addLiquidity(1000); 76 | } 77 | 78 | await bundleMath.removeLiquidity(1000); 79 | const {makerAmountTotal, makerAmountRemaining, takerAmountRemaining, takerFeeAmountRemaining} = 80 | await bundleMath.bundle(); 81 | expect(makerAmountTotal).to.equal(makerAmountRemaining); 82 | expect(makerAmountTotal).to.equal(1000 * 9); 83 | expect(takerAmountRemaining).to.equal(0); 84 | expect(takerFeeAmountRemaining).to.equal(0); 85 | expect(await bundleMath.makerAmountOut()).to.equal(1000); 86 | expect(await bundleMath.takerAmountOut()).to.equal(0); 87 | expect(await bundleMath.takerFeeAmountOut()).to.equal(0); 88 | }); 89 | 90 | it("1 LP and partial filled", async () => { 91 | const {bundleMath} = await loadFixture(deployFixture); 92 | await bundleMath.addLiquidity(1000); 93 | 94 | await bundleMath.updateForTaker(900, 900, 100); 95 | 96 | await bundleMath.removeLiquidity(1000); 97 | const {makerAmountTotal, makerAmountRemaining, takerAmountRemaining, takerFeeAmountRemaining} = 98 | await bundleMath.bundle(); 99 | expect(makerAmountTotal).to.equal(0); 100 | expect(makerAmountRemaining).to.equal(0); 101 | expect(takerAmountRemaining).to.equal(0); 102 | expect(takerFeeAmountRemaining).to.equal(0); 103 | expect(await bundleMath.takerAmountOut()).to.equal(900); 104 | expect(await bundleMath.takerFeeAmountOut()).to.equal(100); 105 | }); 106 | }); 107 | }); 108 | 109 | describe("#updateForTaker", () => { 110 | it("1 LP and fully filled", async () => { 111 | const {bundleMath} = await loadFixture(deployFixture); 112 | await bundleMath.addLiquidity(1); 113 | 114 | await bundleMath.updateForTaker(1, 1, 0); 115 | 116 | const {makerAmountTotal, makerAmountRemaining, takerAmountRemaining, takerFeeAmountRemaining} = 117 | await bundleMath.bundle(); 118 | expect(makerAmountTotal).to.equal(1); 119 | expect(makerAmountRemaining).to.equal(0); 120 | expect(takerAmountRemaining).to.equal(1); 121 | expect(takerFeeAmountRemaining).to.equal(0); 122 | 123 | const { 124 | amountInUsed, 125 | amountInRemaining, 126 | amountOutUsed, 127 | amountOutRemaining, 128 | takerFeeForMakerAmountUsed, 129 | takerFeeForMakerAmountRemaining, 130 | } = await bundleMath.parameters(); 131 | expect(amountInUsed).to.equal(1); 132 | expect(amountInRemaining).to.equal(0); 133 | expect(amountOutUsed).to.equal(1); 134 | expect(amountOutRemaining).to.equal(0); 135 | expect(takerFeeForMakerAmountUsed).to.equal(0); 136 | expect(takerFeeForMakerAmountRemaining).to.equal(0); 137 | }); 138 | 139 | it("1 LP and fully filled (with exceeded current bundle balance)", async () => { 140 | const {bundleMath} = await loadFixture(deployFixture); 141 | await bundleMath.addLiquidity(1); 142 | 143 | await bundleMath.updateForTaker(1, 2, 0); 144 | 145 | const {makerAmountTotal, makerAmountRemaining, takerAmountRemaining, takerFeeAmountRemaining} = 146 | await bundleMath.bundle(); 147 | expect(makerAmountTotal).to.equal(1); 148 | expect(makerAmountRemaining).to.equal(0); 149 | expect(takerAmountRemaining).to.equal(0); 150 | expect(takerFeeAmountRemaining).to.equal(0); 151 | 152 | const { 153 | amountInUsed, 154 | amountInRemaining, 155 | amountOutUsed, 156 | amountOutRemaining, 157 | takerFeeForMakerAmountUsed, 158 | takerFeeForMakerAmountRemaining, 159 | } = await bundleMath.parameters(); 160 | expect(amountInUsed).to.equal(0); 161 | expect(amountInRemaining).to.equal(1); 162 | expect(amountOutUsed).to.equal(1); 163 | expect(amountOutRemaining).to.equal(1); 164 | expect(takerFeeForMakerAmountUsed).to.equal(0); 165 | expect(takerFeeForMakerAmountRemaining).to.equal(0); 166 | }); 167 | 168 | it("1 LP and not fully filled (with receive taker fee)", async () => { 169 | const {bundleMath} = await loadFixture(deployFixture); 170 | await bundleMath.addLiquidity(1000); 171 | 172 | await bundleMath.updateForTaker(900, 900, 100); 173 | const {makerAmountTotal, makerAmountRemaining, takerAmountRemaining, takerFeeAmountRemaining} = 174 | await bundleMath.bundle(); 175 | expect(makerAmountTotal).to.equal(1000); 176 | expect(makerAmountRemaining).to.equal(100); 177 | expect(takerAmountRemaining).to.equal(900); 178 | expect(takerFeeAmountRemaining).to.equal(100); 179 | 180 | const { 181 | amountInUsed, 182 | amountInRemaining, 183 | amountOutUsed, 184 | amountOutRemaining, 185 | takerFeeForMakerAmountUsed, 186 | takerFeeForMakerAmountRemaining, 187 | } = await bundleMath.parameters(); 188 | expect(amountInUsed).to.equal(900); 189 | expect(amountInRemaining).to.equal(0); 190 | expect(amountOutUsed).to.equal(900); 191 | expect(amountOutRemaining).to.equal(0); 192 | expect(takerFeeForMakerAmountUsed).to.equal(100); 193 | expect(takerFeeForMakerAmountRemaining).to.equal(0); 194 | }); 195 | 196 | it("1 LP and not fully filled (with receive taker fee, with exceeded current bundle balance)", async () => { 197 | const {bundleMath} = await loadFixture(deployFixture); 198 | await bundleMath.addLiquidity(1000); 199 | 200 | await bundleMath.updateForTaker(1100, 1100, 110); 201 | const {makerAmountTotal, makerAmountRemaining, takerAmountRemaining, takerFeeAmountRemaining} = 202 | await bundleMath.bundle(); 203 | expect(makerAmountTotal).to.equal(1000); 204 | expect(makerAmountRemaining).to.equal(0); 205 | expect(takerAmountRemaining).to.equal(1000); 206 | expect(takerFeeAmountRemaining).to.equal(100); 207 | 208 | const { 209 | amountInUsed, 210 | amountInRemaining, 211 | amountOutUsed, 212 | amountOutRemaining, 213 | takerFeeForMakerAmountUsed, 214 | takerFeeForMakerAmountRemaining, 215 | } = await bundleMath.parameters(); 216 | expect(amountInUsed).to.equal(1000); 217 | expect(amountInRemaining).to.equal(100); 218 | expect(amountOutUsed).to.equal(1000); 219 | expect(amountOutRemaining).to.equal(100); 220 | expect(takerFeeForMakerAmountUsed).to.equal(100); 221 | expect(takerFeeForMakerAmountRemaining).to.equal(10); 222 | }); 223 | }); 224 | }); 225 | -------------------------------------------------------------------------------- /test/Grid.gas.test.ts: -------------------------------------------------------------------------------- 1 | import {deployERC20, deployGridFactory, deployGridTestHelper, deployWETH} from "./shared/deployer"; 2 | import {Resolution, RESOLUTION_X96} from "./shared/util"; 3 | import {ethers} from "hardhat"; 4 | import {computeAddress, sortedToken} from "./shared/GridAddress"; 5 | import {loadFixture} from "@nomicfoundation/hardhat-network-helpers"; 6 | import {expect} from "./shared/expect"; 7 | 8 | describe("Grid", () => { 9 | const deployFixture = async () => { 10 | const weth = await deployWETH(); 11 | const {gridFactory} = await deployGridFactory(weth.address); 12 | 13 | const usdc = await deployERC20("USDC", "USDC", 6, 10n ** 18n * 10000n); 14 | const usdt = await deployERC20("USDT", "USDT", 6, 10n ** 18n * 10000n); 15 | 16 | const gridTestHelper = await deployGridTestHelper(gridFactory.address, weth.address); 17 | 18 | await Promise.all([ 19 | weth.approve(gridTestHelper.address, 10n ** 18n * 10000n), 20 | usdc.approve(gridTestHelper.address, 10n ** 18n * 10000n), 21 | usdt.approve(gridTestHelper.address, 10n ** 18n * 10000n), 22 | ]); 23 | 24 | await gridFactory.createGrid(weth.address, usdc.address, Resolution.MEDIUM); 25 | 26 | await gridFactory.createGrid(usdc.address, usdt.address, Resolution.MEDIUM); 27 | 28 | const ETHToERC20Grid = await ethers.getContractAt( 29 | "Grid", 30 | await computeAddress(gridFactory.address, weth.address, usdc.address, Resolution.MEDIUM) 31 | ); 32 | 33 | const [signer] = await ethers.getSigners(); 34 | const parameters = { 35 | tokenA: weth.address, 36 | tokenB: usdc.address, 37 | resolution: Resolution.MEDIUM, 38 | recipient: signer.address, 39 | priceX96: RESOLUTION_X96, 40 | orders0: [ 41 | { 42 | boundaryLower: 0, 43 | amount: 1n, 44 | }, 45 | ], 46 | orders1: [ 47 | { 48 | boundaryLower: 0n, 49 | amount: 1n, 50 | }, 51 | ], 52 | }; 53 | await gridTestHelper.initialize(parameters, {value: 1n}); 54 | await ETHToERC20Grid.settleMakerOrderAndCollectInBatch(signer.address, [1, 2], true); 55 | 56 | const ERC20ToERC20Grid = await ethers.getContractAt( 57 | "Grid", 58 | await computeAddress(gridFactory.address, usdc.address, usdt.address, Resolution.MEDIUM) 59 | ); 60 | 61 | parameters.tokenA = usdc.address; 62 | parameters.tokenB = usdt.address; 63 | await gridTestHelper.initialize(parameters); 64 | await ERC20ToERC20Grid.settleMakerOrderAndCollectInBatch(signer.address, [1, 2], false); 65 | 66 | return {weth, usdc, usdt, gridFactory, gridTestHelper, ETHToERC20Grid, ERC20ToERC20Grid}; 67 | }; 68 | 69 | it("#createGrid", async () => { 70 | const {usdc, usdt, gridFactory} = await loadFixture(deployFixture); 71 | 72 | const tx = await gridFactory.createGrid(usdc.address, usdt.address, Resolution.HIGH); 73 | const receipt = await tx.wait(); 74 | expect(receipt.gasUsed.toNumber()).toMatchSnapshot(); 75 | }); 76 | 77 | it("#initialize", async () => { 78 | const {usdc, usdt, gridFactory, gridTestHelper} = await loadFixture(deployFixture); 79 | 80 | await gridFactory.createGrid(usdc.address, usdt.address, Resolution.HIGH); 81 | 82 | const grid = await ethers.getContractAt( 83 | "Grid", 84 | await computeAddress(gridFactory.address, usdc.address, usdt.address, Resolution.HIGH) 85 | ); 86 | 87 | await gridTestHelper.initialize({ 88 | tokenA: usdc.address, 89 | tokenB: usdt.address, 90 | resolution: Resolution.HIGH, 91 | recipient: ethers.constants.AddressZero, 92 | priceX96: RESOLUTION_X96, 93 | orders0: [ 94 | { 95 | boundaryLower: 0n, 96 | amount: 1n, 97 | }, 98 | ], 99 | orders1: [ 100 | { 101 | boundaryLower: 0n, 102 | amount: 1n, 103 | }, 104 | ], 105 | }); 106 | expect((await gridTestHelper.gasUsed()).toNumber()).toMatchSnapshot(); 107 | }); 108 | 109 | describe("#placeMakerOrder", () => { 110 | it("ETH", async () => { 111 | const {weth, usdc, gridTestHelper} = await loadFixture(deployFixture); 112 | 113 | const {token0} = await sortedToken(weth.address, usdc.address); 114 | for (let i = 0; i < 5; i++) { 115 | await gridTestHelper.placeMakerOrder( 116 | { 117 | tokenA: weth.address, 118 | tokenB: usdc.address, 119 | resolution: Resolution.MEDIUM, 120 | recipient: ethers.constants.AddressZero, 121 | zero: weth.address.toLowerCase() == token0.toLowerCase(), 122 | boundaryLower: 0, 123 | amount: 10n ** 18n, 124 | }, 125 | {value: 10n ** 18n} 126 | ); 127 | 128 | expect((await gridTestHelper.gasUsed()).toNumber()).toMatchSnapshot(); 129 | } 130 | }); 131 | 132 | it("ERC20", async () => { 133 | const {weth, usdc, gridTestHelper} = await loadFixture(deployFixture); 134 | 135 | const {token0} = await sortedToken(weth.address, usdc.address); 136 | for (let i = 0; i < 5; i++) { 137 | await gridTestHelper.placeMakerOrder({ 138 | tokenA: weth.address, 139 | tokenB: usdc.address, 140 | resolution: Resolution.MEDIUM, 141 | recipient: ethers.constants.AddressZero, 142 | zero: weth.address.toLowerCase() != token0.toLowerCase(), 143 | boundaryLower: 0, 144 | amount: 10n ** 18n, 145 | }); 146 | expect((await gridTestHelper.gasUsed()).toNumber()).toMatchSnapshot(); 147 | } 148 | }); 149 | }); 150 | 151 | describe("#swap", () => { 152 | describe("exactOutput", () => { 153 | it("ETH", async () => { 154 | const {weth, usdc, gridTestHelper} = await loadFixture(deployFixture); 155 | 156 | const {token0} = await sortedToken(weth.address, usdc.address); 157 | 158 | await gridTestHelper.placeMakerOrderInBatch({ 159 | tokenA: weth.address, 160 | tokenB: usdc.address, 161 | resolution: Resolution.MEDIUM, 162 | recipient: ethers.constants.AddressZero, 163 | zero: weth.address.toLowerCase() != token0.toLowerCase(), 164 | orders: [ 165 | { 166 | boundaryLower: -Resolution.MEDIUM, 167 | amount: 10n ** 18n * 10n, 168 | }, 169 | { 170 | boundaryLower: 0, 171 | amount: 10n ** 18n * 10n, 172 | }, 173 | { 174 | boundaryLower: Resolution.MEDIUM, 175 | amount: 10n ** 18n * 10n, 176 | }, 177 | ], 178 | }); 179 | 180 | const signers = await ethers.getSigners(); 181 | for (let i = 0; i < 6; i++) { 182 | await gridTestHelper.exactOutput( 183 | { 184 | tokenIn: weth.address, 185 | tokenOut: usdc.address, 186 | resolution: Resolution.MEDIUM, 187 | recipient: signers[0].address, 188 | amountOut: 10n ** 18n, 189 | amountInMaximum: 10n ** 18n * 2n, 190 | priceLimitX96: 0n, 191 | }, 192 | {value: 10n ** 18n * 2n} 193 | ); 194 | expect((await gridTestHelper.gasUsed()).toNumber()).toMatchSnapshot(); 195 | } 196 | }); 197 | 198 | it("ERC20", async () => { 199 | const {weth, usdc, gridTestHelper} = await loadFixture(deployFixture); 200 | 201 | const {token0} = await sortedToken(weth.address, usdc.address); 202 | 203 | await gridTestHelper.placeMakerOrderInBatch( 204 | { 205 | tokenA: weth.address, 206 | tokenB: usdc.address, 207 | resolution: Resolution.MEDIUM, 208 | recipient: ethers.constants.AddressZero, 209 | zero: weth.address.toLowerCase() == token0.toLowerCase(), 210 | orders: [ 211 | { 212 | boundaryLower: -Resolution.MEDIUM, 213 | amount: 10n ** 18n * 10n, 214 | }, 215 | { 216 | boundaryLower: 0, 217 | amount: 10n ** 18n * 10n, 218 | }, 219 | { 220 | boundaryLower: Resolution.MEDIUM, 221 | amount: 10n ** 18n * 10n, 222 | }, 223 | ], 224 | }, 225 | {value: 10n ** 18n * 10n * 3n} 226 | ); 227 | 228 | const signers = await ethers.getSigners(); 229 | for (let i = 0; i < 6; i++) { 230 | await gridTestHelper.exactOutput({ 231 | tokenIn: usdc.address, 232 | tokenOut: weth.address, 233 | resolution: Resolution.MEDIUM, 234 | recipient: signers[0].address, 235 | amountOut: 10n ** 18n, 236 | amountInMaximum: 10n ** 18n * 2n, 237 | priceLimitX96: 0n, 238 | }); 239 | expect((await gridTestHelper.gasUsed()).toNumber()).toMatchSnapshot(); 240 | } 241 | }); 242 | }); 243 | 244 | describe("exactInput", () => { 245 | it("ETH", async () => { 246 | const {weth, usdc, gridTestHelper} = await loadFixture(deployFixture); 247 | 248 | const {token0} = await sortedToken(weth.address, usdc.address); 249 | 250 | await gridTestHelper.placeMakerOrderInBatch({ 251 | tokenA: weth.address, 252 | tokenB: usdc.address, 253 | resolution: Resolution.MEDIUM, 254 | recipient: ethers.constants.AddressZero, 255 | zero: weth.address.toLowerCase() != token0.toLowerCase(), 256 | orders: [ 257 | { 258 | boundaryLower: -Resolution.MEDIUM, 259 | amount: 10n ** 18n * 10n, 260 | }, 261 | { 262 | boundaryLower: 0, 263 | amount: 10n ** 18n * 10n, 264 | }, 265 | { 266 | boundaryLower: Resolution.MEDIUM, 267 | amount: 10n ** 18n * 10n, 268 | }, 269 | ], 270 | }); 271 | 272 | const signers = await ethers.getSigners(); 273 | for (let i = 0; i < 6; i++) { 274 | await gridTestHelper.exactInput( 275 | { 276 | tokenIn: weth.address, 277 | tokenOut: usdc.address, 278 | resolution: Resolution.MEDIUM, 279 | recipient: signers[0].address, 280 | amountIn: 10n ** 18n / 2n, 281 | amountOutMinimum: 0n, 282 | priceLimitX96: 0n, 283 | }, 284 | {value: 10n ** 18n / 2n} 285 | ); 286 | expect((await gridTestHelper.gasUsed()).toNumber()).toMatchSnapshot(); 287 | } 288 | }); 289 | 290 | it("ERC20", async () => { 291 | const {weth, usdc, gridTestHelper} = await loadFixture(deployFixture); 292 | 293 | const {token0} = await sortedToken(weth.address, usdc.address); 294 | 295 | await gridTestHelper.placeMakerOrderInBatch( 296 | { 297 | tokenA: weth.address, 298 | tokenB: usdc.address, 299 | resolution: Resolution.MEDIUM, 300 | recipient: ethers.constants.AddressZero, 301 | zero: weth.address.toLowerCase() == token0.toLowerCase(), 302 | orders: [ 303 | { 304 | boundaryLower: -Resolution.MEDIUM, 305 | amount: 10n ** 18n * 10n, 306 | }, 307 | { 308 | boundaryLower: 0, 309 | amount: 10n ** 18n * 10n, 310 | }, 311 | { 312 | boundaryLower: Resolution.MEDIUM, 313 | amount: 10n ** 18n * 10n, 314 | }, 315 | ], 316 | }, 317 | {value: 10n ** 18n * 10n * 3n} 318 | ); 319 | 320 | const signers = await ethers.getSigners(); 321 | for (let i = 0; i < 6; i++) { 322 | await gridTestHelper.exactInput({ 323 | tokenIn: usdc.address, 324 | tokenOut: weth.address, 325 | resolution: Resolution.MEDIUM, 326 | recipient: signers[0].address, 327 | amountIn: 10n ** 18n / 2n, 328 | amountOutMinimum: 0n, 329 | priceLimitX96: 0n, 330 | }); 331 | expect((await gridTestHelper.gasUsed()).toNumber()).toMatchSnapshot(); 332 | } 333 | }); 334 | }); 335 | }); 336 | }); 337 | -------------------------------------------------------------------------------- /test/GridAddress.test.ts: -------------------------------------------------------------------------------- 1 | import {ethers} from "hardhat"; 2 | import {expect} from "./shared/expect"; 3 | import {loadFixture} from "@nomicfoundation/hardhat-network-helpers"; 4 | import {GridAddressTest} from "../typechain-types"; 5 | import {Resolution} from "./shared/util"; 6 | import {computeAddress, GRID_BYTES_CODE_HASH} from "./shared/GridAddress"; 7 | 8 | describe("GridAddress", () => { 9 | const tokenAddresses = ["0x1000000000000000000000000000000000000000", "0x2000000000000000000000000000000000000000"]; 10 | const gridFactoryAddress = "0x9000000000000000000000000000000000000000"; 11 | 12 | async function deployFixture() { 13 | const [signer, otherAccount] = await ethers.getSigners(); 14 | const gridAddressFactory = await ethers.getContractFactory("GridAddressTest", signer); 15 | const gridAddress = (await gridAddressFactory.deploy()) as GridAddressTest; 16 | return {gridAddress, signer, otherAccount}; 17 | } 18 | 19 | describe("#GRID_BYTES_CODE_HASH", () => { 20 | it(`grid bytes code hash should equal to ${GRID_BYTES_CODE_HASH}`, async () => { 21 | const {gridAddress} = await loadFixture(deployFixture); 22 | expect(await gridAddress.GRID_BYTES_CODE_HASH()).to.equal(GRID_BYTES_CODE_HASH); 23 | }); 24 | }); 25 | 26 | describe("#gridKey", () => { 27 | it("should sort by order", async () => { 28 | const {gridAddress} = await loadFixture(deployFixture); 29 | const {key} = await gridAddress.gridKey(tokenAddresses[0], tokenAddresses[1], Resolution.LOW); 30 | expect(key.token0).to.equal(tokenAddresses[0]); 31 | expect(key.token1).to.equal(tokenAddresses[1]); 32 | expect(key.resolution).to.equal(Resolution.LOW); 33 | }); 34 | 35 | it("should sort by order", async () => { 36 | const {gridAddress} = await loadFixture(deployFixture); 37 | const {key} = await gridAddress.gridKey(tokenAddresses[1], tokenAddresses[0], Resolution.HIGH); 38 | expect(key.token0).to.equal(tokenAddresses[0]); 39 | expect(key.token1).to.equal(tokenAddresses[1]); 40 | expect(key.resolution).to.equal(Resolution.HIGH); 41 | }); 42 | 43 | it("gas used snapshot", async () => { 44 | const {gridAddress} = await loadFixture(deployFixture); 45 | const {gasUsed} = await gridAddress.gridKey(tokenAddresses[1], tokenAddresses[0], Resolution.LOW); 46 | expect(gasUsed.toNumber()).toMatchSnapshot(); 47 | }); 48 | }); 49 | 50 | describe("#computeAddress", () => { 51 | it("should revert if token0 > token1", async () => { 52 | const {gridAddress} = await loadFixture(deployFixture); 53 | await expect( 54 | gridAddress.computeAddress(gridFactoryAddress, { 55 | token0: tokenAddresses[1], 56 | token1: tokenAddresses[0], 57 | resolution: Resolution.MEDIUM, 58 | }) 59 | ).to.reverted; 60 | }); 61 | 62 | it("should equal on chain and off chain", async () => { 63 | const {gridAddress} = await loadFixture(deployFixture); 64 | const onChainAddress = await gridAddress.computeAddress(gridFactoryAddress, { 65 | token0: tokenAddresses[0], 66 | token1: tokenAddresses[1], 67 | resolution: Resolution.MEDIUM, 68 | }); 69 | const offChainAddress = await computeAddress( 70 | gridFactoryAddress, 71 | tokenAddresses[0], 72 | tokenAddresses[1], 73 | Resolution.MEDIUM 74 | ); 75 | expect(onChainAddress).to.equal(offChainAddress); 76 | }); 77 | }); 78 | }); 79 | -------------------------------------------------------------------------------- /test/GridFactory.test.ts: -------------------------------------------------------------------------------- 1 | import {loadFixture} from "@nomicfoundation/hardhat-network-helpers"; 2 | import {expect} from "chai"; 3 | import {Resolution} from "./shared/util"; 4 | import {deployGridFactory, deployWETH} from "./shared/deployer"; 5 | import {computeAddress, sortedToken} from "./shared/GridAddress"; 6 | import {ethers} from "hardhat"; 7 | 8 | describe("GridFactory", () => { 9 | async function deployFixture() { 10 | const weth = await deployWETH(); 11 | const {gridFactory} = await deployGridFactory(weth.address); 12 | return {weth, gridFactory}; 13 | } 14 | 15 | describe("#resolutions", () => { 16 | describe("should return the fee", () => { 17 | const tests = [ 18 | { 19 | name: "resolution for 1", 20 | resolution: 1, 21 | expectTakerFee: 0.0001, 22 | }, 23 | { 24 | name: "resolution for 5", 25 | resolution: 5, 26 | expectTakerFee: 0.0005, 27 | }, 28 | { 29 | name: "resolution for 30", 30 | resolution: 30, 31 | expectTakerFee: 0.003, 32 | }, 33 | { 34 | name: "resolution for 100", 35 | resolution: 100, 36 | expectTakerFee: 0, 37 | }, 38 | ]; 39 | tests.forEach(async (test) => { 40 | it(test.name, async () => { 41 | const {gridFactory} = await loadFixture(deployFixture); 42 | const takerFee = await gridFactory.resolutions(test.resolution); 43 | expect(takerFee / 1e6).to.equal(test.expectTakerFee); 44 | }); 45 | }); 46 | }); 47 | }); 48 | 49 | describe("#createGrid", () => { 50 | it("should reverted with the right error if not initialized", async () => { 51 | const {weth} = await loadFixture(deployFixture); 52 | const gridFactoryFactory = await ethers.getContractFactory("GridFactory"); 53 | const gridFactory = await gridFactoryFactory.deploy(weth.address, [1, 2, 3]); 54 | await expect(gridFactory.createGrid(weth.address, gridFactory.address, Resolution.LOW)).to.be.revertedWith( 55 | "GF_NI" 56 | ); 57 | }); 58 | 59 | it("should reverted with the right error if tokenA is not a contract", async () => { 60 | const {gridFactory} = await loadFixture(deployFixture); 61 | await expect( 62 | gridFactory.createGrid( 63 | "0x1000000000000000000000000000000000000000", 64 | gridFactory.address, 65 | Resolution.LOW 66 | ) 67 | ).to.be.revertedWith("GF_NC"); 68 | }); 69 | 70 | it("should reverted with the right error if tokenB is not a contract", async () => { 71 | const {gridFactory} = await loadFixture(deployFixture); 72 | await expect( 73 | gridFactory.createGrid( 74 | gridFactory.address, 75 | "0x1000000000000000000000000000000000000000", 76 | Resolution.LOW 77 | ) 78 | ).to.be.revertedWith("GF_NC"); 79 | }); 80 | 81 | it("should reverted with the right error if tokenA is the same as tokenB", async () => { 82 | const {gridFactory} = await loadFixture(deployFixture); 83 | await expect( 84 | gridFactory.createGrid(gridFactory.address, gridFactory.address, Resolution.LOW) 85 | ).to.be.revertedWith("GF_TAD"); 86 | }); 87 | 88 | it("should reverted with the right error if resolution is not enabled", async () => { 89 | const {gridFactory, weth} = await loadFixture(deployFixture); 90 | await expect(gridFactory.createGrid(gridFactory.address, weth.address, 1000)).to.be.revertedWith("GF_RNE"); 91 | }); 92 | 93 | describe("should success", async () => { 94 | const tests = [ 95 | { 96 | name: "resolution for low", 97 | resolution: Resolution.LOW, 98 | }, 99 | { 100 | name: "resolution for medium", 101 | resolution: Resolution.MEDIUM, 102 | }, 103 | { 104 | name: "resolution for high", 105 | resolution: Resolution.HIGH, 106 | }, 107 | ]; 108 | tests.forEach((test) => { 109 | it(test.name, async () => { 110 | const {gridFactory, weth} = await loadFixture(deployFixture); 111 | const {token0, token1} = await sortedToken(gridFactory.address, weth.address); 112 | await expect(gridFactory.createGrid(token0, token1, test.resolution)) 113 | .to.emit(gridFactory, "GridCreated") 114 | .withArgs( 115 | token0, 116 | token1, 117 | test.resolution, 118 | await computeAddress(gridFactory.address, token0, token1, test.resolution) 119 | ); 120 | 121 | const gridAddress0 = await gridFactory.grids(token0, token1, test.resolution); 122 | const gridAddress1 = await gridFactory.grids(token1, token0, test.resolution); 123 | expect(gridAddress0).to.equal(gridAddress1); 124 | expect(gridAddress0).to.not.equal(ethers.constants.AddressZero); 125 | }); 126 | }); 127 | }); 128 | 129 | it("should reverted with the right error if the grid already exists", async () => { 130 | const {gridFactory, weth} = await loadFixture(deployFixture); 131 | await expect(gridFactory.createGrid(gridFactory.address, weth.address, Resolution.HIGH)).to.emit( 132 | gridFactory, 133 | "GridCreated" 134 | ); 135 | 136 | await expect(gridFactory.createGrid(gridFactory.address, weth.address, Resolution.HIGH)).to.be.revertedWith( 137 | "GF_PAE" 138 | ); 139 | }); 140 | }); 141 | }); 142 | -------------------------------------------------------------------------------- /test/Uint128Math.test.ts: -------------------------------------------------------------------------------- 1 | import {ethers} from "hardhat"; 2 | import {loadFixture} from "@nomicfoundation/hardhat-network-helpers"; 3 | import {expect} from "chai"; 4 | 5 | describe("Uint128Math", () => { 6 | async function deployFixture() { 7 | const contractFactory = await ethers.getContractFactory("Uint128MathTest"); 8 | const uint128Math = await contractFactory.deploy(); 9 | await uint128Math.deployed(); 10 | return {uint128Math}; 11 | } 12 | 13 | describe("min and max", () => { 14 | const tests = [ 15 | { 16 | a: 0, 17 | b: 0, 18 | expectMin: 0, 19 | expectMax: 0, 20 | }, 21 | { 22 | a: 1, 23 | b: 1, 24 | expectMin: 1, 25 | expectMax: 1, 26 | }, 27 | { 28 | a: 10, 29 | b: 9, 30 | expectMin: 9, 31 | expectMax: 10, 32 | }, 33 | { 34 | a: 9, 35 | b: 10, 36 | expectMin: 9, 37 | expectMax: 10, 38 | }, 39 | ]; 40 | 41 | for (const test of tests) { 42 | it(`${test.a} and ${test.b}`, async () => { 43 | const {uint128Math} = await loadFixture(deployFixture); 44 | expect(await uint128Math.minUint128(test.a, test.b)).to.equal(test.expectMin); 45 | expect(await uint128Math.maxUint128(test.a, test.b)).to.equal(test.expectMax); 46 | }); 47 | } 48 | }); 49 | }); 50 | -------------------------------------------------------------------------------- /test/Uint160Math.test.ts: -------------------------------------------------------------------------------- 1 | import {ethers} from "hardhat"; 2 | import {loadFixture} from "@nomicfoundation/hardhat-network-helpers"; 3 | import {expect} from "chai"; 4 | 5 | describe("Uint160Math", () => { 6 | async function deployFixture() { 7 | const contractFactory = await ethers.getContractFactory("Uint160MathTest"); 8 | const uint160Math = await contractFactory.deploy(); 9 | await uint160Math.deployed(); 10 | return {uint160Math}; 11 | } 12 | 13 | describe("min and max", () => { 14 | const tests = [ 15 | { 16 | a: 0, 17 | b: 0, 18 | expectMin: 0, 19 | expectMax: 0, 20 | }, 21 | { 22 | a: 1, 23 | b: 1, 24 | expectMin: 1, 25 | expectMax: 1, 26 | }, 27 | { 28 | a: 10, 29 | b: 9, 30 | expectMin: 9, 31 | expectMax: 10, 32 | }, 33 | { 34 | a: 9, 35 | b: 10, 36 | expectMin: 9, 37 | expectMax: 10, 38 | }, 39 | ]; 40 | 41 | for (const test of tests) { 42 | it(`${test.a} and ${test.b}`, async () => { 43 | const {uint160Math} = await loadFixture(deployFixture); 44 | expect(await uint160Math.minUint160(test.a, test.b)).to.equal(test.expectMin); 45 | expect(await uint160Math.maxUint160(test.a, test.b)).to.equal(test.expectMax); 46 | }); 47 | } 48 | }); 49 | }); 50 | -------------------------------------------------------------------------------- /test/__snapshots__/BoundaryMath.gas.test.ts.snap: -------------------------------------------------------------------------------- 1 | // Jest Snapshot v1, https://goo.gl/fbAQLP 2 | 3 | exports[`BoundaryMath #getBoundaryAtPriceX96 priceX96 = 989314 1`] = ` 4 | Object { 5 | "boundary": -527400, 6 | "gasUsed": 2098, 7 | } 8 | `; 9 | 10 | exports[`BoundaryMath #getBoundaryAtPriceX96 priceX96 = 15319379 1`] = ` 11 | Object { 12 | "boundary": -500000, 13 | "gasUsed": 2126, 14 | } 15 | `; 16 | 17 | exports[`BoundaryMath #getBoundaryAtPriceX96 priceX96 = 337263108622 1`] = ` 18 | Object { 19 | "boundary": -400000, 20 | "gasUsed": 2112, 21 | } 22 | `; 23 | 24 | exports[`BoundaryMath #getBoundaryAtPriceX96 priceX96 = 7425001144658883 1`] = ` 25 | Object { 26 | "boundary": -300000, 27 | "gasUsed": 2140, 28 | } 29 | `; 30 | 31 | exports[`BoundaryMath #getBoundaryAtPriceX96 priceX96 = 163464786360687385626 1`] = ` 32 | Object { 33 | "boundary": -200000, 34 | "gasUsed": 2112, 35 | } 36 | `; 37 | 38 | exports[`BoundaryMath #getBoundaryAtPriceX96 priceX96 = 3598751819609688046946419 1`] = ` 39 | Object { 40 | "boundary": -100000, 41 | "gasUsed": 2112, 42 | } 43 | `; 44 | 45 | exports[`BoundaryMath #getBoundaryAtPriceX96 priceX96 = 79228162514264337593543950336 1`] = ` 46 | Object { 47 | "boundary": 0, 48 | "gasUsed": 2040, 49 | } 50 | `; 51 | 52 | exports[`BoundaryMath #getBoundaryAtPriceX96 priceX96 = 1744244129640337381386292603617838 1`] = ` 53 | Object { 54 | "boundary": 100000, 55 | "gasUsed": 2157, 56 | } 57 | `; 58 | 59 | exports[`BoundaryMath #getBoundaryAtPriceX96 priceX96 = 38400329974042030913961448288742562464 1`] = ` 60 | Object { 61 | "boundary": 200000, 62 | "gasUsed": 2157, 63 | } 64 | `; 65 | 66 | exports[`BoundaryMath #getBoundaryAtPriceX96 priceX96 = 845400776793423922697130608897531771147615 1`] = ` 67 | Object { 68 | "boundary": 300000, 69 | "gasUsed": 2185, 70 | } 71 | `; 72 | 73 | exports[`BoundaryMath #getBoundaryAtPriceX96 priceX96 = 18611883644907511909590774894315720731532604461 1`] = ` 74 | Object { 75 | "boundary": 400000, 76 | "gasUsed": 2157, 77 | } 78 | `; 79 | 80 | exports[`BoundaryMath #getBoundaryAtPriceX96 priceX96 = 1461300573427867316570072651998408279850435624081 1`] = ` 81 | Object { 82 | "boundary": 443635, 83 | "gasUsed": 2203, 84 | } 85 | `; 86 | 87 | exports[`BoundaryMath #getBoundaryLowerAtBoundary boundary = -527400, resolution = 1 1`] = ` 88 | Object { 89 | "boundaryLower": -527400, 90 | "gasUsed": 155, 91 | } 92 | `; 93 | 94 | exports[`BoundaryMath #getBoundaryLowerAtBoundary boundary = 0, resolution = 1 1`] = ` 95 | Object { 96 | "boundaryLower": 0, 97 | "gasUsed": 155, 98 | } 99 | `; 100 | 101 | exports[`BoundaryMath #getBoundaryLowerAtBoundary boundary = 3, resolution = 1 1`] = ` 102 | Object { 103 | "boundaryLower": 3, 104 | "gasUsed": 155, 105 | } 106 | `; 107 | 108 | exports[`BoundaryMath #getBoundaryLowerAtBoundary boundary = 3, resolution = 5 1`] = ` 109 | Object { 110 | "boundaryLower": 0, 111 | "gasUsed": 155, 112 | } 113 | `; 114 | 115 | exports[`BoundaryMath #getBoundaryLowerAtBoundary boundary = 3, resolution = 30 1`] = ` 116 | Object { 117 | "boundaryLower": 0, 118 | "gasUsed": 155, 119 | } 120 | `; 121 | 122 | exports[`BoundaryMath #getBoundaryLowerAtBoundary boundary = 443635, resolution = 1 1`] = ` 123 | Object { 124 | "boundaryLower": 443635, 125 | "gasUsed": 155, 126 | } 127 | `; 128 | 129 | exports[`BoundaryMath #getPriceX96AtBoundary boundary = -100000 1`] = ` 130 | Object { 131 | "gasUsed": 792, 132 | "priceX96": 3598751819609688046946419n, 133 | } 134 | `; 135 | 136 | exports[`BoundaryMath #getPriceX96AtBoundary boundary = -200000 1`] = ` 137 | Object { 138 | "gasUsed": 792, 139 | "priceX96": 163464786360687385626n, 140 | } 141 | `; 142 | 143 | exports[`BoundaryMath #getPriceX96AtBoundary boundary = -300000 1`] = ` 144 | Object { 145 | "gasUsed": 820, 146 | "priceX96": 7425001144658883n, 147 | } 148 | `; 149 | 150 | exports[`BoundaryMath #getPriceX96AtBoundary boundary = -400000 1`] = ` 151 | Object { 152 | "gasUsed": 792, 153 | "priceX96": 337263108622n, 154 | } 155 | `; 156 | 157 | exports[`BoundaryMath #getPriceX96AtBoundary boundary = -500000 1`] = ` 158 | Object { 159 | "gasUsed": 806, 160 | "priceX96": 15319379n, 161 | } 162 | `; 163 | 164 | exports[`BoundaryMath #getPriceX96AtBoundary boundary = -527400 1`] = ` 165 | Object { 166 | "gasUsed": 778, 167 | "priceX96": 989314n, 168 | } 169 | `; 170 | 171 | exports[`BoundaryMath #getPriceX96AtBoundary boundary = 0 1`] = ` 172 | Object { 173 | "gasUsed": 710, 174 | "priceX96": 79228162514264337593543950336n, 175 | } 176 | `; 177 | 178 | exports[`BoundaryMath #getPriceX96AtBoundary boundary = 100000 1`] = ` 179 | Object { 180 | "gasUsed": 827, 181 | "priceX96": 1744244129640337381386292603617838n, 182 | } 183 | `; 184 | 185 | exports[`BoundaryMath #getPriceX96AtBoundary boundary = 200000 1`] = ` 186 | Object { 187 | "gasUsed": 827, 188 | "priceX96": 38400329974042030913961448288742562464n, 189 | } 190 | `; 191 | 192 | exports[`BoundaryMath #getPriceX96AtBoundary boundary = 300000 1`] = ` 193 | Object { 194 | "gasUsed": 855, 195 | "priceX96": 845400776793423922697130608897531771147615n, 196 | } 197 | `; 198 | 199 | exports[`BoundaryMath #getPriceX96AtBoundary boundary = 400000 1`] = ` 200 | Object { 201 | "gasUsed": 827, 202 | "priceX96": 18611883644907511909590774894315720731532604461n, 203 | } 204 | `; 205 | 206 | exports[`BoundaryMath #getPriceX96AtBoundary boundary = 443635 1`] = ` 207 | Object { 208 | "gasUsed": 873, 209 | "priceX96": 1461300573427867316570072651998408279850435624081n, 210 | } 211 | `; 212 | -------------------------------------------------------------------------------- /test/__snapshots__/Grid.gas.test.ts.snap: -------------------------------------------------------------------------------- 1 | // Jest Snapshot v1, https://goo.gl/fbAQLP 2 | 3 | exports[`Grid #createGrid 1`] = `6698932`; 4 | 5 | exports[`Grid #initialize 1`] = `501599`; 6 | 7 | exports[`Grid #placeMakerOrder ERC20 1`] = `168571`; 8 | 9 | exports[`Grid #placeMakerOrder ERC20 2`] = `111701`; 10 | 11 | exports[`Grid #placeMakerOrder ERC20 3`] = `111701`; 12 | 13 | exports[`Grid #placeMakerOrder ERC20 4`] = `111701`; 14 | 15 | exports[`Grid #placeMakerOrder ERC20 5`] = `111701`; 16 | 17 | exports[`Grid #placeMakerOrder ETH 1`] = `203225`; 18 | 19 | exports[`Grid #placeMakerOrder ETH 2`] = `129244`; 20 | 21 | exports[`Grid #placeMakerOrder ETH 3`] = `129244`; 22 | 23 | exports[`Grid #placeMakerOrder ETH 4`] = `129244`; 24 | 25 | exports[`Grid #placeMakerOrder ETH 5`] = `129244`; 26 | 27 | exports[`Grid #swap exactInput ERC20 1`] = `162455`; 28 | 29 | exports[`Grid #swap exactInput ERC20 2`] = `91970`; 30 | 31 | exports[`Grid #swap exactInput ERC20 3`] = `91970`; 32 | 33 | exports[`Grid #swap exactInput ERC20 4`] = `91953`; 34 | 35 | exports[`Grid #swap exactInput ERC20 5`] = `104232`; 36 | 37 | exports[`Grid #swap exactInput ERC20 6`] = `91952`; 38 | 39 | exports[`Grid #swap exactInput ETH 1`] = `144205`; 40 | 41 | exports[`Grid #swap exactInput ETH 2`] = `110024`; 42 | 43 | exports[`Grid #swap exactInput ETH 3`] = `110024`; 44 | 45 | exports[`Grid #swap exactInput ETH 4`] = `110024`; 46 | 47 | exports[`Grid #swap exactInput ETH 5`] = `122305`; 48 | 49 | exports[`Grid #swap exactInput ETH 6`] = `110054`; 50 | 51 | exports[`Grid #swap exactOutput ERC20 1`] = `162300`; 52 | 53 | exports[`Grid #swap exactOutput ERC20 2`] = `91798`; 54 | 55 | exports[`Grid #swap exactOutput ERC20 3`] = `104077`; 56 | 57 | exports[`Grid #swap exactOutput ERC20 4`] = `91828`; 58 | 59 | exports[`Grid #swap exactOutput ERC20 5`] = `104107`; 60 | 61 | exports[`Grid #swap exactOutput ERC20 6`] = `91812`; 62 | 63 | exports[`Grid #swap exactOutput ETH 1`] = `143598`; 64 | 65 | exports[`Grid #swap exactOutput ETH 2`] = `121679`; 66 | 67 | exports[`Grid #swap exactOutput ETH 3`] = `109428`; 68 | 69 | exports[`Grid #swap exactOutput ETH 4`] = `121634`; 70 | 71 | exports[`Grid #swap exactOutput ETH 5`] = `108537`; 72 | 73 | exports[`Grid #swap exactOutput ETH 6`] = `121618`; 74 | -------------------------------------------------------------------------------- /test/__snapshots__/GridAddress.test.ts.snap: -------------------------------------------------------------------------------- 1 | // Jest Snapshot v1, https://goo.gl/fbAQLP 2 | 3 | exports[`GridAddress #gridKey gas used snapshot 1`] = `285`; 4 | -------------------------------------------------------------------------------- /test/shared/GridAddress.ts: -------------------------------------------------------------------------------- 1 | // @ts-ignore 2 | import {bytecode} from "../../artifacts/contracts/Grid.sol/Grid.json"; 3 | import {utils} from "ethers"; 4 | import {PromiseOrValue} from "../../typechain-types/common"; 5 | 6 | export const GRID_BYTES_CODE_HASH = utils.keccak256(bytecode); 7 | 8 | export async function computeAddress( 9 | factoryAddress: PromiseOrValue, 10 | tokenA: PromiseOrValue, 11 | tokenB: PromiseOrValue, 12 | resolution: PromiseOrValue 13 | ): Promise { 14 | const {token0, token1} = await sortedToken(tokenA, tokenB); 15 | const encodedValue = utils.defaultAbiCoder.encode(["address", "address", "int24"], [token0, token1, resolution]); 16 | return utils.getCreate2Address(await factoryAddress, utils.keccak256(encodedValue), GRID_BYTES_CODE_HASH); 17 | } 18 | 19 | export async function sortedToken(tokenA: PromiseOrValue, tokenB: PromiseOrValue) { 20 | const ta = await tokenA; 21 | const tb = await tokenB; 22 | const [token0, token1] = ta.toLowerCase() < tb.toLowerCase() ? [ta, tb] : [tb, ta]; 23 | return {token0, token1}; 24 | } 25 | -------------------------------------------------------------------------------- /test/shared/deployer.ts: -------------------------------------------------------------------------------- 1 | import {ethers} from "hardhat"; 2 | import { 3 | ERC20Test, 4 | FlashTest, 5 | GridFactory, 6 | GridTestHelper, 7 | IERC20, 8 | IWETHMinimum, 9 | SwapMathTest, 10 | SwapTest, 11 | } from "../../typechain-types"; 12 | import {BigNumberish} from "ethers"; 13 | import {MAX_UINT_128} from "./util"; 14 | import {bytecode} from "../../artifacts/contracts/Grid.sol/Grid.json"; 15 | import {isHexPrefixed} from "hardhat/internal/hardhat-network/provider/utils/isHexPrefixed"; 16 | import {sha256} from "ethers/lib/utils"; 17 | 18 | const WETH9 = require("../contracts/WETH9.json"); 19 | 20 | export const deployWETH = async () => { 21 | const [signer] = await ethers.getSigners(); 22 | const contractFactory = await ethers.getContractFactory(WETH9.abi, WETH9.bytecode, signer); 23 | const weth = await contractFactory.deploy(); 24 | await weth.deployed(); 25 | return weth as IWETHMinimum; 26 | }; 27 | 28 | export const deployGridFactory = async (weth9: string) => { 29 | const gridFactoryFactory = await ethers.getContractFactory("GridFactory"); 30 | 31 | const bytecodeBytes = hexToBytes(bytecode); 32 | const prefixLength = Math.floor(bytecodeBytes.length / 2); 33 | const gridFactory = await gridFactoryFactory.deploy( 34 | weth9, 35 | bytecodeBytes.slice(0, prefixLength), 36 | ); 37 | await gridFactory.deployed(); 38 | await gridFactory.concatGridSuffixCreationCode(bytecodeBytes.slice(prefixLength)); 39 | 40 | return { 41 | gridFactory, 42 | }; 43 | }; 44 | 45 | function hexToBytes(hex: string) { 46 | if (isHexPrefixed(hex)) { 47 | hex = hex.substring(2); 48 | } 49 | let bytes = []; 50 | for (let c = 0; c < hex.length; c += 2) bytes.push(parseInt(hex.substr(c, 2), 16)); 51 | return bytes; 52 | } 53 | 54 | export const deploySwapMath = async () => { 55 | const swapMathFactory = await ethers.getContractFactory("SwapMathTest"); 56 | const swapMath = await swapMathFactory.deploy(); 57 | await swapMath.deployed(); 58 | return swapMath as SwapMathTest; 59 | }; 60 | 61 | export const deployGridTestHelper = async (gridFactoryAddress: string, weth9Address: string) => { 62 | const contractFactory = await ethers.getContractFactory("GridTestHelper"); 63 | const gridTestHelper = await contractFactory.deploy(gridFactoryAddress, weth9Address); 64 | await gridTestHelper.deployed(); 65 | return gridTestHelper as GridTestHelper; 66 | }; 67 | 68 | export const deployERC20 = async ( 69 | name: string, 70 | symbol: string, 71 | decimals: number, 72 | initialSupply: BigNumberish | undefined 73 | ) => { 74 | const [signer] = await ethers.getSigners(); 75 | const contractFactory = await ethers.getContractFactory("ERC20Test", signer); 76 | const erc20 = await contractFactory.deploy(name, symbol, decimals, initialSupply == undefined ? 0 : initialSupply); 77 | await erc20.deployed(); 78 | return erc20 as IERC20; 79 | }; 80 | 81 | export const deployERC20Tokens = async () => { 82 | const tokenFactory = await ethers.getContractFactory("ERC20Test"); 83 | const tokens: [ERC20Test, ERC20Test, ERC20Test] = [ 84 | (await tokenFactory.deploy("Test ERC20", "Test", 18, MAX_UINT_128.div(2))) as ERC20Test, 85 | (await tokenFactory.deploy("Test ERC20", "Test", 18, MAX_UINT_128.div(2))) as ERC20Test, 86 | (await tokenFactory.deploy("Test ERC20", "Test", 18, MAX_UINT_128.div(2))) as ERC20Test, 87 | ]; 88 | 89 | const promises: Promise[] = []; 90 | for (let t of tokens) { 91 | promises.push(t.deployed()); 92 | } 93 | await Promise.all(promises); 94 | 95 | tokens.sort((a, b) => (a.address.toLowerCase() < b.address.toLowerCase() ? -1 : 1)); 96 | return tokens; 97 | }; 98 | 99 | export const deploySwapTest = async (gridFactory: string, weth: string) => { 100 | const contractFactory = await ethers.getContractFactory("SwapTest"); 101 | const swapTest = await contractFactory.deploy(gridFactory, weth); 102 | await swapTest.deployed(); 103 | return swapTest as SwapTest; 104 | }; 105 | 106 | export const deployFlashTest = async (gridFactory: string, weth: string) => { 107 | const contractFactory = await ethers.getContractFactory("FlashTest"); 108 | const flashTest = await contractFactory.deploy(gridFactory, weth); 109 | await flashTest.deployed(); 110 | return flashTest as FlashTest; 111 | }; 112 | -------------------------------------------------------------------------------- /test/shared/expect.ts: -------------------------------------------------------------------------------- 1 | import {expect, use} from "chai"; 2 | import {jestSnapshotPlugin} from "mocha-chai-jest-snapshot"; 3 | 4 | use(jestSnapshotPlugin()); 5 | 6 | export {expect}; 7 | -------------------------------------------------------------------------------- /test/shared/math.ts: -------------------------------------------------------------------------------- 1 | import {BigNumber, BigNumberish} from "ethers"; 2 | 3 | export enum Rounding { 4 | Down, 5 | Up, 6 | } 7 | 8 | export function mulDiv(x: BigNumberish, y: BigNumberish, denominator: BigNumberish, rounding?: Rounding): BigNumberish { 9 | const numerator = BigNumber.from(x).mul(y); 10 | const result = numerator.div(denominator); 11 | if (rounding == Rounding.Down || rounding == undefined) { 12 | return result; 13 | } 14 | const remainder = numerator.mod(denominator); 15 | return remainder.isZero() ? result : result.add(1); 16 | } 17 | -------------------------------------------------------------------------------- /test/shared/util.ts: -------------------------------------------------------------------------------- 1 | import {BigNumber, BigNumberish} from "ethers"; 2 | import {PromiseOrValue} from "../../typechain-types/common"; 3 | import {Grid} from "../../typechain-types"; 4 | 5 | enum Resolution { 6 | LOW = 1, 7 | MEDIUM = 5, 8 | HIGH = 30, 9 | } 10 | 11 | const RESOLUTION_X96 = 1n << 96n; 12 | const MAX_UINT_128 = BigNumber.from(2).pow(128).sub(1); 13 | const MIN_BOUNDARY = -527400; 14 | const MAX_BOUNDARY = 443635; 15 | const MIN_RATIO = 989314n; 16 | const MAX_RATIO = 1461300573427867316570072651998408279850435624081n; 17 | 18 | export function encodePrice(reserve1: BigNumberish, reserve0: BigNumberish): BigNumber { 19 | return BigNumber.from(reserve1).shl(96).div(reserve0); 20 | } 21 | 22 | export function encodePriceWithBaseAndQuote( 23 | base: string, 24 | baseReserve1: BigNumberish, 25 | quote: string, 26 | quoteReserve0: BigNumberish 27 | ): BigNumber { 28 | const token0 = base.toLowerCase() < quote.toLowerCase() ? base : quote; 29 | if (token0 == base) { 30 | return encodePrice(quoteReserve0, baseReserve1); 31 | } 32 | return encodePrice(baseReserve1, quoteReserve0); 33 | } 34 | 35 | function position(boundary: number, resolution: number) { 36 | boundary = boundary / resolution; 37 | return [boundary >> 8, boundary % 256]; 38 | } 39 | 40 | async function expectBoundaryInitialized( 41 | grid: Grid, 42 | zero: boolean, 43 | boundary: number, 44 | resolution: number, 45 | initialized: boolean 46 | ): Promise { 47 | const [wordPos, bitPos] = position(boundary, resolution); 48 | const word = await (zero ? grid.boundaryBitmaps0(wordPos) : grid.boundaryBitmaps1(wordPos)); 49 | const masked = (1n << BigInt(bitPos)) & word.toBigInt(); 50 | return initialized ? masked == 1n << BigInt(bitPos) : masked == 0n; 51 | } 52 | 53 | async function formatBoundaryToBoundaryLower(boundary: PromiseOrValue, resolution: number): Promise { 54 | const boundaryValue = await boundary; 55 | return boundaryValue - (((boundaryValue % resolution) + resolution) % resolution); 56 | } 57 | 58 | export { 59 | RESOLUTION_X96, 60 | MAX_UINT_128, 61 | MIN_BOUNDARY, 62 | MAX_BOUNDARY, 63 | MIN_RATIO, 64 | MAX_RATIO, 65 | Resolution, 66 | position, 67 | expectBoundaryInitialized, 68 | formatBoundaryToBoundaryLower, 69 | }; 70 | -------------------------------------------------------------------------------- /tsconfig.json: -------------------------------------------------------------------------------- 1 | { 2 | "compilerOptions": { 3 | "target": "es2020", 4 | "module": "commonjs", 5 | "sourceMap": true, 6 | "esModuleInterop": true, 7 | "forceConsistentCasingInFileNames": true, 8 | "strict": true, 9 | "skipLibCheck": true, 10 | "resolveJsonModule": true 11 | }, 12 | "include": [ 13 | "./scripts", 14 | "./test", 15 | "./typechain-types" 16 | ], 17 | "files": [ 18 | "./hardhat.config.ts" 19 | ] 20 | } 21 | --------------------------------------------------------------------------------