├── .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 | [](https://github.com/Uniswap/uniswap-v3-core/actions/workflows/lint.yml)
4 | [](https://github.com/Uniswap/uniswap-v3-core/actions/workflows/tests.yml)
5 | [](https://github.com/Uniswap/uniswap-v3-core/actions/workflows/fuzz-testing.yml)
6 | [](https://github.com/Uniswap/uniswap-v3-core/actions/workflows/mythx.yml)
7 | [](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 |
--------------------------------------------------------------------------------