├── .gitattributes ├── .github ├── stale.yml └── workflows │ ├── fuzz-testing.yml │ ├── lint.yml │ ├── mythx.yml │ └── tests.yml ├── .gitignore ├── .prettierrc ├── .solhint.json ├── .yarnrc ├── LICENSE ├── README.md ├── audits ├── abdk │ └── audit.pdf └── tob │ ├── README.md │ ├── audit.pdf │ └── contracts │ └── crytic │ ├── echidna │ ├── E2E_mint_burn.config.yaml │ ├── E2E_mint_burn.sol │ ├── E2E_swap.config.yaml │ ├── E2E_swap.sol │ ├── Other.config.yaml │ ├── Other.sol │ └── Setup.sol │ └── manticore │ ├── 001.sol │ ├── 002.sol │ └── 003.sol ├── bug-bounty.md ├── contracts ├── NoDelegateCall.sol ├── UniswapV3Factory.sol ├── UniswapV3Pool.sol ├── UniswapV3PoolDeployer.sol ├── interfaces │ ├── IERC20Minimal.sol │ ├── IUniswapV3Factory.sol │ ├── IUniswapV3Pool.sol │ ├── IUniswapV3PoolDeployer.sol │ ├── LICENSE │ ├── callback │ │ ├── IUniswapV3FlashCallback.sol │ │ ├── IUniswapV3MintCallback.sol │ │ └── IUniswapV3SwapCallback.sol │ └── pool │ │ ├── IUniswapV3PoolActions.sol │ │ ├── IUniswapV3PoolDerivedState.sol │ │ ├── IUniswapV3PoolEvents.sol │ │ ├── IUniswapV3PoolImmutables.sol │ │ ├── IUniswapV3PoolOwnerActions.sol │ │ └── IUniswapV3PoolState.sol ├── libraries │ ├── BitMath.sol │ ├── FixedPoint128.sol │ ├── FixedPoint96.sol │ ├── FullMath.sol │ ├── LICENSE │ ├── LICENSE_MIT │ ├── LiquidityMath.sol │ ├── LowGasSafeMath.sol │ ├── Oracle.sol │ ├── Position.sol │ ├── SafeCast.sol │ ├── SqrtPriceMath.sol │ ├── SwapMath.sol │ ├── Tick.sol │ ├── TickBitmap.sol │ ├── TickMath.sol │ ├── TransferHelper.sol │ └── UnsafeMath.sol └── test │ ├── BitMathEchidnaTest.sol │ ├── BitMathTest.sol │ ├── FullMathEchidnaTest.sol │ ├── FullMathTest.sol │ ├── LiquidityMathTest.sol │ ├── LowGasSafeMathEchidnaTest.sol │ ├── MockTimeUniswapV3Pool.sol │ ├── MockTimeUniswapV3PoolDeployer.sol │ ├── NoDelegateCallTest.sol │ ├── OracleEchidnaTest.sol │ ├── OracleTest.sol │ ├── SqrtPriceMathEchidnaTest.sol │ ├── SqrtPriceMathTest.sol │ ├── SwapMathEchidnaTest.sol │ ├── SwapMathTest.sol │ ├── TestERC20.sol │ ├── TestUniswapV3Callee.sol │ ├── TestUniswapV3ReentrantCallee.sol │ ├── TestUniswapV3Router.sol │ ├── TestUniswapV3SwapPay.sol │ ├── TickBitmapEchidnaTest.sol │ ├── TickBitmapTest.sol │ ├── TickEchidnaTest.sol │ ├── TickMathEchidnaTest.sol │ ├── TickMathTest.sol │ ├── TickOverflowSafetyEchidnaTest.sol │ ├── TickTest.sol │ ├── UniswapV3PoolSwapTest.sol │ └── UnsafeMathEchidnaTest.sol ├── echidna.config.yml ├── hardhat.config.ts ├── package.json ├── test ├── BitMath.spec.ts ├── FullMath.spec.ts ├── LiquidityMath.spec.ts ├── NoDelegateCall.spec.ts ├── Oracle.spec.ts ├── SqrtPriceMath.spec.ts ├── SwapMath.spec.ts ├── Tick.spec.ts ├── TickBitmap.spec.ts ├── TickMath.spec.ts ├── UniswapV3Factory.spec.ts ├── UniswapV3Pool.arbitrage.spec.ts ├── UniswapV3Pool.gas.spec.ts ├── UniswapV3Pool.spec.ts ├── UniswapV3Pool.swaps.spec.ts ├── UniswapV3Router.spec.ts ├── __snapshots__ │ ├── BitMath.spec.ts.snap │ ├── LiquidityMath.spec.ts.snap │ ├── NoDelegateCall.spec.ts.snap │ ├── Oracle.spec.ts.snap │ ├── SqrtPriceMath.spec.ts.snap │ ├── SwapMath.spec.ts.snap │ ├── TickBitmap.spec.ts.snap │ ├── TickMath.spec.ts.snap │ ├── UniswapV3Factory.spec.ts.snap │ ├── UniswapV3Pool.arbitrage.spec.ts.snap │ ├── UniswapV3Pool.gas.spec.ts.snap │ └── UniswapV3Pool.swaps.spec.ts.snap └── shared │ ├── checkObservationEquals.ts │ ├── expect.ts │ ├── fixtures.ts │ ├── format.ts │ ├── snapshotGasCost.ts │ └── utilities.ts ├── tsconfig.json └── yarn.lock /.gitattributes: -------------------------------------------------------------------------------- 1 | *.sol linguist-language=Solidity -------------------------------------------------------------------------------- /.github/stale.yml: -------------------------------------------------------------------------------- 1 | # Configuration for probot-stale - https://github.com/probot/stale 2 | 3 | issues: 4 | # Number of days of inactivity before an Issue or Pull Request becomes stale 5 | daysUntilStale: 7 6 | 7 | # Number of days of inactivity before an Issue or Pull Request with the stale label is closed. 8 | # Set to false to disable. If disabled, issues still need to be closed manually, but will remain marked as stale. 9 | daysUntilClose: 7 10 | 11 | # Only issues or pull requests with all of these labels are check if stale. Defaults to `[]` (disabled) 12 | onlyLabels: 13 | - question 14 | - autoclose 15 | 16 | # Issues or Pull Requests with these labels will never be considered stale. Set to `[]` to disable 17 | exemptLabels: 18 | - p0 19 | - bug 20 | 21 | # Comment to post when marking as stale. Set to `false` to disable 22 | markComment: > 23 | This issue has been automatically marked as stale because it has not had 24 | recent activity. It will be closed if no further activity occurs. Thank you 25 | for your contributions. 26 | -------------------------------------------------------------------------------- /.github/workflows/fuzz-testing.yml: -------------------------------------------------------------------------------- 1 | name: Fuzz Testing 2 | 3 | on: 4 | push: 5 | branches: 6 | - main 7 | pull_request: 8 | 9 | jobs: 10 | echidna: 11 | name: Echidna 12 | runs-on: ubuntu-latest 13 | strategy: 14 | fail-fast: false 15 | matrix: 16 | testName: 17 | - TickBitmapEchidnaTest 18 | - TickMathEchidnaTest 19 | - SqrtPriceMathEchidnaTest 20 | - SwapMathEchidnaTest 21 | - TickEchidnaTest 22 | - TickOverflowSafetyEchidnaTest 23 | - OracleEchidnaTest 24 | - BitMathEchidnaTest 25 | - LowGasSafeMathEchidnaTest 26 | - UnsafeMathEchidnaTest 27 | - FullMathEchidnaTest 28 | 29 | steps: 30 | - uses: actions/checkout@v2 31 | 32 | - name: Set up node 33 | uses: actions/setup-node@v1 34 | with: 35 | node-version: 12 36 | 37 | - name: Set up Python 3.8 38 | uses: actions/setup-python@v2 39 | with: 40 | python-version: 3.8 41 | 42 | - name: Install node dependencies 43 | run: yarn install --frozen-lockfile 44 | 45 | - name: Install pip3 46 | run: | 47 | python -m pip install --upgrade pip 48 | 49 | - name: Install slither 50 | run: | 51 | pip3 install slither-analyzer 52 | 53 | - name: Install echidna 54 | run: | 55 | sudo wget -O /tmp/echidna-test.tar.gz https://github.com/crytic/echidna/releases/download/v1.6.0/echidna-test-v1.6.0-Ubuntu-18.04.tar.gz 56 | sudo tar -xf /tmp/echidna-test.tar.gz -C /usr/bin 57 | sudo chmod +x /usr/bin/echidna-test 58 | 59 | - name: Run ${{ matrix.testName }} 60 | run: echidna-test . --contract ${{ matrix.testName }} --config echidna.config.yml 61 | -------------------------------------------------------------------------------- /.github/workflows/lint.yml: -------------------------------------------------------------------------------- 1 | name: Lint 2 | 3 | on: 4 | push: 5 | branches: 6 | - main 7 | pull_request: 8 | 9 | jobs: 10 | run-linters: 11 | name: Run linters 12 | runs-on: ubuntu-latest 13 | 14 | steps: 15 | - name: Check out Git repository 16 | uses: actions/checkout@v2 17 | 18 | - name: Set up node 19 | uses: actions/setup-node@v1 20 | with: 21 | node-version: 12 22 | 23 | - name: Install dependencies 24 | run: yarn install --frozen-lockfile 25 | 26 | - name: Run linters 27 | uses: wearerequired/lint-action@a8497ddb33fb1205941fd40452ca9fff07e0770d 28 | with: 29 | github_token: ${{ secrets.github_token }} 30 | prettier: true 31 | auto_fix: true 32 | prettier_extensions: 'css,html,js,json,jsx,md,sass,scss,ts,tsx,vue,yaml,yml,sol' 33 | -------------------------------------------------------------------------------- /.github/workflows/mythx.yml: -------------------------------------------------------------------------------- 1 | name: Mythx 2 | 3 | on: 4 | workflow_dispatch: 5 | 6 | jobs: 7 | mythx: 8 | name: Submit to Mythx 9 | runs-on: ubuntu-latest 10 | 11 | steps: 12 | - uses: actions/checkout@v2 13 | 14 | - name: Set up node 15 | uses: actions/setup-node@v1 16 | with: 17 | node-version: 12 18 | 19 | - name: Set up Python 3.8 20 | uses: actions/setup-python@v2 21 | with: 22 | python-version: 3.8 23 | 24 | - name: Install node dependencies 25 | run: yarn install --frozen-lockfile 26 | 27 | - name: Install pip3 28 | run: | 29 | python -m pip install --upgrade pip 30 | 31 | - name: Install mythx CLI 32 | run: | 33 | pip3 install mythx-cli 34 | 35 | - name: Install solc-select 36 | run: | 37 | pip3 install solc-select 38 | 39 | - name: Install solc 0.7.6 40 | run: | 41 | solc-select install 0.7.6 42 | solc-select use 0.7.6 43 | 44 | - name: Submit code to Mythx 45 | run: | 46 | mythx --api-key ${{ secrets.MYTHX_API_KEY }} \ 47 | --yes \ 48 | analyze \ 49 | --mode deep \ 50 | --async \ 51 | --create-group \ 52 | --group-name "@uniswap/v3-core@${{ github.sha }}" \ 53 | --solc-version 0.7.6 \ 54 | --check-properties \ 55 | contracts/test/TickBitmapEchidnaTest.sol --include TickBitmapEchidnaTest \ 56 | contracts/test/TickMathEchidnaTest.sol --include TickMathEchidnaTest \ 57 | contracts/test/SqrtPriceMathEchidnaTest.sol --include SqrtPriceMathEchidnaTest \ 58 | contracts/test/SwapMathEchidnaTest.sol --include SwapMathEchidnaTest \ 59 | contracts/test/TickEchidnaTest.sol --include TickEchidnaTest \ 60 | contracts/test/TickOverflowSafetyEchidnaTest.sol --include TickOverflowSafetyEchidnaTest \ 61 | contracts/test/OracleEchidnaTest.sol --include OracleEchidnaTest \ 62 | contracts/test/BitMathEchidnaTest.sol --include BitMathEchidnaTest \ 63 | contracts/test/LowGasSafeMathEchidnaTest.sol --include LowGasSafeMathEchidnaTest \ 64 | contracts/test/UnsafeMathEchidnaTest.sol --include UnsafeMathEchidnaTest \ 65 | contracts/test/FullMathEchidnaTest.sol --include FullMathEchidnaTest 66 | -------------------------------------------------------------------------------- /.github/workflows/tests.yml: -------------------------------------------------------------------------------- 1 | name: Tests 2 | 3 | on: 4 | push: 5 | branches: 6 | - main 7 | pull_request: 8 | 9 | jobs: 10 | unit-tests: 11 | name: Unit Tests 12 | runs-on: ubuntu-latest 13 | 14 | steps: 15 | - uses: actions/checkout@v1 16 | - uses: actions/setup-node@v1 17 | with: 18 | node-version: 12.x 19 | 20 | - id: yarn-cache 21 | run: echo "::set-output name=dir::$(yarn cache dir)" 22 | 23 | - uses: actions/cache@v1 24 | with: 25 | path: ${{ steps.yarn-cache.outputs.dir }} 26 | key: yarn-${{ hashFiles('**/yarn.lock') }} 27 | restore-keys: | 28 | yarn- 29 | 30 | - name: Install dependencies 31 | run: yarn install --frozen-lockfile 32 | 33 | # This is required separately from yarn test because it generates the typechain definitions 34 | - name: Compile 35 | run: yarn compile 36 | 37 | - name: Run unit tests 38 | run: yarn test 39 | -------------------------------------------------------------------------------- /.gitignore: -------------------------------------------------------------------------------- 1 | artifacts/ 2 | cache/ 3 | crytic-export/ 4 | node_modules/ 5 | typechain/ 6 | -------------------------------------------------------------------------------- /.prettierrc: -------------------------------------------------------------------------------- 1 | { 2 | "semi": false, 3 | "singleQuote": true, 4 | "printWidth": 120 5 | } 6 | -------------------------------------------------------------------------------- /.solhint.json: -------------------------------------------------------------------------------- 1 | { 2 | "plugins": ["prettier"], 3 | "rules": { 4 | "prettier/prettier": "error" 5 | } 6 | } 7 | -------------------------------------------------------------------------------- /.yarnrc: -------------------------------------------------------------------------------- 1 | ignore-scripts true 2 | -------------------------------------------------------------------------------- /LICENSE: -------------------------------------------------------------------------------- 1 | Business Source License 1.1 2 | 3 | License text copyright (c) 2017 MariaDB Corporation Ab, All Rights Reserved. 4 | "Business Source License" is a trademark of MariaDB Corporation Ab. 5 | 6 | ----------------------------------------------------------------------------- 7 | 8 | Parameters 9 | 10 | Licensor: Uniswap Labs 11 | 12 | Licensed Work: Uniswap V3 Core 13 | The Licensed Work is (c) 2021 Uniswap Labs 14 | 15 | Additional Use Grant: Any uses listed and defined at 16 | v3-core-license-grants.uniswap.eth 17 | 18 | Change Date: The earlier of 2023-04-01 or a date specified at 19 | v3-core-license-date.uniswap.eth 20 | 21 | Change License: GNU General Public License v2.0 or later 22 | 23 | ----------------------------------------------------------------------------- 24 | 25 | Terms 26 | 27 | The Licensor hereby grants you the right to copy, modify, create derivative 28 | works, redistribute, and make non-production use of the Licensed Work. The 29 | Licensor may make an Additional Use Grant, above, permitting limited 30 | production use. 31 | 32 | Effective on the Change Date, or the fourth anniversary of the first publicly 33 | available distribution of a specific version of the Licensed Work under this 34 | License, whichever comes first, the Licensor hereby grants you rights under 35 | the terms of the Change License, and the rights granted in the paragraph 36 | above terminate. 37 | 38 | If your use of the Licensed Work does not comply with the requirements 39 | currently in effect as described in this License, you must purchase a 40 | commercial license from the Licensor, its affiliated entities, or authorized 41 | resellers, or you must refrain from using the Licensed Work. 42 | 43 | All copies of the original and modified Licensed Work, and derivative works 44 | of the Licensed Work, are subject to this License. This License applies 45 | separately for each version of the Licensed Work and the Change Date may vary 46 | for each version of the Licensed Work released by Licensor. 47 | 48 | You must conspicuously display this License on each original or modified copy 49 | of the Licensed Work. If you receive the Licensed Work in original or 50 | modified form from a third party, the terms and conditions set forth in this 51 | License apply to your use of that work. 52 | 53 | Any use of the Licensed Work in violation of this License will automatically 54 | terminate your rights under this License for the current and all other 55 | versions of the Licensed Work. 56 | 57 | This License does not grant you any right in any trademark or logo of 58 | Licensor or its affiliates (provided that you may use a trademark or logo of 59 | Licensor as expressly required by this License). 60 | 61 | TO THE EXTENT PERMITTED BY APPLICABLE LAW, THE LICENSED WORK IS PROVIDED ON 62 | AN "AS IS" BASIS. LICENSOR HEREBY DISCLAIMS ALL WARRANTIES AND CONDITIONS, 63 | EXPRESS OR IMPLIED, INCLUDING (WITHOUT LIMITATION) WARRANTIES OF 64 | MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE, NON-INFRINGEMENT, AND 65 | TITLE. 66 | 67 | MariaDB hereby grants you permission to use this License’s text to license 68 | your works, and to refer to it using the trademark "Business Source License", 69 | as long as you comply with the Covenants of Licensor below. 70 | 71 | ----------------------------------------------------------------------------- 72 | 73 | Covenants of Licensor 74 | 75 | In consideration of the right to use this License’s text and the "Business 76 | Source License" name and trademark, Licensor covenants to MariaDB, and to all 77 | other recipients of the licensed work to be provided by Licensor: 78 | 79 | 1. To specify as the Change License the GPL Version 2.0 or any later version, 80 | or a license that is compatible with GPL Version 2.0 or a later version, 81 | where "compatible" means that software provided under the Change License can 82 | be included in a program with software provided under GPL Version 2.0 or a 83 | later version. Licensor may specify additional Change Licenses without 84 | limitation. 85 | 86 | 2. To either: (a) specify an additional grant of rights to use that does not 87 | impose any additional restriction on the right granted in this License, as 88 | the Additional Use Grant; or (b) insert the text "None". 89 | 90 | 3. To specify a Change Date. 91 | 92 | 4. Not to modify this License in any other way. 93 | 94 | ----------------------------------------------------------------------------- 95 | 96 | Notice 97 | 98 | The Business Source License (this document, or the "License") is not an Open 99 | Source license. However, the Licensed Work will eventually be made available 100 | under an Open Source License, as stated in this License. 101 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | # Uniswap V3 2 | 3 | [![Lint](https://github.com/Uniswap/uniswap-v3-core/actions/workflows/lint.yml/badge.svg)](https://github.com/Uniswap/uniswap-v3-core/actions/workflows/lint.yml) 4 | [![Tests](https://github.com/Uniswap/uniswap-v3-core/actions/workflows/tests.yml/badge.svg)](https://github.com/Uniswap/uniswap-v3-core/actions/workflows/tests.yml) 5 | [![Fuzz Testing](https://github.com/Uniswap/uniswap-v3-core/actions/workflows/fuzz-testing.yml/badge.svg)](https://github.com/Uniswap/uniswap-v3-core/actions/workflows/fuzz-testing.yml) 6 | [![Mythx](https://github.com/Uniswap/uniswap-v3-core/actions/workflows/mythx.yml/badge.svg)](https://github.com/Uniswap/uniswap-v3-core/actions/workflows/mythx.yml) 7 | [![npm version](https://img.shields.io/npm/v/@uniswap/v3-core/latest.svg)](https://www.npmjs.com/package/@uniswap/v3-core/v/latest) 8 | 9 | This repository contains the core smart contracts for the Uniswap V3 Protocol. 10 | For higher level contracts, see the [uniswap-v3-periphery](https://github.com/Uniswap/uniswap-v3-periphery) 11 | repository. 12 | 13 | ## Bug bounty 14 | 15 | This repository is subject to the Uniswap V3 bug bounty program, per the terms defined [here](./bug-bounty.md). 16 | 17 | ## Local deployment 18 | 19 | In order to deploy this code to a local testnet, you should install the npm package 20 | `@uniswap/v3-core` 21 | and import the factory bytecode located at 22 | `@uniswap/v3-core/artifacts/contracts/UniswapV3Factory.sol/UniswapV3Factory.json`. 23 | For example: 24 | 25 | ```typescript 26 | import { 27 | abi as FACTORY_ABI, 28 | bytecode as FACTORY_BYTECODE, 29 | } from '@uniswap/v3-core/artifacts/contracts/UniswapV3Factory.sol/UniswapV3Factory.json' 30 | 31 | // deploy the bytecode 32 | ``` 33 | 34 | This will ensure that you are testing against the same bytecode that is deployed to 35 | mainnet and public testnets, and all Uniswap code will correctly interoperate with 36 | your local deployment. 37 | 38 | ## Using solidity interfaces 39 | 40 | The Uniswap v3 interfaces are available for import into solidity smart contracts 41 | via the npm artifact `@uniswap/v3-core`, e.g.: 42 | 43 | ```solidity 44 | import '@uniswap/v3-core/contracts/interfaces/IUniswapV3Pool.sol'; 45 | 46 | contract MyContract { 47 | IUniswapV3Pool pool; 48 | 49 | function doSomethingWithPool() { 50 | // pool.swap(...); 51 | } 52 | } 53 | 54 | ``` 55 | 56 | ## Licensing 57 | 58 | The primary license for Uniswap V3 Core is the Business Source License 1.1 (`BUSL-1.1`), see [`LICENSE`](./LICENSE). However, some files are dual licensed under `GPL-2.0-or-later`: 59 | 60 | - All files in `contracts/interfaces/` may also be licensed under `GPL-2.0-or-later` (as indicated in their SPDX headers), see [`contracts/interfaces/LICENSE`](./contracts/interfaces/LICENSE) 61 | - Several files in `contracts/libraries/` may also be licensed under `GPL-2.0-or-later` (as indicated in their SPDX headers), see [`contracts/libraries/LICENSE`](contracts/libraries/LICENSE) 62 | 63 | ### Other Exceptions 64 | 65 | - `contracts/libraries/FullMath.sol` is licensed under `MIT` (as indicated in its SPDX header), see [`contracts/libraries/LICENSE_MIT`](contracts/libraries/LICENSE_MIT) 66 | - All files in `contracts/test` remain unlicensed (as indicated in their SPDX headers). 67 | -------------------------------------------------------------------------------- /audits/abdk/audit.pdf: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/Uniswap/v3-core/d8b1c635c275d2a9450bd6a78f3fa2484fef73eb/audits/abdk/audit.pdf -------------------------------------------------------------------------------- /audits/tob/audit.pdf: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/Uniswap/v3-core/d8b1c635c275d2a9450bd6a78f3fa2484fef73eb/audits/tob/audit.pdf -------------------------------------------------------------------------------- /audits/tob/contracts/crytic/echidna/E2E_mint_burn.config.yaml: -------------------------------------------------------------------------------- 1 | checkAsserts: true 2 | coverage: true 3 | codeSize: 0x60000 4 | corpusDir: echidna_e2e_mint_burn_corpus 5 | seqLen: 10 6 | testLimit: 100000 7 | timeout: 600 # 10 minutes 8 | 9 | # blacklist 10 | filterFunctions: 11 | [ 12 | 'E2E_mint_burn.viewInitRandomPoolParams(uint128)', 13 | 'E2E_mint_burn.viewMintRandomNewPosition(uint128,int24,uint24,int24)', 14 | 'E2E_mint_burn.viewBurnRandomPositionIdx(uint128,uint128)', 15 | 'E2E_mint_burn.viewBurnRandomPositionBurnAmount(uint128,uint128)', 16 | ] 17 | -------------------------------------------------------------------------------- /audits/tob/contracts/crytic/echidna/E2E_swap.config.yaml: -------------------------------------------------------------------------------- 1 | checkAsserts: true 2 | coverage: true 3 | codeSize: 0x60000 4 | corpusDir: echidna_e2e_swap_corpus 5 | seqLen: 10 6 | testLimit: 100000 7 | timeout: 3600 # 1 hour 8 | 9 | # blacklist 10 | filterFunctions: ['E2E_swap.viewRandomInit(uint128)'] 11 | -------------------------------------------------------------------------------- /audits/tob/contracts/crytic/echidna/Other.config.yaml: -------------------------------------------------------------------------------- 1 | checkAsserts: true 2 | coverage: true 3 | codeSize: 0x60000 4 | corpusDir: echidna_other_corpus 5 | seqLen: 1000 6 | testLimit: 100000 7 | timeout: 3600 # 1 hour 8 | -------------------------------------------------------------------------------- /audits/tob/contracts/crytic/echidna/Other.sol: -------------------------------------------------------------------------------- 1 | pragma solidity =0.7.6; 2 | 3 | import '../../../../../contracts/libraries/SqrtPriceMath.sol'; 4 | import '../../../../../contracts/libraries/TickMath.sol'; 5 | 6 | contract Other { 7 | // prop #30 8 | function test_getNextSqrtPriceFromInAndOutput( 9 | uint160 sqrtPX96, 10 | uint128 liquidity, 11 | uint256 amount, 12 | bool add 13 | ) public { 14 | require(sqrtPX96 >= TickMath.MIN_SQRT_RATIO && sqrtPX96 < TickMath.MAX_SQRT_RATIO); 15 | require(liquidity < 3121856577256316178563069792952001939); // max liquidity per tick 16 | uint256 next_sqrt = SqrtPriceMath.getNextSqrtPriceFromInput(sqrtPX96, liquidity, amount, add); 17 | assert(next_sqrt >= TickMath.MIN_SQRT_RATIO && next_sqrt < TickMath.MAX_SQRT_RATIO); 18 | next_sqrt = SqrtPriceMath.getNextSqrtPriceFromOutput(sqrtPX96, liquidity, amount, add); 19 | assert(next_sqrt >= TickMath.MIN_SQRT_RATIO && next_sqrt < TickMath.MAX_SQRT_RATIO); 20 | } 21 | } 22 | -------------------------------------------------------------------------------- /audits/tob/contracts/crytic/echidna/Setup.sol: -------------------------------------------------------------------------------- 1 | pragma solidity =0.7.6; 2 | pragma abicoder v2; 3 | 4 | import '../../../../../contracts/test/TestERC20.sol'; 5 | import '../../../../../contracts/UniswapV3Pool.sol'; 6 | import '../../../../../contracts/UniswapV3Factory.sol'; 7 | 8 | contract SetupToken { 9 | TestERC20 public token; 10 | 11 | constructor() public { 12 | // this contract will receive the total supply of 100 tokens 13 | token = new TestERC20(1e12 ether); 14 | } 15 | 16 | function mintTo(address _recipient, uint256 _amount) public { 17 | token.transfer(_recipient, _amount); 18 | } 19 | } 20 | 21 | contract SetupTokens { 22 | SetupToken tokenSetup0; 23 | SetupToken tokenSetup1; 24 | 25 | TestERC20 public token0; 26 | TestERC20 public token1; 27 | 28 | constructor() public { 29 | // create the token wrappers 30 | tokenSetup0 = new SetupToken(); 31 | tokenSetup1 = new SetupToken(); 32 | 33 | // switch them around so that token0's address is lower than token1's 34 | // since this is what the uniswap factory will do when you create the pool 35 | if (address(tokenSetup0.token()) > address(tokenSetup1.token())) { 36 | (tokenSetup0, tokenSetup1) = (tokenSetup1, tokenSetup0); 37 | } 38 | 39 | // save the erc20 tokens 40 | token0 = tokenSetup0.token(); 41 | token1 = tokenSetup1.token(); 42 | } 43 | 44 | // mint either token0 or token1 to a chosen account 45 | function mintTo( 46 | uint256 _tokenIdx, 47 | address _recipient, 48 | uint256 _amount 49 | ) public { 50 | require(_tokenIdx == 0 || _tokenIdx == 1, 'invalid token idx'); 51 | if (_tokenIdx == 0) tokenSetup0.mintTo(_recipient, _amount); 52 | if (_tokenIdx == 1) tokenSetup1.mintTo(_recipient, _amount); 53 | } 54 | } 55 | 56 | contract SetupUniswap { 57 | UniswapV3Pool public pool; 58 | TestERC20 token0; 59 | TestERC20 token1; 60 | 61 | // will create the following enabled fees and corresponding tickSpacing 62 | // fee 500 + tickSpacing 10 63 | // fee 3000 + tickSpacing 60 64 | // fee 10000 + tickSpacing 200 65 | UniswapV3Factory factory; 66 | 67 | constructor(TestERC20 _token0, TestERC20 _token1) public { 68 | factory = new UniswapV3Factory(); 69 | token0 = _token0; 70 | token1 = _token1; 71 | } 72 | 73 | function createPool(uint24 _fee, uint160 _startPrice) public { 74 | pool = UniswapV3Pool(factory.createPool(address(token0), address(token1), _fee)); 75 | pool.initialize(_startPrice); 76 | } 77 | } 78 | 79 | contract UniswapMinter { 80 | UniswapV3Pool pool; 81 | TestERC20 token0; 82 | TestERC20 token1; 83 | 84 | struct MinterStats { 85 | uint128 liq; 86 | uint128 tL_liqGross; 87 | int128 tL_liqNet; 88 | uint128 tU_liqGross; 89 | int128 tU_liqNet; 90 | } 91 | 92 | constructor(TestERC20 _token0, TestERC20 _token1) public { 93 | token0 = _token0; 94 | token1 = _token1; 95 | } 96 | 97 | function setPool(UniswapV3Pool _pool) public { 98 | pool = _pool; 99 | } 100 | 101 | function uniswapV3MintCallback( 102 | uint256 amount0Owed, 103 | uint256 amount1Owed, 104 | bytes calldata data 105 | ) external { 106 | if (amount0Owed > 0) token0.transfer(address(pool), amount0Owed); 107 | if (amount1Owed > 0) token1.transfer(address(pool), amount1Owed); 108 | } 109 | 110 | function getTickLiquidityVars(int24 _tickLower, int24 _tickUpper) 111 | internal 112 | view 113 | returns ( 114 | uint128, 115 | int128, 116 | uint128, 117 | int128 118 | ) 119 | { 120 | (uint128 tL_liqGross, int128 tL_liqNet, , ) = pool.ticks(_tickLower); 121 | (uint128 tU_liqGross, int128 tU_liqNet, , ) = pool.ticks(_tickUpper); 122 | return (tL_liqGross, tL_liqNet, tU_liqGross, tU_liqNet); 123 | } 124 | 125 | function getStats(int24 _tickLower, int24 _tickUpper) internal view returns (MinterStats memory stats) { 126 | (uint128 tL_lg, int128 tL_ln, uint128 tU_lg, int128 tU_ln) = getTickLiquidityVars(_tickLower, _tickUpper); 127 | return MinterStats(pool.liquidity(), tL_lg, tL_ln, tU_lg, tU_ln); 128 | } 129 | 130 | function doMint( 131 | int24 _tickLower, 132 | int24 _tickUpper, 133 | uint128 _amount 134 | ) public returns (MinterStats memory bfre, MinterStats memory aftr) { 135 | bfre = getStats(_tickLower, _tickUpper); 136 | pool.mint(address(this), _tickLower, _tickUpper, _amount, new bytes(0)); 137 | aftr = getStats(_tickLower, _tickUpper); 138 | } 139 | 140 | function doBurn( 141 | int24 _tickLower, 142 | int24 _tickUpper, 143 | uint128 _amount 144 | ) public returns (MinterStats memory bfre, MinterStats memory aftr) { 145 | bfre = getStats(_tickLower, _tickUpper); 146 | pool.burn(_tickLower, _tickUpper, _amount); 147 | aftr = getStats(_tickLower, _tickUpper); 148 | } 149 | } 150 | 151 | contract UniswapSwapper { 152 | UniswapV3Pool pool; 153 | TestERC20 token0; 154 | TestERC20 token1; 155 | 156 | struct SwapperStats { 157 | uint128 liq; 158 | uint256 feeGrowthGlobal0X128; 159 | uint256 feeGrowthGlobal1X128; 160 | uint256 bal0; 161 | uint256 bal1; 162 | int24 tick; 163 | } 164 | 165 | constructor(TestERC20 _token0, TestERC20 _token1) public { 166 | token0 = _token0; 167 | token1 = _token1; 168 | } 169 | 170 | function setPool(UniswapV3Pool _pool) public { 171 | pool = _pool; 172 | } 173 | 174 | function uniswapV3SwapCallback( 175 | int256 amount0Delta, 176 | int256 amount1Delta, 177 | bytes calldata data 178 | ) external { 179 | if (amount0Delta > 0) token0.transfer(address(pool), uint256(amount0Delta)); 180 | if (amount1Delta > 0) token1.transfer(address(pool), uint256(amount1Delta)); 181 | } 182 | 183 | function getStats() internal view returns (SwapperStats memory stats) { 184 | (, int24 currentTick, , , , , ) = pool.slot0(); 185 | return 186 | SwapperStats( 187 | pool.liquidity(), 188 | pool.feeGrowthGlobal0X128(), 189 | pool.feeGrowthGlobal1X128(), 190 | token0.balanceOf(address(this)), 191 | token1.balanceOf(address(this)), 192 | currentTick 193 | ); 194 | } 195 | 196 | function doSwap( 197 | bool _zeroForOne, 198 | int256 _amountSpecified, 199 | uint160 _sqrtPriceLimitX96 200 | ) public returns (SwapperStats memory bfre, SwapperStats memory aftr) { 201 | bfre = getStats(); 202 | pool.swap(address(this), _zeroForOne, _amountSpecified, _sqrtPriceLimitX96, new bytes(0)); 203 | aftr = getStats(); 204 | } 205 | } 206 | -------------------------------------------------------------------------------- /audits/tob/contracts/crytic/manticore/001.sol: -------------------------------------------------------------------------------- 1 | import '../../../../../contracts/libraries/BitMath.sol'; 2 | 3 | contract VerifyBitMathMsb { 4 | function verify(uint256 x) external { 5 | uint256 msb = BitMath.mostSignificantBit(x); 6 | 7 | bool property = x >= 2**msb && (msb == 255 || x < 2**(msb + 1)); 8 | 9 | require(!property); 10 | } 11 | } 12 | -------------------------------------------------------------------------------- /audits/tob/contracts/crytic/manticore/002.sol: -------------------------------------------------------------------------------- 1 | import '../../../../../contracts/libraries/BitMath.sol'; 2 | 3 | contract VerifyBitMathLsb { 4 | function verify(uint256 x) external { 5 | uint256 lsb = BitMath.leastSignificantBit(x); 6 | 7 | // (x & 2**leastSignificantBit(x)) != 0 and (x & (2**(leastSignificantBit(x)) - 1)) == 0) 8 | bool property = ((x & (2**lsb)) != 0) && ((x & (2**(lsb - 1))) == 0); 9 | 10 | require(!property); 11 | } 12 | } 13 | -------------------------------------------------------------------------------- /audits/tob/contracts/crytic/manticore/003.sol: -------------------------------------------------------------------------------- 1 | import '../../../../../contracts/libraries/LiquidityMath.sol'; 2 | 3 | contract VerifyLiquidityMathAddDelta { 4 | function verify(uint128 x, int128 y) external { 5 | uint256 z = LiquidityMath.addDelta(x, y); 6 | 7 | require(z != x + uint128(y)); 8 | } 9 | } 10 | -------------------------------------------------------------------------------- /bug-bounty.md: -------------------------------------------------------------------------------- 1 | # Uniswap V3 Bug Bounty 2 | 3 | ## Overview 4 | 5 | Starting on March 23rd, 2021, the [uniswap-v3-core](https://github.com/Uniswap/uniswap-v3-core) repository is subject to the Uniswap V3 Bug Bounty (the “Program”) to incentivize responsible bug disclosure. 6 | 7 | We are limiting the scope of the Program to critical and high severity bugs, and are offering a reward of up to $500,000. Happy hunting! 8 | 9 | ## Scope 10 | 11 | The scope of the Program is limited to bugs that result in the draining of contract funds. 12 | 13 | The following are not within the scope of the Program: 14 | 15 | - Any contract located under [contracts/test](./contracts/test). 16 | - Bugs in any third party contract or platform that interacts with Uniswap V3. 17 | - Vulnerabilities already reported and/or discovered in contracts built by third parties on Uniswap V3. 18 | - Any already-reported bugs. 19 | 20 | Vulnerabilities contingent upon the occurrence of any of the following also are outside the scope of this Program: 21 | 22 | - Frontend bugs 23 | - DDOS attacks 24 | - Spamming 25 | - Phishing 26 | - Automated tools (Github Actions, AWS, etc.) 27 | - Compromise or misuse of third party systems or services 28 | 29 | ## Assumptions 30 | 31 | Uniswap V3 was developed with the following assumptions, and thus any bug must also adhere to the following assumptions 32 | to be eligible for the bug bounty: 33 | 34 | - The total supply of any token does not exceed 2128 - 1, i.e. `type(uint128).max`. 35 | - The `transfer` and `transferFrom` methods of any token strictly decrease the balance of the token sender by the transfer amount and increases the balance of token recipient by the transfer amount, i.e. fee on transfer tokens are excluded. 36 | - The token balance of an address can only change due to a call to `transfer` by the sender or `transferFrom` by an approved address, i.e. rebase tokens and interest bearing tokens are excluded. 37 | 38 | ## Rewards 39 | 40 | Rewards will be allocated based on the severity of the bug disclosed and will be evaluated and rewarded at the discretion of the Uniswap Labs team. For critical bugs that lead to any loss of LP funds, rewards of up to $500,000 will be granted. Lower severity bugs will be rewarded at the discretion of the team. In addition, all vulnerabilities disclosed prior to the mainnet launch date will be subject to receive higher rewards. 41 | 42 | ## Disclosure 43 | 44 | Any vulnerability or bug discovered must be reported only to the following email: [security@uniswap.org](mailto:security@uniswap.org). 45 | 46 | The vulnerability must not be disclosed publicly or to any other person, entity or email address before Uniswap Labs has been notified, has fixed the issue, and has granted permission for public disclosure. In addition, disclosure must be made within 24 hours following discovery of the vulnerability. 47 | 48 | A detailed report of a vulnerability increases the likelihood of a reward and may increase the reward amount. Please provide as much information about the vulnerability as possible, including: 49 | 50 | - The conditions on which reproducing the bug is contingent. 51 | - The steps needed to reproduce the bug or, preferably, a proof of concept. 52 | - The potential implications of the vulnerability being abused. 53 | 54 | Anyone who reports a unique, previously-unreported vulnerability that results in a change to the code or a configuration change and who keeps such vulnerability confidential until it has been resolved by our engineers will be recognized publicly for their contribution if they so choose. 55 | 56 | ## Eligibility 57 | 58 | To be eligible for a reward under this Program, you must: 59 | 60 | - Discover a previously unreported, non-public vulnerability that would result in a loss of and/or lock on any ERC-20 token on Uniswap V3 (but not on any third party platform interacting with Uniswap V3) and that is within the scope of this Program. Vulnerabilities must be distinct from the issues covered in the Trail of Bits or ABDK audits. 61 | - Be the first to disclose the unique vulnerability to [security@uniswap.org](mailto:security@uniswap.org), in compliance with the disclosure requirements above. If similar vulnerabilities are reported within the same 24 hour period, rewards will be split at the discretion of Uniswap Labs. 62 | - Provide sufficient information to enable our engineers to reproduce and fix the vulnerability. 63 | - Not engage in any unlawful conduct when disclosing the bug, including through threats, demands, or any other coercive tactics. 64 | - Not exploit the vulnerability in any way, including through making it public or by obtaining a profit (other than a reward under this Program). 65 | - Make a good faith effort to avoid privacy violations, destruction of data, interruption or degradation of Uniswap V3. 66 | - Submit only one vulnerability per submission, unless you need to chain vulnerabilities to provide impact regarding any of the vulnerabilities. 67 | - Not submit a vulnerability caused by an underlying issue that is the same as an issue on which a reward has been paid under this Program. 68 | - Not be one of our current or former employees, vendors, or contractors or an employee of any of those vendors or contractors. 69 | - Not be subject to US sanctions or reside in a US-embargoed country. 70 | - Be at least 18 years of age or, if younger, submit your vulnerability with the consent of your parent or guardian. 71 | 72 | ## Other Terms 73 | 74 | By submitting your report, you grant Uniswap Labs any and all rights, including intellectual property rights, needed to validate, mitigate, and disclose the vulnerability. All reward decisions, including eligibility for and amounts of the rewards and the manner in which such rewards will be paid, are made at our sole discretion. 75 | 76 | The terms and conditions of this Program may be altered at any time. 77 | -------------------------------------------------------------------------------- /contracts/NoDelegateCall.sol: -------------------------------------------------------------------------------- 1 | // SPDX-License-Identifier: BUSL-1.1 2 | pragma solidity =0.7.6; 3 | 4 | /// @title Prevents delegatecall to a contract 5 | /// @notice Base contract that provides a modifier for preventing delegatecall to methods in a child contract 6 | abstract contract NoDelegateCall { 7 | /// @dev The original address of this contract 8 | address private immutable original; 9 | 10 | constructor() { 11 | // Immutables are computed in the init code of the contract, and then inlined into the deployed bytecode. 12 | // In other words, this variable won't change when it's checked at runtime. 13 | original = address(this); 14 | } 15 | 16 | /// @dev Private method is used instead of inlining into modifier because modifiers are copied into each method, 17 | /// and the use of immutable means the address bytes are copied in every place the modifier is used. 18 | function checkNotDelegateCall() private view { 19 | require(address(this) == original); 20 | } 21 | 22 | /// @notice Prevents delegatecall into the modified method 23 | modifier noDelegateCall() { 24 | checkNotDelegateCall(); 25 | _; 26 | } 27 | } 28 | -------------------------------------------------------------------------------- /contracts/UniswapV3Factory.sol: -------------------------------------------------------------------------------- 1 | // SPDX-License-Identifier: BUSL-1.1 2 | pragma solidity =0.7.6; 3 | 4 | import './interfaces/IUniswapV3Factory.sol'; 5 | 6 | import './UniswapV3PoolDeployer.sol'; 7 | import './NoDelegateCall.sol'; 8 | 9 | import './UniswapV3Pool.sol'; 10 | 11 | /// @title Canonical Uniswap V3 factory 12 | /// @notice Deploys Uniswap V3 pools and manages ownership and control over pool protocol fees 13 | contract UniswapV3Factory is IUniswapV3Factory, UniswapV3PoolDeployer, NoDelegateCall { 14 | /// @inheritdoc IUniswapV3Factory 15 | address public override owner; 16 | 17 | /// @inheritdoc IUniswapV3Factory 18 | mapping(uint24 => int24) public override feeAmountTickSpacing; 19 | /// @inheritdoc IUniswapV3Factory 20 | mapping(address => mapping(address => mapping(uint24 => address))) public override getPool; 21 | 22 | constructor() { 23 | owner = msg.sender; 24 | emit OwnerChanged(address(0), msg.sender); 25 | 26 | feeAmountTickSpacing[500] = 10; 27 | emit FeeAmountEnabled(500, 10); 28 | feeAmountTickSpacing[3000] = 60; 29 | emit FeeAmountEnabled(3000, 60); 30 | feeAmountTickSpacing[10000] = 200; 31 | emit FeeAmountEnabled(10000, 200); 32 | } 33 | 34 | /// @inheritdoc IUniswapV3Factory 35 | function createPool( 36 | address tokenA, 37 | address tokenB, 38 | uint24 fee 39 | ) external override noDelegateCall returns (address pool) { 40 | require(tokenA != tokenB); 41 | (address token0, address token1) = tokenA < tokenB ? (tokenA, tokenB) : (tokenB, tokenA); 42 | require(token0 != address(0)); 43 | int24 tickSpacing = feeAmountTickSpacing[fee]; 44 | require(tickSpacing != 0); 45 | require(getPool[token0][token1][fee] == address(0)); 46 | pool = deploy(address(this), token0, token1, fee, tickSpacing); 47 | getPool[token0][token1][fee] = pool; 48 | // populate mapping in the reverse direction, deliberate choice to avoid the cost of comparing addresses 49 | getPool[token1][token0][fee] = pool; 50 | emit PoolCreated(token0, token1, fee, tickSpacing, pool); 51 | } 52 | 53 | /// @inheritdoc IUniswapV3Factory 54 | function setOwner(address _owner) external override { 55 | require(msg.sender == owner); 56 | emit OwnerChanged(owner, _owner); 57 | owner = _owner; 58 | } 59 | 60 | /// @inheritdoc IUniswapV3Factory 61 | function enableFeeAmount(uint24 fee, int24 tickSpacing) public override { 62 | require(msg.sender == owner); 63 | require(fee < 1000000); 64 | // tick spacing is capped at 16384 to prevent the situation where tickSpacing is so large that 65 | // TickBitmap#nextInitializedTickWithinOneWord overflows int24 container from a valid tick 66 | // 16384 ticks represents a >5x price change with ticks of 1 bips 67 | require(tickSpacing > 0 && tickSpacing < 16384); 68 | require(feeAmountTickSpacing[fee] == 0); 69 | 70 | feeAmountTickSpacing[fee] = tickSpacing; 71 | emit FeeAmountEnabled(fee, tickSpacing); 72 | } 73 | } 74 | -------------------------------------------------------------------------------- /contracts/UniswapV3PoolDeployer.sol: -------------------------------------------------------------------------------- 1 | // SPDX-License-Identifier: BUSL-1.1 2 | pragma solidity =0.7.6; 3 | 4 | import './interfaces/IUniswapV3PoolDeployer.sol'; 5 | 6 | import './UniswapV3Pool.sol'; 7 | 8 | contract UniswapV3PoolDeployer is IUniswapV3PoolDeployer { 9 | struct Parameters { 10 | address factory; 11 | address token0; 12 | address token1; 13 | uint24 fee; 14 | int24 tickSpacing; 15 | } 16 | 17 | /// @inheritdoc IUniswapV3PoolDeployer 18 | Parameters public override parameters; 19 | 20 | /// @dev Deploys a pool with the given parameters by transiently setting the parameters storage slot and then 21 | /// clearing it after deploying the pool. 22 | /// @param factory The contract address of the Uniswap V3 factory 23 | /// @param token0 The first token of the pool by address sort order 24 | /// @param token1 The second token of the pool by address sort order 25 | /// @param fee The fee collected upon every swap in the pool, denominated in hundredths of a bip 26 | /// @param tickSpacing The spacing between usable ticks 27 | function deploy( 28 | address factory, 29 | address token0, 30 | address token1, 31 | uint24 fee, 32 | int24 tickSpacing 33 | ) internal returns (address pool) { 34 | parameters = Parameters({factory: factory, token0: token0, token1: token1, fee: fee, tickSpacing: tickSpacing}); 35 | pool = address(new UniswapV3Pool{salt: keccak256(abi.encode(token0, token1, fee))}()); 36 | delete parameters; 37 | } 38 | } 39 | -------------------------------------------------------------------------------- /contracts/interfaces/IERC20Minimal.sol: -------------------------------------------------------------------------------- 1 | // SPDX-License-Identifier: GPL-2.0-or-later 2 | pragma solidity >=0.5.0; 3 | 4 | /// @title Minimal ERC20 interface for Uniswap 5 | /// @notice Contains a subset of the full ERC20 interface that is used in Uniswap V3 6 | interface IERC20Minimal { 7 | /// @notice Returns the balance of a token 8 | /// @param account The account for which to look up the number of tokens it has, i.e. its balance 9 | /// @return The number of tokens held by the account 10 | function balanceOf(address account) external view returns (uint256); 11 | 12 | /// @notice Transfers the amount of token from the `msg.sender` to the recipient 13 | /// @param recipient The account that will receive the amount transferred 14 | /// @param amount The number of tokens to send from the sender to the recipient 15 | /// @return Returns true for a successful transfer, false for an unsuccessful transfer 16 | function transfer(address recipient, uint256 amount) external returns (bool); 17 | 18 | /// @notice Returns the current allowance given to a spender by an owner 19 | /// @param owner The account of the token owner 20 | /// @param spender The account of the token spender 21 | /// @return The current allowance granted by `owner` to `spender` 22 | function allowance(address owner, address spender) external view returns (uint256); 23 | 24 | /// @notice Sets the allowance of a spender from the `msg.sender` to the value `amount` 25 | /// @param spender The account which will be allowed to spend a given amount of the owners tokens 26 | /// @param amount The amount of tokens allowed to be used by `spender` 27 | /// @return Returns true for a successful approval, false for unsuccessful 28 | function approve(address spender, uint256 amount) external returns (bool); 29 | 30 | /// @notice Transfers `amount` tokens from `sender` to `recipient` up to the allowance given to the `msg.sender` 31 | /// @param sender The account from which the transfer will be initiated 32 | /// @param recipient The recipient of the transfer 33 | /// @param amount The amount of the transfer 34 | /// @return Returns true for a successful transfer, false for unsuccessful 35 | function transferFrom( 36 | address sender, 37 | address recipient, 38 | uint256 amount 39 | ) external returns (bool); 40 | 41 | /// @notice Event emitted when tokens are transferred from one address to another, either via `#transfer` or `#transferFrom`. 42 | /// @param from The account from which the tokens were sent, i.e. the balance decreased 43 | /// @param to The account to which the tokens were sent, i.e. the balance increased 44 | /// @param value The amount of tokens that were transferred 45 | event Transfer(address indexed from, address indexed to, uint256 value); 46 | 47 | /// @notice Event emitted when the approval amount for the spender of a given owner's tokens changes. 48 | /// @param owner The account that approved spending of its tokens 49 | /// @param spender The account for which the spending allowance was modified 50 | /// @param value The new allowance from the owner to the spender 51 | event Approval(address indexed owner, address indexed spender, uint256 value); 52 | } 53 | -------------------------------------------------------------------------------- /contracts/interfaces/IUniswapV3Factory.sol: -------------------------------------------------------------------------------- 1 | // SPDX-License-Identifier: GPL-2.0-or-later 2 | pragma solidity >=0.5.0; 3 | 4 | /// @title The interface for the Uniswap V3 Factory 5 | /// @notice The Uniswap V3 Factory facilitates creation of Uniswap V3 pools and control over the protocol fees 6 | interface IUniswapV3Factory { 7 | /// @notice Emitted when the owner of the factory is changed 8 | /// @param oldOwner The owner before the owner was changed 9 | /// @param newOwner The owner after the owner was changed 10 | event OwnerChanged(address indexed oldOwner, address indexed newOwner); 11 | 12 | /// @notice Emitted when a pool is created 13 | /// @param token0 The first token of the pool by address sort order 14 | /// @param token1 The second token of the pool by address sort order 15 | /// @param fee The fee collected upon every swap in the pool, denominated in hundredths of a bip 16 | /// @param tickSpacing The minimum number of ticks between initialized ticks 17 | /// @param pool The address of the created pool 18 | event PoolCreated( 19 | address indexed token0, 20 | address indexed token1, 21 | uint24 indexed fee, 22 | int24 tickSpacing, 23 | address pool 24 | ); 25 | 26 | /// @notice Emitted when a new fee amount is enabled for pool creation via the factory 27 | /// @param fee The enabled fee, denominated in hundredths of a bip 28 | /// @param tickSpacing The minimum number of ticks between initialized ticks for pools created with the given fee 29 | event FeeAmountEnabled(uint24 indexed fee, int24 indexed tickSpacing); 30 | 31 | /// @notice Returns the current owner of the factory 32 | /// @dev Can be changed by the current owner via setOwner 33 | /// @return The address of the factory owner 34 | function owner() external view returns (address); 35 | 36 | /// @notice Returns the tick spacing for a given fee amount, if enabled, or 0 if not enabled 37 | /// @dev A fee amount can never be removed, so this value should be hard coded or cached in the calling context 38 | /// @param fee The enabled fee, denominated in hundredths of a bip. Returns 0 in case of unenabled fee 39 | /// @return The tick spacing 40 | function feeAmountTickSpacing(uint24 fee) external view returns (int24); 41 | 42 | /// @notice Returns the pool address for a given pair of tokens and a fee, or address 0 if it does not exist 43 | /// @dev tokenA and tokenB may be passed in either token0/token1 or token1/token0 order 44 | /// @param tokenA The contract address of either token0 or token1 45 | /// @param tokenB The contract address of the other token 46 | /// @param fee The fee collected upon every swap in the pool, denominated in hundredths of a bip 47 | /// @return pool The pool address 48 | function getPool( 49 | address tokenA, 50 | address tokenB, 51 | uint24 fee 52 | ) external view returns (address pool); 53 | 54 | /// @notice Creates a pool for the given two tokens and fee 55 | /// @param tokenA One of the two tokens in the desired pool 56 | /// @param tokenB The other of the two tokens in the desired pool 57 | /// @param fee The desired fee for the pool 58 | /// @dev tokenA and tokenB may be passed in either order: token0/token1 or token1/token0. tickSpacing is retrieved 59 | /// from the fee. The call will revert if the pool already exists, the fee is invalid, or the token arguments 60 | /// are invalid. 61 | /// @return pool The address of the newly created pool 62 | function createPool( 63 | address tokenA, 64 | address tokenB, 65 | uint24 fee 66 | ) external returns (address pool); 67 | 68 | /// @notice Updates the owner of the factory 69 | /// @dev Must be called by the current owner 70 | /// @param _owner The new owner of the factory 71 | function setOwner(address _owner) external; 72 | 73 | /// @notice Enables a fee amount with the given tickSpacing 74 | /// @dev Fee amounts may never be removed once enabled 75 | /// @param fee The fee amount to enable, denominated in hundredths of a bip (i.e. 1e-6) 76 | /// @param tickSpacing The spacing between ticks to be enforced for all pools created with the given fee amount 77 | function enableFeeAmount(uint24 fee, int24 tickSpacing) external; 78 | } 79 | -------------------------------------------------------------------------------- /contracts/interfaces/IUniswapV3Pool.sol: -------------------------------------------------------------------------------- 1 | // SPDX-License-Identifier: GPL-2.0-or-later 2 | pragma solidity >=0.5.0; 3 | 4 | import './pool/IUniswapV3PoolImmutables.sol'; 5 | import './pool/IUniswapV3PoolState.sol'; 6 | import './pool/IUniswapV3PoolDerivedState.sol'; 7 | import './pool/IUniswapV3PoolActions.sol'; 8 | import './pool/IUniswapV3PoolOwnerActions.sol'; 9 | import './pool/IUniswapV3PoolEvents.sol'; 10 | 11 | /// @title The interface for a Uniswap V3 Pool 12 | /// @notice A Uniswap pool facilitates swapping and automated market making between any two assets that strictly conform 13 | /// to the ERC20 specification 14 | /// @dev The pool interface is broken up into many smaller pieces 15 | interface IUniswapV3Pool is 16 | IUniswapV3PoolImmutables, 17 | IUniswapV3PoolState, 18 | IUniswapV3PoolDerivedState, 19 | IUniswapV3PoolActions, 20 | IUniswapV3PoolOwnerActions, 21 | IUniswapV3PoolEvents 22 | { 23 | 24 | } 25 | -------------------------------------------------------------------------------- /contracts/interfaces/IUniswapV3PoolDeployer.sol: -------------------------------------------------------------------------------- 1 | // SPDX-License-Identifier: GPL-2.0-or-later 2 | pragma solidity >=0.5.0; 3 | 4 | /// @title An interface for a contract that is capable of deploying Uniswap V3 Pools 5 | /// @notice A contract that constructs a pool must implement this to pass arguments to the pool 6 | /// @dev This is used to avoid having constructor arguments in the pool contract, which results in the init code hash 7 | /// of the pool being constant allowing the CREATE2 address of the pool to be cheaply computed on-chain 8 | interface IUniswapV3PoolDeployer { 9 | /// @notice Get the parameters to be used in constructing the pool, set transiently during pool creation. 10 | /// @dev Called by the pool constructor to fetch the parameters of the pool 11 | /// Returns factory The factory address 12 | /// Returns token0 The first token of the pool by address sort order 13 | /// Returns token1 The second token of the pool by address sort order 14 | /// Returns fee The fee collected upon every swap in the pool, denominated in hundredths of a bip 15 | /// Returns tickSpacing The minimum number of ticks between initialized ticks 16 | function parameters() 17 | external 18 | view 19 | returns ( 20 | address factory, 21 | address token0, 22 | address token1, 23 | uint24 fee, 24 | int24 tickSpacing 25 | ); 26 | } 27 | -------------------------------------------------------------------------------- /contracts/interfaces/callback/IUniswapV3FlashCallback.sol: -------------------------------------------------------------------------------- 1 | // SPDX-License-Identifier: GPL-2.0-or-later 2 | pragma solidity >=0.5.0; 3 | 4 | /// @title Callback for IUniswapV3PoolActions#flash 5 | /// @notice Any contract that calls IUniswapV3PoolActions#flash must implement this interface 6 | interface IUniswapV3FlashCallback { 7 | /// @notice Called to `msg.sender` after transferring to the recipient from IUniswapV3Pool#flash. 8 | /// @dev In the implementation you must repay the pool the tokens sent by flash plus the computed fee amounts. 9 | /// The caller of this method must be checked to be a UniswapV3Pool deployed by the canonical UniswapV3Factory. 10 | /// @param fee0 The fee amount in token0 due to the pool by the end of the flash 11 | /// @param fee1 The fee amount in token1 due to the pool by the end of the flash 12 | /// @param data Any data passed through by the caller via the IUniswapV3PoolActions#flash call 13 | function uniswapV3FlashCallback( 14 | uint256 fee0, 15 | uint256 fee1, 16 | bytes calldata data 17 | ) external; 18 | } 19 | -------------------------------------------------------------------------------- /contracts/interfaces/callback/IUniswapV3MintCallback.sol: -------------------------------------------------------------------------------- 1 | // SPDX-License-Identifier: GPL-2.0-or-later 2 | pragma solidity >=0.5.0; 3 | 4 | /// @title Callback for IUniswapV3PoolActions#mint 5 | /// @notice Any contract that calls IUniswapV3PoolActions#mint must implement this interface 6 | interface IUniswapV3MintCallback { 7 | /// @notice Called to `msg.sender` after minting liquidity to a position from IUniswapV3Pool#mint. 8 | /// @dev In the implementation you must pay the pool tokens owed for the minted liquidity. 9 | /// The caller of this method must be checked to be a UniswapV3Pool deployed by the canonical UniswapV3Factory. 10 | /// @param amount0Owed The amount of token0 due to the pool for the minted liquidity 11 | /// @param amount1Owed The amount of token1 due to the pool for the minted liquidity 12 | /// @param data Any data passed through by the caller via the IUniswapV3PoolActions#mint call 13 | function uniswapV3MintCallback( 14 | uint256 amount0Owed, 15 | uint256 amount1Owed, 16 | bytes calldata data 17 | ) external; 18 | } 19 | -------------------------------------------------------------------------------- /contracts/interfaces/callback/IUniswapV3SwapCallback.sol: -------------------------------------------------------------------------------- 1 | // SPDX-License-Identifier: GPL-2.0-or-later 2 | pragma solidity >=0.5.0; 3 | 4 | /// @title Callback for IUniswapV3PoolActions#swap 5 | /// @notice Any contract that calls IUniswapV3PoolActions#swap must implement this interface 6 | interface IUniswapV3SwapCallback { 7 | /// @notice Called to `msg.sender` after executing a swap via IUniswapV3Pool#swap. 8 | /// @dev In the implementation you must pay the pool tokens owed for the swap. 9 | /// The caller of this method must be checked to be a UniswapV3Pool deployed by the canonical UniswapV3Factory. 10 | /// amount0Delta and amount1Delta can both be 0 if no tokens were swapped. 11 | /// @param amount0Delta The amount of token0 that was sent (negative) or must be received (positive) by the pool by 12 | /// the end of the swap. If positive, the callback must send that amount of token0 to the pool. 13 | /// @param amount1Delta The amount of token1 that was sent (negative) or must be received (positive) by the pool by 14 | /// the end of the swap. If positive, the callback must send that amount of token1 to the pool. 15 | /// @param data Any data passed through by the caller via the IUniswapV3PoolActions#swap call 16 | function uniswapV3SwapCallback( 17 | int256 amount0Delta, 18 | int256 amount1Delta, 19 | bytes calldata data 20 | ) external; 21 | } 22 | -------------------------------------------------------------------------------- /contracts/interfaces/pool/IUniswapV3PoolActions.sol: -------------------------------------------------------------------------------- 1 | // SPDX-License-Identifier: GPL-2.0-or-later 2 | pragma solidity >=0.5.0; 3 | 4 | /// @title Permissionless pool actions 5 | /// @notice Contains pool methods that can be called by anyone 6 | interface IUniswapV3PoolActions { 7 | /// @notice Sets the initial price for the pool 8 | /// @dev Price is represented as a sqrt(amountToken1/amountToken0) Q64.96 value 9 | /// @param sqrtPriceX96 the initial sqrt price of the pool as a Q64.96 10 | function initialize(uint160 sqrtPriceX96) external; 11 | 12 | /// @notice Adds liquidity for the given recipient/tickLower/tickUpper position 13 | /// @dev The caller of this method receives a callback in the form of IUniswapV3MintCallback#uniswapV3MintCallback 14 | /// in which they must pay any token0 or token1 owed for the liquidity. The amount of token0/token1 due depends 15 | /// on tickLower, tickUpper, the amount of liquidity, and the current price. 16 | /// @param recipient The address for which the liquidity will be created 17 | /// @param tickLower The lower tick of the position in which to add liquidity 18 | /// @param tickUpper The upper tick of the position in which to add liquidity 19 | /// @param amount The amount of liquidity to mint 20 | /// @param data Any data that should be passed through to the callback 21 | /// @return amount0 The amount of token0 that was paid to mint the given amount of liquidity. Matches the value in the callback 22 | /// @return amount1 The amount of token1 that was paid to mint the given amount of liquidity. Matches the value in the callback 23 | function mint( 24 | address recipient, 25 | int24 tickLower, 26 | int24 tickUpper, 27 | uint128 amount, 28 | bytes calldata data 29 | ) external returns (uint256 amount0, uint256 amount1); 30 | 31 | /// @notice Collects tokens owed to a position 32 | /// @dev Does not recompute fees earned, which must be done either via mint or burn of any amount of liquidity. 33 | /// Collect must be called by the position owner. To withdraw only token0 or only token1, amount0Requested or 34 | /// amount1Requested may be set to zero. To withdraw all tokens owed, caller may pass any value greater than the 35 | /// actual tokens owed, e.g. type(uint128).max. Tokens owed may be from accumulated swap fees or burned liquidity. 36 | /// @param recipient The address which should receive the fees collected 37 | /// @param tickLower The lower tick of the position for which to collect fees 38 | /// @param tickUpper The upper tick of the position for which to collect fees 39 | /// @param amount0Requested How much token0 should be withdrawn from the fees owed 40 | /// @param amount1Requested How much token1 should be withdrawn from the fees owed 41 | /// @return amount0 The amount of fees collected in token0 42 | /// @return amount1 The amount of fees collected in token1 43 | function collect( 44 | address recipient, 45 | int24 tickLower, 46 | int24 tickUpper, 47 | uint128 amount0Requested, 48 | uint128 amount1Requested 49 | ) external returns (uint128 amount0, uint128 amount1); 50 | 51 | /// @notice Burn liquidity from the sender and account tokens owed for the liquidity to the position 52 | /// @dev Can be used to trigger a recalculation of fees owed to a position by calling with an amount of 0 53 | /// @dev Fees must be collected separately via a call to #collect 54 | /// @param tickLower The lower tick of the position for which to burn liquidity 55 | /// @param tickUpper The upper tick of the position for which to burn liquidity 56 | /// @param amount How much liquidity to burn 57 | /// @return amount0 The amount of token0 sent to the recipient 58 | /// @return amount1 The amount of token1 sent to the recipient 59 | function burn( 60 | int24 tickLower, 61 | int24 tickUpper, 62 | uint128 amount 63 | ) external returns (uint256 amount0, uint256 amount1); 64 | 65 | /// @notice Swap token0 for token1, or token1 for token0 66 | /// @dev The caller of this method receives a callback in the form of IUniswapV3SwapCallback#uniswapV3SwapCallback 67 | /// @param recipient The address to receive the output of the swap 68 | /// @param zeroForOne The direction of the swap, true for token0 to token1, false for token1 to token0 69 | /// @param amountSpecified The amount of the swap, which implicitly configures the swap as exact input (positive), or exact output (negative) 70 | /// @param sqrtPriceLimitX96 The Q64.96 sqrt price limit. If zero for one, the price cannot be less than this 71 | /// value after the swap. If one for zero, the price cannot be greater than this value after the swap 72 | /// @param data Any data to be passed through to the callback 73 | /// @return amount0 The delta of the balance of token0 of the pool, exact when negative, minimum when positive 74 | /// @return amount1 The delta of the balance of token1 of the pool, exact when negative, minimum when positive 75 | function swap( 76 | address recipient, 77 | bool zeroForOne, 78 | int256 amountSpecified, 79 | uint160 sqrtPriceLimitX96, 80 | bytes calldata data 81 | ) external returns (int256 amount0, int256 amount1); 82 | 83 | /// @notice Receive token0 and/or token1 and pay it back, plus a fee, in the callback 84 | /// @dev The caller of this method receives a callback in the form of IUniswapV3FlashCallback#uniswapV3FlashCallback 85 | /// @dev Can be used to donate underlying tokens pro-rata to currently in-range liquidity providers by calling 86 | /// with 0 amount{0,1} and sending the donation amount(s) from the callback 87 | /// @param recipient The address which will receive the token0 and token1 amounts 88 | /// @param amount0 The amount of token0 to send 89 | /// @param amount1 The amount of token1 to send 90 | /// @param data Any data to be passed through to the callback 91 | function flash( 92 | address recipient, 93 | uint256 amount0, 94 | uint256 amount1, 95 | bytes calldata data 96 | ) external; 97 | 98 | /// @notice Increase the maximum number of price and liquidity observations that this pool will store 99 | /// @dev This method is no-op if the pool already has an observationCardinalityNext greater than or equal to 100 | /// the input observationCardinalityNext. 101 | /// @param observationCardinalityNext The desired minimum number of observations for the pool to store 102 | function increaseObservationCardinalityNext(uint16 observationCardinalityNext) external; 103 | } 104 | -------------------------------------------------------------------------------- /contracts/interfaces/pool/IUniswapV3PoolDerivedState.sol: -------------------------------------------------------------------------------- 1 | // SPDX-License-Identifier: GPL-2.0-or-later 2 | pragma solidity >=0.5.0; 3 | 4 | /// @title Pool state that is not stored 5 | /// @notice Contains view functions to provide information about the pool that is computed rather than stored on the 6 | /// blockchain. The functions here may have variable gas costs. 7 | interface IUniswapV3PoolDerivedState { 8 | /// @notice Returns the cumulative tick and liquidity as of each timestamp `secondsAgo` from the current block timestamp 9 | /// @dev To get a time weighted average tick or liquidity-in-range, you must call this with two values, one representing 10 | /// the beginning of the period and another for the end of the period. E.g., to get the last hour time-weighted average tick, 11 | /// you must call it with secondsAgos = [3600, 0]. 12 | /// @dev The time weighted average tick represents the geometric time weighted average price of the pool, in 13 | /// log base sqrt(1.0001) of token1 / token0. The TickMath library can be used to go from a tick value to a ratio. 14 | /// @param secondsAgos From how long ago each cumulative tick and liquidity value should be returned 15 | /// @return tickCumulatives Cumulative tick values as of each `secondsAgos` from the current block timestamp 16 | /// @return secondsPerLiquidityCumulativeX128s Cumulative seconds per liquidity-in-range value as of each `secondsAgos` from the current block 17 | /// timestamp 18 | function observe(uint32[] calldata secondsAgos) 19 | external 20 | view 21 | returns (int56[] memory tickCumulatives, uint160[] memory secondsPerLiquidityCumulativeX128s); 22 | 23 | /// @notice Returns a snapshot of the tick cumulative, seconds per liquidity and seconds inside a tick range 24 | /// @dev Snapshots must only be compared to other snapshots, taken over a period for which a position existed. 25 | /// I.e., snapshots cannot be compared if a position is not held for the entire period between when the first 26 | /// snapshot is taken and the second snapshot is taken. 27 | /// @param tickLower The lower tick of the range 28 | /// @param tickUpper The upper tick of the range 29 | /// @return tickCumulativeInside The snapshot of the tick accumulator for the range 30 | /// @return secondsPerLiquidityInsideX128 The snapshot of seconds per liquidity for the range 31 | /// @return secondsInside The snapshot of seconds per liquidity for the range 32 | function snapshotCumulativesInside(int24 tickLower, int24 tickUpper) 33 | external 34 | view 35 | returns ( 36 | int56 tickCumulativeInside, 37 | uint160 secondsPerLiquidityInsideX128, 38 | uint32 secondsInside 39 | ); 40 | } 41 | -------------------------------------------------------------------------------- /contracts/interfaces/pool/IUniswapV3PoolEvents.sol: -------------------------------------------------------------------------------- 1 | // SPDX-License-Identifier: GPL-2.0-or-later 2 | pragma solidity >=0.5.0; 3 | 4 | /// @title Events emitted by a pool 5 | /// @notice Contains all events emitted by the pool 6 | interface IUniswapV3PoolEvents { 7 | /// @notice Emitted exactly once by a pool when #initialize is first called on the pool 8 | /// @dev Mint/Burn/Swap cannot be emitted by the pool before Initialize 9 | /// @param sqrtPriceX96 The initial sqrt price of the pool, as a Q64.96 10 | /// @param tick The initial tick of the pool, i.e. log base 1.0001 of the starting price of the pool 11 | event Initialize(uint160 sqrtPriceX96, int24 tick); 12 | 13 | /// @notice Emitted when liquidity is minted for a given position 14 | /// @param sender The address that minted the liquidity 15 | /// @param owner The owner of the position and recipient of any minted liquidity 16 | /// @param tickLower The lower tick of the position 17 | /// @param tickUpper The upper tick of the position 18 | /// @param amount The amount of liquidity minted to the position range 19 | /// @param amount0 How much token0 was required for the minted liquidity 20 | /// @param amount1 How much token1 was required for the minted liquidity 21 | event Mint( 22 | address sender, 23 | address indexed owner, 24 | int24 indexed tickLower, 25 | int24 indexed tickUpper, 26 | uint128 amount, 27 | uint256 amount0, 28 | uint256 amount1 29 | ); 30 | 31 | /// @notice Emitted when fees are collected by the owner of a position 32 | /// @dev Collect events may be emitted with zero amount0 and amount1 when the caller chooses not to collect fees 33 | /// @param owner The owner of the position for which fees are collected 34 | /// @param tickLower The lower tick of the position 35 | /// @param tickUpper The upper tick of the position 36 | /// @param amount0 The amount of token0 fees collected 37 | /// @param amount1 The amount of token1 fees collected 38 | event Collect( 39 | address indexed owner, 40 | address recipient, 41 | int24 indexed tickLower, 42 | int24 indexed tickUpper, 43 | uint128 amount0, 44 | uint128 amount1 45 | ); 46 | 47 | /// @notice Emitted when a position's liquidity is removed 48 | /// @dev Does not withdraw any fees earned by the liquidity position, which must be withdrawn via #collect 49 | /// @param owner The owner of the position for which liquidity is removed 50 | /// @param tickLower The lower tick of the position 51 | /// @param tickUpper The upper tick of the position 52 | /// @param amount The amount of liquidity to remove 53 | /// @param amount0 The amount of token0 withdrawn 54 | /// @param amount1 The amount of token1 withdrawn 55 | event Burn( 56 | address indexed owner, 57 | int24 indexed tickLower, 58 | int24 indexed tickUpper, 59 | uint128 amount, 60 | uint256 amount0, 61 | uint256 amount1 62 | ); 63 | 64 | /// @notice Emitted by the pool for any swaps between token0 and token1 65 | /// @param sender The address that initiated the swap call, and that received the callback 66 | /// @param recipient The address that received the output of the swap 67 | /// @param amount0 The delta of the token0 balance of the pool 68 | /// @param amount1 The delta of the token1 balance of the pool 69 | /// @param sqrtPriceX96 The sqrt(price) of the pool after the swap, as a Q64.96 70 | /// @param liquidity The liquidity of the pool after the swap 71 | /// @param tick The log base 1.0001 of price of the pool after the swap 72 | event Swap( 73 | address indexed sender, 74 | address indexed recipient, 75 | int256 amount0, 76 | int256 amount1, 77 | uint160 sqrtPriceX96, 78 | uint128 liquidity, 79 | int24 tick 80 | ); 81 | 82 | /// @notice Emitted by the pool for any flashes of token0/token1 83 | /// @param sender The address that initiated the swap call, and that received the callback 84 | /// @param recipient The address that received the tokens from flash 85 | /// @param amount0 The amount of token0 that was flashed 86 | /// @param amount1 The amount of token1 that was flashed 87 | /// @param paid0 The amount of token0 paid for the flash, which can exceed the amount0 plus the fee 88 | /// @param paid1 The amount of token1 paid for the flash, which can exceed the amount1 plus the fee 89 | event Flash( 90 | address indexed sender, 91 | address indexed recipient, 92 | uint256 amount0, 93 | uint256 amount1, 94 | uint256 paid0, 95 | uint256 paid1 96 | ); 97 | 98 | /// @notice Emitted by the pool for increases to the number of observations that can be stored 99 | /// @dev observationCardinalityNext is not the observation cardinality until an observation is written at the index 100 | /// just before a mint/swap/burn. 101 | /// @param observationCardinalityNextOld The previous value of the next observation cardinality 102 | /// @param observationCardinalityNextNew The updated value of the next observation cardinality 103 | event IncreaseObservationCardinalityNext( 104 | uint16 observationCardinalityNextOld, 105 | uint16 observationCardinalityNextNew 106 | ); 107 | 108 | /// @notice Emitted when the protocol fee is changed by the pool 109 | /// @param feeProtocol0Old The previous value of the token0 protocol fee 110 | /// @param feeProtocol1Old The previous value of the token1 protocol fee 111 | /// @param feeProtocol0New The updated value of the token0 protocol fee 112 | /// @param feeProtocol1New The updated value of the token1 protocol fee 113 | event SetFeeProtocol(uint8 feeProtocol0Old, uint8 feeProtocol1Old, uint8 feeProtocol0New, uint8 feeProtocol1New); 114 | 115 | /// @notice Emitted when the collected protocol fees are withdrawn by the factory owner 116 | /// @param sender The address that collects the protocol fees 117 | /// @param recipient The address that receives the collected protocol fees 118 | /// @param amount0 The amount of token0 protocol fees that is withdrawn 119 | /// @param amount0 The amount of token1 protocol fees that is withdrawn 120 | event CollectProtocol(address indexed sender, address indexed recipient, uint128 amount0, uint128 amount1); 121 | } 122 | -------------------------------------------------------------------------------- /contracts/interfaces/pool/IUniswapV3PoolImmutables.sol: -------------------------------------------------------------------------------- 1 | // SPDX-License-Identifier: GPL-2.0-or-later 2 | pragma solidity >=0.5.0; 3 | 4 | /// @title Pool state that never changes 5 | /// @notice These parameters are fixed for a pool forever, i.e., the methods will always return the same values 6 | interface IUniswapV3PoolImmutables { 7 | /// @notice The contract that deployed the pool, which must adhere to the IUniswapV3Factory interface 8 | /// @return The contract address 9 | function factory() external view returns (address); 10 | 11 | /// @notice The first of the two tokens of the pool, sorted by address 12 | /// @return The token contract address 13 | function token0() external view returns (address); 14 | 15 | /// @notice The second of the two tokens of the pool, sorted by address 16 | /// @return The token contract address 17 | function token1() external view returns (address); 18 | 19 | /// @notice The pool's fee in hundredths of a bip, i.e. 1e-6 20 | /// @return The fee 21 | function fee() external view returns (uint24); 22 | 23 | /// @notice The pool tick spacing 24 | /// @dev Ticks can only be used at multiples of this value, minimum of 1 and always positive 25 | /// e.g.: a tickSpacing of 3 means ticks can be initialized every 3rd tick, i.e., ..., -6, -3, 0, 3, 6, ... 26 | /// This value is an int24 to avoid casting even though it is always positive. 27 | /// @return The tick spacing 28 | function tickSpacing() external view returns (int24); 29 | 30 | /// @notice The maximum amount of position liquidity that can use any tick in the range 31 | /// @dev This parameter is enforced per tick to prevent liquidity from overflowing a uint128 at any point, and 32 | /// also prevents out-of-range liquidity from being used to prevent adding in-range liquidity to a pool 33 | /// @return The max amount of liquidity per tick 34 | function maxLiquidityPerTick() external view returns (uint128); 35 | } 36 | -------------------------------------------------------------------------------- /contracts/interfaces/pool/IUniswapV3PoolOwnerActions.sol: -------------------------------------------------------------------------------- 1 | // SPDX-License-Identifier: GPL-2.0-or-later 2 | pragma solidity >=0.5.0; 3 | 4 | /// @title Permissioned pool actions 5 | /// @notice Contains pool methods that may only be called by the factory owner 6 | interface IUniswapV3PoolOwnerActions { 7 | /// @notice Set the denominator of the protocol's % share of the fees 8 | /// @param feeProtocol0 new protocol fee for token0 of the pool 9 | /// @param feeProtocol1 new protocol fee for token1 of the pool 10 | function setFeeProtocol(uint8 feeProtocol0, uint8 feeProtocol1) external; 11 | 12 | /// @notice Collect the protocol fee accrued to the pool 13 | /// @param recipient The address to which collected protocol fees should be sent 14 | /// @param amount0Requested The maximum amount of token0 to send, can be 0 to collect fees in only token1 15 | /// @param amount1Requested The maximum amount of token1 to send, can be 0 to collect fees in only token0 16 | /// @return amount0 The protocol fee collected in token0 17 | /// @return amount1 The protocol fee collected in token1 18 | function collectProtocol( 19 | address recipient, 20 | uint128 amount0Requested, 21 | uint128 amount1Requested 22 | ) external returns (uint128 amount0, uint128 amount1); 23 | } 24 | -------------------------------------------------------------------------------- /contracts/interfaces/pool/IUniswapV3PoolState.sol: -------------------------------------------------------------------------------- 1 | // SPDX-License-Identifier: GPL-2.0-or-later 2 | pragma solidity >=0.5.0; 3 | 4 | /// @title Pool state that can change 5 | /// @notice These methods compose the pool's state, and can change with any frequency including multiple times 6 | /// per transaction 7 | interface IUniswapV3PoolState { 8 | /// @notice The 0th storage slot in the pool stores many values, and is exposed as a single method to save gas 9 | /// when accessed externally. 10 | /// @return sqrtPriceX96 The current price of the pool as a sqrt(token1/token0) Q64.96 value 11 | /// tick The current tick of the pool, i.e. according to the last tick transition that was run. 12 | /// This value may not always be equal to SqrtTickMath.getTickAtSqrtRatio(sqrtPriceX96) if the price is on a tick 13 | /// boundary. 14 | /// observationIndex The index of the last oracle observation that was written, 15 | /// observationCardinality The current maximum number of observations stored in the pool, 16 | /// observationCardinalityNext The next maximum number of observations, to be updated when the observation. 17 | /// feeProtocol The protocol fee for both tokens of the pool. 18 | /// Encoded as two 4 bit values, where the protocol fee of token1 is shifted 4 bits and the protocol fee of token0 19 | /// is the lower 4 bits. Used as the denominator of a fraction of the swap fee, e.g. 4 means 1/4th of the swap fee. 20 | /// unlocked Whether the pool is currently locked to reentrancy 21 | function slot0() 22 | external 23 | view 24 | returns ( 25 | uint160 sqrtPriceX96, 26 | int24 tick, 27 | uint16 observationIndex, 28 | uint16 observationCardinality, 29 | uint16 observationCardinalityNext, 30 | uint8 feeProtocol, 31 | bool unlocked 32 | ); 33 | 34 | /// @notice The fee growth as a Q128.128 fees of token0 collected per unit of liquidity for the entire life of the pool 35 | /// @dev This value can overflow the uint256 36 | function feeGrowthGlobal0X128() external view returns (uint256); 37 | 38 | /// @notice The fee growth as a Q128.128 fees of token1 collected per unit of liquidity for the entire life of the pool 39 | /// @dev This value can overflow the uint256 40 | function feeGrowthGlobal1X128() external view returns (uint256); 41 | 42 | /// @notice The amounts of token0 and token1 that are owed to the protocol 43 | /// @dev Protocol fees will never exceed uint128 max in either token 44 | function protocolFees() external view returns (uint128 token0, uint128 token1); 45 | 46 | /// @notice The currently in range liquidity available to the pool 47 | /// @dev This value has no relationship to the total liquidity across all ticks 48 | function liquidity() external view returns (uint128); 49 | 50 | /// @notice Look up information about a specific tick in the pool 51 | /// @param tick The tick to look up 52 | /// @return liquidityGross the total amount of position liquidity that uses the pool either as tick lower or 53 | /// tick upper, 54 | /// liquidityNet how much liquidity changes when the pool price crosses the tick, 55 | /// feeGrowthOutside0X128 the fee growth on the other side of the tick from the current tick in token0, 56 | /// feeGrowthOutside1X128 the fee growth on the other side of the tick from the current tick in token1, 57 | /// tickCumulativeOutside the cumulative tick value on the other side of the tick from the current tick 58 | /// secondsPerLiquidityOutsideX128 the seconds spent per liquidity on the other side of the tick from the current tick, 59 | /// secondsOutside the seconds spent on the other side of the tick from the current tick, 60 | /// initialized Set to true if the tick is initialized, i.e. liquidityGross is greater than 0, otherwise equal to false. 61 | /// Outside values can only be used if the tick is initialized, i.e. if liquidityGross is greater than 0. 62 | /// In addition, these values are only relative and must be used only in comparison to previous snapshots for 63 | /// a specific position. 64 | function ticks(int24 tick) 65 | external 66 | view 67 | returns ( 68 | uint128 liquidityGross, 69 | int128 liquidityNet, 70 | uint256 feeGrowthOutside0X128, 71 | uint256 feeGrowthOutside1X128, 72 | int56 tickCumulativeOutside, 73 | uint160 secondsPerLiquidityOutsideX128, 74 | uint32 secondsOutside, 75 | bool initialized 76 | ); 77 | 78 | /// @notice Returns 256 packed tick initialized boolean values. See TickBitmap for more information 79 | function tickBitmap(int16 wordPosition) external view returns (uint256); 80 | 81 | /// @notice Returns the information about a position by the position's key 82 | /// @param key The position's key is a hash of a preimage composed by the owner, tickLower and tickUpper 83 | /// @return _liquidity The amount of liquidity in the position, 84 | /// Returns feeGrowthInside0LastX128 fee growth of token0 inside the tick range as of the last mint/burn/poke, 85 | /// Returns feeGrowthInside1LastX128 fee growth of token1 inside the tick range as of the last mint/burn/poke, 86 | /// Returns tokensOwed0 the computed amount of token0 owed to the position as of the last mint/burn/poke, 87 | /// Returns tokensOwed1 the computed amount of token1 owed to the position as of the last mint/burn/poke 88 | function positions(bytes32 key) 89 | external 90 | view 91 | returns ( 92 | uint128 _liquidity, 93 | uint256 feeGrowthInside0LastX128, 94 | uint256 feeGrowthInside1LastX128, 95 | uint128 tokensOwed0, 96 | uint128 tokensOwed1 97 | ); 98 | 99 | /// @notice Returns data about a specific observation index 100 | /// @param index The element of the observations array to fetch 101 | /// @dev You most likely want to use #observe() instead of this method to get an observation as of some amount of time 102 | /// ago, rather than at a specific index in the array. 103 | /// @return blockTimestamp The timestamp of the observation, 104 | /// Returns tickCumulative the tick multiplied by seconds elapsed for the life of the pool as of the observation timestamp, 105 | /// Returns secondsPerLiquidityCumulativeX128 the seconds per in range liquidity for the life of the pool as of the observation timestamp, 106 | /// Returns initialized whether the observation has been initialized and the values are safe to use 107 | function observations(uint256 index) 108 | external 109 | view 110 | returns ( 111 | uint32 blockTimestamp, 112 | int56 tickCumulative, 113 | uint160 secondsPerLiquidityCumulativeX128, 114 | bool initialized 115 | ); 116 | } 117 | -------------------------------------------------------------------------------- /contracts/libraries/BitMath.sol: -------------------------------------------------------------------------------- 1 | // SPDX-License-Identifier: GPL-2.0-or-later 2 | pragma solidity >=0.5.0; 3 | 4 | /// @title BitMath 5 | /// @dev This library provides functionality for computing bit properties of an 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 | if (x >= 0x100000000000000000000000000000000) { 17 | x >>= 128; 18 | r += 128; 19 | } 20 | if (x >= 0x10000000000000000) { 21 | x >>= 64; 22 | r += 64; 23 | } 24 | if (x >= 0x100000000) { 25 | x >>= 32; 26 | r += 32; 27 | } 28 | if (x >= 0x10000) { 29 | x >>= 16; 30 | r += 16; 31 | } 32 | if (x >= 0x100) { 33 | x >>= 8; 34 | r += 8; 35 | } 36 | if (x >= 0x10) { 37 | x >>= 4; 38 | r += 4; 39 | } 40 | if (x >= 0x4) { 41 | x >>= 2; 42 | r += 2; 43 | } 44 | if (x >= 0x2) r += 1; 45 | } 46 | 47 | /// @notice Returns the index of the least significant bit of the number, 48 | /// where the least significant bit is at index 0 and the most significant bit is at index 255 49 | /// @dev The function satisfies the property: 50 | /// (x & 2**leastSignificantBit(x)) != 0 and (x & (2**(leastSignificantBit(x)) - 1)) == 0) 51 | /// @param x the value for which to compute the least significant bit, must be greater than 0 52 | /// @return r the index of the least significant bit 53 | function leastSignificantBit(uint256 x) internal pure returns (uint8 r) { 54 | require(x > 0); 55 | 56 | r = 255; 57 | if (x & type(uint128).max > 0) { 58 | r -= 128; 59 | } else { 60 | x >>= 128; 61 | } 62 | if (x & type(uint64).max > 0) { 63 | r -= 64; 64 | } else { 65 | x >>= 64; 66 | } 67 | if (x & type(uint32).max > 0) { 68 | r -= 32; 69 | } else { 70 | x >>= 32; 71 | } 72 | if (x & type(uint16).max > 0) { 73 | r -= 16; 74 | } else { 75 | x >>= 16; 76 | } 77 | if (x & type(uint8).max > 0) { 78 | r -= 8; 79 | } else { 80 | x >>= 8; 81 | } 82 | if (x & 0xf > 0) { 83 | r -= 4; 84 | } else { 85 | x >>= 4; 86 | } 87 | if (x & 0x3 > 0) { 88 | r -= 2; 89 | } else { 90 | x >>= 2; 91 | } 92 | if (x & 0x1 > 0) r -= 1; 93 | } 94 | } 95 | -------------------------------------------------------------------------------- /contracts/libraries/FixedPoint128.sol: -------------------------------------------------------------------------------- 1 | // SPDX-License-Identifier: GPL-2.0-or-later 2 | pragma solidity >=0.4.0; 3 | 4 | /// @title FixedPoint128 5 | /// @notice A library for handling binary fixed point numbers, see https://en.wikipedia.org/wiki/Q_(number_format) 6 | library FixedPoint128 { 7 | uint256 internal constant Q128 = 0x100000000000000000000000000000000; 8 | } 9 | -------------------------------------------------------------------------------- /contracts/libraries/FixedPoint96.sol: -------------------------------------------------------------------------------- 1 | // SPDX-License-Identifier: GPL-2.0-or-later 2 | pragma solidity >=0.4.0; 3 | 4 | /// @title FixedPoint96 5 | /// @notice A library for handling binary fixed point numbers, see https://en.wikipedia.org/wiki/Q_(number_format) 6 | /// @dev Used in SqrtPriceMath.sol 7 | library FixedPoint96 { 8 | uint8 internal constant RESOLUTION = 96; 9 | uint256 internal constant Q96 = 0x1000000000000000000000000; 10 | } 11 | -------------------------------------------------------------------------------- /contracts/libraries/FullMath.sol: -------------------------------------------------------------------------------- 1 | // SPDX-License-Identifier: MIT 2 | pragma solidity >=0.4.0 <0.8.0; 3 | 4 | /// @title Contains 512-bit math functions 5 | /// @notice Facilitates multiplication and division that can have overflow of an intermediate value without any loss of precision 6 | /// @dev Handles "phantom overflow" i.e., allows multiplication and division where an intermediate value overflows 256 bits 7 | library FullMath { 8 | /// @notice Calculates floor(a×b÷denominator) with full precision. Throws if result overflows a uint256 or denominator == 0 9 | /// @param a The multiplicand 10 | /// @param b The multiplier 11 | /// @param denominator The divisor 12 | /// @return result The 256-bit result 13 | /// @dev Credit to Remco Bloemen under MIT license https://xn--2-umb.com/21/muldiv 14 | function mulDiv( 15 | uint256 a, 16 | uint256 b, 17 | uint256 denominator 18 | ) internal pure returns (uint256 result) { 19 | // 512-bit multiply [prod1 prod0] = a * b 20 | // Compute the product mod 2**256 and mod 2**256 - 1 21 | // then use the Chinese Remainder Theorem to reconstruct 22 | // the 512 bit result. The result is stored in two 256 23 | // variables such that product = prod1 * 2**256 + prod0 24 | uint256 prod0; // Least significant 256 bits of the product 25 | uint256 prod1; // Most significant 256 bits of the product 26 | assembly { 27 | let mm := mulmod(a, b, not(0)) 28 | prod0 := mul(a, b) 29 | prod1 := sub(sub(mm, prod0), lt(mm, prod0)) 30 | } 31 | 32 | // Handle non-overflow cases, 256 by 256 division 33 | if (prod1 == 0) { 34 | require(denominator > 0); 35 | assembly { 36 | result := div(prod0, denominator) 37 | } 38 | return result; 39 | } 40 | 41 | // Make sure the result is less than 2**256. 42 | // Also prevents denominator == 0 43 | require(denominator > prod1); 44 | 45 | /////////////////////////////////////////////// 46 | // 512 by 256 division. 47 | /////////////////////////////////////////////// 48 | 49 | // Make division exact by subtracting the remainder from [prod1 prod0] 50 | // Compute remainder using mulmod 51 | uint256 remainder; 52 | assembly { 53 | remainder := mulmod(a, b, denominator) 54 | } 55 | // Subtract 256 bit number from 512 bit number 56 | assembly { 57 | prod1 := sub(prod1, gt(remainder, prod0)) 58 | prod0 := sub(prod0, remainder) 59 | } 60 | 61 | // Factor powers of two out of denominator 62 | // Compute largest power of two divisor of denominator. 63 | // Always >= 1. 64 | uint256 twos = -denominator & denominator; 65 | // Divide denominator by power of two 66 | assembly { 67 | denominator := div(denominator, twos) 68 | } 69 | 70 | // Divide [prod1 prod0] by the factors of two 71 | assembly { 72 | prod0 := div(prod0, twos) 73 | } 74 | // Shift in bits from prod1 into prod0. For this we need 75 | // to flip `twos` such that it is 2**256 / twos. 76 | // If twos is zero, then it becomes one 77 | assembly { 78 | twos := add(div(sub(0, twos), twos), 1) 79 | } 80 | prod0 |= prod1 * twos; 81 | 82 | // Invert denominator mod 2**256 83 | // Now that denominator is an odd number, it has an inverse 84 | // modulo 2**256 such that denominator * inv = 1 mod 2**256. 85 | // Compute the inverse by starting with a seed that is correct 86 | // correct for four bits. That is, denominator * inv = 1 mod 2**4 87 | uint256 inv = (3 * denominator) ^ 2; 88 | // Now use Newton-Raphson iteration to improve the precision. 89 | // Thanks to Hensel's lifting lemma, this also works in modular 90 | // arithmetic, doubling the correct bits in each step. 91 | inv *= 2 - denominator * inv; // inverse mod 2**8 92 | inv *= 2 - denominator * inv; // inverse mod 2**16 93 | inv *= 2 - denominator * inv; // inverse mod 2**32 94 | inv *= 2 - denominator * inv; // inverse mod 2**64 95 | inv *= 2 - denominator * inv; // inverse mod 2**128 96 | inv *= 2 - denominator * inv; // inverse mod 2**256 97 | 98 | // Because the division is now exact we can divide by multiplying 99 | // with the modular inverse of denominator. This will give us the 100 | // correct result modulo 2**256. Since the precoditions guarantee 101 | // that the outcome is less than 2**256, this is the final result. 102 | // We don't need to compute the high bits of the result and prod1 103 | // is no longer required. 104 | result = prod0 * inv; 105 | return result; 106 | } 107 | 108 | /// @notice Calculates ceil(a×b÷denominator) with full precision. Throws if result overflows a uint256 or denominator == 0 109 | /// @param a The multiplicand 110 | /// @param b The multiplier 111 | /// @param denominator The divisor 112 | /// @return result The 256-bit result 113 | function mulDivRoundingUp( 114 | uint256 a, 115 | uint256 b, 116 | uint256 denominator 117 | ) internal pure returns (uint256 result) { 118 | result = mulDiv(a, b, denominator); 119 | if (mulmod(a, b, denominator) > 0) { 120 | require(result < type(uint256).max); 121 | result++; 122 | } 123 | } 124 | } 125 | -------------------------------------------------------------------------------- /contracts/libraries/LICENSE_MIT: -------------------------------------------------------------------------------- 1 | Copyright (c) 2021 Remco Bloemen 2 | 3 | Permission is hereby granted, free of charge, to any person obtaining 4 | a copy of this software and associated documentation files (the 5 | "Software"), to deal in the Software without restriction, including 6 | without limitation the rights to use, copy, modify, merge, publish, 7 | distribute, sublicense, and/or sell copies of the Software, and to 8 | permit persons to whom the Software is furnished to do so, subject to 9 | the following conditions: 10 | 11 | The above copyright notice and this permission notice shall be 12 | included in all copies or substantial portions of the Software. 13 | 14 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, 15 | EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF 16 | MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND 17 | NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE 18 | LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION 19 | OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION 20 | WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. -------------------------------------------------------------------------------- /contracts/libraries/LiquidityMath.sol: -------------------------------------------------------------------------------- 1 | // SPDX-License-Identifier: GPL-2.0-or-later 2 | pragma solidity >=0.5.0; 3 | 4 | /// @title Math library for liquidity 5 | library LiquidityMath { 6 | /// @notice Add a signed liquidity delta to liquidity and revert if it overflows or underflows 7 | /// @param x The liquidity before change 8 | /// @param y The delta by which liquidity should be changed 9 | /// @return z The liquidity delta 10 | function addDelta(uint128 x, int128 y) internal pure returns (uint128 z) { 11 | if (y < 0) { 12 | require((z = x - uint128(-y)) < x, 'LS'); 13 | } else { 14 | require((z = x + uint128(y)) >= x, 'LA'); 15 | } 16 | } 17 | } 18 | -------------------------------------------------------------------------------- /contracts/libraries/LowGasSafeMath.sol: -------------------------------------------------------------------------------- 1 | // SPDX-License-Identifier: GPL-2.0-or-later 2 | pragma solidity >=0.7.0; 3 | 4 | /// @title Optimized overflow and underflow safe math operations 5 | /// @notice Contains methods for doing math operations that revert on overflow or underflow for minimal gas cost 6 | library LowGasSafeMath { 7 | /// @notice Returns x + y, reverts if sum overflows uint256 8 | /// @param x The augend 9 | /// @param y The addend 10 | /// @return z The sum of x and y 11 | function add(uint256 x, uint256 y) internal pure returns (uint256 z) { 12 | require((z = x + y) >= x); 13 | } 14 | 15 | /// @notice Returns x - y, reverts if underflows 16 | /// @param x The minuend 17 | /// @param y The subtrahend 18 | /// @return z The difference of x and y 19 | function sub(uint256 x, uint256 y) internal pure returns (uint256 z) { 20 | require((z = x - y) <= x); 21 | } 22 | 23 | /// @notice Returns x * y, reverts if overflows 24 | /// @param x The multiplicand 25 | /// @param y The multiplier 26 | /// @return z The product of x and y 27 | function mul(uint256 x, uint256 y) internal pure returns (uint256 z) { 28 | require(x == 0 || (z = x * y) / x == y); 29 | } 30 | 31 | /// @notice Returns x + y, reverts if overflows or underflows 32 | /// @param x The augend 33 | /// @param y The addend 34 | /// @return z The sum of x and y 35 | function add(int256 x, int256 y) internal pure returns (int256 z) { 36 | require((z = x + y) >= x == (y >= 0)); 37 | } 38 | 39 | /// @notice Returns x - y, reverts if overflows or underflows 40 | /// @param x The minuend 41 | /// @param y The subtrahend 42 | /// @return z The difference of x and y 43 | function sub(int256 x, int256 y) internal pure returns (int256 z) { 44 | require((z = x - y) <= x == (y >= 0)); 45 | } 46 | } 47 | -------------------------------------------------------------------------------- /contracts/libraries/Position.sol: -------------------------------------------------------------------------------- 1 | // SPDX-License-Identifier: BUSL-1.1 2 | pragma solidity >=0.5.0 <0.8.0; 3 | 4 | import './FullMath.sol'; 5 | import './FixedPoint128.sol'; 6 | import './LiquidityMath.sol'; 7 | 8 | /// @title Position 9 | /// @notice Positions represent an owner address' liquidity between a lower and upper tick boundary 10 | /// @dev Positions store additional state for tracking fees owed to the position 11 | library Position { 12 | // info stored for each user's position 13 | struct Info { 14 | // the amount of liquidity owned by this position 15 | uint128 liquidity; 16 | // fee growth per unit of liquidity as of the last update to liquidity or fees owed 17 | uint256 feeGrowthInside0LastX128; 18 | uint256 feeGrowthInside1LastX128; 19 | // the fees owed to the position owner in token0/token1 20 | uint128 tokensOwed0; 21 | uint128 tokensOwed1; 22 | } 23 | 24 | /// @notice Returns the Info struct of a position, given an owner and position boundaries 25 | /// @param self The mapping containing all user positions 26 | /// @param owner The address of the position owner 27 | /// @param tickLower The lower tick boundary of the position 28 | /// @param tickUpper The upper tick boundary of the position 29 | /// @return position The position info struct of the given owners' position 30 | function get( 31 | mapping(bytes32 => Info) storage self, 32 | address owner, 33 | int24 tickLower, 34 | int24 tickUpper 35 | ) internal view returns (Position.Info storage position) { 36 | position = self[keccak256(abi.encodePacked(owner, tickLower, tickUpper))]; 37 | } 38 | 39 | /// @notice Credits accumulated fees to a user's position 40 | /// @param self The individual position to update 41 | /// @param liquidityDelta The change in pool liquidity as a result of the position update 42 | /// @param feeGrowthInside0X128 The all-time fee growth in token0, per unit of liquidity, inside the position's tick boundaries 43 | /// @param feeGrowthInside1X128 The all-time fee growth in token1, per unit of liquidity, inside the position's tick boundaries 44 | function update( 45 | Info storage self, 46 | int128 liquidityDelta, 47 | uint256 feeGrowthInside0X128, 48 | uint256 feeGrowthInside1X128 49 | ) internal { 50 | Info memory _self = self; 51 | 52 | uint128 liquidityNext; 53 | if (liquidityDelta == 0) { 54 | require(_self.liquidity > 0, 'NP'); // disallow pokes for 0 liquidity positions 55 | liquidityNext = _self.liquidity; 56 | } else { 57 | liquidityNext = LiquidityMath.addDelta(_self.liquidity, liquidityDelta); 58 | } 59 | 60 | // calculate accumulated fees 61 | uint128 tokensOwed0 = 62 | uint128( 63 | FullMath.mulDiv( 64 | feeGrowthInside0X128 - _self.feeGrowthInside0LastX128, 65 | _self.liquidity, 66 | FixedPoint128.Q128 67 | ) 68 | ); 69 | uint128 tokensOwed1 = 70 | uint128( 71 | FullMath.mulDiv( 72 | feeGrowthInside1X128 - _self.feeGrowthInside1LastX128, 73 | _self.liquidity, 74 | FixedPoint128.Q128 75 | ) 76 | ); 77 | 78 | // update the position 79 | if (liquidityDelta != 0) self.liquidity = liquidityNext; 80 | self.feeGrowthInside0LastX128 = feeGrowthInside0X128; 81 | self.feeGrowthInside1LastX128 = feeGrowthInside1X128; 82 | if (tokensOwed0 > 0 || tokensOwed1 > 0) { 83 | // overflow is acceptable, have to withdraw before you hit type(uint128).max fees 84 | self.tokensOwed0 += tokensOwed0; 85 | self.tokensOwed1 += tokensOwed1; 86 | } 87 | } 88 | } 89 | -------------------------------------------------------------------------------- /contracts/libraries/SafeCast.sol: -------------------------------------------------------------------------------- 1 | // SPDX-License-Identifier: GPL-2.0-or-later 2 | pragma solidity >=0.5.0; 3 | 4 | /// @title Safe casting methods 5 | /// @notice Contains methods for safely casting between types 6 | library SafeCast { 7 | /// @notice Cast a uint256 to a uint160, revert on overflow 8 | /// @param y The uint256 to be downcasted 9 | /// @return z The downcasted integer, now type uint160 10 | function toUint160(uint256 y) internal pure returns (uint160 z) { 11 | require((z = uint160(y)) == y); 12 | } 13 | 14 | /// @notice Cast a int256 to a int128, revert on overflow or underflow 15 | /// @param y The int256 to be downcasted 16 | /// @return z The downcasted integer, now type int128 17 | function toInt128(int256 y) internal pure returns (int128 z) { 18 | require((z = int128(y)) == y); 19 | } 20 | 21 | /// @notice Cast a uint256 to a int256, revert on overflow 22 | /// @param y The uint256 to be casted 23 | /// @return z The casted integer, now type int256 24 | function toInt256(uint256 y) internal pure returns (int256 z) { 25 | require(y < 2**255); 26 | z = int256(y); 27 | } 28 | } 29 | -------------------------------------------------------------------------------- /contracts/libraries/SwapMath.sol: -------------------------------------------------------------------------------- 1 | // SPDX-License-Identifier: BUSL-1.1 2 | pragma solidity >=0.5.0; 3 | 4 | import './FullMath.sol'; 5 | import './SqrtPriceMath.sol'; 6 | 7 | /// @title Computes the result of a swap within ticks 8 | /// @notice Contains methods for computing the result of a swap within a single tick price range, i.e., a single tick. 9 | library SwapMath { 10 | /// @notice Computes the result of swapping some amount in, or amount out, given the parameters of the swap 11 | /// @dev The fee, plus the amount in, will never exceed the amount remaining if the swap's `amountSpecified` is positive 12 | /// @param sqrtRatioCurrentX96 The current sqrt price of the pool 13 | /// @param sqrtRatioTargetX96 The price that cannot be exceeded, from which the direction of the swap is inferred 14 | /// @param liquidity The usable liquidity 15 | /// @param amountRemaining How much input or output amount is remaining to be swapped in/out 16 | /// @param feePips The fee taken from the input amount, expressed in hundredths of a bip 17 | /// @return sqrtRatioNextX96 The price after swapping the amount in/out, not to exceed the price target 18 | /// @return amountIn The amount to be swapped in, of either token0 or token1, based on the direction of the swap 19 | /// @return amountOut The amount to be received, of either token0 or token1, based on the direction of the swap 20 | /// @return feeAmount The amount of input that will be taken as a fee 21 | function computeSwapStep( 22 | uint160 sqrtRatioCurrentX96, 23 | uint160 sqrtRatioTargetX96, 24 | uint128 liquidity, 25 | int256 amountRemaining, 26 | uint24 feePips 27 | ) 28 | internal 29 | pure 30 | returns ( 31 | uint160 sqrtRatioNextX96, 32 | uint256 amountIn, 33 | uint256 amountOut, 34 | uint256 feeAmount 35 | ) 36 | { 37 | bool zeroForOne = sqrtRatioCurrentX96 >= sqrtRatioTargetX96; 38 | bool exactIn = amountRemaining >= 0; 39 | 40 | if (exactIn) { 41 | uint256 amountRemainingLessFee = FullMath.mulDiv(uint256(amountRemaining), 1e6 - feePips, 1e6); 42 | amountIn = zeroForOne 43 | ? SqrtPriceMath.getAmount0Delta(sqrtRatioTargetX96, sqrtRatioCurrentX96, liquidity, true) 44 | : SqrtPriceMath.getAmount1Delta(sqrtRatioCurrentX96, sqrtRatioTargetX96, liquidity, true); 45 | if (amountRemainingLessFee >= amountIn) sqrtRatioNextX96 = sqrtRatioTargetX96; 46 | else 47 | sqrtRatioNextX96 = SqrtPriceMath.getNextSqrtPriceFromInput( 48 | sqrtRatioCurrentX96, 49 | liquidity, 50 | amountRemainingLessFee, 51 | zeroForOne 52 | ); 53 | } else { 54 | amountOut = zeroForOne 55 | ? SqrtPriceMath.getAmount1Delta(sqrtRatioTargetX96, sqrtRatioCurrentX96, liquidity, false) 56 | : SqrtPriceMath.getAmount0Delta(sqrtRatioCurrentX96, sqrtRatioTargetX96, liquidity, false); 57 | if (uint256(-amountRemaining) >= amountOut) sqrtRatioNextX96 = sqrtRatioTargetX96; 58 | else 59 | sqrtRatioNextX96 = SqrtPriceMath.getNextSqrtPriceFromOutput( 60 | sqrtRatioCurrentX96, 61 | liquidity, 62 | uint256(-amountRemaining), 63 | zeroForOne 64 | ); 65 | } 66 | 67 | bool max = sqrtRatioTargetX96 == sqrtRatioNextX96; 68 | 69 | // get the input/output amounts 70 | if (zeroForOne) { 71 | amountIn = max && exactIn 72 | ? amountIn 73 | : SqrtPriceMath.getAmount0Delta(sqrtRatioNextX96, sqrtRatioCurrentX96, liquidity, true); 74 | amountOut = max && !exactIn 75 | ? amountOut 76 | : SqrtPriceMath.getAmount1Delta(sqrtRatioNextX96, sqrtRatioCurrentX96, liquidity, false); 77 | } else { 78 | amountIn = max && exactIn 79 | ? amountIn 80 | : SqrtPriceMath.getAmount1Delta(sqrtRatioCurrentX96, sqrtRatioNextX96, liquidity, true); 81 | amountOut = max && !exactIn 82 | ? amountOut 83 | : SqrtPriceMath.getAmount0Delta(sqrtRatioCurrentX96, sqrtRatioNextX96, liquidity, false); 84 | } 85 | 86 | // cap the output amount to not exceed the remaining output amount 87 | if (!exactIn && amountOut > uint256(-amountRemaining)) { 88 | amountOut = uint256(-amountRemaining); 89 | } 90 | 91 | if (exactIn && sqrtRatioNextX96 != sqrtRatioTargetX96) { 92 | // we didn't reach the target, so take the remainder of the maximum input as fee 93 | feeAmount = uint256(amountRemaining) - amountIn; 94 | } else { 95 | feeAmount = FullMath.mulDivRoundingUp(amountIn, feePips, 1e6 - feePips); 96 | } 97 | } 98 | } 99 | -------------------------------------------------------------------------------- /contracts/libraries/TickBitmap.sol: -------------------------------------------------------------------------------- 1 | // SPDX-License-Identifier: BUSL-1.1 2 | pragma solidity >=0.5.0; 3 | 4 | import './BitMath.sol'; 5 | 6 | /// @title Packed tick initialized state library 7 | /// @notice Stores a packed mapping of tick index to its initialized state 8 | /// @dev The mapping uses int16 for keys since ticks are represented as int24 and there are 256 (2^8) values per word. 9 | library TickBitmap { 10 | /// @notice Computes the position in the mapping where the initialized bit for a tick lives 11 | /// @param tick The tick for which to compute the position 12 | /// @return wordPos The key in the mapping containing the word in which the bit is stored 13 | /// @return bitPos The bit position in the word where the flag is stored 14 | function position(int24 tick) private pure returns (int16 wordPos, uint8 bitPos) { 15 | wordPos = int16(tick >> 8); 16 | bitPos = uint8(tick % 256); 17 | } 18 | 19 | /// @notice Flips the initialized state for a given tick from false to true, or vice versa 20 | /// @param self The mapping in which to flip the tick 21 | /// @param tick The tick to flip 22 | /// @param tickSpacing The spacing between usable ticks 23 | function flipTick( 24 | mapping(int16 => uint256) storage self, 25 | int24 tick, 26 | int24 tickSpacing 27 | ) internal { 28 | require(tick % tickSpacing == 0); // ensure that the tick is spaced 29 | (int16 wordPos, uint8 bitPos) = position(tick / tickSpacing); 30 | uint256 mask = 1 << bitPos; 31 | self[wordPos] ^= mask; 32 | } 33 | 34 | /// @notice Returns the next initialized tick contained in the same word (or adjacent word) as the tick that is either 35 | /// to the left (less than or equal to) or right (greater than) of the given tick 36 | /// @param self The mapping in which to compute the next initialized tick 37 | /// @param tick The starting tick 38 | /// @param tickSpacing The spacing between usable ticks 39 | /// @param lte Whether to search for the next initialized tick to the left (less than or equal to the starting tick) 40 | /// @return next The next initialized or uninitialized tick up to 256 ticks away from the current tick 41 | /// @return initialized Whether the next tick is initialized, as the function only searches within up to 256 ticks 42 | function nextInitializedTickWithinOneWord( 43 | mapping(int16 => uint256) storage self, 44 | int24 tick, 45 | int24 tickSpacing, 46 | bool lte 47 | ) internal view returns (int24 next, bool initialized) { 48 | int24 compressed = tick / tickSpacing; 49 | if (tick < 0 && tick % tickSpacing != 0) compressed--; // round towards negative infinity 50 | 51 | if (lte) { 52 | (int16 wordPos, uint8 bitPos) = position(compressed); 53 | // all the 1s at or to the right of the current bitPos 54 | uint256 mask = (1 << bitPos) - 1 + (1 << bitPos); 55 | uint256 masked = self[wordPos] & mask; 56 | 57 | // if there are no initialized ticks to the right of or at the current tick, return rightmost in the word 58 | initialized = masked != 0; 59 | // overflow/underflow is possible, but prevented externally by limiting both tickSpacing and tick 60 | next = initialized 61 | ? (compressed - int24(bitPos - BitMath.mostSignificantBit(masked))) * tickSpacing 62 | : (compressed - int24(bitPos)) * tickSpacing; 63 | } else { 64 | // start from the word of the next tick, since the current tick state doesn't matter 65 | (int16 wordPos, uint8 bitPos) = position(compressed + 1); 66 | // all the 1s at or to the left of the bitPos 67 | uint256 mask = ~((1 << bitPos) - 1); 68 | uint256 masked = self[wordPos] & mask; 69 | 70 | // if there are no initialized ticks to the left of the current tick, return leftmost in the word 71 | initialized = masked != 0; 72 | // overflow/underflow is possible, but prevented externally by limiting both tickSpacing and tick 73 | next = initialized 74 | ? (compressed + 1 + int24(BitMath.leastSignificantBit(masked) - bitPos)) * tickSpacing 75 | : (compressed + 1 + int24(type(uint8).max - bitPos)) * tickSpacing; 76 | } 77 | } 78 | } 79 | -------------------------------------------------------------------------------- /contracts/libraries/TransferHelper.sol: -------------------------------------------------------------------------------- 1 | // SPDX-License-Identifier: GPL-2.0-or-later 2 | pragma solidity >=0.6.0; 3 | 4 | import '../interfaces/IERC20Minimal.sol'; 5 | 6 | /// @title TransferHelper 7 | /// @notice Contains helper methods for interacting with ERC20 tokens that do not consistently return true/false 8 | library TransferHelper { 9 | /// @notice Transfers tokens from msg.sender to a recipient 10 | /// @dev Calls transfer on token contract, errors with TF if transfer fails 11 | /// @param token The contract address of the token which will be transferred 12 | /// @param to The recipient of the transfer 13 | /// @param value The value of the transfer 14 | function safeTransfer( 15 | address token, 16 | address to, 17 | uint256 value 18 | ) internal { 19 | (bool success, bytes memory data) = 20 | token.call(abi.encodeWithSelector(IERC20Minimal.transfer.selector, to, value)); 21 | require(success && (data.length == 0 || abi.decode(data, (bool))), 'TF'); 22 | } 23 | } 24 | -------------------------------------------------------------------------------- /contracts/libraries/UnsafeMath.sol: -------------------------------------------------------------------------------- 1 | // SPDX-License-Identifier: GPL-2.0-or-later 2 | pragma solidity >=0.5.0; 3 | 4 | /// @title Math functions that do not check inputs or outputs 5 | /// @notice Contains methods that perform common math functions but do not do any overflow or underflow checks 6 | library UnsafeMath { 7 | /// @notice Returns ceil(x / y) 8 | /// @dev division by 0 has unspecified behavior, and must be checked externally 9 | /// @param x The dividend 10 | /// @param y The divisor 11 | /// @return z The quotient, ceil(x / y) 12 | function divRoundingUp(uint256 x, uint256 y) internal pure returns (uint256 z) { 13 | assembly { 14 | z := add(div(x, y), gt(mod(x, y), 0)) 15 | } 16 | } 17 | } 18 | -------------------------------------------------------------------------------- /contracts/test/BitMathEchidnaTest.sol: -------------------------------------------------------------------------------- 1 | // SPDX-License-Identifier: UNLICENSED 2 | pragma solidity =0.7.6; 3 | 4 | import '../libraries/BitMath.sol'; 5 | 6 | contract BitMathEchidnaTest { 7 | function mostSignificantBitInvariant(uint256 input) external pure { 8 | uint8 msb = BitMath.mostSignificantBit(input); 9 | assert(input >= (uint256(2)**msb)); 10 | assert(msb == 255 || input < uint256(2)**(msb + 1)); 11 | } 12 | 13 | function leastSignificantBitInvariant(uint256 input) external pure { 14 | uint8 lsb = BitMath.leastSignificantBit(input); 15 | assert(input & (uint256(2)**lsb) != 0); 16 | assert(input & (uint256(2)**lsb - 1) == 0); 17 | } 18 | } 19 | -------------------------------------------------------------------------------- /contracts/test/BitMathTest.sol: -------------------------------------------------------------------------------- 1 | // SPDX-License-Identifier: UNLICENSED 2 | pragma solidity =0.7.6; 3 | 4 | import '../libraries/BitMath.sol'; 5 | 6 | contract BitMathTest { 7 | function mostSignificantBit(uint256 x) external pure returns (uint8 r) { 8 | return BitMath.mostSignificantBit(x); 9 | } 10 | 11 | function getGasCostOfMostSignificantBit(uint256 x) external view returns (uint256) { 12 | uint256 gasBefore = gasleft(); 13 | BitMath.mostSignificantBit(x); 14 | return gasBefore - gasleft(); 15 | } 16 | 17 | function leastSignificantBit(uint256 x) external pure returns (uint8 r) { 18 | return BitMath.leastSignificantBit(x); 19 | } 20 | 21 | function getGasCostOfLeastSignificantBit(uint256 x) external view returns (uint256) { 22 | uint256 gasBefore = gasleft(); 23 | BitMath.leastSignificantBit(x); 24 | return gasBefore - gasleft(); 25 | } 26 | } 27 | -------------------------------------------------------------------------------- /contracts/test/FullMathEchidnaTest.sol: -------------------------------------------------------------------------------- 1 | // SPDX-License-Identifier: UNLICENSED 2 | pragma solidity =0.7.6; 3 | 4 | import '../libraries/FullMath.sol'; 5 | 6 | contract FullMathEchidnaTest { 7 | function checkMulDivRounding( 8 | uint256 x, 9 | uint256 y, 10 | uint256 d 11 | ) external pure { 12 | require(d > 0); 13 | 14 | uint256 ceiled = FullMath.mulDivRoundingUp(x, y, d); 15 | uint256 floored = FullMath.mulDiv(x, y, d); 16 | 17 | if (mulmod(x, y, d) > 0) { 18 | assert(ceiled - floored == 1); 19 | } else { 20 | assert(ceiled == floored); 21 | } 22 | } 23 | 24 | function checkMulDiv( 25 | uint256 x, 26 | uint256 y, 27 | uint256 d 28 | ) external pure { 29 | require(d > 0); 30 | uint256 z = FullMath.mulDiv(x, y, d); 31 | if (x == 0 || y == 0) { 32 | assert(z == 0); 33 | return; 34 | } 35 | 36 | // recompute x and y via mulDiv of the result of floor(x*y/d), should always be less than original inputs by < d 37 | uint256 x2 = FullMath.mulDiv(z, d, y); 38 | uint256 y2 = FullMath.mulDiv(z, d, x); 39 | assert(x2 <= x); 40 | assert(y2 <= y); 41 | 42 | assert(x - x2 < d); 43 | assert(y - y2 < d); 44 | } 45 | 46 | function checkMulDivRoundingUp( 47 | uint256 x, 48 | uint256 y, 49 | uint256 d 50 | ) external pure { 51 | require(d > 0); 52 | uint256 z = FullMath.mulDivRoundingUp(x, y, d); 53 | if (x == 0 || y == 0) { 54 | assert(z == 0); 55 | return; 56 | } 57 | 58 | // recompute x and y via mulDiv of the result of floor(x*y/d), should always be less than original inputs by < d 59 | uint256 x2 = FullMath.mulDiv(z, d, y); 60 | uint256 y2 = FullMath.mulDiv(z, d, x); 61 | assert(x2 >= x); 62 | assert(y2 >= y); 63 | 64 | assert(x2 - x < d); 65 | assert(y2 - y < d); 66 | } 67 | } 68 | -------------------------------------------------------------------------------- /contracts/test/FullMathTest.sol: -------------------------------------------------------------------------------- 1 | // SPDX-License-Identifier: UNLICENSED 2 | pragma solidity =0.7.6; 3 | 4 | import '../libraries/FullMath.sol'; 5 | 6 | contract FullMathTest { 7 | function mulDiv( 8 | uint256 x, 9 | uint256 y, 10 | uint256 z 11 | ) external pure returns (uint256) { 12 | return FullMath.mulDiv(x, y, z); 13 | } 14 | 15 | function mulDivRoundingUp( 16 | uint256 x, 17 | uint256 y, 18 | uint256 z 19 | ) external pure returns (uint256) { 20 | return FullMath.mulDivRoundingUp(x, y, z); 21 | } 22 | } 23 | -------------------------------------------------------------------------------- /contracts/test/LiquidityMathTest.sol: -------------------------------------------------------------------------------- 1 | // SPDX-License-Identifier: UNLICENSED 2 | pragma solidity =0.7.6; 3 | 4 | import '../libraries/LiquidityMath.sol'; 5 | 6 | contract LiquidityMathTest { 7 | function addDelta(uint128 x, int128 y) external pure returns (uint128 z) { 8 | return LiquidityMath.addDelta(x, y); 9 | } 10 | 11 | function getGasCostOfAddDelta(uint128 x, int128 y) external view returns (uint256) { 12 | uint256 gasBefore = gasleft(); 13 | LiquidityMath.addDelta(x, y); 14 | return gasBefore - gasleft(); 15 | } 16 | } 17 | -------------------------------------------------------------------------------- /contracts/test/LowGasSafeMathEchidnaTest.sol: -------------------------------------------------------------------------------- 1 | // SPDX-License-Identifier: UNLICENSED 2 | pragma solidity =0.7.6; 3 | 4 | import '../libraries/LowGasSafeMath.sol'; 5 | 6 | contract LowGasSafeMathEchidnaTest { 7 | function checkAdd(uint256 x, uint256 y) external pure { 8 | uint256 z = LowGasSafeMath.add(x, y); 9 | assert(z == x + y); 10 | assert(z >= x && z >= y); 11 | } 12 | 13 | function checkSub(uint256 x, uint256 y) external pure { 14 | uint256 z = LowGasSafeMath.sub(x, y); 15 | assert(z == x - y); 16 | assert(z <= x); 17 | } 18 | 19 | function checkMul(uint256 x, uint256 y) external pure { 20 | uint256 z = LowGasSafeMath.mul(x, y); 21 | assert(z == x * y); 22 | assert(x == 0 || y == 0 || (z >= x && z >= y)); 23 | } 24 | 25 | function checkAddi(int256 x, int256 y) external pure { 26 | int256 z = LowGasSafeMath.add(x, y); 27 | assert(z == x + y); 28 | assert(y < 0 ? z < x : z >= x); 29 | } 30 | 31 | function checkSubi(int256 x, int256 y) external pure { 32 | int256 z = LowGasSafeMath.sub(x, y); 33 | assert(z == x - y); 34 | assert(y < 0 ? z > x : z <= x); 35 | } 36 | } 37 | -------------------------------------------------------------------------------- /contracts/test/MockTimeUniswapV3Pool.sol: -------------------------------------------------------------------------------- 1 | // SPDX-License-Identifier: UNLICENSED 2 | pragma solidity =0.7.6; 3 | 4 | import '../UniswapV3Pool.sol'; 5 | 6 | // used for testing time dependent behavior 7 | contract MockTimeUniswapV3Pool is UniswapV3Pool { 8 | // Monday, October 5, 2020 9:00:00 AM GMT-05:00 9 | uint256 public time = 1601906400; 10 | 11 | function setFeeGrowthGlobal0X128(uint256 _feeGrowthGlobal0X128) external { 12 | feeGrowthGlobal0X128 = _feeGrowthGlobal0X128; 13 | } 14 | 15 | function setFeeGrowthGlobal1X128(uint256 _feeGrowthGlobal1X128) external { 16 | feeGrowthGlobal1X128 = _feeGrowthGlobal1X128; 17 | } 18 | 19 | function advanceTime(uint256 by) external { 20 | time += by; 21 | } 22 | 23 | function _blockTimestamp() internal view override returns (uint32) { 24 | return uint32(time); 25 | } 26 | } 27 | -------------------------------------------------------------------------------- /contracts/test/MockTimeUniswapV3PoolDeployer.sol: -------------------------------------------------------------------------------- 1 | // SPDX-License-Identifier: UNLICENSED 2 | pragma solidity =0.7.6; 3 | 4 | import '../interfaces/IUniswapV3PoolDeployer.sol'; 5 | 6 | import './MockTimeUniswapV3Pool.sol'; 7 | 8 | contract MockTimeUniswapV3PoolDeployer is IUniswapV3PoolDeployer { 9 | struct Parameters { 10 | address factory; 11 | address token0; 12 | address token1; 13 | uint24 fee; 14 | int24 tickSpacing; 15 | } 16 | 17 | Parameters public override parameters; 18 | 19 | event PoolDeployed(address pool); 20 | 21 | function deploy( 22 | address factory, 23 | address token0, 24 | address token1, 25 | uint24 fee, 26 | int24 tickSpacing 27 | ) external returns (address pool) { 28 | parameters = Parameters({factory: factory, token0: token0, token1: token1, fee: fee, tickSpacing: tickSpacing}); 29 | pool = address( 30 | new MockTimeUniswapV3Pool{salt: keccak256(abi.encodePacked(token0, token1, fee, tickSpacing))}() 31 | ); 32 | emit PoolDeployed(pool); 33 | delete parameters; 34 | } 35 | } 36 | -------------------------------------------------------------------------------- /contracts/test/NoDelegateCallTest.sol: -------------------------------------------------------------------------------- 1 | // SPDX-License-Identifier: UNLICENSED 2 | pragma solidity =0.7.6; 3 | 4 | import '../NoDelegateCall.sol'; 5 | 6 | contract NoDelegateCallTest is NoDelegateCall { 7 | function canBeDelegateCalled() public view returns (uint256) { 8 | return block.timestamp / 5; 9 | } 10 | 11 | function cannotBeDelegateCalled() public view noDelegateCall returns (uint256) { 12 | return block.timestamp / 5; 13 | } 14 | 15 | function getGasCostOfCanBeDelegateCalled() external view returns (uint256) { 16 | uint256 gasBefore = gasleft(); 17 | canBeDelegateCalled(); 18 | return gasBefore - gasleft(); 19 | } 20 | 21 | function getGasCostOfCannotBeDelegateCalled() external view returns (uint256) { 22 | uint256 gasBefore = gasleft(); 23 | cannotBeDelegateCalled(); 24 | return gasBefore - gasleft(); 25 | } 26 | 27 | function callsIntoNoDelegateCallFunction() external view { 28 | noDelegateCallPrivate(); 29 | } 30 | 31 | function noDelegateCallPrivate() private view noDelegateCall {} 32 | } 33 | -------------------------------------------------------------------------------- /contracts/test/OracleEchidnaTest.sol: -------------------------------------------------------------------------------- 1 | // SPDX-License-Identifier: UNLICENSED 2 | pragma solidity =0.7.6; 3 | pragma abicoder v2; 4 | 5 | import './OracleTest.sol'; 6 | 7 | contract OracleEchidnaTest { 8 | OracleTest private oracle; 9 | 10 | bool private initialized; 11 | uint32 private timePassed; 12 | 13 | constructor() { 14 | oracle = new OracleTest(); 15 | } 16 | 17 | function initialize( 18 | uint32 time, 19 | int24 tick, 20 | uint128 liquidity 21 | ) external { 22 | oracle.initialize(OracleTest.InitializeParams({time: time, tick: tick, liquidity: liquidity})); 23 | initialized = true; 24 | } 25 | 26 | function limitTimePassed(uint32 by) private { 27 | require(timePassed + by >= timePassed); 28 | timePassed += by; 29 | } 30 | 31 | function advanceTime(uint32 by) public { 32 | limitTimePassed(by); 33 | oracle.advanceTime(by); 34 | } 35 | 36 | // write an observation, then change tick and liquidity 37 | function update( 38 | uint32 advanceTimeBy, 39 | int24 tick, 40 | uint128 liquidity 41 | ) external { 42 | limitTimePassed(advanceTimeBy); 43 | oracle.update(OracleTest.UpdateParams({advanceTimeBy: advanceTimeBy, tick: tick, liquidity: liquidity})); 44 | } 45 | 46 | function grow(uint16 cardinality) external { 47 | oracle.grow(cardinality); 48 | } 49 | 50 | function checkTimeWeightedResultAssertions(uint32 secondsAgo0, uint32 secondsAgo1) private view { 51 | require(secondsAgo0 != secondsAgo1); 52 | require(initialized); 53 | // secondsAgo0 should be the larger one 54 | if (secondsAgo0 < secondsAgo1) (secondsAgo0, secondsAgo1) = (secondsAgo1, secondsAgo0); 55 | 56 | uint32 timeElapsed = secondsAgo0 - secondsAgo1; 57 | 58 | uint32[] memory secondsAgos = new uint32[](2); 59 | secondsAgos[0] = secondsAgo0; 60 | secondsAgos[1] = secondsAgo1; 61 | 62 | (int56[] memory tickCumulatives, uint160[] memory secondsPerLiquidityCumulativeX128s) = 63 | oracle.observe(secondsAgos); 64 | int56 timeWeightedTick = (tickCumulatives[1] - tickCumulatives[0]) / timeElapsed; 65 | uint256 timeWeightedHarmonicMeanLiquidity = 66 | (uint256(timeElapsed) * type(uint160).max) / 67 | (uint256(secondsPerLiquidityCumulativeX128s[1] - secondsPerLiquidityCumulativeX128s[0]) << 32); 68 | assert(timeWeightedHarmonicMeanLiquidity <= type(uint128).max); 69 | assert(timeWeightedTick <= type(int24).max); 70 | assert(timeWeightedTick >= type(int24).min); 71 | } 72 | 73 | function echidna_indexAlwaysLtCardinality() external view returns (bool) { 74 | return oracle.index() < oracle.cardinality() || !initialized; 75 | } 76 | 77 | function echidna_AlwaysInitialized() external view returns (bool) { 78 | (, , , bool isInitialized) = oracle.observations(0); 79 | return oracle.cardinality() == 0 || isInitialized; 80 | } 81 | 82 | function echidna_cardinalityAlwaysLteNext() external view returns (bool) { 83 | return oracle.cardinality() <= oracle.cardinalityNext(); 84 | } 85 | 86 | function echidna_canAlwaysObserve0IfInitialized() external view returns (bool) { 87 | if (!initialized) { 88 | return true; 89 | } 90 | uint32[] memory arr = new uint32[](1); 91 | arr[0] = 0; 92 | (bool success, ) = address(oracle).staticcall(abi.encodeWithSelector(OracleTest.observe.selector, arr)); 93 | return success; 94 | } 95 | 96 | function checkTwoAdjacentObservationsTickCumulativeModTimeElapsedAlways0(uint16 index) external view { 97 | uint16 cardinality = oracle.cardinality(); 98 | // check that the observations are initialized, and that the index is not the oldest observation 99 | require(index < cardinality && index != (oracle.index() + 1) % cardinality); 100 | 101 | (uint32 blockTimestamp0, int56 tickCumulative0, , bool initialized0) = 102 | oracle.observations(index == 0 ? cardinality - 1 : index - 1); 103 | (uint32 blockTimestamp1, int56 tickCumulative1, , bool initialized1) = oracle.observations(index); 104 | 105 | require(initialized0); 106 | require(initialized1); 107 | 108 | uint32 timeElapsed = blockTimestamp1 - blockTimestamp0; 109 | assert(timeElapsed > 0); 110 | assert((tickCumulative1 - tickCumulative0) % timeElapsed == 0); 111 | } 112 | 113 | function checkTimeWeightedAveragesAlwaysFitsType(uint32 secondsAgo) external view { 114 | require(initialized); 115 | require(secondsAgo > 0); 116 | uint32[] memory secondsAgos = new uint32[](2); 117 | secondsAgos[0] = secondsAgo; 118 | secondsAgos[1] = 0; 119 | (int56[] memory tickCumulatives, uint160[] memory secondsPerLiquidityCumulativeX128s) = 120 | oracle.observe(secondsAgos); 121 | 122 | // compute the time weighted tick, rounded towards negative infinity 123 | int56 numerator = tickCumulatives[1] - tickCumulatives[0]; 124 | int56 timeWeightedTick = numerator / int56(secondsAgo); 125 | if (numerator < 0 && numerator % int56(secondsAgo) != 0) { 126 | timeWeightedTick--; 127 | } 128 | 129 | // the time weighted averages fit in their respective accumulated types 130 | assert(timeWeightedTick <= type(int24).max && timeWeightedTick >= type(int24).min); 131 | 132 | uint256 timeWeightedHarmonicMeanLiquidity = 133 | (uint256(secondsAgo) * type(uint160).max) / 134 | (uint256(secondsPerLiquidityCumulativeX128s[1] - secondsPerLiquidityCumulativeX128s[0]) << 32); 135 | assert(timeWeightedHarmonicMeanLiquidity <= type(uint128).max); 136 | } 137 | } 138 | -------------------------------------------------------------------------------- /contracts/test/OracleTest.sol: -------------------------------------------------------------------------------- 1 | // SPDX-License-Identifier: UNLICENSED 2 | pragma solidity =0.7.6; 3 | pragma abicoder v2; 4 | 5 | import '../libraries/Oracle.sol'; 6 | 7 | contract OracleTest { 8 | using Oracle for Oracle.Observation[65535]; 9 | 10 | Oracle.Observation[65535] public observations; 11 | 12 | uint32 public time; 13 | int24 public tick; 14 | uint128 public liquidity; 15 | uint16 public index; 16 | uint16 public cardinality; 17 | uint16 public cardinalityNext; 18 | 19 | struct InitializeParams { 20 | uint32 time; 21 | int24 tick; 22 | uint128 liquidity; 23 | } 24 | 25 | function initialize(InitializeParams calldata params) external { 26 | require(cardinality == 0, 'already initialized'); 27 | time = params.time; 28 | tick = params.tick; 29 | liquidity = params.liquidity; 30 | (cardinality, cardinalityNext) = observations.initialize(params.time); 31 | } 32 | 33 | function advanceTime(uint32 by) public { 34 | time += by; 35 | } 36 | 37 | struct UpdateParams { 38 | uint32 advanceTimeBy; 39 | int24 tick; 40 | uint128 liquidity; 41 | } 42 | 43 | // write an observation, then change tick and liquidity 44 | function update(UpdateParams calldata params) external { 45 | advanceTime(params.advanceTimeBy); 46 | (index, cardinality) = observations.write(index, time, tick, liquidity, cardinality, cardinalityNext); 47 | tick = params.tick; 48 | liquidity = params.liquidity; 49 | } 50 | 51 | function batchUpdate(UpdateParams[] calldata params) external { 52 | // sload everything 53 | int24 _tick = tick; 54 | uint128 _liquidity = liquidity; 55 | uint16 _index = index; 56 | uint16 _cardinality = cardinality; 57 | uint16 _cardinalityNext = cardinalityNext; 58 | uint32 _time = time; 59 | 60 | for (uint256 i = 0; i < params.length; i++) { 61 | _time += params[i].advanceTimeBy; 62 | (_index, _cardinality) = observations.write( 63 | _index, 64 | _time, 65 | _tick, 66 | _liquidity, 67 | _cardinality, 68 | _cardinalityNext 69 | ); 70 | _tick = params[i].tick; 71 | _liquidity = params[i].liquidity; 72 | } 73 | 74 | // sstore everything 75 | tick = _tick; 76 | liquidity = _liquidity; 77 | index = _index; 78 | cardinality = _cardinality; 79 | time = _time; 80 | } 81 | 82 | function grow(uint16 _cardinalityNext) external { 83 | cardinalityNext = observations.grow(cardinalityNext, _cardinalityNext); 84 | } 85 | 86 | function observe(uint32[] calldata secondsAgos) 87 | external 88 | view 89 | returns (int56[] memory tickCumulatives, uint160[] memory secondsPerLiquidityCumulativeX128s) 90 | { 91 | return observations.observe(time, secondsAgos, tick, index, liquidity, cardinality); 92 | } 93 | 94 | function getGasCostOfObserve(uint32[] calldata secondsAgos) external view returns (uint256) { 95 | (uint32 _time, int24 _tick, uint128 _liquidity, uint16 _index) = (time, tick, liquidity, index); 96 | uint256 gasBefore = gasleft(); 97 | observations.observe(_time, secondsAgos, _tick, _index, _liquidity, cardinality); 98 | return gasBefore - gasleft(); 99 | } 100 | } 101 | -------------------------------------------------------------------------------- /contracts/test/SqrtPriceMathTest.sol: -------------------------------------------------------------------------------- 1 | // SPDX-License-Identifier: UNLICENSED 2 | pragma solidity =0.7.6; 3 | 4 | import '../libraries/SqrtPriceMath.sol'; 5 | 6 | contract SqrtPriceMathTest { 7 | function getNextSqrtPriceFromInput( 8 | uint160 sqrtP, 9 | uint128 liquidity, 10 | uint256 amountIn, 11 | bool zeroForOne 12 | ) external pure returns (uint160 sqrtQ) { 13 | return SqrtPriceMath.getNextSqrtPriceFromInput(sqrtP, liquidity, amountIn, zeroForOne); 14 | } 15 | 16 | function getGasCostOfGetNextSqrtPriceFromInput( 17 | uint160 sqrtP, 18 | uint128 liquidity, 19 | uint256 amountIn, 20 | bool zeroForOne 21 | ) external view returns (uint256) { 22 | uint256 gasBefore = gasleft(); 23 | SqrtPriceMath.getNextSqrtPriceFromInput(sqrtP, liquidity, amountIn, zeroForOne); 24 | return gasBefore - gasleft(); 25 | } 26 | 27 | function getNextSqrtPriceFromOutput( 28 | uint160 sqrtP, 29 | uint128 liquidity, 30 | uint256 amountOut, 31 | bool zeroForOne 32 | ) external pure returns (uint160 sqrtQ) { 33 | return SqrtPriceMath.getNextSqrtPriceFromOutput(sqrtP, liquidity, amountOut, zeroForOne); 34 | } 35 | 36 | function getGasCostOfGetNextSqrtPriceFromOutput( 37 | uint160 sqrtP, 38 | uint128 liquidity, 39 | uint256 amountOut, 40 | bool zeroForOne 41 | ) external view returns (uint256) { 42 | uint256 gasBefore = gasleft(); 43 | SqrtPriceMath.getNextSqrtPriceFromOutput(sqrtP, liquidity, amountOut, zeroForOne); 44 | return gasBefore - gasleft(); 45 | } 46 | 47 | function getAmount0Delta( 48 | uint160 sqrtLower, 49 | uint160 sqrtUpper, 50 | uint128 liquidity, 51 | bool roundUp 52 | ) external pure returns (uint256 amount0) { 53 | return SqrtPriceMath.getAmount0Delta(sqrtLower, sqrtUpper, liquidity, roundUp); 54 | } 55 | 56 | function getAmount1Delta( 57 | uint160 sqrtLower, 58 | uint160 sqrtUpper, 59 | uint128 liquidity, 60 | bool roundUp 61 | ) external pure returns (uint256 amount1) { 62 | return SqrtPriceMath.getAmount1Delta(sqrtLower, sqrtUpper, liquidity, roundUp); 63 | } 64 | 65 | function getGasCostOfGetAmount0Delta( 66 | uint160 sqrtLower, 67 | uint160 sqrtUpper, 68 | uint128 liquidity, 69 | bool roundUp 70 | ) external view returns (uint256) { 71 | uint256 gasBefore = gasleft(); 72 | SqrtPriceMath.getAmount0Delta(sqrtLower, sqrtUpper, liquidity, roundUp); 73 | return gasBefore - gasleft(); 74 | } 75 | 76 | function getGasCostOfGetAmount1Delta( 77 | uint160 sqrtLower, 78 | uint160 sqrtUpper, 79 | uint128 liquidity, 80 | bool roundUp 81 | ) external view returns (uint256) { 82 | uint256 gasBefore = gasleft(); 83 | SqrtPriceMath.getAmount1Delta(sqrtLower, sqrtUpper, liquidity, roundUp); 84 | return gasBefore - gasleft(); 85 | } 86 | } 87 | -------------------------------------------------------------------------------- /contracts/test/SwapMathEchidnaTest.sol: -------------------------------------------------------------------------------- 1 | // SPDX-License-Identifier: UNLICENSED 2 | pragma solidity =0.7.6; 3 | 4 | import '../libraries/SwapMath.sol'; 5 | 6 | contract SwapMathEchidnaTest { 7 | function checkComputeSwapStepInvariants( 8 | uint160 sqrtPriceRaw, 9 | uint160 sqrtPriceTargetRaw, 10 | uint128 liquidity, 11 | int256 amountRemaining, 12 | uint24 feePips 13 | ) external pure { 14 | require(sqrtPriceRaw > 0); 15 | require(sqrtPriceTargetRaw > 0); 16 | require(feePips > 0); 17 | require(feePips < 1e6); 18 | 19 | (uint160 sqrtQ, uint256 amountIn, uint256 amountOut, uint256 feeAmount) = 20 | SwapMath.computeSwapStep(sqrtPriceRaw, sqrtPriceTargetRaw, liquidity, amountRemaining, feePips); 21 | 22 | assert(amountIn <= type(uint256).max - feeAmount); 23 | 24 | if (amountRemaining < 0) { 25 | assert(amountOut <= uint256(-amountRemaining)); 26 | } else { 27 | assert(amountIn + feeAmount <= uint256(amountRemaining)); 28 | } 29 | 30 | if (sqrtPriceRaw == sqrtPriceTargetRaw) { 31 | assert(amountIn == 0); 32 | assert(amountOut == 0); 33 | assert(feeAmount == 0); 34 | assert(sqrtQ == sqrtPriceTargetRaw); 35 | } 36 | 37 | // didn't reach price target, entire amount must be consumed 38 | if (sqrtQ != sqrtPriceTargetRaw) { 39 | if (amountRemaining < 0) assert(amountOut == uint256(-amountRemaining)); 40 | else assert(amountIn + feeAmount == uint256(amountRemaining)); 41 | } 42 | 43 | // next price is between price and price target 44 | if (sqrtPriceTargetRaw <= sqrtPriceRaw) { 45 | assert(sqrtQ <= sqrtPriceRaw); 46 | assert(sqrtQ >= sqrtPriceTargetRaw); 47 | } else { 48 | assert(sqrtQ >= sqrtPriceRaw); 49 | assert(sqrtQ <= sqrtPriceTargetRaw); 50 | } 51 | } 52 | } 53 | -------------------------------------------------------------------------------- /contracts/test/SwapMathTest.sol: -------------------------------------------------------------------------------- 1 | // SPDX-License-Identifier: UNLICENSED 2 | pragma solidity =0.7.6; 3 | 4 | import '../libraries/SwapMath.sol'; 5 | 6 | contract SwapMathTest { 7 | function computeSwapStep( 8 | uint160 sqrtP, 9 | uint160 sqrtPTarget, 10 | uint128 liquidity, 11 | int256 amountRemaining, 12 | uint24 feePips 13 | ) 14 | external 15 | pure 16 | returns ( 17 | uint160 sqrtQ, 18 | uint256 amountIn, 19 | uint256 amountOut, 20 | uint256 feeAmount 21 | ) 22 | { 23 | return SwapMath.computeSwapStep(sqrtP, sqrtPTarget, liquidity, amountRemaining, feePips); 24 | } 25 | 26 | function getGasCostOfComputeSwapStep( 27 | uint160 sqrtP, 28 | uint160 sqrtPTarget, 29 | uint128 liquidity, 30 | int256 amountRemaining, 31 | uint24 feePips 32 | ) external view returns (uint256) { 33 | uint256 gasBefore = gasleft(); 34 | SwapMath.computeSwapStep(sqrtP, sqrtPTarget, liquidity, amountRemaining, feePips); 35 | return gasBefore - gasleft(); 36 | } 37 | } 38 | -------------------------------------------------------------------------------- /contracts/test/TestERC20.sol: -------------------------------------------------------------------------------- 1 | // SPDX-License-Identifier: UNLICENSED 2 | pragma solidity =0.7.6; 3 | 4 | import '../interfaces/IERC20Minimal.sol'; 5 | 6 | contract TestERC20 is IERC20Minimal { 7 | mapping(address => uint256) public override balanceOf; 8 | mapping(address => mapping(address => uint256)) public override allowance; 9 | 10 | constructor(uint256 amountToMint) { 11 | mint(msg.sender, amountToMint); 12 | } 13 | 14 | function mint(address to, uint256 amount) public { 15 | uint256 balanceNext = balanceOf[to] + amount; 16 | require(balanceNext >= amount, 'overflow balance'); 17 | balanceOf[to] = balanceNext; 18 | } 19 | 20 | function transfer(address recipient, uint256 amount) external override returns (bool) { 21 | uint256 balanceBefore = balanceOf[msg.sender]; 22 | require(balanceBefore >= amount, 'insufficient balance'); 23 | balanceOf[msg.sender] = balanceBefore - amount; 24 | 25 | uint256 balanceRecipient = balanceOf[recipient]; 26 | require(balanceRecipient + amount >= balanceRecipient, 'recipient balance overflow'); 27 | balanceOf[recipient] = balanceRecipient + amount; 28 | 29 | emit Transfer(msg.sender, recipient, amount); 30 | return true; 31 | } 32 | 33 | function approve(address spender, uint256 amount) external override returns (bool) { 34 | allowance[msg.sender][spender] = amount; 35 | emit Approval(msg.sender, spender, amount); 36 | return true; 37 | } 38 | 39 | function transferFrom( 40 | address sender, 41 | address recipient, 42 | uint256 amount 43 | ) external override returns (bool) { 44 | uint256 allowanceBefore = allowance[sender][msg.sender]; 45 | require(allowanceBefore >= amount, 'allowance insufficient'); 46 | 47 | allowance[sender][msg.sender] = allowanceBefore - amount; 48 | 49 | uint256 balanceRecipient = balanceOf[recipient]; 50 | require(balanceRecipient + amount >= balanceRecipient, 'overflow balance recipient'); 51 | balanceOf[recipient] = balanceRecipient + amount; 52 | uint256 balanceSender = balanceOf[sender]; 53 | require(balanceSender >= amount, 'underflow balance sender'); 54 | balanceOf[sender] = balanceSender - amount; 55 | 56 | emit Transfer(sender, recipient, amount); 57 | return true; 58 | } 59 | } 60 | -------------------------------------------------------------------------------- /contracts/test/TestUniswapV3Callee.sol: -------------------------------------------------------------------------------- 1 | // SPDX-License-Identifier: UNLICENSED 2 | pragma solidity =0.7.6; 3 | 4 | import '../interfaces/IERC20Minimal.sol'; 5 | 6 | import '../libraries/SafeCast.sol'; 7 | import '../libraries/TickMath.sol'; 8 | 9 | import '../interfaces/callback/IUniswapV3MintCallback.sol'; 10 | import '../interfaces/callback/IUniswapV3SwapCallback.sol'; 11 | import '../interfaces/callback/IUniswapV3FlashCallback.sol'; 12 | 13 | import '../interfaces/IUniswapV3Pool.sol'; 14 | 15 | contract TestUniswapV3Callee is IUniswapV3MintCallback, IUniswapV3SwapCallback, IUniswapV3FlashCallback { 16 | using SafeCast for uint256; 17 | 18 | function swapExact0For1( 19 | address pool, 20 | uint256 amount0In, 21 | address recipient, 22 | uint160 sqrtPriceLimitX96 23 | ) external { 24 | IUniswapV3Pool(pool).swap(recipient, true, amount0In.toInt256(), sqrtPriceLimitX96, abi.encode(msg.sender)); 25 | } 26 | 27 | function swap0ForExact1( 28 | address pool, 29 | uint256 amount1Out, 30 | address recipient, 31 | uint160 sqrtPriceLimitX96 32 | ) external { 33 | IUniswapV3Pool(pool).swap(recipient, true, -amount1Out.toInt256(), sqrtPriceLimitX96, abi.encode(msg.sender)); 34 | } 35 | 36 | function swapExact1For0( 37 | address pool, 38 | uint256 amount1In, 39 | address recipient, 40 | uint160 sqrtPriceLimitX96 41 | ) external { 42 | IUniswapV3Pool(pool).swap(recipient, false, amount1In.toInt256(), sqrtPriceLimitX96, abi.encode(msg.sender)); 43 | } 44 | 45 | function swap1ForExact0( 46 | address pool, 47 | uint256 amount0Out, 48 | address recipient, 49 | uint160 sqrtPriceLimitX96 50 | ) external { 51 | IUniswapV3Pool(pool).swap(recipient, false, -amount0Out.toInt256(), sqrtPriceLimitX96, abi.encode(msg.sender)); 52 | } 53 | 54 | function swapToLowerSqrtPrice( 55 | address pool, 56 | uint160 sqrtPriceX96, 57 | address recipient 58 | ) external { 59 | IUniswapV3Pool(pool).swap(recipient, true, type(int256).max, sqrtPriceX96, abi.encode(msg.sender)); 60 | } 61 | 62 | function swapToHigherSqrtPrice( 63 | address pool, 64 | uint160 sqrtPriceX96, 65 | address recipient 66 | ) external { 67 | IUniswapV3Pool(pool).swap(recipient, false, type(int256).max, sqrtPriceX96, abi.encode(msg.sender)); 68 | } 69 | 70 | event SwapCallback(int256 amount0Delta, int256 amount1Delta); 71 | 72 | function uniswapV3SwapCallback( 73 | int256 amount0Delta, 74 | int256 amount1Delta, 75 | bytes calldata data 76 | ) external override { 77 | address sender = abi.decode(data, (address)); 78 | 79 | emit SwapCallback(amount0Delta, amount1Delta); 80 | 81 | if (amount0Delta > 0) { 82 | IERC20Minimal(IUniswapV3Pool(msg.sender).token0()).transferFrom(sender, msg.sender, uint256(amount0Delta)); 83 | } else if (amount1Delta > 0) { 84 | IERC20Minimal(IUniswapV3Pool(msg.sender).token1()).transferFrom(sender, msg.sender, uint256(amount1Delta)); 85 | } else { 86 | // if both are not gt 0, both must be 0. 87 | assert(amount0Delta == 0 && amount1Delta == 0); 88 | } 89 | } 90 | 91 | function mint( 92 | address pool, 93 | address recipient, 94 | int24 tickLower, 95 | int24 tickUpper, 96 | uint128 amount 97 | ) external { 98 | IUniswapV3Pool(pool).mint(recipient, tickLower, tickUpper, amount, abi.encode(msg.sender)); 99 | } 100 | 101 | event MintCallback(uint256 amount0Owed, uint256 amount1Owed); 102 | 103 | function uniswapV3MintCallback( 104 | uint256 amount0Owed, 105 | uint256 amount1Owed, 106 | bytes calldata data 107 | ) external override { 108 | address sender = abi.decode(data, (address)); 109 | 110 | emit MintCallback(amount0Owed, amount1Owed); 111 | if (amount0Owed > 0) 112 | IERC20Minimal(IUniswapV3Pool(msg.sender).token0()).transferFrom(sender, msg.sender, amount0Owed); 113 | if (amount1Owed > 0) 114 | IERC20Minimal(IUniswapV3Pool(msg.sender).token1()).transferFrom(sender, msg.sender, amount1Owed); 115 | } 116 | 117 | event FlashCallback(uint256 fee0, uint256 fee1); 118 | 119 | function flash( 120 | address pool, 121 | address recipient, 122 | uint256 amount0, 123 | uint256 amount1, 124 | uint256 pay0, 125 | uint256 pay1 126 | ) external { 127 | IUniswapV3Pool(pool).flash(recipient, amount0, amount1, abi.encode(msg.sender, pay0, pay1)); 128 | } 129 | 130 | function uniswapV3FlashCallback( 131 | uint256 fee0, 132 | uint256 fee1, 133 | bytes calldata data 134 | ) external override { 135 | emit FlashCallback(fee0, fee1); 136 | 137 | (address sender, uint256 pay0, uint256 pay1) = abi.decode(data, (address, uint256, uint256)); 138 | 139 | if (pay0 > 0) IERC20Minimal(IUniswapV3Pool(msg.sender).token0()).transferFrom(sender, msg.sender, pay0); 140 | if (pay1 > 0) IERC20Minimal(IUniswapV3Pool(msg.sender).token1()).transferFrom(sender, msg.sender, pay1); 141 | } 142 | } 143 | -------------------------------------------------------------------------------- /contracts/test/TestUniswapV3ReentrantCallee.sol: -------------------------------------------------------------------------------- 1 | // SPDX-License-Identifier: UNLICENSED 2 | pragma solidity =0.7.6; 3 | 4 | import '../libraries/TickMath.sol'; 5 | 6 | import '../interfaces/callback/IUniswapV3SwapCallback.sol'; 7 | 8 | import '../interfaces/IUniswapV3Pool.sol'; 9 | 10 | contract TestUniswapV3ReentrantCallee is IUniswapV3SwapCallback { 11 | string private constant expectedReason = 'LOK'; 12 | 13 | function swapToReenter(address pool) external { 14 | IUniswapV3Pool(pool).swap(address(0), false, 1, TickMath.MAX_SQRT_RATIO - 1, new bytes(0)); 15 | } 16 | 17 | function uniswapV3SwapCallback( 18 | int256, 19 | int256, 20 | bytes calldata 21 | ) external override { 22 | // try to reenter swap 23 | try IUniswapV3Pool(msg.sender).swap(address(0), false, 1, 0, new bytes(0)) {} catch Error( 24 | string memory reason 25 | ) { 26 | require(keccak256(abi.encode(reason)) == keccak256(abi.encode(expectedReason))); 27 | } 28 | 29 | // try to reenter mint 30 | try IUniswapV3Pool(msg.sender).mint(address(0), 0, 0, 0, new bytes(0)) {} catch Error(string memory reason) { 31 | require(keccak256(abi.encode(reason)) == keccak256(abi.encode(expectedReason))); 32 | } 33 | 34 | // try to reenter collect 35 | try IUniswapV3Pool(msg.sender).collect(address(0), 0, 0, 0, 0) {} catch Error(string memory reason) { 36 | require(keccak256(abi.encode(reason)) == keccak256(abi.encode(expectedReason))); 37 | } 38 | 39 | // try to reenter burn 40 | try IUniswapV3Pool(msg.sender).burn(0, 0, 0) {} catch Error(string memory reason) { 41 | require(keccak256(abi.encode(reason)) == keccak256(abi.encode(expectedReason))); 42 | } 43 | 44 | // try to reenter flash 45 | try IUniswapV3Pool(msg.sender).flash(address(0), 0, 0, new bytes(0)) {} catch Error(string memory reason) { 46 | require(keccak256(abi.encode(reason)) == keccak256(abi.encode(expectedReason))); 47 | } 48 | 49 | // try to reenter collectProtocol 50 | try IUniswapV3Pool(msg.sender).collectProtocol(address(0), 0, 0) {} catch Error(string memory reason) { 51 | require(keccak256(abi.encode(reason)) == keccak256(abi.encode(expectedReason))); 52 | } 53 | 54 | require(false, 'Unable to reenter'); 55 | } 56 | } 57 | -------------------------------------------------------------------------------- /contracts/test/TestUniswapV3Router.sol: -------------------------------------------------------------------------------- 1 | // SPDX-License-Identifier: UNLICENSED 2 | pragma solidity =0.7.6; 3 | 4 | import '../libraries/SafeCast.sol'; 5 | import '../libraries/TickMath.sol'; 6 | 7 | import '../interfaces/IERC20Minimal.sol'; 8 | import '../interfaces/callback/IUniswapV3SwapCallback.sol'; 9 | import '../interfaces/IUniswapV3Pool.sol'; 10 | 11 | contract TestUniswapV3Router is IUniswapV3SwapCallback { 12 | using SafeCast for uint256; 13 | 14 | // flash swaps for an exact amount of token0 in the output pool 15 | function swapForExact0Multi( 16 | address recipient, 17 | address poolInput, 18 | address poolOutput, 19 | uint256 amount0Out 20 | ) external { 21 | address[] memory pools = new address[](1); 22 | pools[0] = poolInput; 23 | IUniswapV3Pool(poolOutput).swap( 24 | recipient, 25 | false, 26 | -amount0Out.toInt256(), 27 | TickMath.MAX_SQRT_RATIO - 1, 28 | abi.encode(pools, msg.sender) 29 | ); 30 | } 31 | 32 | // flash swaps for an exact amount of token1 in the output pool 33 | function swapForExact1Multi( 34 | address recipient, 35 | address poolInput, 36 | address poolOutput, 37 | uint256 amount1Out 38 | ) external { 39 | address[] memory pools = new address[](1); 40 | pools[0] = poolInput; 41 | IUniswapV3Pool(poolOutput).swap( 42 | recipient, 43 | true, 44 | -amount1Out.toInt256(), 45 | TickMath.MIN_SQRT_RATIO + 1, 46 | abi.encode(pools, msg.sender) 47 | ); 48 | } 49 | 50 | event SwapCallback(int256 amount0Delta, int256 amount1Delta); 51 | 52 | function uniswapV3SwapCallback( 53 | int256 amount0Delta, 54 | int256 amount1Delta, 55 | bytes calldata data 56 | ) public override { 57 | emit SwapCallback(amount0Delta, amount1Delta); 58 | 59 | (address[] memory pools, address payer) = abi.decode(data, (address[], address)); 60 | 61 | if (pools.length == 1) { 62 | // get the address and amount of the token that we need to pay 63 | address tokenToBePaid = 64 | amount0Delta > 0 ? IUniswapV3Pool(msg.sender).token0() : IUniswapV3Pool(msg.sender).token1(); 65 | int256 amountToBePaid = amount0Delta > 0 ? amount0Delta : amount1Delta; 66 | 67 | bool zeroForOne = tokenToBePaid == IUniswapV3Pool(pools[0]).token1(); 68 | IUniswapV3Pool(pools[0]).swap( 69 | msg.sender, 70 | zeroForOne, 71 | -amountToBePaid, 72 | zeroForOne ? TickMath.MIN_SQRT_RATIO + 1 : TickMath.MAX_SQRT_RATIO - 1, 73 | abi.encode(new address[](0), payer) 74 | ); 75 | } else { 76 | if (amount0Delta > 0) { 77 | IERC20Minimal(IUniswapV3Pool(msg.sender).token0()).transferFrom( 78 | payer, 79 | msg.sender, 80 | uint256(amount0Delta) 81 | ); 82 | } else { 83 | IERC20Minimal(IUniswapV3Pool(msg.sender).token1()).transferFrom( 84 | payer, 85 | msg.sender, 86 | uint256(amount1Delta) 87 | ); 88 | } 89 | } 90 | } 91 | } 92 | -------------------------------------------------------------------------------- /contracts/test/TestUniswapV3SwapPay.sol: -------------------------------------------------------------------------------- 1 | // SPDX-License-Identifier: UNLICENSED 2 | pragma solidity =0.7.6; 3 | 4 | import '../interfaces/IERC20Minimal.sol'; 5 | 6 | import '../interfaces/callback/IUniswapV3SwapCallback.sol'; 7 | import '../interfaces/IUniswapV3Pool.sol'; 8 | 9 | contract TestUniswapV3SwapPay is IUniswapV3SwapCallback { 10 | function swap( 11 | address pool, 12 | address recipient, 13 | bool zeroForOne, 14 | uint160 sqrtPriceX96, 15 | int256 amountSpecified, 16 | uint256 pay0, 17 | uint256 pay1 18 | ) external { 19 | IUniswapV3Pool(pool).swap( 20 | recipient, 21 | zeroForOne, 22 | amountSpecified, 23 | sqrtPriceX96, 24 | abi.encode(msg.sender, pay0, pay1) 25 | ); 26 | } 27 | 28 | function uniswapV3SwapCallback( 29 | int256, 30 | int256, 31 | bytes calldata data 32 | ) external override { 33 | (address sender, uint256 pay0, uint256 pay1) = abi.decode(data, (address, uint256, uint256)); 34 | 35 | if (pay0 > 0) { 36 | IERC20Minimal(IUniswapV3Pool(msg.sender).token0()).transferFrom(sender, msg.sender, uint256(pay0)); 37 | } else if (pay1 > 0) { 38 | IERC20Minimal(IUniswapV3Pool(msg.sender).token1()).transferFrom(sender, msg.sender, uint256(pay1)); 39 | } 40 | } 41 | } 42 | -------------------------------------------------------------------------------- /contracts/test/TickBitmapEchidnaTest.sol: -------------------------------------------------------------------------------- 1 | // SPDX-License-Identifier: UNLICENSED 2 | pragma solidity =0.7.6; 3 | 4 | import '../libraries/TickBitmap.sol'; 5 | 6 | contract TickBitmapEchidnaTest { 7 | using TickBitmap for mapping(int16 => uint256); 8 | 9 | mapping(int16 => uint256) private bitmap; 10 | 11 | // returns whether the given tick is initialized 12 | function isInitialized(int24 tick) private view returns (bool) { 13 | (int24 next, bool initialized) = bitmap.nextInitializedTickWithinOneWord(tick, 1, true); 14 | return next == tick ? initialized : false; 15 | } 16 | 17 | function flipTick(int24 tick) external { 18 | bool before = isInitialized(tick); 19 | bitmap.flipTick(tick, 1); 20 | assert(isInitialized(tick) == !before); 21 | } 22 | 23 | function checkNextInitializedTickWithinOneWordInvariants(int24 tick, bool lte) external view { 24 | (int24 next, bool initialized) = bitmap.nextInitializedTickWithinOneWord(tick, 1, lte); 25 | if (lte) { 26 | // type(int24).min + 256 27 | require(tick >= -8388352); 28 | assert(next <= tick); 29 | assert(tick - next < 256); 30 | // all the ticks between the input tick and the next tick should be uninitialized 31 | for (int24 i = tick; i > next; i--) { 32 | assert(!isInitialized(i)); 33 | } 34 | assert(isInitialized(next) == initialized); 35 | } else { 36 | // type(int24).max - 256 37 | require(tick < 8388351); 38 | assert(next > tick); 39 | assert(next - tick <= 256); 40 | // all the ticks between the input tick and the next tick should be uninitialized 41 | for (int24 i = tick + 1; i < next; i++) { 42 | assert(!isInitialized(i)); 43 | } 44 | assert(isInitialized(next) == initialized); 45 | } 46 | } 47 | } 48 | -------------------------------------------------------------------------------- /contracts/test/TickBitmapTest.sol: -------------------------------------------------------------------------------- 1 | // SPDX-License-Identifier: UNLICENSED 2 | pragma solidity =0.7.6; 3 | 4 | import '../libraries/TickBitmap.sol'; 5 | 6 | contract TickBitmapTest { 7 | using TickBitmap for mapping(int16 => uint256); 8 | 9 | mapping(int16 => uint256) public bitmap; 10 | 11 | function flipTick(int24 tick) external { 12 | bitmap.flipTick(tick, 1); 13 | } 14 | 15 | function getGasCostOfFlipTick(int24 tick) external returns (uint256) { 16 | uint256 gasBefore = gasleft(); 17 | bitmap.flipTick(tick, 1); 18 | return gasBefore - gasleft(); 19 | } 20 | 21 | function nextInitializedTickWithinOneWord(int24 tick, bool lte) 22 | external 23 | view 24 | returns (int24 next, bool initialized) 25 | { 26 | return bitmap.nextInitializedTickWithinOneWord(tick, 1, lte); 27 | } 28 | 29 | function getGasCostOfNextInitializedTickWithinOneWord(int24 tick, bool lte) external view returns (uint256) { 30 | uint256 gasBefore = gasleft(); 31 | bitmap.nextInitializedTickWithinOneWord(tick, 1, lte); 32 | return gasBefore - gasleft(); 33 | } 34 | 35 | // returns whether the given tick is initialized 36 | function isInitialized(int24 tick) external view returns (bool) { 37 | (int24 next, bool initialized) = bitmap.nextInitializedTickWithinOneWord(tick, 1, true); 38 | return next == tick ? initialized : false; 39 | } 40 | } 41 | -------------------------------------------------------------------------------- /contracts/test/TickEchidnaTest.sol: -------------------------------------------------------------------------------- 1 | // SPDX-License-Identifier: UNLICENSED 2 | pragma solidity =0.7.6; 3 | 4 | import '../libraries/Tick.sol'; 5 | 6 | contract TickEchidnaTest { 7 | function checkTickSpacingToParametersInvariants(int24 tickSpacing) external pure { 8 | require(tickSpacing <= TickMath.MAX_TICK); 9 | require(tickSpacing > 0); 10 | 11 | int24 minTick = (TickMath.MIN_TICK / tickSpacing) * tickSpacing; 12 | int24 maxTick = (TickMath.MAX_TICK / tickSpacing) * tickSpacing; 13 | 14 | uint128 maxLiquidityPerTick = Tick.tickSpacingToMaxLiquidityPerTick(tickSpacing); 15 | 16 | // symmetry around 0 tick 17 | assert(maxTick == -minTick); 18 | // positive max tick 19 | assert(maxTick > 0); 20 | // divisibility 21 | assert((maxTick - minTick) % tickSpacing == 0); 22 | 23 | uint256 numTicks = uint256((maxTick - minTick) / tickSpacing) + 1; 24 | // max liquidity at every tick is less than the cap 25 | assert(uint256(maxLiquidityPerTick) * numTicks <= type(uint128).max); 26 | } 27 | } 28 | -------------------------------------------------------------------------------- /contracts/test/TickMathEchidnaTest.sol: -------------------------------------------------------------------------------- 1 | // SPDX-License-Identifier: UNLICENSED 2 | pragma solidity =0.7.6; 3 | 4 | import '../libraries/TickMath.sol'; 5 | 6 | contract TickMathEchidnaTest { 7 | // uniqueness and increasing order 8 | function checkGetSqrtRatioAtTickInvariants(int24 tick) external pure { 9 | uint160 ratio = TickMath.getSqrtRatioAtTick(tick); 10 | assert(TickMath.getSqrtRatioAtTick(tick - 1) < ratio && ratio < TickMath.getSqrtRatioAtTick(tick + 1)); 11 | assert(ratio >= TickMath.MIN_SQRT_RATIO); 12 | assert(ratio <= TickMath.MAX_SQRT_RATIO); 13 | } 14 | 15 | // the ratio is always between the returned tick and the returned tick+1 16 | function checkGetTickAtSqrtRatioInvariants(uint160 ratio) external pure { 17 | int24 tick = TickMath.getTickAtSqrtRatio(ratio); 18 | assert(ratio >= TickMath.getSqrtRatioAtTick(tick) && ratio < TickMath.getSqrtRatioAtTick(tick + 1)); 19 | assert(tick >= TickMath.MIN_TICK); 20 | assert(tick < TickMath.MAX_TICK); 21 | } 22 | } 23 | -------------------------------------------------------------------------------- /contracts/test/TickMathTest.sol: -------------------------------------------------------------------------------- 1 | // SPDX-License-Identifier: UNLICENSED 2 | pragma solidity =0.7.6; 3 | 4 | import '../libraries/TickMath.sol'; 5 | 6 | contract TickMathTest { 7 | function getSqrtRatioAtTick(int24 tick) external pure returns (uint160) { 8 | return TickMath.getSqrtRatioAtTick(tick); 9 | } 10 | 11 | function getGasCostOfGetSqrtRatioAtTick(int24 tick) external view returns (uint256) { 12 | uint256 gasBefore = gasleft(); 13 | TickMath.getSqrtRatioAtTick(tick); 14 | return gasBefore - gasleft(); 15 | } 16 | 17 | function getTickAtSqrtRatio(uint160 sqrtPriceX96) external pure returns (int24) { 18 | return TickMath.getTickAtSqrtRatio(sqrtPriceX96); 19 | } 20 | 21 | function getGasCostOfGetTickAtSqrtRatio(uint160 sqrtPriceX96) external view returns (uint256) { 22 | uint256 gasBefore = gasleft(); 23 | TickMath.getTickAtSqrtRatio(sqrtPriceX96); 24 | return gasBefore - gasleft(); 25 | } 26 | 27 | function MIN_SQRT_RATIO() external pure returns (uint160) { 28 | return TickMath.MIN_SQRT_RATIO; 29 | } 30 | 31 | function MAX_SQRT_RATIO() external pure returns (uint160) { 32 | return TickMath.MAX_SQRT_RATIO; 33 | } 34 | } 35 | -------------------------------------------------------------------------------- /contracts/test/TickOverflowSafetyEchidnaTest.sol: -------------------------------------------------------------------------------- 1 | // SPDX-License-Identifier: UNLICENSED 2 | pragma solidity =0.7.6; 3 | 4 | import '../libraries/Tick.sol'; 5 | 6 | contract TickOverflowSafetyEchidnaTest { 7 | using Tick for mapping(int24 => Tick.Info); 8 | 9 | int24 private constant MIN_TICK = -16; 10 | int24 private constant MAX_TICK = 16; 11 | uint128 private constant MAX_LIQUIDITY = type(uint128).max / 32; 12 | 13 | mapping(int24 => Tick.Info) private ticks; 14 | int24 private tick = 0; 15 | 16 | // used to track how much total liquidity has been added. should never be negative 17 | int256 totalLiquidity = 0; 18 | // half the cap of fee growth has happened, this can overflow 19 | uint256 private feeGrowthGlobal0X128 = type(uint256).max / 2; 20 | uint256 private feeGrowthGlobal1X128 = type(uint256).max / 2; 21 | // how much total growth has happened, this cannot overflow 22 | uint256 private totalGrowth0 = 0; 23 | uint256 private totalGrowth1 = 0; 24 | 25 | function increaseFeeGrowthGlobal0X128(uint256 amount) external { 26 | require(totalGrowth0 + amount > totalGrowth0); // overflow check 27 | feeGrowthGlobal0X128 += amount; // overflow desired 28 | totalGrowth0 += amount; 29 | } 30 | 31 | function increaseFeeGrowthGlobal1X128(uint256 amount) external { 32 | require(totalGrowth1 + amount > totalGrowth1); // overflow check 33 | feeGrowthGlobal1X128 += amount; // overflow desired 34 | totalGrowth1 += amount; 35 | } 36 | 37 | function setPosition( 38 | int24 tickLower, 39 | int24 tickUpper, 40 | int128 liquidityDelta 41 | ) external { 42 | require(tickLower > MIN_TICK); 43 | require(tickUpper < MAX_TICK); 44 | require(tickLower < tickUpper); 45 | bool flippedLower = 46 | ticks.update( 47 | tickLower, 48 | tick, 49 | liquidityDelta, 50 | feeGrowthGlobal0X128, 51 | feeGrowthGlobal1X128, 52 | 0, 53 | 0, 54 | uint32(block.timestamp), 55 | false, 56 | MAX_LIQUIDITY 57 | ); 58 | bool flippedUpper = 59 | ticks.update( 60 | tickUpper, 61 | tick, 62 | liquidityDelta, 63 | feeGrowthGlobal0X128, 64 | feeGrowthGlobal1X128, 65 | 0, 66 | 0, 67 | uint32(block.timestamp), 68 | true, 69 | MAX_LIQUIDITY 70 | ); 71 | 72 | if (flippedLower) { 73 | if (liquidityDelta < 0) { 74 | assert(ticks[tickLower].liquidityGross == 0); 75 | ticks.clear(tickLower); 76 | } else assert(ticks[tickLower].liquidityGross > 0); 77 | } 78 | 79 | if (flippedUpper) { 80 | if (liquidityDelta < 0) { 81 | assert(ticks[tickUpper].liquidityGross == 0); 82 | ticks.clear(tickUpper); 83 | } else assert(ticks[tickUpper].liquidityGross > 0); 84 | } 85 | 86 | totalLiquidity += liquidityDelta; 87 | // requires should have prevented this 88 | assert(totalLiquidity >= 0); 89 | 90 | if (totalLiquidity == 0) { 91 | totalGrowth0 = 0; 92 | totalGrowth1 = 0; 93 | } 94 | } 95 | 96 | function moveToTick(int24 target) external { 97 | require(target > MIN_TICK); 98 | require(target < MAX_TICK); 99 | while (tick != target) { 100 | if (tick < target) { 101 | if (ticks[tick + 1].liquidityGross > 0) 102 | ticks.cross(tick + 1, feeGrowthGlobal0X128, feeGrowthGlobal1X128, 0, 0, uint32(block.timestamp)); 103 | tick++; 104 | } else { 105 | if (ticks[tick].liquidityGross > 0) 106 | ticks.cross(tick, feeGrowthGlobal0X128, feeGrowthGlobal1X128, 0, 0, uint32(block.timestamp)); 107 | tick--; 108 | } 109 | } 110 | } 111 | } 112 | -------------------------------------------------------------------------------- /contracts/test/TickTest.sol: -------------------------------------------------------------------------------- 1 | // SPDX-License-Identifier: UNLICENSED 2 | pragma solidity =0.7.6; 3 | pragma abicoder v2; 4 | 5 | import '../libraries/Tick.sol'; 6 | 7 | contract TickTest { 8 | using Tick for mapping(int24 => Tick.Info); 9 | 10 | mapping(int24 => Tick.Info) public ticks; 11 | 12 | function tickSpacingToMaxLiquidityPerTick(int24 tickSpacing) external pure returns (uint128) { 13 | return Tick.tickSpacingToMaxLiquidityPerTick(tickSpacing); 14 | } 15 | 16 | function setTick(int24 tick, Tick.Info memory info) external { 17 | ticks[tick] = info; 18 | } 19 | 20 | function getFeeGrowthInside( 21 | int24 tickLower, 22 | int24 tickUpper, 23 | int24 tickCurrent, 24 | uint256 feeGrowthGlobal0X128, 25 | uint256 feeGrowthGlobal1X128 26 | ) external view returns (uint256 feeGrowthInside0X128, uint256 feeGrowthInside1X128) { 27 | return ticks.getFeeGrowthInside(tickLower, tickUpper, tickCurrent, feeGrowthGlobal0X128, feeGrowthGlobal1X128); 28 | } 29 | 30 | function update( 31 | int24 tick, 32 | int24 tickCurrent, 33 | int128 liquidityDelta, 34 | uint256 feeGrowthGlobal0X128, 35 | uint256 feeGrowthGlobal1X128, 36 | uint160 secondsPerLiquidityCumulativeX128, 37 | int56 tickCumulative, 38 | uint32 time, 39 | bool upper, 40 | uint128 maxLiquidity 41 | ) external returns (bool flipped) { 42 | return 43 | ticks.update( 44 | tick, 45 | tickCurrent, 46 | liquidityDelta, 47 | feeGrowthGlobal0X128, 48 | feeGrowthGlobal1X128, 49 | secondsPerLiquidityCumulativeX128, 50 | tickCumulative, 51 | time, 52 | upper, 53 | maxLiquidity 54 | ); 55 | } 56 | 57 | function clear(int24 tick) external { 58 | ticks.clear(tick); 59 | } 60 | 61 | function cross( 62 | int24 tick, 63 | uint256 feeGrowthGlobal0X128, 64 | uint256 feeGrowthGlobal1X128, 65 | uint160 secondsPerLiquidityCumulativeX128, 66 | int56 tickCumulative, 67 | uint32 time 68 | ) external returns (int128 liquidityNet) { 69 | return 70 | ticks.cross( 71 | tick, 72 | feeGrowthGlobal0X128, 73 | feeGrowthGlobal1X128, 74 | secondsPerLiquidityCumulativeX128, 75 | tickCumulative, 76 | time 77 | ); 78 | } 79 | } 80 | -------------------------------------------------------------------------------- /contracts/test/UniswapV3PoolSwapTest.sol: -------------------------------------------------------------------------------- 1 | // SPDX-License-Identifier: UNLICENSED 2 | pragma solidity =0.7.6; 3 | 4 | import '../interfaces/IERC20Minimal.sol'; 5 | 6 | import '../interfaces/callback/IUniswapV3SwapCallback.sol'; 7 | import '../interfaces/IUniswapV3Pool.sol'; 8 | 9 | contract UniswapV3PoolSwapTest is IUniswapV3SwapCallback { 10 | int256 private _amount0Delta; 11 | int256 private _amount1Delta; 12 | 13 | function getSwapResult( 14 | address pool, 15 | bool zeroForOne, 16 | int256 amountSpecified, 17 | uint160 sqrtPriceLimitX96 18 | ) 19 | external 20 | returns ( 21 | int256 amount0Delta, 22 | int256 amount1Delta, 23 | uint160 nextSqrtRatio 24 | ) 25 | { 26 | (amount0Delta, amount1Delta) = IUniswapV3Pool(pool).swap( 27 | address(0), 28 | zeroForOne, 29 | amountSpecified, 30 | sqrtPriceLimitX96, 31 | abi.encode(msg.sender) 32 | ); 33 | 34 | (nextSqrtRatio, , , , , , ) = IUniswapV3Pool(pool).slot0(); 35 | } 36 | 37 | function uniswapV3SwapCallback( 38 | int256 amount0Delta, 39 | int256 amount1Delta, 40 | bytes calldata data 41 | ) external override { 42 | address sender = abi.decode(data, (address)); 43 | 44 | if (amount0Delta > 0) { 45 | IERC20Minimal(IUniswapV3Pool(msg.sender).token0()).transferFrom(sender, msg.sender, uint256(amount0Delta)); 46 | } else if (amount1Delta > 0) { 47 | IERC20Minimal(IUniswapV3Pool(msg.sender).token1()).transferFrom(sender, msg.sender, uint256(amount1Delta)); 48 | } 49 | } 50 | } 51 | -------------------------------------------------------------------------------- /contracts/test/UnsafeMathEchidnaTest.sol: -------------------------------------------------------------------------------- 1 | // SPDX-License-Identifier: UNLICENSED 2 | pragma solidity =0.7.6; 3 | 4 | import '../libraries/UnsafeMath.sol'; 5 | 6 | contract UnsafeMathEchidnaTest { 7 | function checkDivRoundingUp(uint256 x, uint256 d) external pure { 8 | require(d > 0); 9 | uint256 z = UnsafeMath.divRoundingUp(x, d); 10 | uint256 diff = z - (x / d); 11 | if (x % d == 0) { 12 | assert(diff == 0); 13 | } else { 14 | assert(diff == 1); 15 | } 16 | } 17 | } 18 | -------------------------------------------------------------------------------- /echidna.config.yml: -------------------------------------------------------------------------------- 1 | #format can be "text" or "json" for different output (human or machine readable) 2 | format: 'text' 3 | #checkAsserts checks assertions 4 | checkAsserts: true 5 | #coverage controls coverage guided testing 6 | coverage: false 7 | # #psender is the sender for property transactions; by default intentionally 8 | # #the same as contract deployer 9 | # psender: "0x00a329c0648769a73afac7f9381e08fb43dbea70" 10 | # #prefix is the prefix for Boolean functions that are properties to be checked 11 | # prefix: "echidna_" 12 | # #propMaxGas defines gas cost at which a property fails 13 | # propMaxGas: 8000030 14 | # #testMaxGas is a gas limit; does not cause failure, but terminates sequence 15 | # testMaxGas: 8000030 16 | # #maxGasprice is the maximum gas price 17 | # maxGasprice: 100000000000 18 | # #testLimit is the number of test sequences to run 19 | # testLimit: 50000 20 | # #stopOnFail makes echidna terminate as soon as any property fails and has been shrunk 21 | # stopOnFail: false 22 | # #estimateGas makes echidna perform analysis of maximum gas costs for functions (experimental) 23 | # estimateGas: false 24 | # #seqLen defines how many transactions are in a test sequence 25 | # seqLen: 100 26 | # #shrinkLimit determines how much effort is spent shrinking failing sequences 27 | # shrinkLimit: 5000 28 | # #contractAddr is the address of the contract itself 29 | # contractAddr: "0x00a329c0648769a73afac7f9381e08fb43dbea72" 30 | # #deployer is address of the contract deployer (who often is privileged owner, etc.) 31 | # deployer: "0x00a329c0648769a73afac7f9381e08fb43dbea70" 32 | # #sender is set of addresses transactions may originate from 33 | # sender: ["0x10000", "0x20000", "0x00a329c0648769a73afac7f9381e08fb43dbea70"] 34 | # #balanceAddr is default balance for addresses 35 | # balanceAddr: 0xffffffff 36 | # #balanceContract overrides balanceAddr for the contract address 37 | # balanceContract: 0 38 | # #solcArgs allows special args to solc 39 | # solcArgs: "" 40 | # #solcLibs is solc libraries 41 | # solcLibs: [] 42 | # #cryticArgs allows special args to crytic 43 | # cryticArgs: [] 44 | # #quiet produces (much) less verbose output 45 | # quiet: false 46 | # #initialize the blockchain with some data 47 | # initialize: null 48 | # #whether ot not to use the multi-abi mode of testing 49 | # multi-abi: false 50 | # #benchmarkMode enables benchmark mode 51 | # benchmarkMode: false 52 | # #timeout controls test timeout settings 53 | # timeout: null 54 | # #seed not defined by default, is the random seed 55 | # #seed: 0 56 | # #dictFreq controls how often to use echidna's internal dictionary vs random 57 | # #values 58 | # dictFreq: 0.40 59 | # maxTimeDelay: 604800 60 | # #maximum time between generated txs; default is one week 61 | # maxBlockDelay: 60480 62 | # #maximum number of blocks elapsed between generated txs; default is expected increment in one week 63 | # # timeout: 64 | # #campaign timeout (in seconds) 65 | # # list of methods to filter 66 | # filterFunctions: [] 67 | # # by default, blacklist methods in filterFunctions 68 | # filterBlacklist: true 69 | # #directory to save the corpus; by default is disabled 70 | # corpusDir: null 71 | # # constants for corpus mutations (for experimentation only) 72 | # mutConsts: [100, 1, 1] 73 | # # maximum value to send to payable functions 74 | # maxValue: 100000000000000000000 # 100 eth 75 | -------------------------------------------------------------------------------- /hardhat.config.ts: -------------------------------------------------------------------------------- 1 | import 'hardhat-typechain' 2 | import '@nomiclabs/hardhat-ethers' 3 | import '@nomiclabs/hardhat-waffle' 4 | import '@nomiclabs/hardhat-etherscan' 5 | 6 | export default { 7 | networks: { 8 | hardhat: { 9 | allowUnlimitedContractSize: false, 10 | }, 11 | mainnet: { 12 | url: `https://mainnet.infura.io/v3/${process.env.INFURA_API_KEY}`, 13 | }, 14 | ropsten: { 15 | url: `https://ropsten.infura.io/v3/${process.env.INFURA_API_KEY}`, 16 | }, 17 | rinkeby: { 18 | url: `https://rinkeby.infura.io/v3/${process.env.INFURA_API_KEY}`, 19 | }, 20 | goerli: { 21 | url: `https://goerli.infura.io/v3/${process.env.INFURA_API_KEY}`, 22 | }, 23 | kovan: { 24 | url: `https://kovan.infura.io/v3/${process.env.INFURA_API_KEY}`, 25 | }, 26 | arbitrumRinkeby: { 27 | url: `https://arbitrum-rinkeby.infura.io/v3/${process.env.INFURA_API_KEY}`, 28 | }, 29 | arbitrum: { 30 | url: `https://arbitrum-mainnet.infura.io/v3/${process.env.INFURA_API_KEY}`, 31 | }, 32 | optimismKovan: { 33 | url: `https://optimism-kovan.infura.io/v3/${process.env.INFURA_API_KEY}`, 34 | }, 35 | optimism: { 36 | url: `https://optimism-mainnet.infura.io/v3/${process.env.INFURA_API_KEY}`, 37 | }, 38 | mumbai: { 39 | url: `https://polygon-mumbai.infura.io/v3/${process.env.INFURA_API_KEY}`, 40 | }, 41 | polygon: { 42 | url: `https://polygon-mainnet.infura.io/v3/${process.env.INFURA_API_KEY}`, 43 | }, 44 | bnb: { 45 | url: `https://bsc-dataseed.binance.org/`, 46 | }, 47 | }, 48 | etherscan: { 49 | // Your API key for Etherscan 50 | // Obtain one at https://etherscan.io/ 51 | apiKey: process.env.ETHERSCAN_API_KEY, 52 | }, 53 | solidity: { 54 | version: '0.7.6', 55 | settings: { 56 | optimizer: { 57 | enabled: true, 58 | runs: 800, 59 | }, 60 | metadata: { 61 | // do not include the metadata hash, since this is machine dependent 62 | // and we want all generated code to be deterministic 63 | // https://docs.soliditylang.org/en/v0.7.6/metadata.html 64 | bytecodeHash: 'none', 65 | }, 66 | }, 67 | }, 68 | } 69 | -------------------------------------------------------------------------------- /package.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "@uniswap/v3-core", 3 | "description": "🦄 Core smart contracts of Uniswap V3", 4 | "license": "BUSL-1.1", 5 | "publishConfig": { 6 | "access": "public" 7 | }, 8 | "version": "1.0.1", 9 | "homepage": "https://uniswap.org", 10 | "keywords": [ 11 | "uniswap", 12 | "core", 13 | "v3" 14 | ], 15 | "repository": { 16 | "type": "git", 17 | "url": "https://github.com/Uniswap/uniswap-v3-core" 18 | }, 19 | "files": [ 20 | "contracts/interfaces", 21 | "contracts/libraries", 22 | "artifacts/contracts/UniswapV3Factory.sol/UniswapV3Factory.json", 23 | "artifacts/contracts/UniswapV3Pool.sol/UniswapV3Pool.json", 24 | "artifacts/contracts/interfaces/**/*.json", 25 | "!artifacts/contracts/interfaces/**/*.dbg.json" 26 | ], 27 | "engines": { 28 | "node": ">=10" 29 | }, 30 | "dependencies": {}, 31 | "devDependencies": { 32 | "@nomiclabs/hardhat-ethers": "^2.0.2", 33 | "@nomiclabs/hardhat-etherscan": "^2.1.8", 34 | "@nomiclabs/hardhat-waffle": "^2.0.1", 35 | "@typechain/ethers-v5": "^4.0.0", 36 | "@types/chai": "^4.2.6", 37 | "@types/mocha": "^5.2.7", 38 | "chai": "^4.2.0", 39 | "decimal.js": "^10.2.1", 40 | "ethereum-waffle": "^3.0.2", 41 | "ethers": "^5.0.8", 42 | "hardhat": "^2.2.0", 43 | "hardhat-typechain": "^0.3.5", 44 | "mocha": "^6.2.2", 45 | "mocha-chai-jest-snapshot": "^1.1.0", 46 | "prettier": "^2.0.5", 47 | "prettier-plugin-solidity": "^1.0.0-alpha.59", 48 | "solhint": "^3.2.1", 49 | "solhint-plugin-prettier": "^0.0.5", 50 | "ts-generator": "^0.1.1", 51 | "ts-node": "^8.5.4", 52 | "typechain": "^4.0.0", 53 | "typescript": "^3.7.3" 54 | }, 55 | "scripts": { 56 | "compile": "hardhat compile", 57 | "test": "hardhat test" 58 | } 59 | } 60 | -------------------------------------------------------------------------------- /test/BitMath.spec.ts: -------------------------------------------------------------------------------- 1 | import { expect } from './shared/expect' 2 | import { BitMathTest } from '../typechain/BitMathTest' 3 | import { ethers, waffle } from 'hardhat' 4 | import snapshotGasCost from './shared/snapshotGasCost' 5 | 6 | const { BigNumber } = ethers 7 | 8 | describe('BitMath', () => { 9 | let bitMath: BitMathTest 10 | const fixture = async () => { 11 | const factory = await ethers.getContractFactory('BitMathTest') 12 | return (await factory.deploy()) as BitMathTest 13 | } 14 | beforeEach('deploy BitMathTest', async () => { 15 | bitMath = await waffle.loadFixture(fixture) 16 | }) 17 | 18 | describe('#mostSignificantBit', () => { 19 | it('0', async () => { 20 | await expect(bitMath.mostSignificantBit(0)).to.be.reverted 21 | }) 22 | it('1', async () => { 23 | expect(await bitMath.mostSignificantBit(1)).to.eq(0) 24 | }) 25 | it('2', async () => { 26 | expect(await bitMath.mostSignificantBit(2)).to.eq(1) 27 | }) 28 | it('all powers of 2', async () => { 29 | const results = await Promise.all( 30 | [...Array(255)].map((_, i) => bitMath.mostSignificantBit(BigNumber.from(2).pow(i))) 31 | ) 32 | expect(results).to.deep.eq([...Array(255)].map((_, i) => i)) 33 | }) 34 | it('uint256(-1)', async () => { 35 | expect(await bitMath.mostSignificantBit(BigNumber.from(2).pow(256).sub(1))).to.eq(255) 36 | }) 37 | 38 | it('gas cost of smaller number', async () => { 39 | await snapshotGasCost(bitMath.getGasCostOfMostSignificantBit(BigNumber.from(3568))) 40 | }) 41 | it('gas cost of max uint128', async () => { 42 | await snapshotGasCost(bitMath.getGasCostOfMostSignificantBit(BigNumber.from(2).pow(128).sub(1))) 43 | }) 44 | it('gas cost of max uint256', async () => { 45 | await snapshotGasCost(bitMath.getGasCostOfMostSignificantBit(BigNumber.from(2).pow(256).sub(1))) 46 | }) 47 | }) 48 | 49 | describe('#leastSignificantBit', () => { 50 | it('0', async () => { 51 | await expect(bitMath.leastSignificantBit(0)).to.be.reverted 52 | }) 53 | it('1', async () => { 54 | expect(await bitMath.leastSignificantBit(1)).to.eq(0) 55 | }) 56 | it('2', async () => { 57 | expect(await bitMath.leastSignificantBit(2)).to.eq(1) 58 | }) 59 | it('all powers of 2', async () => { 60 | const results = await Promise.all( 61 | [...Array(255)].map((_, i) => bitMath.leastSignificantBit(BigNumber.from(2).pow(i))) 62 | ) 63 | expect(results).to.deep.eq([...Array(255)].map((_, i) => i)) 64 | }) 65 | it('uint256(-1)', async () => { 66 | expect(await bitMath.leastSignificantBit(BigNumber.from(2).pow(256).sub(1))).to.eq(0) 67 | }) 68 | 69 | it('gas cost of smaller number', async () => { 70 | await snapshotGasCost(bitMath.getGasCostOfLeastSignificantBit(BigNumber.from(3568))) 71 | }) 72 | it('gas cost of max uint128', async () => { 73 | await snapshotGasCost(bitMath.getGasCostOfLeastSignificantBit(BigNumber.from(2).pow(128).sub(1))) 74 | }) 75 | it('gas cost of max uint256', async () => { 76 | await snapshotGasCost(bitMath.getGasCostOfLeastSignificantBit(BigNumber.from(2).pow(256).sub(1))) 77 | }) 78 | }) 79 | }) 80 | -------------------------------------------------------------------------------- /test/FullMath.spec.ts: -------------------------------------------------------------------------------- 1 | import { ethers } from 'hardhat' 2 | import { FullMathTest } from '../typechain/FullMathTest' 3 | import { expect } from './shared/expect' 4 | import { Decimal } from 'decimal.js' 5 | 6 | const { 7 | BigNumber, 8 | constants: { MaxUint256 }, 9 | } = ethers 10 | const Q128 = BigNumber.from(2).pow(128) 11 | 12 | Decimal.config({ toExpNeg: -500, toExpPos: 500 }) 13 | 14 | describe('FullMath', () => { 15 | let fullMath: FullMathTest 16 | before('deploy FullMathTest', async () => { 17 | const factory = await ethers.getContractFactory('FullMathTest') 18 | fullMath = (await factory.deploy()) as FullMathTest 19 | }) 20 | 21 | describe('#mulDiv', () => { 22 | it('reverts if denominator is 0', async () => { 23 | await expect(fullMath.mulDiv(Q128, 5, 0)).to.be.reverted 24 | }) 25 | it('reverts if denominator is 0 and numerator overflows', async () => { 26 | await expect(fullMath.mulDiv(Q128, Q128, 0)).to.be.reverted 27 | }) 28 | it('reverts if output overflows uint256', async () => { 29 | await expect(fullMath.mulDiv(Q128, Q128, 1)).to.be.reverted 30 | }) 31 | it('reverts if output overflows uint256', async () => { 32 | await expect(fullMath.mulDiv(Q128, Q128, 1)).to.be.reverted 33 | }) 34 | it('reverts on overflow with all max inputs', async () => { 35 | await expect(fullMath.mulDiv(MaxUint256, MaxUint256, MaxUint256.sub(1))).to.be.reverted 36 | }) 37 | 38 | it('all max inputs', async () => { 39 | expect(await fullMath.mulDiv(MaxUint256, MaxUint256, MaxUint256)).to.eq(MaxUint256) 40 | }) 41 | 42 | it('accurate without phantom overflow', async () => { 43 | const result = Q128.div(3) 44 | expect( 45 | await fullMath.mulDiv( 46 | Q128, 47 | /*0.5=*/ BigNumber.from(50).mul(Q128).div(100), 48 | /*1.5=*/ BigNumber.from(150).mul(Q128).div(100) 49 | ) 50 | ).to.eq(result) 51 | }) 52 | 53 | it('accurate with phantom overflow', async () => { 54 | const result = BigNumber.from(4375).mul(Q128).div(1000) 55 | expect(await fullMath.mulDiv(Q128, BigNumber.from(35).mul(Q128), BigNumber.from(8).mul(Q128))).to.eq(result) 56 | }) 57 | 58 | it('accurate with phantom overflow and repeating decimal', async () => { 59 | const result = BigNumber.from(1).mul(Q128).div(3) 60 | expect(await fullMath.mulDiv(Q128, BigNumber.from(1000).mul(Q128), BigNumber.from(3000).mul(Q128))).to.eq(result) 61 | }) 62 | }) 63 | 64 | describe('#mulDivRoundingUp', () => { 65 | it('reverts if denominator is 0', async () => { 66 | await expect(fullMath.mulDivRoundingUp(Q128, 5, 0)).to.be.reverted 67 | }) 68 | it('reverts if denominator is 0 and numerator overflows', async () => { 69 | await expect(fullMath.mulDivRoundingUp(Q128, Q128, 0)).to.be.reverted 70 | }) 71 | it('reverts if output overflows uint256', async () => { 72 | await expect(fullMath.mulDivRoundingUp(Q128, Q128, 1)).to.be.reverted 73 | }) 74 | it('reverts on overflow with all max inputs', async () => { 75 | await expect(fullMath.mulDivRoundingUp(MaxUint256, MaxUint256, MaxUint256.sub(1))).to.be.reverted 76 | }) 77 | 78 | it('reverts if mulDiv overflows 256 bits after rounding up', async () => { 79 | await expect( 80 | fullMath.mulDivRoundingUp( 81 | '535006138814359', 82 | '432862656469423142931042426214547535783388063929571229938474969', 83 | '2' 84 | ) 85 | ).to.be.reverted 86 | }) 87 | 88 | it('reverts if mulDiv overflows 256 bits after rounding up case 2', async () => { 89 | await expect( 90 | fullMath.mulDivRoundingUp( 91 | '115792089237316195423570985008687907853269984659341747863450311749907997002549', 92 | '115792089237316195423570985008687907853269984659341747863450311749907997002550', 93 | '115792089237316195423570985008687907853269984653042931687443039491902864365164' 94 | ) 95 | ).to.be.reverted 96 | }) 97 | 98 | it('all max inputs', async () => { 99 | expect(await fullMath.mulDivRoundingUp(MaxUint256, MaxUint256, MaxUint256)).to.eq(MaxUint256) 100 | }) 101 | 102 | it('accurate without phantom overflow', async () => { 103 | const result = Q128.div(3).add(1) 104 | expect( 105 | await fullMath.mulDivRoundingUp( 106 | Q128, 107 | /*0.5=*/ BigNumber.from(50).mul(Q128).div(100), 108 | /*1.5=*/ BigNumber.from(150).mul(Q128).div(100) 109 | ) 110 | ).to.eq(result) 111 | }) 112 | 113 | it('accurate with phantom overflow', async () => { 114 | const result = BigNumber.from(4375).mul(Q128).div(1000) 115 | expect(await fullMath.mulDivRoundingUp(Q128, BigNumber.from(35).mul(Q128), BigNumber.from(8).mul(Q128))).to.eq( 116 | result 117 | ) 118 | }) 119 | 120 | it('accurate with phantom overflow and repeating decimal', async () => { 121 | const result = BigNumber.from(1).mul(Q128).div(3).add(1) 122 | expect( 123 | await fullMath.mulDivRoundingUp(Q128, BigNumber.from(1000).mul(Q128), BigNumber.from(3000).mul(Q128)) 124 | ).to.eq(result) 125 | }) 126 | }) 127 | 128 | function pseudoRandomBigNumber() { 129 | return BigNumber.from(new Decimal(MaxUint256.toString()).mul(Math.random().toString()).round().toString()) 130 | } 131 | 132 | // tiny fuzzer. unskip to run 133 | it.skip('check a bunch of random inputs against JS implementation', async () => { 134 | // generates random inputs 135 | const tests = Array(1_000) 136 | .fill(null) 137 | .map(() => { 138 | return { 139 | x: pseudoRandomBigNumber(), 140 | y: pseudoRandomBigNumber(), 141 | d: pseudoRandomBigNumber(), 142 | } 143 | }) 144 | .map(({ x, y, d }) => { 145 | return { 146 | input: { 147 | x, 148 | y, 149 | d, 150 | }, 151 | floored: fullMath.mulDiv(x, y, d), 152 | ceiled: fullMath.mulDivRoundingUp(x, y, d), 153 | } 154 | }) 155 | 156 | await Promise.all( 157 | tests.map(async ({ input: { x, y, d }, floored, ceiled }) => { 158 | if (d.eq(0)) { 159 | await expect(floored).to.be.reverted 160 | await expect(ceiled).to.be.reverted 161 | return 162 | } 163 | 164 | if (x.eq(0) || y.eq(0)) { 165 | await expect(floored).to.eq(0) 166 | await expect(ceiled).to.eq(0) 167 | } else if (x.mul(y).div(d).gt(MaxUint256)) { 168 | await expect(floored).to.be.reverted 169 | await expect(ceiled).to.be.reverted 170 | } else { 171 | expect(await floored).to.eq(x.mul(y).div(d)) 172 | expect(await ceiled).to.eq( 173 | x 174 | .mul(y) 175 | .div(d) 176 | .add(x.mul(y).mod(d).gt(0) ? 1 : 0) 177 | ) 178 | } 179 | }) 180 | ) 181 | }) 182 | }) 183 | -------------------------------------------------------------------------------- /test/LiquidityMath.spec.ts: -------------------------------------------------------------------------------- 1 | import { expect } from './shared/expect' 2 | import { LiquidityMathTest } from '../typechain/LiquidityMathTest' 3 | import { ethers, waffle } from 'hardhat' 4 | import snapshotGasCost from './shared/snapshotGasCost' 5 | 6 | const { BigNumber } = ethers 7 | 8 | describe('LiquidityMath', () => { 9 | let liquidityMath: LiquidityMathTest 10 | const fixture = async () => { 11 | const factory = await ethers.getContractFactory('LiquidityMathTest') 12 | return (await factory.deploy()) as LiquidityMathTest 13 | } 14 | beforeEach('deploy LiquidityMathTest', async () => { 15 | liquidityMath = await waffle.loadFixture(fixture) 16 | }) 17 | 18 | describe('#addDelta', () => { 19 | it('1 + 0', async () => { 20 | expect(await liquidityMath.addDelta(1, 0)).to.eq(1) 21 | }) 22 | it('1 + -1', async () => { 23 | expect(await liquidityMath.addDelta(1, -1)).to.eq(0) 24 | }) 25 | it('1 + 1', async () => { 26 | expect(await liquidityMath.addDelta(1, 1)).to.eq(2) 27 | }) 28 | it('2**128-15 + 15 overflows', async () => { 29 | await expect(liquidityMath.addDelta(BigNumber.from(2).pow(128).sub(15), 15)).to.be.revertedWith('LA') 30 | }) 31 | it('0 + -1 underflows', async () => { 32 | await expect(liquidityMath.addDelta(0, -1)).to.be.revertedWith('LS') 33 | }) 34 | it('3 + -4 underflows', async () => { 35 | await expect(liquidityMath.addDelta(3, -4)).to.be.revertedWith('LS') 36 | }) 37 | it('gas add', async () => { 38 | await snapshotGasCost(liquidityMath.getGasCostOfAddDelta(15, 4)) 39 | }) 40 | it('gas sub', async () => { 41 | await snapshotGasCost(liquidityMath.getGasCostOfAddDelta(15, -4)) 42 | }) 43 | }) 44 | }) 45 | -------------------------------------------------------------------------------- /test/NoDelegateCall.spec.ts: -------------------------------------------------------------------------------- 1 | import { Wallet } from 'ethers' 2 | import { ethers, waffle } from 'hardhat' 3 | import { NoDelegateCallTest } from '../typechain/NoDelegateCallTest' 4 | import { expect } from './shared/expect' 5 | import snapshotGasCost from './shared/snapshotGasCost' 6 | 7 | describe('NoDelegateCall', () => { 8 | let wallet: Wallet, other: Wallet 9 | 10 | let loadFixture: ReturnType 11 | before('create fixture loader', async () => { 12 | ;[wallet, other] = await (ethers as any).getSigners() 13 | loadFixture = waffle.createFixtureLoader([wallet, other]) 14 | }) 15 | 16 | const noDelegateCallFixture = async () => { 17 | const noDelegateCallTestFactory = await ethers.getContractFactory('NoDelegateCallTest') 18 | const noDelegateCallTest = (await noDelegateCallTestFactory.deploy()) as NoDelegateCallTest 19 | const minimalProxyFactory = new ethers.ContractFactory( 20 | noDelegateCallTestFactory.interface, 21 | `3d602d80600a3d3981f3363d3d373d3d3d363d73${noDelegateCallTest.address.slice(2)}5af43d82803e903d91602b57fd5bf3`, 22 | wallet 23 | ) 24 | const proxy = (await minimalProxyFactory.deploy()) as NoDelegateCallTest 25 | return { noDelegateCallTest, proxy } 26 | } 27 | 28 | let base: NoDelegateCallTest 29 | let proxy: NoDelegateCallTest 30 | 31 | beforeEach('deploy test contracts', async () => { 32 | ;({ noDelegateCallTest: base, proxy } = await loadFixture(noDelegateCallFixture)) 33 | }) 34 | 35 | it('runtime overhead', async () => { 36 | await snapshotGasCost( 37 | (await base.getGasCostOfCannotBeDelegateCalled()).sub(await base.getGasCostOfCanBeDelegateCalled()) 38 | ) 39 | }) 40 | 41 | it('proxy can call the method without the modifier', async () => { 42 | await proxy.canBeDelegateCalled() 43 | }) 44 | it('proxy cannot call the method with the modifier', async () => { 45 | await expect(proxy.cannotBeDelegateCalled()).to.be.reverted 46 | }) 47 | 48 | it('can call the method that calls into a private method with the modifier', async () => { 49 | await base.callsIntoNoDelegateCallFunction() 50 | }) 51 | it('proxy cannot call the method that calls a private method with the modifier', async () => { 52 | await expect(proxy.callsIntoNoDelegateCallFunction()).to.be.reverted 53 | }) 54 | }) 55 | -------------------------------------------------------------------------------- /test/TickMath.spec.ts: -------------------------------------------------------------------------------- 1 | import { BigNumber } from 'ethers' 2 | import { ethers } from 'hardhat' 3 | import { TickMathTest } from '../typechain/TickMathTest' 4 | import { expect } from './shared/expect' 5 | import snapshotGasCost from './shared/snapshotGasCost' 6 | import { encodePriceSqrt, MIN_SQRT_RATIO, MAX_SQRT_RATIO } from './shared/utilities' 7 | import Decimal from 'decimal.js' 8 | 9 | const MIN_TICK = -887272 10 | const MAX_TICK = 887272 11 | 12 | Decimal.config({ toExpNeg: -500, toExpPos: 500 }) 13 | 14 | describe('TickMath', () => { 15 | let tickMath: TickMathTest 16 | 17 | before('deploy TickMathTest', async () => { 18 | const factory = await ethers.getContractFactory('TickMathTest') 19 | tickMath = (await factory.deploy()) as TickMathTest 20 | }) 21 | 22 | describe('#getSqrtRatioAtTick', () => { 23 | it('throws for too low', async () => { 24 | await expect(tickMath.getSqrtRatioAtTick(MIN_TICK - 1)).to.be.revertedWith('T') 25 | }) 26 | 27 | it('throws for too low', async () => { 28 | await expect(tickMath.getSqrtRatioAtTick(MAX_TICK + 1)).to.be.revertedWith('T') 29 | }) 30 | 31 | it('min tick', async () => { 32 | expect(await tickMath.getSqrtRatioAtTick(MIN_TICK)).to.eq('4295128739') 33 | }) 34 | 35 | it('min tick +1', async () => { 36 | expect(await tickMath.getSqrtRatioAtTick(MIN_TICK + 1)).to.eq('4295343490') 37 | }) 38 | 39 | it('max tick - 1', async () => { 40 | expect(await tickMath.getSqrtRatioAtTick(MAX_TICK - 1)).to.eq('1461373636630004318706518188784493106690254656249') 41 | }) 42 | 43 | it('min tick ratio is less than js implementation', async () => { 44 | expect(await tickMath.getSqrtRatioAtTick(MIN_TICK)).to.be.lt(encodePriceSqrt(1, BigNumber.from(2).pow(127))) 45 | }) 46 | 47 | it('max tick ratio is greater than js implementation', async () => { 48 | expect(await tickMath.getSqrtRatioAtTick(MAX_TICK)).to.be.gt(encodePriceSqrt(BigNumber.from(2).pow(127), 1)) 49 | }) 50 | 51 | it('max tick', async () => { 52 | expect(await tickMath.getSqrtRatioAtTick(MAX_TICK)).to.eq('1461446703485210103287273052203988822378723970342') 53 | }) 54 | 55 | for (const absTick of [ 56 | 50, 57 | 100, 58 | 250, 59 | 500, 60 | 1_000, 61 | 2_500, 62 | 3_000, 63 | 4_000, 64 | 5_000, 65 | 50_000, 66 | 150_000, 67 | 250_000, 68 | 500_000, 69 | 738_203, 70 | ]) { 71 | for (const tick of [-absTick, absTick]) { 72 | describe(`tick ${tick}`, () => { 73 | it('is at most off by 1/100th of a bips', async () => { 74 | const jsResult = new Decimal(1.0001).pow(tick).sqrt().mul(new Decimal(2).pow(96)) 75 | const result = await tickMath.getSqrtRatioAtTick(tick) 76 | const absDiff = new Decimal(result.toString()).sub(jsResult).abs() 77 | expect(absDiff.div(jsResult).toNumber()).to.be.lt(0.000001) 78 | }) 79 | it('result', async () => { 80 | expect((await tickMath.getSqrtRatioAtTick(tick)).toString()).to.matchSnapshot() 81 | }) 82 | it('gas', async () => { 83 | await snapshotGasCost(tickMath.getGasCostOfGetSqrtRatioAtTick(tick)) 84 | }) 85 | }) 86 | } 87 | } 88 | }) 89 | 90 | describe('#MIN_SQRT_RATIO', async () => { 91 | it('equals #getSqrtRatioAtTick(MIN_TICK)', async () => { 92 | const min = await tickMath.getSqrtRatioAtTick(MIN_TICK) 93 | expect(min).to.eq(await tickMath.MIN_SQRT_RATIO()) 94 | expect(min).to.eq(MIN_SQRT_RATIO) 95 | }) 96 | }) 97 | 98 | describe('#MAX_SQRT_RATIO', async () => { 99 | it('equals #getSqrtRatioAtTick(MAX_TICK)', async () => { 100 | const max = await tickMath.getSqrtRatioAtTick(MAX_TICK) 101 | expect(max).to.eq(await tickMath.MAX_SQRT_RATIO()) 102 | expect(max).to.eq(MAX_SQRT_RATIO) 103 | }) 104 | }) 105 | 106 | describe('#getTickAtSqrtRatio', () => { 107 | it('throws for too low', async () => { 108 | await expect(tickMath.getTickAtSqrtRatio(MIN_SQRT_RATIO.sub(1))).to.be.revertedWith('R') 109 | }) 110 | 111 | it('throws for too high', async () => { 112 | await expect(tickMath.getTickAtSqrtRatio(BigNumber.from(MAX_SQRT_RATIO))).to.be.revertedWith('R') 113 | }) 114 | 115 | it('ratio of min tick', async () => { 116 | expect(await tickMath.getTickAtSqrtRatio(MIN_SQRT_RATIO)).to.eq(MIN_TICK) 117 | }) 118 | it('ratio of min tick + 1', async () => { 119 | expect(await tickMath.getTickAtSqrtRatio('4295343490')).to.eq(MIN_TICK + 1) 120 | }) 121 | it('ratio of max tick - 1', async () => { 122 | expect(await tickMath.getTickAtSqrtRatio('1461373636630004318706518188784493106690254656249')).to.eq(MAX_TICK - 1) 123 | }) 124 | it('ratio closest to max tick', async () => { 125 | expect(await tickMath.getTickAtSqrtRatio(MAX_SQRT_RATIO.sub(1))).to.eq(MAX_TICK - 1) 126 | }) 127 | 128 | for (const ratio of [ 129 | MIN_SQRT_RATIO, 130 | encodePriceSqrt(BigNumber.from(10).pow(12), 1), 131 | encodePriceSqrt(BigNumber.from(10).pow(6), 1), 132 | encodePriceSqrt(1, 64), 133 | encodePriceSqrt(1, 8), 134 | encodePriceSqrt(1, 2), 135 | encodePriceSqrt(1, 1), 136 | encodePriceSqrt(2, 1), 137 | encodePriceSqrt(8, 1), 138 | encodePriceSqrt(64, 1), 139 | encodePriceSqrt(1, BigNumber.from(10).pow(6)), 140 | encodePriceSqrt(1, BigNumber.from(10).pow(12)), 141 | MAX_SQRT_RATIO.sub(1), 142 | ]) { 143 | describe(`ratio ${ratio}`, () => { 144 | it('is at most off by 1', async () => { 145 | const jsResult = new Decimal(ratio.toString()).div(new Decimal(2).pow(96)).pow(2).log(1.0001).floor() 146 | const result = await tickMath.getTickAtSqrtRatio(ratio) 147 | const absDiff = new Decimal(result.toString()).sub(jsResult).abs() 148 | expect(absDiff.toNumber()).to.be.lte(1) 149 | }) 150 | it('ratio is between the tick and tick+1', async () => { 151 | const tick = await tickMath.getTickAtSqrtRatio(ratio) 152 | const ratioOfTick = await tickMath.getSqrtRatioAtTick(tick) 153 | const ratioOfTickPlusOne = await tickMath.getSqrtRatioAtTick(tick + 1) 154 | expect(ratio).to.be.gte(ratioOfTick) 155 | expect(ratio).to.be.lt(ratioOfTickPlusOne) 156 | }) 157 | it('result', async () => { 158 | expect(await tickMath.getTickAtSqrtRatio(ratio)).to.matchSnapshot() 159 | }) 160 | it('gas', async () => { 161 | await snapshotGasCost(tickMath.getGasCostOfGetTickAtSqrtRatio(ratio)) 162 | }) 163 | }) 164 | } 165 | }) 166 | }) 167 | -------------------------------------------------------------------------------- /test/UniswapV3Factory.spec.ts: -------------------------------------------------------------------------------- 1 | import { Wallet } from 'ethers' 2 | import { ethers, waffle } from 'hardhat' 3 | import { UniswapV3Factory } from '../typechain/UniswapV3Factory' 4 | import { expect } from './shared/expect' 5 | import snapshotGasCost from './shared/snapshotGasCost' 6 | 7 | import { FeeAmount, getCreate2Address, TICK_SPACINGS } from './shared/utilities' 8 | 9 | const { constants } = ethers 10 | 11 | const TEST_ADDRESSES: [string, string] = [ 12 | '0x1000000000000000000000000000000000000000', 13 | '0x2000000000000000000000000000000000000000', 14 | ] 15 | 16 | const createFixtureLoader = waffle.createFixtureLoader 17 | 18 | describe('UniswapV3Factory', () => { 19 | let wallet: Wallet, other: Wallet 20 | 21 | let factory: UniswapV3Factory 22 | let poolBytecode: string 23 | const fixture = async () => { 24 | const factoryFactory = await ethers.getContractFactory('UniswapV3Factory') 25 | return (await factoryFactory.deploy()) as UniswapV3Factory 26 | } 27 | 28 | let loadFixture: ReturnType 29 | before('create fixture loader', async () => { 30 | ;[wallet, other] = await (ethers as any).getSigners() 31 | 32 | loadFixture = createFixtureLoader([wallet, other]) 33 | }) 34 | 35 | before('load pool bytecode', async () => { 36 | poolBytecode = (await ethers.getContractFactory('UniswapV3Pool')).bytecode 37 | }) 38 | 39 | beforeEach('deploy factory', async () => { 40 | factory = await loadFixture(fixture) 41 | }) 42 | 43 | it('owner is deployer', async () => { 44 | expect(await factory.owner()).to.eq(wallet.address) 45 | }) 46 | 47 | it('factory bytecode size', async () => { 48 | expect(((await waffle.provider.getCode(factory.address)).length - 2) / 2).to.matchSnapshot() 49 | }) 50 | 51 | it('pool bytecode size', async () => { 52 | await factory.createPool(TEST_ADDRESSES[0], TEST_ADDRESSES[1], FeeAmount.MEDIUM) 53 | const poolAddress = getCreate2Address(factory.address, TEST_ADDRESSES, FeeAmount.MEDIUM, poolBytecode) 54 | expect(((await waffle.provider.getCode(poolAddress)).length - 2) / 2).to.matchSnapshot() 55 | }) 56 | 57 | it('initial enabled fee amounts', async () => { 58 | expect(await factory.feeAmountTickSpacing(FeeAmount.LOW)).to.eq(TICK_SPACINGS[FeeAmount.LOW]) 59 | expect(await factory.feeAmountTickSpacing(FeeAmount.MEDIUM)).to.eq(TICK_SPACINGS[FeeAmount.MEDIUM]) 60 | expect(await factory.feeAmountTickSpacing(FeeAmount.HIGH)).to.eq(TICK_SPACINGS[FeeAmount.HIGH]) 61 | }) 62 | 63 | async function createAndCheckPool( 64 | tokens: [string, string], 65 | feeAmount: FeeAmount, 66 | tickSpacing: number = TICK_SPACINGS[feeAmount] 67 | ) { 68 | const create2Address = getCreate2Address(factory.address, tokens, feeAmount, poolBytecode) 69 | const create = factory.createPool(tokens[0], tokens[1], feeAmount) 70 | 71 | await expect(create) 72 | .to.emit(factory, 'PoolCreated') 73 | .withArgs(TEST_ADDRESSES[0], TEST_ADDRESSES[1], feeAmount, tickSpacing, create2Address) 74 | 75 | await expect(factory.createPool(tokens[0], tokens[1], feeAmount)).to.be.reverted 76 | await expect(factory.createPool(tokens[1], tokens[0], feeAmount)).to.be.reverted 77 | expect(await factory.getPool(tokens[0], tokens[1], feeAmount), 'getPool in order').to.eq(create2Address) 78 | expect(await factory.getPool(tokens[1], tokens[0], feeAmount), 'getPool in reverse').to.eq(create2Address) 79 | 80 | const poolContractFactory = await ethers.getContractFactory('UniswapV3Pool') 81 | const pool = poolContractFactory.attach(create2Address) 82 | expect(await pool.factory(), 'pool factory address').to.eq(factory.address) 83 | expect(await pool.token0(), 'pool token0').to.eq(TEST_ADDRESSES[0]) 84 | expect(await pool.token1(), 'pool token1').to.eq(TEST_ADDRESSES[1]) 85 | expect(await pool.fee(), 'pool fee').to.eq(feeAmount) 86 | expect(await pool.tickSpacing(), 'pool tick spacing').to.eq(tickSpacing) 87 | } 88 | 89 | describe('#createPool', () => { 90 | it('succeeds for low fee pool', async () => { 91 | await createAndCheckPool(TEST_ADDRESSES, FeeAmount.LOW) 92 | }) 93 | 94 | it('succeeds for medium fee pool', async () => { 95 | await createAndCheckPool(TEST_ADDRESSES, FeeAmount.MEDIUM) 96 | }) 97 | it('succeeds for high fee pool', async () => { 98 | await createAndCheckPool(TEST_ADDRESSES, FeeAmount.HIGH) 99 | }) 100 | 101 | it('succeeds if tokens are passed in reverse', async () => { 102 | await createAndCheckPool([TEST_ADDRESSES[1], TEST_ADDRESSES[0]], FeeAmount.MEDIUM) 103 | }) 104 | 105 | it('fails if token a == token b', async () => { 106 | await expect(factory.createPool(TEST_ADDRESSES[0], TEST_ADDRESSES[0], FeeAmount.LOW)).to.be.reverted 107 | }) 108 | 109 | it('fails if token a is 0 or token b is 0', async () => { 110 | await expect(factory.createPool(TEST_ADDRESSES[0], constants.AddressZero, FeeAmount.LOW)).to.be.reverted 111 | await expect(factory.createPool(constants.AddressZero, TEST_ADDRESSES[0], FeeAmount.LOW)).to.be.reverted 112 | await expect(factory.createPool(constants.AddressZero, constants.AddressZero, FeeAmount.LOW)).to.be.revertedWith( 113 | '' 114 | ) 115 | }) 116 | 117 | it('fails if fee amount is not enabled', async () => { 118 | await expect(factory.createPool(TEST_ADDRESSES[0], TEST_ADDRESSES[1], 250)).to.be.reverted 119 | }) 120 | 121 | it('gas', async () => { 122 | await snapshotGasCost(factory.createPool(TEST_ADDRESSES[0], TEST_ADDRESSES[1], FeeAmount.MEDIUM)) 123 | }) 124 | }) 125 | 126 | describe('#setOwner', () => { 127 | it('fails if caller is not owner', async () => { 128 | await expect(factory.connect(other).setOwner(wallet.address)).to.be.reverted 129 | }) 130 | 131 | it('updates owner', async () => { 132 | await factory.setOwner(other.address) 133 | expect(await factory.owner()).to.eq(other.address) 134 | }) 135 | 136 | it('emits event', async () => { 137 | await expect(factory.setOwner(other.address)) 138 | .to.emit(factory, 'OwnerChanged') 139 | .withArgs(wallet.address, other.address) 140 | }) 141 | 142 | it('cannot be called by original owner', async () => { 143 | await factory.setOwner(other.address) 144 | await expect(factory.setOwner(wallet.address)).to.be.reverted 145 | }) 146 | }) 147 | 148 | describe('#enableFeeAmount', () => { 149 | it('fails if caller is not owner', async () => { 150 | await expect(factory.connect(other).enableFeeAmount(100, 2)).to.be.reverted 151 | }) 152 | it('fails if fee is too great', async () => { 153 | await expect(factory.enableFeeAmount(1000000, 10)).to.be.reverted 154 | }) 155 | it('fails if tick spacing is too small', async () => { 156 | await expect(factory.enableFeeAmount(500, 0)).to.be.reverted 157 | }) 158 | it('fails if tick spacing is too large', async () => { 159 | await expect(factory.enableFeeAmount(500, 16834)).to.be.reverted 160 | }) 161 | it('fails if already initialized', async () => { 162 | await factory.enableFeeAmount(100, 5) 163 | await expect(factory.enableFeeAmount(100, 10)).to.be.reverted 164 | }) 165 | it('sets the fee amount in the mapping', async () => { 166 | await factory.enableFeeAmount(100, 5) 167 | expect(await factory.feeAmountTickSpacing(100)).to.eq(5) 168 | }) 169 | it('emits an event', async () => { 170 | await expect(factory.enableFeeAmount(100, 5)).to.emit(factory, 'FeeAmountEnabled').withArgs(100, 5) 171 | }) 172 | it('enables pool creation', async () => { 173 | await factory.enableFeeAmount(250, 15) 174 | await createAndCheckPool([TEST_ADDRESSES[0], TEST_ADDRESSES[1]], 250, 15) 175 | }) 176 | }) 177 | }) 178 | -------------------------------------------------------------------------------- /test/UniswapV3Router.spec.ts: -------------------------------------------------------------------------------- 1 | import { Wallet } from 'ethers' 2 | import { ethers, waffle } from 'hardhat' 3 | import { TestERC20 } from '../typechain/TestERC20' 4 | import { UniswapV3Factory } from '../typechain/UniswapV3Factory' 5 | import { MockTimeUniswapV3Pool } from '../typechain/MockTimeUniswapV3Pool' 6 | import { expect } from './shared/expect' 7 | 8 | import { poolFixture } from './shared/fixtures' 9 | 10 | import { 11 | FeeAmount, 12 | TICK_SPACINGS, 13 | createPoolFunctions, 14 | PoolFunctions, 15 | createMultiPoolFunctions, 16 | encodePriceSqrt, 17 | getMinTick, 18 | getMaxTick, 19 | expandTo18Decimals, 20 | } from './shared/utilities' 21 | import { TestUniswapV3Router } from '../typechain/TestUniswapV3Router' 22 | import { TestUniswapV3Callee } from '../typechain/TestUniswapV3Callee' 23 | 24 | const feeAmount = FeeAmount.MEDIUM 25 | const tickSpacing = TICK_SPACINGS[feeAmount] 26 | 27 | const createFixtureLoader = waffle.createFixtureLoader 28 | 29 | type ThenArg = T extends PromiseLike ? U : T 30 | 31 | describe('UniswapV3Pool', () => { 32 | let wallet: Wallet, other: Wallet 33 | 34 | let token0: TestERC20 35 | let token1: TestERC20 36 | let token2: TestERC20 37 | let factory: UniswapV3Factory 38 | let pool0: MockTimeUniswapV3Pool 39 | let pool1: MockTimeUniswapV3Pool 40 | 41 | let pool0Functions: PoolFunctions 42 | let pool1Functions: PoolFunctions 43 | 44 | let minTick: number 45 | let maxTick: number 46 | 47 | let swapTargetCallee: TestUniswapV3Callee 48 | let swapTargetRouter: TestUniswapV3Router 49 | 50 | let loadFixture: ReturnType 51 | let createPool: ThenArg>['createPool'] 52 | 53 | before('create fixture loader', async () => { 54 | ;[wallet, other] = await (ethers as any).getSigners() 55 | 56 | loadFixture = createFixtureLoader([wallet, other]) 57 | }) 58 | 59 | beforeEach('deploy first fixture', async () => { 60 | ;({ token0, token1, token2, factory, createPool, swapTargetCallee, swapTargetRouter } = await loadFixture( 61 | poolFixture 62 | )) 63 | 64 | const createPoolWrapped = async ( 65 | amount: number, 66 | spacing: number, 67 | firstToken: TestERC20, 68 | secondToken: TestERC20 69 | ): Promise<[MockTimeUniswapV3Pool, any]> => { 70 | const pool = await createPool(amount, spacing, firstToken, secondToken) 71 | const poolFunctions = createPoolFunctions({ 72 | swapTarget: swapTargetCallee, 73 | token0: firstToken, 74 | token1: secondToken, 75 | pool, 76 | }) 77 | minTick = getMinTick(spacing) 78 | maxTick = getMaxTick(spacing) 79 | return [pool, poolFunctions] 80 | } 81 | 82 | // default to the 30 bips pool 83 | ;[pool0, pool0Functions] = await createPoolWrapped(feeAmount, tickSpacing, token0, token1) 84 | ;[pool1, pool1Functions] = await createPoolWrapped(feeAmount, tickSpacing, token1, token2) 85 | }) 86 | 87 | it('constructor initializes immutables', async () => { 88 | expect(await pool0.factory()).to.eq(factory.address) 89 | expect(await pool0.token0()).to.eq(token0.address) 90 | expect(await pool0.token1()).to.eq(token1.address) 91 | expect(await pool1.factory()).to.eq(factory.address) 92 | expect(await pool1.token0()).to.eq(token1.address) 93 | expect(await pool1.token1()).to.eq(token2.address) 94 | }) 95 | 96 | describe('multi-swaps', () => { 97 | let inputToken: TestERC20 98 | let outputToken: TestERC20 99 | 100 | beforeEach('initialize both pools', async () => { 101 | inputToken = token0 102 | outputToken = token2 103 | 104 | await pool0.initialize(encodePriceSqrt(1, 1)) 105 | await pool1.initialize(encodePriceSqrt(1, 1)) 106 | 107 | await pool0Functions.mint(wallet.address, minTick, maxTick, expandTo18Decimals(1)) 108 | await pool1Functions.mint(wallet.address, minTick, maxTick, expandTo18Decimals(1)) 109 | }) 110 | 111 | it('multi-swap', async () => { 112 | const token0OfPoolOutput = await pool1.token0() 113 | const ForExact0 = outputToken.address === token0OfPoolOutput 114 | 115 | const { swapForExact0Multi, swapForExact1Multi } = createMultiPoolFunctions({ 116 | inputToken: token0, 117 | swapTarget: swapTargetRouter, 118 | poolInput: pool0, 119 | poolOutput: pool1, 120 | }) 121 | 122 | const method = ForExact0 ? swapForExact0Multi : swapForExact1Multi 123 | 124 | await expect(method(100, wallet.address)) 125 | .to.emit(outputToken, 'Transfer') 126 | .withArgs(pool1.address, wallet.address, 100) 127 | .to.emit(token1, 'Transfer') 128 | .withArgs(pool0.address, pool1.address, 102) 129 | .to.emit(inputToken, 'Transfer') 130 | .withArgs(wallet.address, pool0.address, 104) 131 | }) 132 | }) 133 | }) 134 | -------------------------------------------------------------------------------- /test/__snapshots__/BitMath.spec.ts.snap: -------------------------------------------------------------------------------- 1 | // Jest Snapshot v1, https://goo.gl/fbAQLP 2 | 3 | exports[`BitMath #leastSignificantBit gas cost of max uint128 1`] = `431`; 4 | 5 | exports[`BitMath #leastSignificantBit gas cost of max uint256 1`] = `431`; 6 | 7 | exports[`BitMath #leastSignificantBit gas cost of smaller number 1`] = `429`; 8 | 9 | exports[`BitMath #mostSignificantBit gas cost of max uint128 1`] = `367`; 10 | 11 | exports[`BitMath #mostSignificantBit gas cost of max uint256 1`] = `385`; 12 | 13 | exports[`BitMath #mostSignificantBit gas cost of smaller number 1`] = `295`; 14 | -------------------------------------------------------------------------------- /test/__snapshots__/LiquidityMath.spec.ts.snap: -------------------------------------------------------------------------------- 1 | // Jest Snapshot v1, https://goo.gl/fbAQLP 2 | 3 | exports[`LiquidityMath #addDelta gas add 1`] = `162`; 4 | 5 | exports[`LiquidityMath #addDelta gas sub 1`] = `176`; 6 | -------------------------------------------------------------------------------- /test/__snapshots__/NoDelegateCall.spec.ts.snap: -------------------------------------------------------------------------------- 1 | // Jest Snapshot v1, https://goo.gl/fbAQLP 2 | 3 | exports[`NoDelegateCall runtime overhead 1`] = `30`; 4 | -------------------------------------------------------------------------------- /test/__snapshots__/Oracle.spec.ts.snap: -------------------------------------------------------------------------------- 1 | // Jest Snapshot v1, https://goo.gl/fbAQLP 2 | 3 | exports[`Oracle #grow gas for growing by 1 slot when index != cardinality - 1 1`] = `49081`; 4 | 5 | exports[`Oracle #grow gas for growing by 1 slot when index == cardinality - 1 1`] = `49081`; 6 | 7 | exports[`Oracle #grow gas for growing by 10 slots when index != cardinality - 1 1`] = `249223`; 8 | 9 | exports[`Oracle #grow gas for growing by 10 slots when index == cardinality - 1 1`] = `249223`; 10 | 11 | exports[`Oracle #initialize gas 1`] = `67770`; 12 | 13 | exports[`Oracle #observe before initialization gas for observe since most recent 1`] = `4746`; 14 | 15 | exports[`Oracle #observe before initialization gas for single observation at current time 1`] = `3565`; 16 | 17 | exports[`Oracle #observe before initialization gas for single observation at current time counterfactually computed 1`] = `4067`; 18 | 19 | exports[`Oracle #observe initialized with 5 observations with starting time of 5 fetch many values 1`] = ` 20 | Object { 21 | "secondsPerLiquidityCumulativeX128s": Array [ 22 | "544451787073501541541399371890829138329", 23 | "799663562264205389138930327464655296921", 24 | "1045423049484883168306923099498710116305", 25 | "1423514568285925905488450441089563684590", 26 | "2152691068830794041481396028443352709138", 27 | "2347138135642758877746181518404363115684", 28 | "2395749902345750086812377890894615717321", 29 | ], 30 | "tickCumulatives": Array [ 31 | -13, 32 | -31, 33 | -43, 34 | -37, 35 | -15, 36 | 9, 37 | 15, 38 | ], 39 | } 40 | `; 41 | 42 | exports[`Oracle #observe initialized with 5 observations with starting time of 5 gas all of last 20 seconds 1`] = `91193`; 43 | 44 | exports[`Oracle #observe initialized with 5 observations with starting time of 5 gas between oldest and oldest + 1 1`] = `15811`; 45 | 46 | exports[`Oracle #observe initialized with 5 observations with starting time of 5 gas latest equal 1`] = `3565`; 47 | 48 | exports[`Oracle #observe initialized with 5 observations with starting time of 5 gas latest transform 1`] = `4067`; 49 | 50 | exports[`Oracle #observe initialized with 5 observations with starting time of 5 gas middle 1`] = `13986`; 51 | 52 | exports[`Oracle #observe initialized with 5 observations with starting time of 5 gas oldest 1`] = `15538`; 53 | 54 | exports[`Oracle #observe initialized with 5 observations with starting time of 4294967291 fetch many values 1`] = ` 55 | Object { 56 | "secondsPerLiquidityCumulativeX128s": Array [ 57 | "544451787073501541541399371890829138329", 58 | "799663562264205389138930327464655296921", 59 | "1045423049484883168306923099498710116305", 60 | "1423514568285925905488450441089563684590", 61 | "2152691068830794041481396028443352709138", 62 | "2347138135642758877746181518404363115684", 63 | "2395749902345750086812377890894615717321", 64 | ], 65 | "tickCumulatives": Array [ 66 | -13, 67 | -31, 68 | -43, 69 | -37, 70 | -15, 71 | 9, 72 | 15, 73 | ], 74 | } 75 | `; 76 | 77 | exports[`Oracle #observe initialized with 5 observations with starting time of 4294967291 gas all of last 20 seconds 1`] = `91193`; 78 | 79 | exports[`Oracle #observe initialized with 5 observations with starting time of 4294967291 gas between oldest and oldest + 1 1`] = `15811`; 80 | 81 | exports[`Oracle #observe initialized with 5 observations with starting time of 4294967291 gas latest equal 1`] = `3565`; 82 | 83 | exports[`Oracle #observe initialized with 5 observations with starting time of 4294967291 gas latest transform 1`] = `4067`; 84 | 85 | exports[`Oracle #observe initialized with 5 observations with starting time of 4294967291 gas middle 1`] = `13986`; 86 | 87 | exports[`Oracle #observe initialized with 5 observations with starting time of 4294967291 gas oldest 1`] = `15538`; 88 | -------------------------------------------------------------------------------- /test/__snapshots__/SqrtPriceMath.spec.ts.snap: -------------------------------------------------------------------------------- 1 | // Jest Snapshot v1, https://goo.gl/fbAQLP 2 | 3 | exports[`SqrtPriceMath #getAmount0Delta gas cost for amount0 where roundUp = true 1`] = `610`; 4 | 5 | exports[`SqrtPriceMath #getAmount0Delta gas cost for amount0 where roundUp = true 2`] = `478`; 6 | 7 | exports[`SqrtPriceMath #getAmount1Delta gas cost for amount0 where roundUp = false 1`] = `478`; 8 | 9 | exports[`SqrtPriceMath #getAmount1Delta gas cost for amount0 where roundUp = true 1`] = `610`; 10 | 11 | exports[`SqrtPriceMath #getNextSqrtPriceFromInput zeroForOne = false gas 1`] = `536`; 12 | 13 | exports[`SqrtPriceMath #getNextSqrtPriceFromInput zeroForOne = true gas 1`] = `753`; 14 | 15 | exports[`SqrtPriceMath #getNextSqrtPriceFromOutput zeroForOne = false gas 1`] = `848`; 16 | 17 | exports[`SqrtPriceMath #getNextSqrtPriceFromOutput zeroForOne = true gas 1`] = `441`; 18 | -------------------------------------------------------------------------------- /test/__snapshots__/SwapMath.spec.ts.snap: -------------------------------------------------------------------------------- 1 | // Jest Snapshot v1, https://goo.gl/fbAQLP 2 | 3 | exports[`SwapMath #computeSwapStep gas swap one for zero exact in capped 1`] = `2103`; 4 | 5 | exports[`SwapMath #computeSwapStep gas swap one for zero exact in partial 1`] = `2802`; 6 | 7 | exports[`SwapMath #computeSwapStep gas swap one for zero exact out capped 1`] = `1855`; 8 | 9 | exports[`SwapMath #computeSwapStep gas swap one for zero exact out partial 1`] = `2802`; 10 | 11 | exports[`SwapMath #computeSwapStep gas swap zero for one exact in capped 1`] = `2104`; 12 | 13 | exports[`SwapMath #computeSwapStep gas swap zero for one exact in partial 1`] = `3106`; 14 | 15 | exports[`SwapMath #computeSwapStep gas swap zero for one exact out capped 1`] = `1856`; 16 | 17 | exports[`SwapMath #computeSwapStep gas swap zero for one exact out partial 1`] = `3106`; 18 | -------------------------------------------------------------------------------- /test/__snapshots__/TickBitmap.spec.ts.snap: -------------------------------------------------------------------------------- 1 | // Jest Snapshot v1, https://goo.gl/fbAQLP 2 | 3 | exports[`TickBitmap #flipTick gas cost of flipping a tick that results in deleting a word 1`] = `13427`; 4 | 5 | exports[`TickBitmap #flipTick gas cost of flipping first tick in word to initialized 1`] = `43965`; 6 | 7 | exports[`TickBitmap #flipTick gas cost of flipping second tick in word to initialized 1`] = `26865`; 8 | 9 | exports[`TickBitmap #nextInitializedTickWithinOneWord lte = false gas cost for entire word 1`] = `2627`; 10 | 11 | exports[`TickBitmap #nextInitializedTickWithinOneWord lte = false gas cost just below boundary 1`] = `2627`; 12 | 13 | exports[`TickBitmap #nextInitializedTickWithinOneWord lte = false gas cost on boundary 1`] = `2627`; 14 | 15 | exports[`TickBitmap #nextInitializedTickWithinOneWord lte = true gas cost for entire word 1`] = `2618`; 16 | 17 | exports[`TickBitmap #nextInitializedTickWithinOneWord lte = true gas cost just below boundary 1`] = `2928`; 18 | 19 | exports[`TickBitmap #nextInitializedTickWithinOneWord lte = true gas cost on boundary 1`] = `2618`; 20 | -------------------------------------------------------------------------------- /test/__snapshots__/UniswapV3Factory.spec.ts.snap: -------------------------------------------------------------------------------- 1 | // Jest Snapshot v1, https://goo.gl/fbAQLP 2 | 3 | exports[`UniswapV3Factory #createPool gas 1`] = `4557092`; 4 | 5 | exports[`UniswapV3Factory factory bytecode size 1`] = `24535`; 6 | 7 | exports[`UniswapV3Factory pool bytecode size 1`] = `22142`; 8 | -------------------------------------------------------------------------------- /test/shared/checkObservationEquals.ts: -------------------------------------------------------------------------------- 1 | import { BigNumber, BigNumberish } from 'ethers' 2 | import { expect } from './expect' 3 | 4 | // helper function because we cannot do a simple deep equals with the 5 | // observation result object returned from ethers because it extends array 6 | export default function checkObservationEquals( 7 | { 8 | tickCumulative, 9 | blockTimestamp, 10 | initialized, 11 | secondsPerLiquidityCumulativeX128, 12 | }: { 13 | tickCumulative: BigNumber 14 | secondsPerLiquidityCumulativeX128: BigNumber 15 | initialized: boolean 16 | blockTimestamp: number 17 | }, 18 | expected: { 19 | tickCumulative: BigNumberish 20 | secondsPerLiquidityCumulativeX128: BigNumberish 21 | initialized: boolean 22 | blockTimestamp: number 23 | } 24 | ) { 25 | expect( 26 | { 27 | initialized, 28 | blockTimestamp, 29 | tickCumulative: tickCumulative.toString(), 30 | secondsPerLiquidityCumulativeX128: secondsPerLiquidityCumulativeX128.toString(), 31 | }, 32 | `observation is equivalent` 33 | ).to.deep.eq({ 34 | ...expected, 35 | tickCumulative: expected.tickCumulative.toString(), 36 | secondsPerLiquidityCumulativeX128: expected.secondsPerLiquidityCumulativeX128.toString(), 37 | }) 38 | } 39 | -------------------------------------------------------------------------------- /test/shared/expect.ts: -------------------------------------------------------------------------------- 1 | import { expect, use } from 'chai' 2 | import { solidity } from 'ethereum-waffle' 3 | import { jestSnapshotPlugin } from 'mocha-chai-jest-snapshot' 4 | 5 | use(solidity) 6 | use(jestSnapshotPlugin()) 7 | 8 | export { expect } 9 | -------------------------------------------------------------------------------- /test/shared/fixtures.ts: -------------------------------------------------------------------------------- 1 | import { BigNumber } from 'ethers' 2 | import { ethers } from 'hardhat' 3 | import { MockTimeUniswapV3Pool } from '../../typechain/MockTimeUniswapV3Pool' 4 | import { TestERC20 } from '../../typechain/TestERC20' 5 | import { UniswapV3Factory } from '../../typechain/UniswapV3Factory' 6 | import { TestUniswapV3Callee } from '../../typechain/TestUniswapV3Callee' 7 | import { TestUniswapV3Router } from '../../typechain/TestUniswapV3Router' 8 | import { MockTimeUniswapV3PoolDeployer } from '../../typechain/MockTimeUniswapV3PoolDeployer' 9 | 10 | import { Fixture } from 'ethereum-waffle' 11 | 12 | interface FactoryFixture { 13 | factory: UniswapV3Factory 14 | } 15 | 16 | async function factoryFixture(): Promise { 17 | const factoryFactory = await ethers.getContractFactory('UniswapV3Factory') 18 | const factory = (await factoryFactory.deploy()) as UniswapV3Factory 19 | return { factory } 20 | } 21 | 22 | interface TokensFixture { 23 | token0: TestERC20 24 | token1: TestERC20 25 | token2: TestERC20 26 | } 27 | 28 | async function tokensFixture(): Promise { 29 | const tokenFactory = await ethers.getContractFactory('TestERC20') 30 | const tokenA = (await tokenFactory.deploy(BigNumber.from(2).pow(255))) as TestERC20 31 | const tokenB = (await tokenFactory.deploy(BigNumber.from(2).pow(255))) as TestERC20 32 | const tokenC = (await tokenFactory.deploy(BigNumber.from(2).pow(255))) as TestERC20 33 | 34 | const [token0, token1, token2] = [tokenA, tokenB, tokenC].sort((tokenA, tokenB) => 35 | tokenA.address.toLowerCase() < tokenB.address.toLowerCase() ? -1 : 1 36 | ) 37 | 38 | return { token0, token1, token2 } 39 | } 40 | 41 | type TokensAndFactoryFixture = FactoryFixture & TokensFixture 42 | 43 | interface PoolFixture extends TokensAndFactoryFixture { 44 | swapTargetCallee: TestUniswapV3Callee 45 | swapTargetRouter: TestUniswapV3Router 46 | createPool( 47 | fee: number, 48 | tickSpacing: number, 49 | firstToken?: TestERC20, 50 | secondToken?: TestERC20 51 | ): Promise 52 | } 53 | 54 | // Monday, October 5, 2020 9:00:00 AM GMT-05:00 55 | export const TEST_POOL_START_TIME = 1601906400 56 | 57 | export const poolFixture: Fixture = async function (): Promise { 58 | const { factory } = await factoryFixture() 59 | const { token0, token1, token2 } = await tokensFixture() 60 | 61 | const MockTimeUniswapV3PoolDeployerFactory = await ethers.getContractFactory('MockTimeUniswapV3PoolDeployer') 62 | const MockTimeUniswapV3PoolFactory = await ethers.getContractFactory('MockTimeUniswapV3Pool') 63 | 64 | const calleeContractFactory = await ethers.getContractFactory('TestUniswapV3Callee') 65 | const routerContractFactory = await ethers.getContractFactory('TestUniswapV3Router') 66 | 67 | const swapTargetCallee = (await calleeContractFactory.deploy()) as TestUniswapV3Callee 68 | const swapTargetRouter = (await routerContractFactory.deploy()) as TestUniswapV3Router 69 | 70 | return { 71 | token0, 72 | token1, 73 | token2, 74 | factory, 75 | swapTargetCallee, 76 | swapTargetRouter, 77 | createPool: async (fee, tickSpacing, firstToken = token0, secondToken = token1) => { 78 | const mockTimePoolDeployer = (await MockTimeUniswapV3PoolDeployerFactory.deploy()) as MockTimeUniswapV3PoolDeployer 79 | const tx = await mockTimePoolDeployer.deploy( 80 | factory.address, 81 | firstToken.address, 82 | secondToken.address, 83 | fee, 84 | tickSpacing 85 | ) 86 | 87 | const receipt = await tx.wait() 88 | const poolAddress = receipt.events?.[0].args?.pool as string 89 | return MockTimeUniswapV3PoolFactory.attach(poolAddress) as MockTimeUniswapV3Pool 90 | }, 91 | } 92 | } 93 | -------------------------------------------------------------------------------- /test/shared/format.ts: -------------------------------------------------------------------------------- 1 | import { Decimal } from 'decimal.js' 2 | import { BigNumberish } from 'ethers' 3 | 4 | export function formatTokenAmount(num: BigNumberish): string { 5 | return new Decimal(num.toString()).dividedBy(new Decimal(10).pow(18)).toPrecision(5) 6 | } 7 | 8 | export function formatPrice(price: BigNumberish): string { 9 | return new Decimal(price.toString()).dividedBy(new Decimal(2).pow(96)).pow(2).toPrecision(5) 10 | } 11 | -------------------------------------------------------------------------------- /test/shared/snapshotGasCost.ts: -------------------------------------------------------------------------------- 1 | import { TransactionReceipt, TransactionResponse } from '@ethersproject/abstract-provider' 2 | import { expect } from './expect' 3 | import { Contract, BigNumber, ContractTransaction } from 'ethers' 4 | 5 | export default async function snapshotGasCost( 6 | x: 7 | | TransactionResponse 8 | | Promise 9 | | ContractTransaction 10 | | Promise 11 | | TransactionReceipt 12 | | Promise 13 | | BigNumber 14 | | Contract 15 | | Promise 16 | ): Promise { 17 | const resolved = await x 18 | if ('deployTransaction' in resolved) { 19 | const receipt = await resolved.deployTransaction.wait() 20 | expect(receipt.gasUsed.toNumber()).toMatchSnapshot() 21 | } else if ('wait' in resolved) { 22 | const waited = await resolved.wait() 23 | expect(waited.gasUsed.toNumber()).toMatchSnapshot() 24 | } else if (BigNumber.isBigNumber(resolved)) { 25 | expect(resolved.toNumber()).toMatchSnapshot() 26 | } 27 | } 28 | -------------------------------------------------------------------------------- /tsconfig.json: -------------------------------------------------------------------------------- 1 | { 2 | "compilerOptions": { 3 | "target": "es2018", 4 | "module": "commonjs", 5 | "strict": true, 6 | "esModuleInterop": true, 7 | "outDir": "dist", 8 | "typeRoots": ["./typechain", "./node_modules/@types"], 9 | "types": ["@nomiclabs/hardhat-ethers", "@nomiclabs/hardhat-waffle"] 10 | }, 11 | "include": ["./test"], 12 | "files": ["./hardhat.config.ts"] 13 | } 14 | --------------------------------------------------------------------------------