From e2b7fe1895b87cb9db42f3bb07f6418b47ead696 Mon Sep 17 00:00:00 2001 From: Jochen Hoenicke Date: Tue, 19 Aug 2025 18:53:29 +0200 Subject: [PATCH 1/5] Uniswap Hook Example This directory contains the examples from my Atrium talk August 20, 2025. --- CVLByExample/UniswapHooks/LICENSE | 21 ++ CVLByExample/UniswapHooks/README.md | 85 ++++++ .../UniswapHooks/certora/confs/HookRules.conf | 12 + .../certora/confs/PointsHook.conf | 27 ++ .../certora/confs/UniswapTake.conf | 17 ++ .../certora/harness/PointsHookHarness.sol | 37 +++ .../certora/harness/PoolManagerHarness.sol | 25 ++ .../UniswapHooks/certora/specs/HookRules.spec | 115 ++++++++ .../certora/specs/PointsHook.spec | 58 ++++ .../certora/specs/PoolManagerSummaries.spec | 118 ++++++++ .../certora/specs/UniswapTake.spec | 11 + .../UniswapHooks/certora/specs/extsload.spec | 20 ++ CVLByExample/UniswapHooks/foundry.toml | 10 + CVLByExample/UniswapHooks/remappings.txt | 13 + .../UniswapHooks/script/00_DeployHook.s.sol | 32 +++ .../script/01_CreatePoolAndAddLiquidity.s.sol | 90 +++++++ .../UniswapHooks/script/02_AddLiquidity.s.sol | 90 +++++++ .../UniswapHooks/script/03_Swap.s.sol | 38 +++ .../UniswapHooks/script/base/BaseScript.sol | 71 +++++ .../script/base/LiquidityHelpers.sol | 47 ++++ CVLByExample/UniswapHooks/src/Counter.sol | 87 ++++++ CVLByExample/UniswapHooks/src/PointsHook.sol | 130 +++++++++ CVLByExample/UniswapHooks/src/PointsToken.sol | 13 + CVLByExample/UniswapHooks/test/Counter.t.sol | 138 ++++++++++ .../UniswapHooks/test/PointsHookTest.sol | 92 +++++++ .../UniswapHooks/test/utils/Deployers.sol | 123 +++++++++ .../test/utils/libraries/EasyPosm.sol | 203 ++++++++++++++ .../test/utils/libraries/EasyPosm.t.sol | 251 ++++++++++++++++++ 28 files changed, 1974 insertions(+) create mode 100644 CVLByExample/UniswapHooks/LICENSE create mode 100644 CVLByExample/UniswapHooks/README.md create mode 100644 CVLByExample/UniswapHooks/certora/confs/HookRules.conf create mode 100644 CVLByExample/UniswapHooks/certora/confs/PointsHook.conf create mode 100644 CVLByExample/UniswapHooks/certora/confs/UniswapTake.conf create mode 100644 CVLByExample/UniswapHooks/certora/harness/PointsHookHarness.sol create mode 100644 CVLByExample/UniswapHooks/certora/harness/PoolManagerHarness.sol create mode 100644 CVLByExample/UniswapHooks/certora/specs/HookRules.spec create mode 100644 CVLByExample/UniswapHooks/certora/specs/PointsHook.spec create mode 100644 CVLByExample/UniswapHooks/certora/specs/PoolManagerSummaries.spec create mode 100644 CVLByExample/UniswapHooks/certora/specs/UniswapTake.spec create mode 100644 CVLByExample/UniswapHooks/certora/specs/extsload.spec create mode 100644 CVLByExample/UniswapHooks/foundry.toml create mode 100644 CVLByExample/UniswapHooks/remappings.txt create mode 100644 CVLByExample/UniswapHooks/script/00_DeployHook.s.sol create mode 100644 CVLByExample/UniswapHooks/script/01_CreatePoolAndAddLiquidity.s.sol create mode 100644 CVLByExample/UniswapHooks/script/02_AddLiquidity.s.sol create mode 100644 CVLByExample/UniswapHooks/script/03_Swap.s.sol create mode 100644 CVLByExample/UniswapHooks/script/base/BaseScript.sol create mode 100644 CVLByExample/UniswapHooks/script/base/LiquidityHelpers.sol create mode 100644 CVLByExample/UniswapHooks/src/Counter.sol create mode 100644 CVLByExample/UniswapHooks/src/PointsHook.sol create mode 100644 CVLByExample/UniswapHooks/src/PointsToken.sol create mode 100644 CVLByExample/UniswapHooks/test/Counter.t.sol create mode 100644 CVLByExample/UniswapHooks/test/PointsHookTest.sol create mode 100644 CVLByExample/UniswapHooks/test/utils/Deployers.sol create mode 100644 CVLByExample/UniswapHooks/test/utils/libraries/EasyPosm.sol create mode 100644 CVLByExample/UniswapHooks/test/utils/libraries/EasyPosm.t.sol diff --git a/CVLByExample/UniswapHooks/LICENSE b/CVLByExample/UniswapHooks/LICENSE new file mode 100644 index 00000000..381ddcd2 --- /dev/null +++ b/CVLByExample/UniswapHooks/LICENSE @@ -0,0 +1,21 @@ +MIT License + +Copyright (c) 2023 saucepoint + +Permission is hereby granted, free of charge, to any person obtaining a copy +of this software and associated documentation files (the "Software"), to deal +in the Software without restriction, including without limitation the rights +to use, copy, modify, merge, publish, distribute, sublicense, and/or sell +copies of the Software, and to permit persons to whom the Software is +furnished to do so, subject to the following conditions: + +The above copyright notice and this permission notice shall be included in all +copies or substantial portions of the Software. + +THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR +IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, +FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE +AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER +LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, +OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE +SOFTWARE. diff --git a/CVLByExample/UniswapHooks/README.md b/CVLByExample/UniswapHooks/README.md new file mode 100644 index 00000000..1877544d --- /dev/null +++ b/CVLByExample/UniswapHooks/README.md @@ -0,0 +1,85 @@ +# Uniswap v4 Hook Template + +**A template for writing Uniswap v4 Hooks 🦄** + +### Get Started + +This template provides a starting point for writing Uniswap v4 Hooks, including a simple example and preconfigured test environment. Start by creating a new repository using the "Use this template" button at the top right of this page. Alternatively you can also click this link: + +[![Use this Template](https://img.shields.io/badge/Use%20this%20Template-101010?style=for-the-badge&logo=github)](https://github.com/uniswapfoundation/v4-template/generate) + +1. The example hook [Counter.sol](src/Counter.sol) demonstrates the `beforeSwap()` and `afterSwap()` hooks +2. The test template [Counter.t.sol](test/Counter.t.sol) preconfigures the v4 pool manager, test tokens, and test liquidity. + +
+Updating to v4-template:latest + +This template is actively maintained -- you can update the v4 dependencies, scripts, and helpers: + +```bash +git remote add template https://github.com/uniswapfoundation/v4-template +git fetch template +git merge template/main --allow-unrelated-histories +``` + +
+ +### Requirements + +This template is designed to work with Foundry (stable). If you are using Foundry Nightly, you may encounter compatibility issues. You can update your Foundry installation to the latest stable version by running: + +``` +foundryup +``` + +To set up the project, run the following commands in your terminal to install dependencies and run the tests: + +``` +forge install +forge test +``` + +### Local Development + +Other than writing unit tests (recommended!), you can only deploy & test hooks on [anvil](https://book.getfoundry.sh/anvil/) locally. Scripts are available in the `script/` directory, which can be used to deploy hooks, create pools, provide liquidity and swap tokens. The scripts support both local `anvil` environment as well as running them directly on a production network. + +### Troubleshooting + +
+ +#### Permission Denied + +When installing dependencies with `forge install`, Github may throw a `Permission Denied` error + +Typically caused by missing Github SSH keys, and can be resolved by following the steps [here](https://docs.github.com/en/github/authenticating-to-github/connecting-to-github-with-ssh) + +Or [adding the keys to your ssh-agent](https://docs.github.com/en/authentication/connecting-to-github-with-ssh/generating-a-new-ssh-key-and-adding-it-to-the-ssh-agent#adding-your-ssh-key-to-the-ssh-agent), if you have already uploaded SSH keys + +#### Anvil fork test failures + +Some versions of Foundry may limit contract code size to ~25kb, which could prevent local tests to fail. You can resolve this by setting the `code-size-limit` flag + +``` +anvil --code-size-limit 40000 +``` + +#### Hook deployment failures + +Hook deployment failures are caused by incorrect flags or incorrect salt mining + +1. Verify the flags are in agreement: + - `getHookCalls()` returns the correct flags + - `flags` provided to `HookMiner.find(...)` +2. Verify salt mining is correct: + - In **forge test**: the _deployer_ for: `new Hook{salt: salt}(...)` and `HookMiner.find(deployer, ...)` are the same. This will be `address(this)`. If using `vm.prank`, the deployer will be the pranking address + - In **forge script**: the deployer must be the CREATE2 Proxy: `0x4e59b44847b379578588920cA78FbF26c0B4956C` + - If anvil does not have the CREATE2 deployer, your foundry may be out of date. You can update it with `foundryup` + +
+ +### Additional Resources + +- [Uniswap v4 docs](https://docs.uniswap.org/contracts/v4/overview) +- [v4-periphery](https://github.com/uniswap/v4-periphery) +- [v4-core](https://github.com/uniswap/v4-core) +- [v4-by-example](https://v4-by-example.org) diff --git a/CVLByExample/UniswapHooks/certora/confs/HookRules.conf b/CVLByExample/UniswapHooks/certora/confs/HookRules.conf new file mode 100644 index 00000000..5a4cc84b --- /dev/null +++ b/CVLByExample/UniswapHooks/certora/confs/HookRules.conf @@ -0,0 +1,12 @@ +{ + "build_cache": true, + "files": [ + "certora/harness/PointsHookHarness.sol", + "lib/uniswap-hooks/lib/v4-core/src/libraries/Hooks.sol", + ], + "parametric_contracts": [ + "PointsHookHarness" + ], + "solc": "solc8.28", + "verify": "PointsHookHarness:certora/specs/HookRules.spec" +} diff --git a/CVLByExample/UniswapHooks/certora/confs/PointsHook.conf b/CVLByExample/UniswapHooks/certora/confs/PointsHook.conf new file mode 100644 index 00000000..f29b1aa6 --- /dev/null +++ b/CVLByExample/UniswapHooks/certora/confs/PointsHook.conf @@ -0,0 +1,27 @@ +{ + "build_cache": true, + "files": [ + "certora/harness/PointsHookHarness.sol", + "src/PointsToken.sol", + "certora/harness/PoolManagerHarness.sol", + ], + "link": [ + "PointsHookHarness:pointsToken=PointsToken", + ], + "parametric_contracts": [ + "PointsHookHarness", + "PoolManagerHarness" + ], + "solc": "solc8.26", + "solc_via_ir_map": { + "PointsHookHarness": false, + "PointsToken": false, + "PoolManagerHarness": true, + }, + "solc_optimize_map": { + "PointsHookHarness": "0", + "PointsToken": "0", + "PoolManagerHarness": "1000000", + }, + "verify": "PointsHookHarness:certora/specs/PointsHook.spec" +} diff --git a/CVLByExample/UniswapHooks/certora/confs/UniswapTake.conf b/CVLByExample/UniswapHooks/certora/confs/UniswapTake.conf new file mode 100644 index 00000000..bfb61330 --- /dev/null +++ b/CVLByExample/UniswapHooks/certora/confs/UniswapTake.conf @@ -0,0 +1,17 @@ +{ + "build_cache": true, + "files": [ + "certora/harness/PoolManagerHarness.sol", + ], + "parametric_contracts": [ + "PoolManagerHarness" + ], + "solc": "solc8.26", + "solc_via_ir_map": { + "PoolManagerHarness": true, + }, + "solc_optimize_map": { + "PoolManagerHarness": "1000000", + }, + "verify": "PoolManagerHarness:certora/specs/UniswapTake.spec" +} diff --git a/CVLByExample/UniswapHooks/certora/harness/PointsHookHarness.sol b/CVLByExample/UniswapHooks/certora/harness/PointsHookHarness.sol new file mode 100644 index 00000000..22318a67 --- /dev/null +++ b/CVLByExample/UniswapHooks/certora/harness/PointsHookHarness.sol @@ -0,0 +1,37 @@ +// SPDX-License-Identifier: GPL-3.0-or-later +pragma solidity ^0.8.24; + +import {PointsHook} from "../../src/PointsHook.sol"; +import {IPoolManager} from "v4-core/src/interfaces/IPoolManager.sol"; +import {Hooks} from "v4-core/src/libraries/Hooks.sol"; + +contract PointsHookHarness is PointsHook { + constructor(IPoolManager _poolManager) PointsHook(_poolManager) {} + + function checkHookPermission(uint160 flag) + external + view + returns (bool) + { + validateHookAddress(this); + return Hooks.hasPermission(this, flag); + } + + function wrapValidateHookAddress() + external view + { + validateHookAddress(this); + } + + function checkHookAddress() + external + view + returns (bool) + { + try this.wrapValidateHookAddress() { + return true; + } catch { + return false; + } + } +} diff --git a/CVLByExample/UniswapHooks/certora/harness/PoolManagerHarness.sol b/CVLByExample/UniswapHooks/certora/harness/PoolManagerHarness.sol new file mode 100644 index 00000000..9928ed60 --- /dev/null +++ b/CVLByExample/UniswapHooks/certora/harness/PoolManagerHarness.sol @@ -0,0 +1,25 @@ +// SPDX-License-Identifier: GPL-3.0-or-later +pragma solidity 0.8.26; + +import {PoolManager} from "v4-core/src/PoolManager.sol"; +import {BalanceDelta, toBalanceDelta} from "v4-core/src/types/BalanceDelta.sol"; +import {Currency} from "v4-core/src/types/Currency.sol"; +import {CurrencyDelta} from "v4-core/src/libraries/CurrencyDelta.sol"; + +contract PoolManagerHarness is PoolManager { + using CurrencyDelta for Currency; + + constructor(address initialOwner) PoolManager(initialOwner) {} + + function computeBalanceDelta(int128 _amount0, int128 _amount1) external pure returns (BalanceDelta) { + return toBalanceDelta( + _amount0, + _amount1 + ); + } + + + function getDelta(Currency currency, address account) external view returns (int256 delta) { + return currency.getDelta(account); + } +} diff --git a/CVLByExample/UniswapHooks/certora/specs/HookRules.spec b/CVLByExample/UniswapHooks/certora/specs/HookRules.spec new file mode 100644 index 00000000..ef99535a --- /dev/null +++ b/CVLByExample/UniswapHooks/certora/specs/HookRules.spec @@ -0,0 +1,115 @@ +methods { + function checkHookPermission(uint160 flag) external returns (bool) envfree; +} + +definition BEFORE_INITIALIZE_FLAG() returns uint160 = 2^13; +definition AFTER_INITIALIZE_FLAG() returns uint160 = 2^12; + +definition BEFORE_ADD_LIQUIDITY_FLAG() returns uint160 = 2^11; +definition AFTER_ADD_LIQUIDITY_FLAG() returns uint160 = 2^10; + +definition BEFORE_REMOVE_LIQUIDITY_FLAG() returns uint160 = 2^9; +definition AFTER_REMOVE_LIQUIDITY_FLAG() returns uint160 = 2^8; + +definition BEFORE_SWAP_FLAG() returns uint160 = 2^7; +definition AFTER_SWAP_FLAG() returns uint160 = 2^6; + +definition BEFORE_DONATE_FLAG() returns uint160 = 2^5; +definition AFTER_DONATE_FLAG() returns uint160 = 2^4; + +definition BEFORE_SWAP_RETURNS_DELTA_FLAG() returns uint160 = 2^3; +definition AFTER_SWAP_RETURNS_DELTA_FLAG() returns uint160 = 2^2; +definition AFTER_ADD_LIQUIDITY_RETURNS_DELTA_FLAG() returns uint160 = 2^1; +definition AFTER_REMOVE_LIQUIDITY_RETURNS_DELTA_FLAG() returns uint160 = 2^0; + + +rule onlyCallableByPoolManager(method f) +filtered { f -> !f.isView } +{ + env e; + calldataarg args; + f@withrevert(e, args); + + assert !lastReverted => e.msg.sender == currentContract.poolManager, "Only callable by PoolManager"; +} + +rule hookFlagsSetForBeforeInitialize() { + env e; + calldataarg args; + currentContract.beforeInitialize@withrevert(e, args); + + assert !lastReverted => currentContract.checkHookPermission(BEFORE_INITIALIZE_FLAG()), "beforeInitialize flag not set"; +} + +rule hookFlagsSetForAfterInitialize() { + env e; + calldataarg args; + currentContract.afterInitialize@withrevert(e, args); + + assert !lastReverted => currentContract.checkHookPermission(AFTER_INITIALIZE_FLAG()), "afterInitialize flag not set"; +} + +rule hookFlagsSetForBeforeRemoveLiquidity() { + env e; + calldataarg args; + currentContract.beforeRemoveLiquidity@withrevert(e, args); + + assert !lastReverted => currentContract.checkHookPermission(BEFORE_REMOVE_LIQUIDITY_FLAG()), "beforeRemoveLiquidity flag not set"; +} + +rule hookFlagsSetForAfterRemoveLiquidity() { + env e; + calldataarg args; + currentContract.afterRemoveLiquidity@withrevert(e, args); + + assert !lastReverted => currentContract.checkHookPermission(AFTER_REMOVE_LIQUIDITY_FLAG()), "afterRemoveLiquidity flag not set"; +} + +rule hookFlagsSetForBeforeDonate() { + env e; + calldataarg args; + currentContract.beforeDonate@withrevert(e, args); + + assert !lastReverted => currentContract.checkHookPermission(BEFORE_DONATE_FLAG()), "beforeDonate flag not set"; +} + +rule hookFlagsSetForAfterDonate() { + env e; + calldataarg args; + currentContract.afterDonate@withrevert(e, args); + + assert !lastReverted => currentContract.checkHookPermission(AFTER_DONATE_FLAG()), "afterDonate flag not set"; +} + +rule hookFlagsSetForBeforeSwap() { + env e; + calldataarg args; + currentContract.beforeSwap@withrevert(e, args); + + assert !lastReverted => currentContract.checkHookPermission(BEFORE_SWAP_FLAG()), "beforeSwap flag not set"; +} + +rule hookFlagsSetForAfterSwap() { + env e; + calldataarg args; + currentContract.afterSwap@withrevert(e, args); + + assert !lastReverted => currentContract.checkHookPermission(AFTER_SWAP_FLAG()), "afterSwap flag not set"; +} + +rule swapOnlyCalledViaRouter() { + env e; + + address sender; + PointsHook.PoolKey key; + PointsHook.SwapParams swapParams; + //PointsHook.BalanceDelta delta; + bytes data; + currentContract.beforeSwap(e, sender, key, swapParams, data); + + assert e.msg.sender == currentContract.poolManager, "Only callable by PoolManager"; + //assert sender == currentContract.router, "Only callable by Router"; + //assert key == currentContract.poolKey, "Invalid PoolKey"; +} + + diff --git a/CVLByExample/UniswapHooks/certora/specs/PointsHook.spec b/CVLByExample/UniswapHooks/certora/specs/PointsHook.spec new file mode 100644 index 00000000..df8b1940 --- /dev/null +++ b/CVLByExample/UniswapHooks/certora/specs/PointsHook.spec @@ -0,0 +1,58 @@ +import "PoolManagerSummaries.spec"; + +using PointsToken as token; + +methods { + function PointsHookHarness.checkHookAddress() external returns (bool) envfree; + function PointsToken.totalSupply() external returns (uint256) envfree; +} + +rule mintingMatchesOnSwap() { + mathint tokenBalancePre = poolToken0Balance; + mathint pointsTotalSupplyPre = token.totalSupply(); + env e; + PoolManager.PoolKey key; + PoolManager.SwapParams params; + bytes hookData; + + require currentContract.checkHookAddress(), "PointsHook correctly deployed"; + require e.msg.sender != currentContract, "PointsHook will never initiate a swap"; + require key.currency0 == 0, "Pool must use ether"; + require key.hooks == currentContract, "PointsHook must be set in pool"; + require params.zeroForOne, "swap must sell ether for token"; + + poolManager.swap(e, key, params, hookData); + + mathint tokenBalancePost = poolToken0Balance; + mathint pointsTotalSupplyPost = token.totalSupply(); + + assert tokenBalancePost - tokenBalancePre == + pointsTotalSupplyPost - pointsTotalSupplyPre, "minting matches"; +} + +rule mintingMatchesOnAddLiquidity() { + mathint tokenBalancePre = poolToken0Balance; + mathint pointsTotalSupplyPre = token.totalSupply(); + env e; + PoolManager.PoolKey key; + PoolManager.ModifyLiquidityParams params; + bytes hookData; + + require currentContract.checkHookAddress(), "PointsHook correctly deployed"; + require e.msg.sender != currentContract, "PointsHook will never add liquidity"; + require key.currency0 == 0, "Pool must use ether"; + require key.hooks == currentContract, "PointsHook must be set in pool"; + require params.liquidityDelta > 0, "Must add liquidity"; + + poolManager.modifyLiquidity(e, key, params, hookData); + + mathint tokenBalancePost = poolToken0Balance; + mathint pointsTotalSupplyPost = token.totalSupply(); + + if (tokenBalancePost < tokenBalancePre) { + assert pointsTotalSupplyPost == pointsTotalSupplyPre, "no mint if only fees were redeposited"; + } else { + assert tokenBalancePost - tokenBalancePre == + pointsTotalSupplyPost - pointsTotalSupplyPre, "minting matches"; + } +} diff --git a/CVLByExample/UniswapHooks/certora/specs/PoolManagerSummaries.spec b/CVLByExample/UniswapHooks/certora/specs/PoolManagerSummaries.spec new file mode 100644 index 00000000..2bf5bd26 --- /dev/null +++ b/CVLByExample/UniswapHooks/certora/specs/PoolManagerSummaries.spec @@ -0,0 +1,118 @@ +import "extsload.spec"; + +using PoolManagerHarness as poolManager; + +methods { + function _.beforeAddLiquidity(address, PoolManagerHarness.PoolKey, PoolManagerHarness.ModifyLiquidityParams, bytes) external => DISPATCHER(true); + function _.beforeRemoveLiquidity(address, PoolManagerHarness.PoolKey, PoolManagerHarness.ModifyLiquidityParams, bytes) external => DISPATCHER(true); + function _.beforeSwap(address, PoolManagerHarness.PoolKey, PoolManagerHarness.SwapParams, bytes) external => DISPATCHER(true); + function _.afterAddLiquidity(address, PoolManagerHarness.PoolKey, PoolManagerHarness.ModifyLiquidityParams, PoolManagerHarness.BalanceDelta, PoolManagerHarness.BalanceDelta, bytes) external => DISPATCHER(true); + function _.afterRemoveLiquidity(address, PoolManagerHarness.PoolKey, PoolManagerHarness.ModifyLiquidityParams, PoolManagerHarness.BalanceDelta, PoolManagerHarness.BalanceDelta, bytes) external => DISPATCHER(true); + function _.afterSwap(address, PoolManagerHarness.PoolKey, PoolManagerHarness.SwapParams, PoolManagerHarness.BalanceDelta, bytes) external => DISPATCHER(true); + function _.beforeInitialize(address, PoolManagerHarness.PoolKey, uint160) external => DISPATCHER(true); + function _.afterInitialize(address, PoolManagerHarness.PoolKey, uint160, int24) external => DISPATCHER(true); + + function PoolManager._swap(Pool.State storage, PoolManager.PoolId id, Pool.SwapParams memory swapParams, Pool.Currency inputCurrency) internal returns (PoolManager.BalanceDelta) => swapSummary(id, swapParams, inputCurrency); + function Pool.modifyLiquidity(Pool.State storage, Pool.ModifyLiquidityParams memory liquidityParams) internal returns (PoolManager.BalanceDelta, PoolManager.BalanceDelta) => modifyLiquiditySummary(liquidityParams); + function Pool.initialize(Pool.State storage, uint160 sqrtPrice, uint24 lpFee) internal returns (int24) => initializeSummary(sqrtPrice, lpFee); + + function PoolManagerHarness.computeBalanceDelta(int128 _amount0, int128 _amount1) external returns (PoolManager.BalanceDelta) envfree; + function PoolManagerHarness.getDelta(PoolManagerHarness.Currency currency, address account) external returns (int256) envfree; +} + +// These ghost variables track the number of tokens that were put into the pool. +// Token0/Token1 are the two tokens that the pool swaps between. +ghost mathint poolToken0Balance; +ghost mathint poolToken1Balance; + +// This summary simulates the _swap function of the Uniswap PoolManager. Instead of +// having the exact computation that Uniswap would be doing, we do an overabstraction: +// we allow more behavior that Uniswap would not allow, but these behaviors are still +// fine to ensure our property. We do this by non-deterministically generating the +// amounts (by not initializing the local variables). +function swapSummary(PoolManager.PoolId id, Pool.SwapParams swapParams, Pool.Currency inputCurrency) returns PoolManager.BalanceDelta { + // uninitialized amounts -> prover tries every possible swap + int128 amount0Delta; + int128 amount1Delta; + + // but we require some restrictions, so that trade corresponds to swapParams + if (swapParams.zeroForOne) { + // user should pay token0 (negative) and receive token1 (positive) + require amount0Delta <= 0 && amount1Delta >= 0, "swap token0 for token1"; + + // check that we did not overshoot the specified amount + if (swapParams.amountSpecified > 0) { + require amount1Delta <= swapParams.amountSpecified, "swap at most amount specified"; + } else { + require amount0Delta >= swapParams.amountSpecified, "swap at most amount specified"; + } + } else { + // user should pay token1 (negative) and receive token0 (positive) + require amount0Delta >= 0 && amount1Delta <= 0, "swap token1 for token0"; + + // check that we did not overshoot the specified amount + if (swapParams.amountSpecified > 0) { + require amount0Delta <= swapParams.amountSpecified, "swap at most amount specified"; + } else { + require amount1Delta >= swapParams.amountSpecified, "swap at most amount specified"; + } + } + + // update the pool balances. + // The pool receives what the user pays, so we subtract the amounts. + poolToken0Balance = poolToken0Balance - amount0Delta; + poolToken1Balance = poolToken1Balance - amount1Delta; + + // Check that the pool had enough tokens for the trade + require poolToken0Balance >= 0 && poolToken1Balance >= 0, "swap ensures that token balance cannot be negative"; + + // Return the amounts + return poolManager.computeBalanceDelta(amount0Delta, amount1Delta); +} + + // This summary simulates the modifyLiquidity function of the Uniswap Pool. Again, + // we do an overabstraction and allow more behavior that Uniswap would not allow. + // In this case we non-deterministically choose the number of tokens to pay to match the + // liquidity and the amount of fees that the user already earned for his position. + function modifyLiquiditySummary(Pool.ModifyLiquidityParams liquidityParams) returns (PoolManager.BalanceDelta, PoolManager.BalanceDelta) { + Pool.BalanceDelta liqDelta; + Pool.BalanceDelta feeDelta; + int128 liqDelta0; + int128 liqDelta1; + int128 feeDelta0; + int128 feeDelta1; + + // The user only receives fees, so feeDelta is always non-negative. + require feeDelta0 >=0 && feeDelta1 >= 0, "fee deltas must be non-negative"; + + // Check that the sign of liqDelta is the opposite of the sign of liquidityDelta: if the + // user puts in liquidity (positive), he should pay for liquidity in liqDelta (negative). + if (liquidityParams.liquidityDelta > 0) { + require liqDelta0 <= 0 && liqDelta1 <= 0, "liquidity deltas must be non-positive"; + } else if (liquidityParams.liquidityDelta == 0) { + require liqDelta0 == 0 && liqDelta1 == 0, "currencyDelta sign must match liquidityDelta sign"; + } else { + require liqDelta0 >= 0 && liqDelta1 >= 0, "liquidity deltas must be non-negative"; + } + + // update the pool balance, again the sign is flipped because we switch to the perspective + // of the pool. + poolToken0Balance = poolToken0Balance - liqDelta0 - feeDelta0; + poolToken1Balance = poolToken1Balance - liqDelta1 - feeDelta1; + + // There should be some tokens left in the pool, or uniswap is seriously broken. + // Note that we don't verify uniswap here, but we assume that uniswap works. + require poolToken0Balance >= 0 && poolToken1Balance >= 0, "uniswap is always solvent"; + + // Return the balance deltas. + liqDelta = poolManager.computeBalanceDelta(liqDelta0, liqDelta1); + feeDelta = poolManager.computeBalanceDelta(feeDelta0, feeDelta1); + return (liqDelta, feeDelta); +} + + +function initializeSummary(uint160 price, uint24 fee) returns int24 { + int24 tick; + require price > 0, "price must be positive"; + return tick; // nondeterministic tick +} diff --git a/CVLByExample/UniswapHooks/certora/specs/UniswapTake.spec b/CVLByExample/UniswapHooks/certora/specs/UniswapTake.spec new file mode 100644 index 00000000..bb0814e4 --- /dev/null +++ b/CVLByExample/UniswapHooks/certora/specs/UniswapTake.spec @@ -0,0 +1,11 @@ +methods { + function PoolManagerHarness.getDelta(PoolManagerHarness.Currency currency, address account) external returns (int256) envfree; +} + +rule take_accounting_ok(PoolManagerHarness.Currency currency, address to, uint256 amount) { + env e; + mathint before_take = getDelta(currency, e.msg.sender); + take(e, currency, to, amount); + mathint after_take = getDelta(currency, e.msg.sender); + assert after_take == before_take - amount; +} diff --git a/CVLByExample/UniswapHooks/certora/specs/extsload.spec b/CVLByExample/UniswapHooks/certora/specs/extsload.spec new file mode 100644 index 00000000..cfacdda3 --- /dev/null +++ b/CVLByExample/UniswapHooks/certora/specs/extsload.spec @@ -0,0 +1,20 @@ +methods { + function PoolManagerHarness.extsload(bytes32 slot) external returns (bytes32) => NONDET DELETE; + function PoolManagerHarness.extsload(bytes32[] slots) external returns (bytes32[] memory) => ArbBytes32(slots) DELETE; + function PoolManagerHarness.extsload(bytes32 startSlot, uint256 nSlots) external returns (bytes32[] memory) => ArbNBytes32(startSlot, nSlots) DELETE; + function PoolManagerHarness.exttload(bytes32 slot) external returns (bytes32) => NONDET DELETE; + function PoolManagerHarness.exttload(bytes32[] slots) external returns (bytes32[] memory) => ArbBytes32(slots) DELETE; +} + +function ArbBytes32(bytes32[] slots) returns bytes32[] { + bytes32[] data; + require data.length == slots.length; + return data; +} + +/// Returns an arbitrary bytes32 array of length nSlots. +function ArbNBytes32(bytes32 startSlot, uint256 nSlots) returns bytes32[] { + bytes32[] data; + require data.length == nSlots; + return data; +} diff --git a/CVLByExample/UniswapHooks/foundry.toml b/CVLByExample/UniswapHooks/foundry.toml new file mode 100644 index 00000000..ef230b2d --- /dev/null +++ b/CVLByExample/UniswapHooks/foundry.toml @@ -0,0 +1,10 @@ +[profile.default] +evm_version = "cancun" +ffi = true +fs_permissions = [{access = "read-write", path = ".forge-snapshots/"}] +libs = ["lib"] +out = "out" +solc_version = "0.8.28" +src = "src" + +# See more config options https://github.com/foundry-rs/foundry/tree/master/config diff --git a/CVLByExample/UniswapHooks/remappings.txt b/CVLByExample/UniswapHooks/remappings.txt new file mode 100644 index 00000000..3fbba38a --- /dev/null +++ b/CVLByExample/UniswapHooks/remappings.txt @@ -0,0 +1,13 @@ +forge-std/=lib/forge-std/src/ + +@uniswap/v4-core/=lib/uniswap-hooks/lib/v4-core/ +@uniswap/v4-periphery/=lib/uniswap-hooks/lib/v4-periphery/ +v4-core/=lib/uniswap-hooks/lib/v4-core/ +v4-periphery/=lib/uniswap-hooks/lib/v4-periphery/ + +@openzeppelin/uniswap-hooks/=lib/uniswap-hooks/ +@openzeppelin/contracts/=lib/uniswap-hooks/lib/v4-core/lib/openzeppelin-contracts/contracts/ + +hookmate/=lib/hookmate/src/ +permit2/=lib/uniswap-hooks/lib/v4-periphery/lib/permit2/ +solmate/=lib/uniswap-hooks/lib/v4-core/lib/solmate/ \ No newline at end of file diff --git a/CVLByExample/UniswapHooks/script/00_DeployHook.s.sol b/CVLByExample/UniswapHooks/script/00_DeployHook.s.sol new file mode 100644 index 00000000..4e64cf60 --- /dev/null +++ b/CVLByExample/UniswapHooks/script/00_DeployHook.s.sol @@ -0,0 +1,32 @@ +// SPDX-License-Identifier: MIT +pragma solidity ^0.8.26; + +import {Hooks} from "@uniswap/v4-core/src/libraries/Hooks.sol"; +import {HookMiner} from "@uniswap/v4-periphery/src/utils/HookMiner.sol"; + +import {BaseScript} from "./base/BaseScript.sol"; + +import {Counter} from "../src/Counter.sol"; + +/// @notice Mines the address and deploys the Counter.sol Hook contract +contract DeployHookScript is BaseScript { + function run() public { + // hook contracts must have specific flags encoded in the address + uint160 flags = uint160( + Hooks.BEFORE_SWAP_FLAG | Hooks.AFTER_SWAP_FLAG | Hooks.BEFORE_ADD_LIQUIDITY_FLAG + | Hooks.BEFORE_REMOVE_LIQUIDITY_FLAG + ); + + // Mine a salt that will produce a hook address with the correct flags + bytes memory constructorArgs = abi.encode(poolManager); + (address hookAddress, bytes32 salt) = + HookMiner.find(CREATE2_FACTORY, flags, type(Counter).creationCode, constructorArgs); + + // Deploy the hook using CREATE2 + vm.startBroadcast(); + Counter counter = new Counter{salt: salt}(poolManager); + vm.stopBroadcast(); + + require(address(counter) == hookAddress, "DeployHookScript: Hook Address Mismatch"); + } +} diff --git a/CVLByExample/UniswapHooks/script/01_CreatePoolAndAddLiquidity.s.sol b/CVLByExample/UniswapHooks/script/01_CreatePoolAndAddLiquidity.s.sol new file mode 100644 index 00000000..8a8163b7 --- /dev/null +++ b/CVLByExample/UniswapHooks/script/01_CreatePoolAndAddLiquidity.s.sol @@ -0,0 +1,90 @@ +// SPDX-License-Identifier: MIT +pragma solidity ^0.8.26; + +import {console2} from "forge-std/Script.sol"; +import {IERC20} from "forge-std/interfaces/IERC20.sol"; + +import {PoolKey} from "@uniswap/v4-core/src/types/PoolKey.sol"; +import {CurrencyLibrary, Currency} from "@uniswap/v4-core/src/types/Currency.sol"; +import {Actions} from "@uniswap/v4-periphery/src/libraries/Actions.sol"; +import {LiquidityAmounts} from "@uniswap/v4-core/test/utils/LiquidityAmounts.sol"; +import {TickMath} from "@uniswap/v4-core/src/libraries/TickMath.sol"; + +import {BaseScript} from "./base/BaseScript.sol"; +import {LiquidityHelpers} from "./base/LiquidityHelpers.sol"; + +contract CreatePoolAndAddLiquidityScript is BaseScript, LiquidityHelpers { + using CurrencyLibrary for Currency; + + ///////////////////////////////////// + // --- Configure These --- + ///////////////////////////////////// + + uint24 lpFee = 3000; // 0.30% + int24 tickSpacing = 60; + uint160 startingPrice = 2 ** 96; // Starting price, sqrtPriceX96; floor(sqrt(1) * 2^96) + + // --- liquidity position configuration --- // + uint256 public token0Amount = 100e18; + uint256 public token1Amount = 100e18; + + // range of the position, must be a multiple of tickSpacing + int24 tickLower; + int24 tickUpper; + ///////////////////////////////////// + + function run() external { + PoolKey memory poolKey = PoolKey({ + currency0: currency0, + currency1: currency1, + fee: lpFee, + tickSpacing: tickSpacing, + hooks: hookContract + }); + + bytes memory hookData = new bytes(0); + + int24 currentTick = TickMath.getTickAtSqrtPrice(startingPrice); + + tickLower = ((currentTick - 750 * tickSpacing) / tickSpacing) * tickSpacing; + tickUpper = ((currentTick + 750 * tickSpacing) / tickSpacing) * tickSpacing; + + // Converts token amounts to liquidity units + uint128 liquidity = LiquidityAmounts.getLiquidityForAmounts( + startingPrice, + TickMath.getSqrtPriceAtTick(tickLower), + TickMath.getSqrtPriceAtTick(tickUpper), + token0Amount, + token1Amount + ); + + // slippage limits + uint256 amount0Max = token0Amount + 1; + uint256 amount1Max = token1Amount + 1; + + (bytes memory actions, bytes[] memory mintParams) = _mintLiquidityParams( + poolKey, tickLower, tickUpper, liquidity, amount0Max, amount1Max, deployerAddress, hookData + ); + + // multicall parameters + bytes[] memory params = new bytes[](2); + + // Initialize Pool + params[0] = abi.encodeWithSelector(positionManager.initializePool.selector, poolKey, startingPrice, hookData); + + // Mint Liquidity + params[1] = abi.encodeWithSelector( + positionManager.modifyLiquidities.selector, abi.encode(actions, mintParams), block.timestamp + 3600 + ); + + // If the pool is an ETH pair, native tokens are to be transferred + uint256 valueToPass = currency0.isAddressZero() ? amount0Max : 0; + + vm.startBroadcast(); + tokenApprovals(); + + // Multicall to atomically create pool & add liquidity + positionManager.multicall{value: valueToPass}(params); + vm.stopBroadcast(); + } +} diff --git a/CVLByExample/UniswapHooks/script/02_AddLiquidity.s.sol b/CVLByExample/UniswapHooks/script/02_AddLiquidity.s.sol new file mode 100644 index 00000000..31a4445b --- /dev/null +++ b/CVLByExample/UniswapHooks/script/02_AddLiquidity.s.sol @@ -0,0 +1,90 @@ +// SPDX-License-Identifier: MIT +pragma solidity ^0.8.26; + +import {console2} from "forge-std/Script.sol"; +import {IERC20} from "forge-std/interfaces/IERC20.sol"; + +import {IPoolManager} from "@uniswap/v4-core/src/interfaces/IPoolManager.sol"; +import {PoolKey} from "@uniswap/v4-core/src/types/PoolKey.sol"; +import {CurrencyLibrary, Currency} from "@uniswap/v4-core/src/types/Currency.sol"; +import {Actions} from "@uniswap/v4-periphery/src/libraries/Actions.sol"; +import {LiquidityAmounts} from "@uniswap/v4-core/test/utils/LiquidityAmounts.sol"; +import {TickMath} from "@uniswap/v4-core/src/libraries/TickMath.sol"; +import {StateLibrary} from "@uniswap/v4-core/src/libraries/StateLibrary.sol"; + +import {BaseScript} from "./base/BaseScript.sol"; +import {LiquidityHelpers} from "./base/LiquidityHelpers.sol"; + +contract AddLiquidityScript is BaseScript, LiquidityHelpers { + using CurrencyLibrary for Currency; + using StateLibrary for IPoolManager; + + ///////////////////////////////////// + // --- Configure These --- + ///////////////////////////////////// + + uint24 lpFee = 3000; // 0.30% + int24 tickSpacing = 60; + + // --- liquidity position configuration --- // + uint256 public token0Amount = 1e18; + uint256 public token1Amount = 1e18; + + ///////////////////////////////////// + + int24 tickLower; + int24 tickUpper; + + function run() external { + PoolKey memory poolKey = PoolKey({ + currency0: currency0, + currency1: currency1, + fee: lpFee, + tickSpacing: tickSpacing, + hooks: hookContract + }); + bytes memory hookData = new bytes(0); + + (uint160 sqrtPriceX96,,,) = poolManager.getSlot0(poolKey.toId()); + + int24 currentTick = TickMath.getTickAtSqrtPrice(sqrtPriceX96); + + tickLower = ((currentTick - 1000 * tickSpacing) / tickSpacing) * tickSpacing; + tickUpper = ((currentTick + 1000 * tickSpacing) / tickSpacing) * tickSpacing; + + // Converts token amounts to liquidity units + uint128 liquidity = LiquidityAmounts.getLiquidityForAmounts( + sqrtPriceX96, + TickMath.getSqrtPriceAtTick(tickLower), + TickMath.getSqrtPriceAtTick(tickUpper), + token0Amount, + token1Amount + ); + + // slippage limits + uint256 amount0Max = token0Amount + 1 wei; + uint256 amount1Max = token1Amount + 1 wei; + + (bytes memory actions, bytes[] memory mintParams) = _mintLiquidityParams( + poolKey, tickLower, tickUpper, liquidity, amount0Max, amount1Max, deployerAddress, hookData + ); + + // multicall parameters + bytes[] memory params = new bytes[](1); + + // Mint Liquidity + params[0] = abi.encodeWithSelector( + positionManager.modifyLiquidities.selector, abi.encode(actions, mintParams), block.timestamp + 60 + ); + + // If the pool is an ETH pair, native tokens are to be transferred + uint256 valueToPass = currency0.isAddressZero() ? amount0Max : 0; + + vm.startBroadcast(); + tokenApprovals(); + + // Add liquidity to existing pool + positionManager.multicall{value: valueToPass}(params); + vm.stopBroadcast(); + } +} diff --git a/CVLByExample/UniswapHooks/script/03_Swap.s.sol b/CVLByExample/UniswapHooks/script/03_Swap.s.sol new file mode 100644 index 00000000..f3b1243a --- /dev/null +++ b/CVLByExample/UniswapHooks/script/03_Swap.s.sol @@ -0,0 +1,38 @@ +// SPDX-License-Identifier: MIT +pragma solidity ^0.8.26; + +import {PoolKey} from "@uniswap/v4-core/src/types/PoolKey.sol"; + +import {BaseScript} from "./base/BaseScript.sol"; + +contract SwapScript is BaseScript { + function run() external { + PoolKey memory poolKey = PoolKey({ + currency0: currency0, + currency1: currency1, + fee: 3000, + tickSpacing: 60, + hooks: hookContract // This must match the pool + }); + bytes memory hookData = new bytes(0); + + vm.startBroadcast(); + + // We'll approve both, just for testing. + token1.approve(address(swapRouter), type(uint256).max); + token0.approve(address(swapRouter), type(uint256).max); + + // Execute swap + swapRouter.swapExactTokensForTokens({ + amountIn: 1e18, + amountOutMin: 0, // Very bad, but we want to allow for unlimited price impact + zeroForOne: true, + poolKey: poolKey, + hookData: hookData, + receiver: address(this), + deadline: block.timestamp + 30 + }); + + vm.stopBroadcast(); + } +} diff --git a/CVLByExample/UniswapHooks/script/base/BaseScript.sol b/CVLByExample/UniswapHooks/script/base/BaseScript.sol new file mode 100644 index 00000000..be9fb9c2 --- /dev/null +++ b/CVLByExample/UniswapHooks/script/base/BaseScript.sol @@ -0,0 +1,71 @@ +// SPDX-License-Identifier: MIT +pragma solidity ^0.8.26; + +import {Script} from "forge-std/Script.sol"; +import {IERC20} from "forge-std/interfaces/IERC20.sol"; + +import {IHooks} from "@uniswap/v4-core/src/interfaces/IHooks.sol"; +import {Currency} from "@uniswap/v4-core/src/types/Currency.sol"; +import {IPoolManager} from "@uniswap/v4-core/src/interfaces/IPoolManager.sol"; +import {IPositionManager} from "@uniswap/v4-periphery/src/interfaces/IPositionManager.sol"; +import {IPermit2} from "permit2/src/interfaces/IPermit2.sol"; + +import {IUniswapV4Router04} from "hookmate/interfaces/router/IUniswapV4Router04.sol"; +import {AddressConstants} from "hookmate/constants/AddressConstants.sol"; + +/// @notice Shared configuration between scripts +contract BaseScript is Script { + IPermit2 immutable permit2 = IPermit2(0x000000000022D473030F116dDEE9F6B43aC78BA3); + IPoolManager immutable poolManager; + IPositionManager immutable positionManager; + IUniswapV4Router04 immutable swapRouter; + address immutable deployerAddress; + + ///////////////////////////////////// + // --- Configure These --- + ///////////////////////////////////// + IERC20 internal constant token0 = IERC20(0x0165878A594ca255338adfa4d48449f69242Eb8F); + IERC20 internal constant token1 = IERC20(0xa513E6E4b8f2a923D98304ec87F64353C4D5C853); + IHooks constant hookContract = IHooks(address(0)); + ///////////////////////////////////// + + Currency immutable currency0; + Currency immutable currency1; + + constructor() { + poolManager = IPoolManager(AddressConstants.getPoolManagerAddress(block.chainid)); + positionManager = IPositionManager(payable(AddressConstants.getPositionManagerAddress(block.chainid))); + swapRouter = IUniswapV4Router04(payable(AddressConstants.getV4SwapRouterAddress(block.chainid))); + + deployerAddress = getDeployer(); + + (currency0, currency1) = getCurrencies(); + + vm.label(address(token0), "Token0"); + vm.label(address(token1), "Token1"); + + vm.label(address(deployerAddress), "Deployer"); + vm.label(address(poolManager), "PoolManager"); + vm.label(address(positionManager), "PositionManager"); + vm.label(address(swapRouter), "SwapRouter"); + vm.label(address(hookContract), "HookContract"); + } + + function getCurrencies() public pure returns (Currency, Currency) { + require(address(token0) != address(token1)); + + if (token0 < token1) { + return (Currency.wrap(address(token0)), Currency.wrap(address(token1))); + } else { + return (Currency.wrap(address(token1)), Currency.wrap(address(token0))); + } + } + + function getDeployer() public returns (address) { + address[] memory wallets = vm.getWallets(); + + require(wallets.length > 0, "No wallets found"); + + return wallets[0]; + } +} diff --git a/CVLByExample/UniswapHooks/script/base/LiquidityHelpers.sol b/CVLByExample/UniswapHooks/script/base/LiquidityHelpers.sol new file mode 100644 index 00000000..b9ea11dd --- /dev/null +++ b/CVLByExample/UniswapHooks/script/base/LiquidityHelpers.sol @@ -0,0 +1,47 @@ +// SPDX-License-Identifier: MIT +pragma solidity ^0.8.20; + +import {PoolKey} from "@uniswap/v4-core/src/types/PoolKey.sol"; +import {CurrencyLibrary, Currency} from "@uniswap/v4-core/src/types/Currency.sol"; +import {Actions} from "@uniswap/v4-periphery/src/libraries/Actions.sol"; + +import {BaseScript} from "./BaseScript.sol"; + +contract LiquidityHelpers is BaseScript { + using CurrencyLibrary for Currency; + + function _mintLiquidityParams( + PoolKey memory poolKey, + int24 _tickLower, + int24 _tickUpper, + uint256 liquidity, + uint256 amount0Max, + uint256 amount1Max, + address recipient, + bytes memory hookData + ) internal pure returns (bytes memory, bytes[] memory) { + bytes memory actions = abi.encodePacked( + uint8(Actions.MINT_POSITION), uint8(Actions.SETTLE_PAIR), uint8(Actions.SWEEP), uint8(Actions.SWEEP) + ); + + bytes[] memory params = new bytes[](4); + params[0] = abi.encode(poolKey, _tickLower, _tickUpper, liquidity, amount0Max, amount1Max, recipient, hookData); + params[1] = abi.encode(poolKey.currency0, poolKey.currency1); + params[2] = abi.encode(poolKey.currency0, recipient); + params[3] = abi.encode(poolKey.currency1, recipient); + + return (actions, params); + } + + function tokenApprovals() public { + if (!currency0.isAddressZero()) { + token0.approve(address(permit2), type(uint256).max); + permit2.approve(address(token0), address(positionManager), type(uint160).max, type(uint48).max); + } + + if (!currency1.isAddressZero()) { + token1.approve(address(permit2), type(uint256).max); + permit2.approve(address(token1), address(positionManager), type(uint160).max, type(uint48).max); + } + } +} diff --git a/CVLByExample/UniswapHooks/src/Counter.sol b/CVLByExample/UniswapHooks/src/Counter.sol new file mode 100644 index 00000000..6bfe48ec --- /dev/null +++ b/CVLByExample/UniswapHooks/src/Counter.sol @@ -0,0 +1,87 @@ +// SPDX-License-Identifier: MIT +pragma solidity ^0.8.26; + +import {BaseHook} from "@openzeppelin/uniswap-hooks/src/base/BaseHook.sol"; + +import {Hooks} from "@uniswap/v4-core/src/libraries/Hooks.sol"; +import {IPoolManager, SwapParams, ModifyLiquidityParams} from "@uniswap/v4-core/src/interfaces/IPoolManager.sol"; +import {PoolKey} from "@uniswap/v4-core/src/types/PoolKey.sol"; +import {PoolId, PoolIdLibrary} from "@uniswap/v4-core/src/types/PoolId.sol"; +import {BalanceDelta} from "@uniswap/v4-core/src/types/BalanceDelta.sol"; +import {BeforeSwapDelta, BeforeSwapDeltaLibrary} from "@uniswap/v4-core/src/types/BeforeSwapDelta.sol"; + +contract Counter is BaseHook { + using PoolIdLibrary for PoolKey; + + // NOTE: --------------------------------------------------------- + // state variables should typically be unique to a pool + // a single hook contract should be able to service multiple pools + // --------------------------------------------------------------- + + mapping(PoolId => uint256 count) public beforeSwapCount; + mapping(PoolId => uint256 count) public afterSwapCount; + + mapping(PoolId => uint256 count) public beforeAddLiquidityCount; + mapping(PoolId => uint256 count) public beforeRemoveLiquidityCount; + + constructor(IPoolManager _poolManager) BaseHook(_poolManager) {} + + function getHookPermissions() public pure override returns (Hooks.Permissions memory) { + return Hooks.Permissions({ + beforeInitialize: false, + afterInitialize: false, + beforeAddLiquidity: true, + afterAddLiquidity: false, + beforeRemoveLiquidity: true, + afterRemoveLiquidity: false, + beforeSwap: true, + afterSwap: true, + beforeDonate: false, + afterDonate: false, + beforeSwapReturnDelta: false, + afterSwapReturnDelta: false, + afterAddLiquidityReturnDelta: false, + afterRemoveLiquidityReturnDelta: false + }); + } + + // ----------------------------------------------- + // NOTE: see IHooks.sol for function documentation + // ----------------------------------------------- + + function _beforeSwap(address, PoolKey calldata key, SwapParams calldata, bytes calldata) + internal + override + returns (bytes4, BeforeSwapDelta, uint24) + { + beforeSwapCount[key.toId()]++; + return (BaseHook.beforeSwap.selector, BeforeSwapDeltaLibrary.ZERO_DELTA, 0); + } + + function _afterSwap(address, PoolKey calldata key, SwapParams calldata, BalanceDelta, bytes calldata) + internal + override + returns (bytes4, int128) + { + afterSwapCount[key.toId()]++; + return (BaseHook.afterSwap.selector, 0); + } + + function _beforeAddLiquidity(address, PoolKey calldata key, ModifyLiquidityParams calldata, bytes calldata) + internal + override + returns (bytes4) + { + beforeAddLiquidityCount[key.toId()]++; + return BaseHook.beforeAddLiquidity.selector; + } + + function _beforeRemoveLiquidity(address, PoolKey calldata key, ModifyLiquidityParams calldata, bytes calldata) + internal + override + returns (bytes4) + { + beforeRemoveLiquidityCount[key.toId()]++; + return BaseHook.beforeRemoveLiquidity.selector; + } +} diff --git a/CVLByExample/UniswapHooks/src/PointsHook.sol b/CVLByExample/UniswapHooks/src/PointsHook.sol new file mode 100644 index 00000000..1b378851 --- /dev/null +++ b/CVLByExample/UniswapHooks/src/PointsHook.sol @@ -0,0 +1,130 @@ +// SPDX-License-Identifier: MIT +pragma solidity ^0.8.24; + +import {BaseHook} from "v4-periphery/src/utils/BaseHook.sol"; + +import {Hooks} from "v4-core/src/libraries/Hooks.sol"; +import {IPoolManager, SwapParams, ModifyLiquidityParams} from "v4-core/src/interfaces/IPoolManager.sol"; +import {PoolKey} from "v4-core/src/types/PoolKey.sol"; +import {PoolId, PoolIdLibrary} from "v4-core/src/types/PoolId.sol"; +import {BalanceDelta, toBalanceDelta} from "v4-core/src/types/BalanceDelta.sol"; +import {BeforeSwapDelta, BeforeSwapDeltaLibrary} from "v4-core/src/types/BeforeSwapDelta.sol"; + +import {PointsToken} from "./PointsToken.sol"; + +contract PointsHook is BaseHook { + PointsToken public pointsToken; + constructor(IPoolManager _poolManager) BaseHook(_poolManager) { + pointsToken = new PointsToken(); + } + + function getHookPermissions() + public + pure + override + returns (Hooks.Permissions memory) + { + return + Hooks.Permissions({ + beforeInitialize: true, + afterInitialize: false, + beforeAddLiquidity: false, + afterAddLiquidity: true, + beforeRemoveLiquidity: false, + afterRemoveLiquidity: false, + beforeSwap: false, + afterSwap: true, + beforeDonate: false, + afterDonate: false, + beforeSwapReturnDelta: false, + afterSwapReturnDelta: false, + afterAddLiquidityReturnDelta: false, + afterRemoveLiquidityReturnDelta: false + }); + } + + function _beforeInitialize( + address sender, + PoolKey calldata key, + uint160 sqrtPriceX96 + ) internal override returns (bytes4) { + require (key.currency0.isAddressZero(), "Only ETH/TOKEN pools allowed"); + return BaseHook.beforeInitialize.selector; + } + + + function _afterSwap( + address sender, + PoolKey calldata key, + SwapParams calldata swapParams, + BalanceDelta delta, + bytes calldata hookData + ) internal override returns (bytes4, int128) { + // We only award points in the ETH/TOKEN pools. + if (!key.currency0.isAddressZero()) { + return (BaseHook.afterSwap.selector, 0); + } + + // We only award points if the user is buying the TOKEN + if (!swapParams.zeroForOne) { + return (BaseHook.afterSwap.selector, 0); + } + + // Let's figure out who's the user + address user = parseHookData(hookData); + + // How much ETH are they spending? + uint256 ethSpendAmount = uint256(int256(-delta.amount0())); + + // And award the points! + awardPoints(user, ethSpendAmount); + + return (BaseHook.afterSwap.selector, 0); + } + + function _afterAddLiquidity( + address sender, + PoolKey calldata key, + ModifyLiquidityParams calldata params, + BalanceDelta delta, + BalanceDelta feesAccrued, + bytes calldata hookData + ) internal override returns (bytes4, BalanceDelta) { + // We only award points in the ETH/TOKEN pools. + if (!key.currency0.isAddressZero()) { + return (BaseHook.afterAddLiquidity.selector, delta); + } + + // Let's figure out who's the user + address user = parseHookData(hookData); + + // How much ETH are they spending? + if (delta.amount0() < 0) { + uint256 ethSpendAmount = uint256(int256(-delta.amount0())); + + // And award the points! + awardPoints(user, ethSpendAmount); + } + return (BaseHook.afterAddLiquidity.selector, toBalanceDelta(0,0)); + } + + function getPointsForAmount( + uint256 amount + ) internal pure returns (uint256) { + return amount; // 1:1 with ETH + } + + function awardPoints(address to, uint256 amount) internal { + pointsToken.mint(to, getPointsForAmount(amount)); + } + + function getHookData(address user) public pure returns (bytes memory) { + return abi.encode(user); + } + + function parseHookData( + bytes calldata data + ) public pure returns (address user) { + return abi.decode(data, (address)); + } +} diff --git a/CVLByExample/UniswapHooks/src/PointsToken.sol b/CVLByExample/UniswapHooks/src/PointsToken.sol new file mode 100644 index 00000000..67dd17fa --- /dev/null +++ b/CVLByExample/UniswapHooks/src/PointsToken.sol @@ -0,0 +1,13 @@ +// SPDX-License-Identifier: MIT +pragma solidity ^0.8.24; + +import {ERC20} from "@openzeppelin/contracts/token/ERC20/ERC20.sol"; +import {Ownable} from "@openzeppelin/contracts/access/Ownable.sol"; + +contract PointsToken is ERC20, Ownable { + constructor() ERC20("Points Token", "POINTS") Ownable(msg.sender) {} + + function mint(address to, uint256 amount) external onlyOwner { + _mint(to, amount); + } +} diff --git a/CVLByExample/UniswapHooks/test/Counter.t.sol b/CVLByExample/UniswapHooks/test/Counter.t.sol new file mode 100644 index 00000000..15d7de80 --- /dev/null +++ b/CVLByExample/UniswapHooks/test/Counter.t.sol @@ -0,0 +1,138 @@ +// SPDX-License-Identifier: MIT +pragma solidity ^0.8.26; + +import {Test, console2} from "forge-std/Test.sol"; + +import {IHooks} from "@uniswap/v4-core/src/interfaces/IHooks.sol"; +import {Hooks} from "@uniswap/v4-core/src/libraries/Hooks.sol"; +import {TickMath} from "@uniswap/v4-core/src/libraries/TickMath.sol"; +import {IPoolManager} from "@uniswap/v4-core/src/interfaces/IPoolManager.sol"; +import {PoolKey} from "@uniswap/v4-core/src/types/PoolKey.sol"; +import {BalanceDelta} from "@uniswap/v4-core/src/types/BalanceDelta.sol"; +import {PoolId, PoolIdLibrary} from "@uniswap/v4-core/src/types/PoolId.sol"; +import {CurrencyLibrary, Currency} from "@uniswap/v4-core/src/types/Currency.sol"; +import {PoolSwapTest} from "@uniswap/v4-core/src/test/PoolSwapTest.sol"; +import {StateLibrary} from "@uniswap/v4-core/src/libraries/StateLibrary.sol"; +import {LiquidityAmounts} from "@uniswap/v4-core/test/utils/LiquidityAmounts.sol"; +import {IPositionManager} from "@uniswap/v4-periphery/src/interfaces/IPositionManager.sol"; +import {Constants} from "@uniswap/v4-core/test/utils/Constants.sol"; + +import {EasyPosm} from "./utils/libraries/EasyPosm.sol"; +import {Deployers} from "./utils/Deployers.sol"; + +import {Counter} from "../src/Counter.sol"; + +contract CounterTest is Test, Deployers { + using EasyPosm for IPositionManager; + using PoolIdLibrary for PoolKey; + using CurrencyLibrary for Currency; + using StateLibrary for IPoolManager; + + Currency currency0; + Currency currency1; + + PoolKey poolKey; + + Counter hook; + PoolId poolId; + + uint256 tokenId; + int24 tickLower; + int24 tickUpper; + + function setUp() public { + // Deploys all required artifacts. + deployArtifacts(); + + (currency0, currency1) = deployCurrencyPair(); + + // Deploy the hook to an address with the correct flags + address flags = address( + uint160( + Hooks.BEFORE_SWAP_FLAG | Hooks.AFTER_SWAP_FLAG | Hooks.BEFORE_ADD_LIQUIDITY_FLAG + | Hooks.BEFORE_REMOVE_LIQUIDITY_FLAG + ) ^ (0x4444 << 144) // Namespace the hook to avoid collisions + ); + bytes memory constructorArgs = abi.encode(poolManager); // Add all the necessary constructor arguments from the hook + deployCodeTo("Counter.sol:Counter", constructorArgs, flags); + hook = Counter(flags); + + // Create the pool + poolKey = PoolKey(currency0, currency1, 3000, 60, IHooks(hook)); + poolId = poolKey.toId(); + poolManager.initialize(poolKey, Constants.SQRT_PRICE_1_1); + + // Provide full-range liquidity to the pool + tickLower = TickMath.minUsableTick(poolKey.tickSpacing); + tickUpper = TickMath.maxUsableTick(poolKey.tickSpacing); + + uint128 liquidityAmount = 100e18; + + (uint256 amount0Expected, uint256 amount1Expected) = LiquidityAmounts.getAmountsForLiquidity( + Constants.SQRT_PRICE_1_1, + TickMath.getSqrtPriceAtTick(tickLower), + TickMath.getSqrtPriceAtTick(tickUpper), + liquidityAmount + ); + + (tokenId,) = positionManager.mint( + poolKey, + tickLower, + tickUpper, + liquidityAmount, + amount0Expected + 1, + amount1Expected + 1, + address(this), + block.timestamp, + Constants.ZERO_BYTES + ); + } + + function testCounterHooks() public { + // positions were created in setup() + assertEq(hook.beforeAddLiquidityCount(poolId), 1); + assertEq(hook.beforeRemoveLiquidityCount(poolId), 0); + + assertEq(hook.beforeSwapCount(poolId), 0); + assertEq(hook.afterSwapCount(poolId), 0); + + // Perform a test swap // + uint256 amountIn = 1e18; + BalanceDelta swapDelta = swapRouter.swapExactTokensForTokens({ + amountIn: amountIn, + amountOutMin: 0, // Very bad, but we want to allow for unlimited price impact + zeroForOne: true, + poolKey: poolKey, + hookData: Constants.ZERO_BYTES, + receiver: address(this), + deadline: block.timestamp + 1 + }); + // ------------------- // + + assertEq(int256(swapDelta.amount0()), -int256(amountIn)); + + assertEq(hook.beforeSwapCount(poolId), 1); + assertEq(hook.afterSwapCount(poolId), 1); + } + + function testLiquidityHooks() public { + // positions were created in setup() + assertEq(hook.beforeAddLiquidityCount(poolId), 1); + assertEq(hook.beforeRemoveLiquidityCount(poolId), 0); + + // remove liquidity + uint256 liquidityToRemove = 1e18; + positionManager.decreaseLiquidity( + tokenId, + liquidityToRemove, + 0, // Max slippage, token0 + 0, // Max slippage, token1 + address(this), + block.timestamp, + Constants.ZERO_BYTES + ); + + assertEq(hook.beforeAddLiquidityCount(poolId), 1); + assertEq(hook.beforeRemoveLiquidityCount(poolId), 1); + } +} diff --git a/CVLByExample/UniswapHooks/test/PointsHookTest.sol b/CVLByExample/UniswapHooks/test/PointsHookTest.sol new file mode 100644 index 00000000..50b45662 --- /dev/null +++ b/CVLByExample/UniswapHooks/test/PointsHookTest.sol @@ -0,0 +1,92 @@ +// SPDX-License-Identifier: MIT +pragma solidity ^0.8.24; +import {Test} from "forge-std/Test.sol"; +import {Deployers} from "./utils/Deployers.sol"; +import {IPoolManager} from "v4-core/src/interfaces/IPoolManager.sol"; +import {IPositionManager} from "v4-periphery/src/interfaces/IPositionManager.sol"; +import {EasyPosm} from "./utils/libraries/EasyPosm.sol"; +import {StateLibrary} from "v4-core/src/libraries/StateLibrary.sol"; +import {PointsHook} from "../src/PointsHook.sol"; +import {PointsToken} from "../src/PointsToken.sol"; +import {PoolKey} from "v4-core/src/types/PoolKey.sol"; +import {PoolId} from "v4-core/src/types/PoolId.sol"; +import {TickMath} from "v4-core/src/libraries/TickMath.sol"; +import {Hooks} from "v4-core/src/libraries/Hooks.sol"; +import {Constants} from "v4-core/test/utils/Constants.sol"; +import {Currency} from "v4-core/src/types/Currency.sol"; +import {IHooks} from "v4-core/src/interfaces/IHooks.sol"; +import {LiquidityAmounts} from "v4-core/test/utils/LiquidityAmounts.sol"; + +contract PointsHookTest is Test, Deployers { + using EasyPosm for IPositionManager; + using StateLibrary for IPoolManager; + + PointsHook hook; + PointsToken pointsToken; + PoolKey key; + PoolId poolId; + + uint256 tokenId; + int24 tickLower; + int24 tickUpper; + Currency currency0; + Currency currency1; + + function setUp() public { + // creates the pool manager, utility routers, and test tokens + deployArtifacts(); + + (currency0, currency1) = deployCurrencyPair(); + + // Deploy the hook to an address with the correct flags + address flags = address( + uint160(Hooks.AFTER_SWAP_FLAG | Hooks.AFTER_ADD_LIQUIDITY_FLAG) ^ + (0x4444 << 144) // Namespace the hook to avoid collisions + ); + bytes memory constructorArgs = abi.encode(poolManager); //Add all the necessary constructor arguments from the hook + deployCodeTo("PointsHook.sol:PointsHook", constructorArgs, flags); + hook = PointsHook(flags); + pointsToken = hook.pointsToken(); + + // Create the pool + key = PoolKey( + Currency.wrap(address(0)), + currency1, + 3000, + 60, + IHooks(hook) + ); + poolId = key.toId(); + poolManager.initialize(key, Constants.SQRT_PRICE_1_1); + + // Provide full-range liquidity to the pool + tickLower = TickMath.minUsableTick(key.tickSpacing); + tickUpper = TickMath.maxUsableTick(key.tickSpacing); + + deal(address(this), 200 ether); + + (uint256 amount0, uint256 amount1) = LiquidityAmounts + .getAmountsForLiquidity( + Constants.SQRT_PRICE_1_1, + TickMath.getSqrtPriceAtTick(tickLower), + TickMath.getSqrtPriceAtTick(tickUpper), + uint128(100e18) + ); + + (tokenId, ) = positionManager.mint( + key, + tickLower, + tickUpper, + 100e18, + amount0 + 1, + amount1 + 1, + address(this), + block.timestamp, + hook.getHookData(address(this)) + ); + } + + function test_PointsHook_Swap() public { + // [code here] + } +} diff --git a/CVLByExample/UniswapHooks/test/utils/Deployers.sol b/CVLByExample/UniswapHooks/test/utils/Deployers.sol new file mode 100644 index 00000000..9981730e --- /dev/null +++ b/CVLByExample/UniswapHooks/test/utils/Deployers.sol @@ -0,0 +1,123 @@ +// SPDX-License-Identifier: UNLICENSED +pragma solidity ^0.8.26; + +import {Test, console2} from "forge-std/Test.sol"; + +import {MockERC20} from "solmate/src/test/utils/mocks/MockERC20.sol"; + +import {Currency} from "@uniswap/v4-core/src/types/Currency.sol"; + +import {IPermit2} from "permit2/src/interfaces/IPermit2.sol"; +import {IPoolManager} from "@uniswap/v4-core/src/interfaces/IPoolManager.sol"; +import {IPositionManager} from "@uniswap/v4-periphery/src/interfaces/IPositionManager.sol"; + +import {IUniswapV4Router04} from "hookmate/interfaces/router/IUniswapV4Router04.sol"; +import {AddressConstants} from "hookmate/constants/AddressConstants.sol"; + +import {Permit2Deployer} from "hookmate/artifacts/Permit2.sol"; +import {V4PoolManagerDeployer} from "hookmate/artifacts/V4PoolManager.sol"; +import {V4PositionManagerDeployer} from "hookmate/artifacts/V4PositionManager.sol"; +import {V4RouterDeployer} from "hookmate/artifacts/V4Router.sol"; + +/** + * Base Deployer Contract for Hook Testing + * + * Automatically does the following: + * 1. Setup deployments for Permit2, PoolManager, PositionManager and V4SwapRouter. + * 2. Check if chainId is 31337, is so, deploys local instances. + * 3. If not, uses existing canonical deployments on the selected network. + * 4. Provides utility functions to deploy tokens and currency pairs. + * + * This contract can be used for both local testing and fork testing. + */ +contract Deployers is Test { + IPermit2 permit2; + IPoolManager poolManager; + IPositionManager positionManager; + IUniswapV4Router04 swapRouter; + + function deployToken() internal returns (MockERC20 token) { + token = new MockERC20("Test Token", "TEST", 18); + token.mint(address(this), 10_000_000 ether); + + token.approve(address(permit2), type(uint256).max); + token.approve(address(swapRouter), type(uint256).max); + + permit2.approve(address(token), address(positionManager), type(uint160).max, type(uint48).max); + permit2.approve(address(token), address(poolManager), type(uint160).max, type(uint48).max); + } + + function deployCurrencyPair() internal returns (Currency currency0, Currency currency1) { + MockERC20 token0 = deployToken(); + MockERC20 token1 = deployToken(); + + if (token0 > token1) { + (token0, token1) = (token1, token0); + } + + currency0 = Currency.wrap(address(token0)); + currency1 = Currency.wrap(address(token1)); + + vm.label(address(token0), "Currency0"); + vm.label(address(token1), "Currency1"); + } + + function deployPermit2() internal { + address permit2Address = AddressConstants.getPermit2Address(); + + if (permit2Address.code.length > 0) { + // Permit2 is already deployed, no need to etch it. + } else { + address tempDeployAddress = address(Permit2Deployer.deploy()); + + vm.etch(permit2Address, tempDeployAddress.code); + } + + permit2 = IPermit2(permit2Address); + vm.label(permit2Address, "Permit2"); + } + + function deployPoolManager() internal { + if (block.chainid == 31337) { + poolManager = IPoolManager(address(V4PoolManagerDeployer.deploy(address(0x4444)))); + } else { + poolManager = IPoolManager(AddressConstants.getPoolManagerAddress(block.chainid)); + } + + vm.label(address(poolManager), "V4PoolManager"); + } + + function deployPositionManager() internal { + if (block.chainid == 31337) { + positionManager = IPositionManager( + address( + V4PositionManagerDeployer.deploy( + address(poolManager), address(permit2), 300_000, address(0), address(0) + ) + ) + ); + } else { + positionManager = IPositionManager(AddressConstants.getPositionManagerAddress(block.chainid)); + } + + vm.label(address(positionManager), "V4PositionManager"); + } + + function deployRouter() internal { + if (block.chainid == 31337) { + swapRouter = IUniswapV4Router04(payable(V4RouterDeployer.deploy(address(poolManager), address(permit2)))); + } else { + swapRouter = IUniswapV4Router04(payable(AddressConstants.getV4SwapRouterAddress(block.chainid))); + } + + vm.label(address(swapRouter), "V4SwapRouter"); + } + + function deployArtifacts() internal { + // Order matters. + deployPermit2(); + deployPoolManager(); + deployPositionManager(); + deployRouter(); + } +} diff --git a/CVLByExample/UniswapHooks/test/utils/libraries/EasyPosm.sol b/CVLByExample/UniswapHooks/test/utils/libraries/EasyPosm.sol new file mode 100644 index 00000000..d49d6a43 --- /dev/null +++ b/CVLByExample/UniswapHooks/test/utils/libraries/EasyPosm.sol @@ -0,0 +1,203 @@ +// SPDX-License-Identifier: GPL-2.0-or-later +pragma solidity ^0.8.21; + +import {PoolKey} from "v4-core/src/types/PoolKey.sol"; +import {BalanceDelta, toBalanceDelta} from "v4-core/src/types/BalanceDelta.sol"; +import {Currency, CurrencyLibrary} from "v4-core/src/types/Currency.sol"; +import {IPositionManager} from "@uniswap/v4-periphery/src/interfaces/IPositionManager.sol"; +import {Actions} from "@uniswap/v4-periphery/src/libraries/Actions.sol"; +import {SafeCast} from "@uniswap/v4-core/src/libraries/SafeCast.sol"; +import {PositionInfo, PositionInfoLibrary} from "@uniswap/v4-periphery/src/libraries/PositionInfoLibrary.sol"; + +/// @title Easy Position Manager +/// @notice A library for abstracting Position Manager calldata +/// @dev Useable onchain, but expensive because of encoding +library EasyPosm { + using CurrencyLibrary for Currency; + using SafeCast for uint256; + using SafeCast for int256; + using PositionInfoLibrary for PositionInfo; + + /// @dev packing data to avoid stack too deep error + struct MintData { + uint256 balance0Before; + uint256 balance1Before; + bytes[] params; + bytes actions; + } + + /// @dev This function supports sending native tokens (ETH), the amount-to-pay is determined by amount0Max. + /// Any excess amount is NOT refunded since it is not encoding the SWEEP action + function mint( + IPositionManager posm, + PoolKey memory poolKey, + int24 tickLower, + int24 tickUpper, + uint256 liquidity, + uint256 amount0Max, + uint256 amount1Max, + address recipient, + uint256 deadline, + bytes memory hookData + ) internal returns (uint256 tokenId, BalanceDelta delta) { + (Currency currency0, Currency currency1) = (poolKey.currency0, poolKey.currency1); + + MintData memory mintData = MintData({ + balance0Before: currency0.balanceOf(address(this)), + balance1Before: currency1.balanceOf(address(this)), + actions: new bytes(0), + params: new bytes[](4) + }); + + mintData.actions = abi.encodePacked( + uint8(Actions.MINT_POSITION), uint8(Actions.SETTLE_PAIR), uint8(Actions.SWEEP), uint8(Actions.SWEEP) + ); + + mintData.params[0] = + abi.encode(poolKey, tickLower, tickUpper, liquidity, amount0Max, amount1Max, recipient, hookData); + mintData.params[1] = abi.encode(currency0, currency1); + mintData.params[2] = abi.encode(currency0, recipient); + mintData.params[3] = abi.encode(currency1, recipient); + + // Mint Liquidity + tokenId = posm.nextTokenId(); + uint256 valueToPass = currency0.isAddressZero() ? amount0Max : 0; + posm.modifyLiquidities{value: valueToPass}(abi.encode(mintData.actions, mintData.params), deadline); + + delta = toBalanceDelta( + -(mintData.balance0Before - currency0.balanceOf(address(this))).toInt128(), + -(mintData.balance1Before - currency1.balanceOf(address(this))).toInt128() + ); + } + + function increaseLiquidity( + IPositionManager posm, + uint256 tokenId, + uint256 liquidityToAdd, + uint256 amount0Max, + uint256 amount1Max, + uint256 deadline, + bytes memory hookData + ) internal returns (BalanceDelta delta) { + (Currency currency0, Currency currency1) = getCurrencies(posm, tokenId); + + bytes[] memory params = new bytes[](3); + params[0] = abi.encode(tokenId, liquidityToAdd, amount0Max, amount1Max, hookData); + params[1] = abi.encode(currency0); + params[2] = abi.encode(currency1); + + uint256 balance0Before = currency0.balanceOf(address(this)); + uint256 balance1Before = currency1.balanceOf(address(this)); + + uint256 valueToPass = currency0.isAddressZero() ? amount0Max : 0; + posm.modifyLiquidities{value: valueToPass}( + abi.encode( + abi.encodePacked( + uint8(Actions.INCREASE_LIQUIDITY), uint8(Actions.CLOSE_CURRENCY), uint8(Actions.CLOSE_CURRENCY) + ), + params + ), + deadline + ); + + delta = toBalanceDelta( + (currency0.balanceOf(address(this)).toInt256() - balance0Before.toInt256()).toInt128(), + (currency1.balanceOf(address(this)).toInt256() - balance1Before.toInt256()).toInt128() + ); + } + + function decreaseLiquidity( + IPositionManager posm, + uint256 tokenId, + uint256 liquidityToRemove, + uint256 amount0Min, + uint256 amount1Min, + address recipient, + uint256 deadline, + bytes memory hookData + ) internal returns (BalanceDelta delta) { + (Currency currency0, Currency currency1) = getCurrencies(posm, tokenId); + + bytes[] memory params = new bytes[](2); + params[0] = abi.encode(tokenId, liquidityToRemove, amount0Min, amount1Min, hookData); + params[1] = abi.encode(currency0, currency1, recipient); + + uint256 balance0Before = currency0.balanceOf(address(this)); + uint256 balance1Before = currency1.balanceOf(address(this)); + + posm.modifyLiquidities( + abi.encode(abi.encodePacked(uint8(Actions.DECREASE_LIQUIDITY), uint8(Actions.TAKE_PAIR)), params), deadline + ); + + delta = toBalanceDelta( + (currency0.balanceOf(address(this)) - balance0Before).toInt128(), + (currency1.balanceOf(address(this)) - balance1Before).toInt128() + ); + } + + function collect( + IPositionManager posm, + uint256 tokenId, + uint256 amount0Min, + uint256 amount1Min, + address recipient, + uint256 deadline, + bytes memory hookData + ) internal returns (BalanceDelta delta) { + (Currency currency0, Currency currency1) = getCurrencies(posm, tokenId); + + bytes[] memory params = new bytes[](2); + // collecting fees is achieved by decreasing liquidity with 0 liquidity removed + params[0] = abi.encode(tokenId, 0, amount0Min, amount1Min, hookData); + params[1] = abi.encode(currency0, currency1, recipient); + + uint256 balance0Before = currency0.balanceOf(recipient); + uint256 balance1Before = currency1.balanceOf(recipient); + + posm.modifyLiquidities( + abi.encode(abi.encodePacked(uint8(Actions.DECREASE_LIQUIDITY), uint8(Actions.TAKE_PAIR)), params), deadline + ); + + delta = toBalanceDelta( + (currency0.balanceOf(recipient) - balance0Before).toInt128(), + (currency1.balanceOf(recipient) - balance1Before).toInt128() + ); + } + + function burn( + IPositionManager posm, + uint256 tokenId, + uint256 amount0Min, + uint256 amount1Min, + address recipient, + uint256 deadline, + bytes memory hookData + ) internal returns (BalanceDelta delta) { + (Currency currency0, Currency currency1) = getCurrencies(posm, tokenId); + + bytes[] memory params = new bytes[](2); + params[0] = abi.encode(tokenId, 0, amount0Min, amount1Min, hookData); + params[1] = abi.encode(currency0, currency1, recipient); + + uint256 balance0Before = currency0.balanceOf(recipient); + uint256 balance1Before = currency1.balanceOf(recipient); + + posm.modifyLiquidities( + abi.encode(abi.encodePacked(uint8(Actions.BURN_POSITION), uint8(Actions.TAKE_PAIR)), params), deadline + ); + + delta = toBalanceDelta( + (currency0.balanceOf(recipient) - balance0Before).toInt128(), + (currency1.balanceOf(recipient) - balance1Before).toInt128() + ); + } + + function getCurrencies(IPositionManager posm, uint256 tokenId) + internal + view + returns (Currency currency0, Currency currency1) + { + (PoolKey memory key,) = posm.getPoolAndPositionInfo(tokenId); + return (key.currency0, key.currency1); + } +} diff --git a/CVLByExample/UniswapHooks/test/utils/libraries/EasyPosm.t.sol b/CVLByExample/UniswapHooks/test/utils/libraries/EasyPosm.t.sol new file mode 100644 index 00000000..9a6a6f3f --- /dev/null +++ b/CVLByExample/UniswapHooks/test/utils/libraries/EasyPosm.t.sol @@ -0,0 +1,251 @@ +// SPDX-License-Identifier: MIT +pragma solidity ^0.8.26; + +import {Test} from "forge-std/Test.sol"; +import {IHooks} from "@uniswap/v4-core/src/interfaces/IHooks.sol"; +import {Hooks} from "@uniswap/v4-core/src/libraries/Hooks.sol"; +import {TickMath} from "@uniswap/v4-core/src/libraries/TickMath.sol"; +import {IPoolManager} from "@uniswap/v4-core/src/interfaces/IPoolManager.sol"; +import {PoolKey} from "@uniswap/v4-core/src/types/PoolKey.sol"; +import {BalanceDelta} from "@uniswap/v4-core/src/types/BalanceDelta.sol"; +import {PoolId, PoolIdLibrary} from "@uniswap/v4-core/src/types/PoolId.sol"; +import {CurrencyLibrary, Currency} from "@uniswap/v4-core/src/types/Currency.sol"; +import {StateLibrary} from "@uniswap/v4-core/src/libraries/StateLibrary.sol"; +import {LiquidityAmounts} from "@uniswap/v4-core/test/utils/LiquidityAmounts.sol"; +import {IPositionManager} from "@uniswap/v4-periphery/src/interfaces/IPositionManager.sol"; +import {Constants} from "@uniswap/v4-core/test/utils/Constants.sol"; + +import {EasyPosm} from "./EasyPosm.sol"; + +import {Deployers} from "../Deployers.sol"; + +contract EasyPosmTest is Test, Deployers { + using EasyPosm for IPositionManager; + using PoolIdLibrary for PoolKey; + using CurrencyLibrary for Currency; + using StateLibrary for IPoolManager; + + Currency currency0; + Currency currency1; + + int24 tickLower; + int24 tickUpper; + + PoolKey key; + PoolKey nativeKey; + + function setUp() public { + deployArtifacts(); + + (currency0, currency1) = deployCurrencyPair(); + + // Create the pool + key = PoolKey(currency0, currency1, 3000, 60, IHooks(address(0))); + nativeKey = PoolKey(Currency.wrap(address(0)), currency1, 3000, 60, IHooks(address(0))); + + poolManager.initialize(key, Constants.SQRT_PRICE_1_1); + poolManager.initialize(nativeKey, Constants.SQRT_PRICE_1_1); + + // full-range liquidity + tickLower = TickMath.minUsableTick(key.tickSpacing); + tickUpper = TickMath.maxUsableTick(key.tickSpacing); + } + + function test_mintLiquidity() public { + uint256 liquidityToMint = 100e18; + address recipient = address(this); + + (uint256 amount0, uint256 amount1) = LiquidityAmounts.getAmountsForLiquidity( + Constants.SQRT_PRICE_1_1, + TickMath.getSqrtPriceAtTick(tickLower), + TickMath.getSqrtPriceAtTick(tickUpper), + uint128(liquidityToMint) + ); + + (, BalanceDelta delta) = positionManager.mint( + key, + tickLower, + tickUpper, + liquidityToMint, + type(uint256).max, + type(uint256).max, + recipient, + block.timestamp + 1, + Constants.ZERO_BYTES + ); + assertEq(delta.amount0(), -int128(uint128(amount0 + 1 wei))); + assertEq(delta.amount1(), -int128(uint128(amount1 + 1 wei))); + } + + function test_mintLiquidityNative() public { + uint256 liquidityToMint = 100e18; + address recipient = address(this); + + (uint256 amount0, uint256 amount1) = LiquidityAmounts.getAmountsForLiquidity( + Constants.SQRT_PRICE_1_1, + TickMath.getSqrtPriceAtTick(tickLower), + TickMath.getSqrtPriceAtTick(tickUpper), + uint128(liquidityToMint) + ); + + vm.deal(address(this), amount0 + 1); + (, BalanceDelta delta) = positionManager.mint( + nativeKey, + tickLower, + tickUpper, + liquidityToMint, + amount0 + 1, + amount1 + 1, + recipient, + block.timestamp + 1, + Constants.ZERO_BYTES + ); + assertEq(delta.amount0(), -int128(uint128(amount0 + 1 wei))); + assertEq(delta.amount1(), -int128(uint128(amount1 + 1 wei))); + } + + function test_increaseLiquidity() public { + (uint256 tokenId,) = positionManager.mint( + key, + tickLower, + tickUpper, + 100e18, + type(uint256).max, + type(uint256).max, + address(this), + block.timestamp + 1, + Constants.ZERO_BYTES + ); + + uint256 liquidityToAdd = 1e18; + + (uint256 amount0, uint256 amount1) = LiquidityAmounts.getAmountsForLiquidity( + Constants.SQRT_PRICE_1_1, + TickMath.getSqrtPriceAtTick(tickLower), + TickMath.getSqrtPriceAtTick(tickUpper), + uint128(liquidityToAdd) + ); + + BalanceDelta delta = positionManager.increaseLiquidity( + tokenId, liquidityToAdd, type(uint256).max, type(uint256).max, block.timestamp + 1, Constants.ZERO_BYTES + ); + assertEq(delta.amount0(), -int128(uint128(amount0 + 1 wei))); + assertEq(delta.amount1(), -int128(uint128(amount1 + 1 wei))); + } + + function test_increaseLiquidityNative() public { + uint256 liquidityToMint = 100e18; + address recipient = address(this); + + (uint256 amount0, uint256 amount1) = LiquidityAmounts.getAmountsForLiquidity( + Constants.SQRT_PRICE_1_1, + TickMath.getSqrtPriceAtTick(tickLower), + TickMath.getSqrtPriceAtTick(tickUpper), + uint128(liquidityToMint) + ); + + vm.deal(address(this), amount0 + 1); + (uint256 tokenId, BalanceDelta delta) = positionManager.mint( + nativeKey, + tickLower, + tickUpper, + liquidityToMint, + amount0 + 1, + amount1 + 1, + recipient, + block.timestamp + 1, + Constants.ZERO_BYTES + ); + + uint256 liquidityToIncrease = 1e18; + + (amount0, amount1) = LiquidityAmounts.getAmountsForLiquidity( + Constants.SQRT_PRICE_1_1, + TickMath.getSqrtPriceAtTick(tickLower), + TickMath.getSqrtPriceAtTick(tickUpper), + uint128(liquidityToIncrease) + ); + + vm.deal(address(this), amount0 + 1); + delta = positionManager.increaseLiquidity( + tokenId, liquidityToIncrease, amount0 + 1, amount1 + 1, block.timestamp + 1, Constants.ZERO_BYTES + ); + assertEq(delta.amount0(), -int128(uint128(amount0 + 1 wei))); + assertEq(delta.amount1(), -int128(uint128(amount1 + 1 wei))); + } + + function test_decreaseLiquidity() public { + (uint256 tokenId,) = positionManager.mint( + key, + tickLower, + tickUpper, + 100e18, + type(uint256).max, + type(uint256).max, + address(this), + block.timestamp + 1, + Constants.ZERO_BYTES + ); + + uint256 liquidityToRemove = 1e18; + + (uint256 amount0, uint256 amount1) = LiquidityAmounts.getAmountsForLiquidity( + Constants.SQRT_PRICE_1_1, + TickMath.getSqrtPriceAtTick(tickLower), + TickMath.getSqrtPriceAtTick(tickUpper), + uint128(liquidityToRemove) + ); + + BalanceDelta delta = positionManager.decreaseLiquidity( + tokenId, liquidityToRemove, 0, 0, address(this), block.timestamp + 1, Constants.ZERO_BYTES + ); + assertEq(delta.amount0(), int128(uint128(amount0))); + assertEq(delta.amount1(), int128(uint128(amount1))); + } + + function test_burn() public { + (uint256 tokenId, BalanceDelta mintDelta) = positionManager.mint( + key, + tickLower, + tickUpper, + 100e18, + type(uint256).max, + type(uint256).max, + address(this), + block.timestamp + 1, + Constants.ZERO_BYTES + ); + + BalanceDelta delta = + positionManager.burn(tokenId, 0, 0, address(this), block.timestamp + 1, Constants.ZERO_BYTES); + assertEq(delta.amount0(), -mintDelta.amount0() - 1 wei); + assertEq(delta.amount1(), -mintDelta.amount1() - 1 wei); + } + + // This test requires a donateRouter, TODO + // function test_collect() public { + // (uint256 tokenId,) = positionManager.mint( + // key, + // tickLower, + // tickUpper, + // 100e18, + // type(uint256).max, + // type(uint256).max, + // address(this), + // block.timestamp + 1, + // Constants.ZERO_BYTES + // ); + + // // donate to regenerate fee revenue + // uint128 feeRevenue0 = 1e18; + // uint128 feeRevenue1 = 0.1e18; + + // poolManager.donate(key, feeRevenue0, feeRevenue1, Constants.ZERO_BYTES); + + // // position collects half of the revenue since 50% of the liquidity is minted in setUp() + // BalanceDelta delta = + // positionManager.collect(tokenId, 0, 0, address(0x123), block.timestamp + 1, Constants.ZERO_BYTES); + // assertEq(uint128(delta.amount0()), feeRevenue0 - 1 wei); + // assertEq(uint128(delta.amount1()), feeRevenue1 - 1 wei); + // } +} From 7da5f22d0a9fbaf733f8ea8dc3b3d4d60c12d7fe Mon Sep 17 00:00:00 2001 From: Jochen Hoenicke Date: Thu, 21 Aug 2025 14:12:31 +0200 Subject: [PATCH 2/5] Added library source files --- .../uniswap-hooks/lib/v4-core/src/ERC6909.sol | 90 +++ .../lib/v4-core/src/ERC6909Claims.sol | 23 + .../lib/v4-core/src/Extsload.sol | 64 ++ .../lib/v4-core/src/Exttload.sol | 40 + .../lib/v4-core/src/NoDelegateCall.sol | 33 + .../lib/v4-core/src/PoolManager.sol | 395 ++++++++++ .../lib/v4-core/src/ProtocolFees.sol | 71 ++ .../lib/v4-core/src/interfaces/IExtsload.sol | 21 + .../lib/v4-core/src/interfaces/IExttload.sol | 15 + .../lib/v4-core/src/interfaces/IHooks.sol | 152 ++++ .../v4-core/src/interfaces/IPoolManager.sol | 217 ++++++ .../v4-core/src/interfaces/IProtocolFees.sol | 52 ++ .../interfaces/callback/IUnlockCallback.sol | 10 + .../src/interfaces/external/IERC20Minimal.sol | 48 ++ .../interfaces/external/IERC6909Claims.sol | 66 ++ .../lib/v4-core/src/libraries/BitMath.sol | 49 ++ .../v4-core/src/libraries/CurrencyDelta.sol | 42 ++ .../src/libraries/CurrencyReserves.sol | 39 + .../v4-core/src/libraries/CustomRevert.sol | 120 +++ .../v4-core/src/libraries/FixedPoint128.sol | 8 + .../v4-core/src/libraries/FixedPoint96.sol | 10 + .../lib/v4-core/src/libraries/FullMath.sol | 117 +++ .../lib/v4-core/src/libraries/Hooks.sol | 340 +++++++++ .../v4-core/src/libraries/LPFeeLibrary.sol | 79 ++ .../v4-core/src/libraries/LiquidityMath.sol | 20 + .../lib/v4-core/src/libraries/Lock.sol | 28 + .../src/libraries/NonzeroDeltaCount.sol | 35 + .../lib/v4-core/src/libraries/ParseBytes.sol | 29 + .../lib/v4-core/src/libraries/Pool.sol | 613 +++++++++++++++ .../lib/v4-core/src/libraries/Position.sol | 103 +++ .../src/libraries/ProtocolFeeLibrary.sol | 47 ++ .../lib/v4-core/src/libraries/SafeCast.sol | 60 ++ .../v4-core/src/libraries/SqrtPriceMath.sol | 289 +++++++ .../v4-core/src/libraries/StateLibrary.sol | 349 +++++++++ .../lib/v4-core/src/libraries/SwapMath.sol | 108 +++ .../lib/v4-core/src/libraries/TickBitmap.sol | 122 +++ .../lib/v4-core/src/libraries/TickMath.sol | 238 ++++++ .../src/libraries/TransientStateLibrary.sol | 49 ++ .../lib/v4-core/src/libraries/UnsafeMath.sol | 29 + .../lib/v4-core/src/test/ActionsRouter.sol | 170 +++++ .../lib/v4-core/src/test/BaseTestHooks.sol | 109 +++ .../lib/v4-core/src/test/CurrencyTest.sol | 30 + .../lib/v4-core/src/test/CustomCurveHook.sol | 71 ++ .../v4-core/src/test/DeltaReturningHook.sol | 100 +++ .../v4-core/src/test/DynamicFeesTestHook.sol | 40 + .../src/test/DynamicReturnFeeTestHook.sol | 39 + .../v4-core/src/test/EmptyRevertContract.sol | 9 + .../lib/v4-core/src/test/EmptyTestHooks.sol | 119 +++ .../lib/v4-core/src/test/FeeTakingHook.sol | 91 +++ .../lib/v4-core/src/test/Fuzzers.sol | 184 +++++ .../lib/v4-core/src/test/HooksTest.sol | 73 ++ .../lib/v4-core/src/test/LPFeeTakingHook.sol | 65 ++ .../v4-core/src/test/LiquidityMathTest.sol | 10 + .../lib/v4-core/src/test/MockContract.sol | 57 ++ .../v4-core/src/test/MockERC6909Claims.sol | 22 + .../lib/v4-core/src/test/MockHooks.sol | 140 ++++ .../lib/v4-core/src/test/NativeERC20.sol | 50 ++ .../v4-core/src/test/NoDelegateCallTest.sol | 32 + .../lib/v4-core/src/test/PoolClaimsTest.sol | 50 ++ .../lib/v4-core/src/test/PoolDonateTest.sol | 68 ++ .../v4-core/src/test/PoolEmptyUnlockTest.sol | 25 + .../src/test/PoolModifyLiquidityTest.sol | 96 +++ .../test/PoolModifyLiquidityTestNoChecks.sol | 74 ++ .../src/test/PoolNestedActionsTest.sol | 236 ++++++ .../lib/v4-core/src/test/PoolSwapTest.sol | 117 +++ .../lib/v4-core/src/test/PoolTakeTest.sol | 61 ++ .../lib/v4-core/src/test/PoolTestBase.sol | 31 + .../src/test/ProtocolFeesImplementation.sol | 38 + .../lib/v4-core/src/test/ProxyPoolManager.sol | 222 ++++++ .../v4-core/src/test/SkipCallsTestHook.sol | 193 +++++ .../src/test/SqrtPriceMathEchidnaTest.sol | 198 +++++ .../v4-core/src/test/SwapRouterNoChecks.sol | 49 ++ .../lib/v4-core/src/test/TestERC20.sol | 55 ++ .../lib/v4-core/src/test/TestInvalidERC20.sol | 56 ++ .../v4-core/src/test/TickMathEchidnaTest.sol | 22 + .../lib/v4-core/src/test/TickMathTest.sol | 42 ++ .../test/TickOverflowSafetyEchidnaTest.sol | 89 +++ .../lib/v4-core/src/types/BalanceDelta.sol | 72 ++ .../lib/v4-core/src/types/BeforeSwapDelta.sol | 38 + .../lib/v4-core/src/types/Currency.sol | 119 +++ .../lib/v4-core/src/types/PoolId.sol | 17 + .../lib/v4-core/src/types/PoolKey.sol | 22 + .../lib/v4-core/src/types/PoolOperation.sol | 26 + .../lib/v4-core/src/types/Slot0.sol | 94 +++ .../v4-periphery/src/PositionDescriptor.sol | 127 ++++ .../lib/v4-periphery/src/PositionManager.sol | 561 ++++++++++++++ .../src/UniswapV4DeployerCompetition.sol | 85 +++ .../lib/v4-periphery/src/V4Router.sol | 173 +++++ .../src/base/BaseActionsRouter.sol | 75 ++ .../v4-periphery/src/base/BaseV4Quoter.sol | 59 ++ .../v4-periphery/src/base/DeltaResolver.sol | 123 +++ .../lib/v4-periphery/src/base/EIP712_v4.sol | 56 ++ .../v4-periphery/src/base/ERC721Permit_v4.sol | 98 +++ .../v4-periphery/src/base/ImmutableState.sol | 25 + .../v4-periphery/src/base/Multicall_v4.sol | 25 + .../v4-periphery/src/base/NativeWrapper.sol | 34 + .../lib/v4-periphery/src/base/Notifier.sol | 132 ++++ .../src/base/Permit2Forwarder.sol | 41 + .../src/base/PoolInitializer_v4.sol | 20 + .../v4-periphery/src/base/ReentrancyLock.sol | 20 + .../v4-periphery/src/base/SafeCallback.sol | 21 + .../v4-periphery/src/base/UnorderedNonce.sol | 28 + .../src/base/hooks/BaseTokenWrapperHook.sol | 217 ++++++ .../lib/v4-periphery/src/hooks/WETHHook.sol | 54 ++ .../lib/v4-periphery/src/hooks/WstETHHook.sol | 87 +++ .../src/interfaces/IEIP712_v4.sol | 10 + .../src/interfaces/IERC721Permit_v4.sol | 38 + .../src/interfaces/IImmutableState.sol | 11 + .../src/interfaces/IMsgSender.sol | 13 + .../src/interfaces/IMulticall_v4.sol | 13 + .../v4-periphery/src/interfaces/INotifier.sol | 54 ++ .../src/interfaces/IPermit2Forwarder.sol | 30 + .../src/interfaces/IPoolInitializer_v4.sol | 15 + .../src/interfaces/IPositionDescriptor.sol | 40 + .../src/interfaces/IPositionManager.sol | 68 ++ .../src/interfaces/IStateView.sol | 133 ++++ .../src/interfaces/ISubscriber.sol | 38 + .../IUniswapV4DeployerCompetition.sol | 25 + .../src/interfaces/IUnorderedNonce.sol | 17 + .../v4-periphery/src/interfaces/IV4Quoter.sol | 71 ++ .../v4-periphery/src/interfaces/IV4Router.sol | 50 ++ .../src/interfaces/external/IWETH9.sol | 13 + .../src/interfaces/external/IWstETH.sol | 25 + .../lib/v4-periphery/src/lens/StateView.sol | 109 +++ .../lib/v4-periphery/src/lens/V4Quoter.sol | 159 ++++ .../src/libraries/ActionConstants.sol | 20 + .../v4-periphery/src/libraries/Actions.sol | 49 ++ .../src/libraries/AddressStringUtil.sol | 46 ++ .../src/libraries/BipsLibrary.sol | 17 + .../src/libraries/CalldataDecoder.sol | 391 ++++++++++ .../src/libraries/CurrencyRatioSortOrder.sol | 16 + .../v4-periphery/src/libraries/Descriptor.sol | 528 +++++++++++++ .../src/libraries/ERC721PermitHash.sol | 70 ++ .../v4-periphery/src/libraries/HexStrings.sol | 22 + .../src/libraries/LiquidityAmounts.sol | 75 ++ .../lib/v4-periphery/src/libraries/Locker.sol | 21 + .../v4-periphery/src/libraries/PathKey.sol | 38 + .../src/libraries/PositionConfig.sol | 35 + .../src/libraries/PositionConfigId.sol | 39 + .../src/libraries/PositionInfoLibrary.sol | 105 +++ .../src/libraries/QuoterRevert.sol | 49 ++ .../lib/v4-periphery/src/libraries/SVG.sol | 471 ++++++++++++ .../src/libraries/SafeCurrencyMetadata.sol | 108 +++ .../src/libraries/SlippageCheck.sol | 53 ++ .../src/libraries/VanityAddressLib.sol | 97 +++ .../lib/v4-periphery/src/utils/BaseHook.sol | 216 ++++++ .../lib/v4-periphery/src/utils/HookMiner.sol | 57 ++ .../uniswap-hooks/src/base/BaseAsyncSwap.sol | 132 ++++ .../src/base/BaseCustomAccounting.sol | 433 +++++++++++ .../src/base/BaseCustomCurve.sol | 312 ++++++++ .../lib/uniswap-hooks/src/base/BaseHook.sol | 344 +++++++++ .../lib/uniswap-hooks/src/base/README.adoc | 21 + .../src/fee/BaseDynamicAfterFee.sol | 184 +++++ .../uniswap-hooks/src/fee/BaseDynamicFee.sol | 98 +++ .../uniswap-hooks/src/fee/BaseOverrideFee.sol | 93 +++ .../lib/uniswap-hooks/src/fee/README.adoc | 18 + .../src/general/AntiSandwichHook.sol | 249 ++++++ .../src/general/LimitOrderHook.sol | 709 ++++++++++++++++++ .../src/general/LiquidityPenaltyHook.sol | 196 +++++ .../lib/uniswap-hooks/src/general/README.adoc | 16 + .../src/interfaces/IHookEvents.sol | 38 + .../uniswap-hooks/src/interfaces/README.adoc | 12 + .../src/utils/CurrencySettler.sol | 69 ++ .../lib/uniswap-hooks/src/utils/README.adoc | 12 + 164 files changed, 16193 insertions(+) create mode 100644 CVLByExample/UniswapHooks/lib/uniswap-hooks/lib/v4-core/src/ERC6909.sol create mode 100644 CVLByExample/UniswapHooks/lib/uniswap-hooks/lib/v4-core/src/ERC6909Claims.sol create mode 100644 CVLByExample/UniswapHooks/lib/uniswap-hooks/lib/v4-core/src/Extsload.sol create mode 100644 CVLByExample/UniswapHooks/lib/uniswap-hooks/lib/v4-core/src/Exttload.sol create mode 100644 CVLByExample/UniswapHooks/lib/uniswap-hooks/lib/v4-core/src/NoDelegateCall.sol create mode 100644 CVLByExample/UniswapHooks/lib/uniswap-hooks/lib/v4-core/src/PoolManager.sol create mode 100644 CVLByExample/UniswapHooks/lib/uniswap-hooks/lib/v4-core/src/ProtocolFees.sol create mode 100644 CVLByExample/UniswapHooks/lib/uniswap-hooks/lib/v4-core/src/interfaces/IExtsload.sol create mode 100644 CVLByExample/UniswapHooks/lib/uniswap-hooks/lib/v4-core/src/interfaces/IExttload.sol create mode 100644 CVLByExample/UniswapHooks/lib/uniswap-hooks/lib/v4-core/src/interfaces/IHooks.sol create mode 100644 CVLByExample/UniswapHooks/lib/uniswap-hooks/lib/v4-core/src/interfaces/IPoolManager.sol create mode 100644 CVLByExample/UniswapHooks/lib/uniswap-hooks/lib/v4-core/src/interfaces/IProtocolFees.sol create mode 100644 CVLByExample/UniswapHooks/lib/uniswap-hooks/lib/v4-core/src/interfaces/callback/IUnlockCallback.sol create mode 100644 CVLByExample/UniswapHooks/lib/uniswap-hooks/lib/v4-core/src/interfaces/external/IERC20Minimal.sol create mode 100644 CVLByExample/UniswapHooks/lib/uniswap-hooks/lib/v4-core/src/interfaces/external/IERC6909Claims.sol create mode 100644 CVLByExample/UniswapHooks/lib/uniswap-hooks/lib/v4-core/src/libraries/BitMath.sol create mode 100644 CVLByExample/UniswapHooks/lib/uniswap-hooks/lib/v4-core/src/libraries/CurrencyDelta.sol create mode 100644 CVLByExample/UniswapHooks/lib/uniswap-hooks/lib/v4-core/src/libraries/CurrencyReserves.sol create mode 100644 CVLByExample/UniswapHooks/lib/uniswap-hooks/lib/v4-core/src/libraries/CustomRevert.sol create mode 100644 CVLByExample/UniswapHooks/lib/uniswap-hooks/lib/v4-core/src/libraries/FixedPoint128.sol create mode 100644 CVLByExample/UniswapHooks/lib/uniswap-hooks/lib/v4-core/src/libraries/FixedPoint96.sol create mode 100644 CVLByExample/UniswapHooks/lib/uniswap-hooks/lib/v4-core/src/libraries/FullMath.sol create mode 100644 CVLByExample/UniswapHooks/lib/uniswap-hooks/lib/v4-core/src/libraries/Hooks.sol create mode 100644 CVLByExample/UniswapHooks/lib/uniswap-hooks/lib/v4-core/src/libraries/LPFeeLibrary.sol create mode 100644 CVLByExample/UniswapHooks/lib/uniswap-hooks/lib/v4-core/src/libraries/LiquidityMath.sol create mode 100644 CVLByExample/UniswapHooks/lib/uniswap-hooks/lib/v4-core/src/libraries/Lock.sol create mode 100644 CVLByExample/UniswapHooks/lib/uniswap-hooks/lib/v4-core/src/libraries/NonzeroDeltaCount.sol create mode 100644 CVLByExample/UniswapHooks/lib/uniswap-hooks/lib/v4-core/src/libraries/ParseBytes.sol create mode 100644 CVLByExample/UniswapHooks/lib/uniswap-hooks/lib/v4-core/src/libraries/Pool.sol create mode 100644 CVLByExample/UniswapHooks/lib/uniswap-hooks/lib/v4-core/src/libraries/Position.sol create mode 100644 CVLByExample/UniswapHooks/lib/uniswap-hooks/lib/v4-core/src/libraries/ProtocolFeeLibrary.sol create mode 100644 CVLByExample/UniswapHooks/lib/uniswap-hooks/lib/v4-core/src/libraries/SafeCast.sol create mode 100644 CVLByExample/UniswapHooks/lib/uniswap-hooks/lib/v4-core/src/libraries/SqrtPriceMath.sol create mode 100644 CVLByExample/UniswapHooks/lib/uniswap-hooks/lib/v4-core/src/libraries/StateLibrary.sol create mode 100644 CVLByExample/UniswapHooks/lib/uniswap-hooks/lib/v4-core/src/libraries/SwapMath.sol create mode 100644 CVLByExample/UniswapHooks/lib/uniswap-hooks/lib/v4-core/src/libraries/TickBitmap.sol create mode 100644 CVLByExample/UniswapHooks/lib/uniswap-hooks/lib/v4-core/src/libraries/TickMath.sol create mode 100644 CVLByExample/UniswapHooks/lib/uniswap-hooks/lib/v4-core/src/libraries/TransientStateLibrary.sol create mode 100644 CVLByExample/UniswapHooks/lib/uniswap-hooks/lib/v4-core/src/libraries/UnsafeMath.sol create mode 100644 CVLByExample/UniswapHooks/lib/uniswap-hooks/lib/v4-core/src/test/ActionsRouter.sol create mode 100644 CVLByExample/UniswapHooks/lib/uniswap-hooks/lib/v4-core/src/test/BaseTestHooks.sol create mode 100644 CVLByExample/UniswapHooks/lib/uniswap-hooks/lib/v4-core/src/test/CurrencyTest.sol create mode 100644 CVLByExample/UniswapHooks/lib/uniswap-hooks/lib/v4-core/src/test/CustomCurveHook.sol create mode 100644 CVLByExample/UniswapHooks/lib/uniswap-hooks/lib/v4-core/src/test/DeltaReturningHook.sol create mode 100644 CVLByExample/UniswapHooks/lib/uniswap-hooks/lib/v4-core/src/test/DynamicFeesTestHook.sol create mode 100644 CVLByExample/UniswapHooks/lib/uniswap-hooks/lib/v4-core/src/test/DynamicReturnFeeTestHook.sol create mode 100644 CVLByExample/UniswapHooks/lib/uniswap-hooks/lib/v4-core/src/test/EmptyRevertContract.sol create mode 100644 CVLByExample/UniswapHooks/lib/uniswap-hooks/lib/v4-core/src/test/EmptyTestHooks.sol create mode 100644 CVLByExample/UniswapHooks/lib/uniswap-hooks/lib/v4-core/src/test/FeeTakingHook.sol create mode 100644 CVLByExample/UniswapHooks/lib/uniswap-hooks/lib/v4-core/src/test/Fuzzers.sol create mode 100644 CVLByExample/UniswapHooks/lib/uniswap-hooks/lib/v4-core/src/test/HooksTest.sol create mode 100644 CVLByExample/UniswapHooks/lib/uniswap-hooks/lib/v4-core/src/test/LPFeeTakingHook.sol create mode 100644 CVLByExample/UniswapHooks/lib/uniswap-hooks/lib/v4-core/src/test/LiquidityMathTest.sol create mode 100644 CVLByExample/UniswapHooks/lib/uniswap-hooks/lib/v4-core/src/test/MockContract.sol create mode 100644 CVLByExample/UniswapHooks/lib/uniswap-hooks/lib/v4-core/src/test/MockERC6909Claims.sol create mode 100644 CVLByExample/UniswapHooks/lib/uniswap-hooks/lib/v4-core/src/test/MockHooks.sol create mode 100644 CVLByExample/UniswapHooks/lib/uniswap-hooks/lib/v4-core/src/test/NativeERC20.sol create mode 100644 CVLByExample/UniswapHooks/lib/uniswap-hooks/lib/v4-core/src/test/NoDelegateCallTest.sol create mode 100644 CVLByExample/UniswapHooks/lib/uniswap-hooks/lib/v4-core/src/test/PoolClaimsTest.sol create mode 100644 CVLByExample/UniswapHooks/lib/uniswap-hooks/lib/v4-core/src/test/PoolDonateTest.sol create mode 100644 CVLByExample/UniswapHooks/lib/uniswap-hooks/lib/v4-core/src/test/PoolEmptyUnlockTest.sol create mode 100644 CVLByExample/UniswapHooks/lib/uniswap-hooks/lib/v4-core/src/test/PoolModifyLiquidityTest.sol create mode 100644 CVLByExample/UniswapHooks/lib/uniswap-hooks/lib/v4-core/src/test/PoolModifyLiquidityTestNoChecks.sol create mode 100644 CVLByExample/UniswapHooks/lib/uniswap-hooks/lib/v4-core/src/test/PoolNestedActionsTest.sol create mode 100644 CVLByExample/UniswapHooks/lib/uniswap-hooks/lib/v4-core/src/test/PoolSwapTest.sol create mode 100644 CVLByExample/UniswapHooks/lib/uniswap-hooks/lib/v4-core/src/test/PoolTakeTest.sol create mode 100644 CVLByExample/UniswapHooks/lib/uniswap-hooks/lib/v4-core/src/test/PoolTestBase.sol create mode 100644 CVLByExample/UniswapHooks/lib/uniswap-hooks/lib/v4-core/src/test/ProtocolFeesImplementation.sol create mode 100644 CVLByExample/UniswapHooks/lib/uniswap-hooks/lib/v4-core/src/test/ProxyPoolManager.sol create mode 100644 CVLByExample/UniswapHooks/lib/uniswap-hooks/lib/v4-core/src/test/SkipCallsTestHook.sol create mode 100644 CVLByExample/UniswapHooks/lib/uniswap-hooks/lib/v4-core/src/test/SqrtPriceMathEchidnaTest.sol create mode 100644 CVLByExample/UniswapHooks/lib/uniswap-hooks/lib/v4-core/src/test/SwapRouterNoChecks.sol create mode 100644 CVLByExample/UniswapHooks/lib/uniswap-hooks/lib/v4-core/src/test/TestERC20.sol create mode 100644 CVLByExample/UniswapHooks/lib/uniswap-hooks/lib/v4-core/src/test/TestInvalidERC20.sol create mode 100644 CVLByExample/UniswapHooks/lib/uniswap-hooks/lib/v4-core/src/test/TickMathEchidnaTest.sol create mode 100644 CVLByExample/UniswapHooks/lib/uniswap-hooks/lib/v4-core/src/test/TickMathTest.sol create mode 100644 CVLByExample/UniswapHooks/lib/uniswap-hooks/lib/v4-core/src/test/TickOverflowSafetyEchidnaTest.sol create mode 100644 CVLByExample/UniswapHooks/lib/uniswap-hooks/lib/v4-core/src/types/BalanceDelta.sol create mode 100644 CVLByExample/UniswapHooks/lib/uniswap-hooks/lib/v4-core/src/types/BeforeSwapDelta.sol create mode 100644 CVLByExample/UniswapHooks/lib/uniswap-hooks/lib/v4-core/src/types/Currency.sol create mode 100644 CVLByExample/UniswapHooks/lib/uniswap-hooks/lib/v4-core/src/types/PoolId.sol create mode 100644 CVLByExample/UniswapHooks/lib/uniswap-hooks/lib/v4-core/src/types/PoolKey.sol create mode 100644 CVLByExample/UniswapHooks/lib/uniswap-hooks/lib/v4-core/src/types/PoolOperation.sol create mode 100644 CVLByExample/UniswapHooks/lib/uniswap-hooks/lib/v4-core/src/types/Slot0.sol create mode 100644 CVLByExample/UniswapHooks/lib/uniswap-hooks/lib/v4-periphery/src/PositionDescriptor.sol create mode 100644 CVLByExample/UniswapHooks/lib/uniswap-hooks/lib/v4-periphery/src/PositionManager.sol create mode 100644 CVLByExample/UniswapHooks/lib/uniswap-hooks/lib/v4-periphery/src/UniswapV4DeployerCompetition.sol create mode 100644 CVLByExample/UniswapHooks/lib/uniswap-hooks/lib/v4-periphery/src/V4Router.sol create mode 100644 CVLByExample/UniswapHooks/lib/uniswap-hooks/lib/v4-periphery/src/base/BaseActionsRouter.sol create mode 100644 CVLByExample/UniswapHooks/lib/uniswap-hooks/lib/v4-periphery/src/base/BaseV4Quoter.sol create mode 100644 CVLByExample/UniswapHooks/lib/uniswap-hooks/lib/v4-periphery/src/base/DeltaResolver.sol create mode 100644 CVLByExample/UniswapHooks/lib/uniswap-hooks/lib/v4-periphery/src/base/EIP712_v4.sol create mode 100644 CVLByExample/UniswapHooks/lib/uniswap-hooks/lib/v4-periphery/src/base/ERC721Permit_v4.sol create mode 100644 CVLByExample/UniswapHooks/lib/uniswap-hooks/lib/v4-periphery/src/base/ImmutableState.sol create mode 100644 CVLByExample/UniswapHooks/lib/uniswap-hooks/lib/v4-periphery/src/base/Multicall_v4.sol create mode 100644 CVLByExample/UniswapHooks/lib/uniswap-hooks/lib/v4-periphery/src/base/NativeWrapper.sol create mode 100644 CVLByExample/UniswapHooks/lib/uniswap-hooks/lib/v4-periphery/src/base/Notifier.sol create mode 100644 CVLByExample/UniswapHooks/lib/uniswap-hooks/lib/v4-periphery/src/base/Permit2Forwarder.sol create mode 100644 CVLByExample/UniswapHooks/lib/uniswap-hooks/lib/v4-periphery/src/base/PoolInitializer_v4.sol create mode 100644 CVLByExample/UniswapHooks/lib/uniswap-hooks/lib/v4-periphery/src/base/ReentrancyLock.sol create mode 100644 CVLByExample/UniswapHooks/lib/uniswap-hooks/lib/v4-periphery/src/base/SafeCallback.sol create mode 100644 CVLByExample/UniswapHooks/lib/uniswap-hooks/lib/v4-periphery/src/base/UnorderedNonce.sol create mode 100644 CVLByExample/UniswapHooks/lib/uniswap-hooks/lib/v4-periphery/src/base/hooks/BaseTokenWrapperHook.sol create mode 100644 CVLByExample/UniswapHooks/lib/uniswap-hooks/lib/v4-periphery/src/hooks/WETHHook.sol create mode 100644 CVLByExample/UniswapHooks/lib/uniswap-hooks/lib/v4-periphery/src/hooks/WstETHHook.sol create mode 100644 CVLByExample/UniswapHooks/lib/uniswap-hooks/lib/v4-periphery/src/interfaces/IEIP712_v4.sol create mode 100644 CVLByExample/UniswapHooks/lib/uniswap-hooks/lib/v4-periphery/src/interfaces/IERC721Permit_v4.sol create mode 100644 CVLByExample/UniswapHooks/lib/uniswap-hooks/lib/v4-periphery/src/interfaces/IImmutableState.sol create mode 100644 CVLByExample/UniswapHooks/lib/uniswap-hooks/lib/v4-periphery/src/interfaces/IMsgSender.sol create mode 100644 CVLByExample/UniswapHooks/lib/uniswap-hooks/lib/v4-periphery/src/interfaces/IMulticall_v4.sol create mode 100644 CVLByExample/UniswapHooks/lib/uniswap-hooks/lib/v4-periphery/src/interfaces/INotifier.sol create mode 100644 CVLByExample/UniswapHooks/lib/uniswap-hooks/lib/v4-periphery/src/interfaces/IPermit2Forwarder.sol create mode 100644 CVLByExample/UniswapHooks/lib/uniswap-hooks/lib/v4-periphery/src/interfaces/IPoolInitializer_v4.sol create mode 100644 CVLByExample/UniswapHooks/lib/uniswap-hooks/lib/v4-periphery/src/interfaces/IPositionDescriptor.sol create mode 100644 CVLByExample/UniswapHooks/lib/uniswap-hooks/lib/v4-periphery/src/interfaces/IPositionManager.sol create mode 100644 CVLByExample/UniswapHooks/lib/uniswap-hooks/lib/v4-periphery/src/interfaces/IStateView.sol create mode 100644 CVLByExample/UniswapHooks/lib/uniswap-hooks/lib/v4-periphery/src/interfaces/ISubscriber.sol create mode 100644 CVLByExample/UniswapHooks/lib/uniswap-hooks/lib/v4-periphery/src/interfaces/IUniswapV4DeployerCompetition.sol create mode 100644 CVLByExample/UniswapHooks/lib/uniswap-hooks/lib/v4-periphery/src/interfaces/IUnorderedNonce.sol create mode 100644 CVLByExample/UniswapHooks/lib/uniswap-hooks/lib/v4-periphery/src/interfaces/IV4Quoter.sol create mode 100644 CVLByExample/UniswapHooks/lib/uniswap-hooks/lib/v4-periphery/src/interfaces/IV4Router.sol create mode 100644 CVLByExample/UniswapHooks/lib/uniswap-hooks/lib/v4-periphery/src/interfaces/external/IWETH9.sol create mode 100644 CVLByExample/UniswapHooks/lib/uniswap-hooks/lib/v4-periphery/src/interfaces/external/IWstETH.sol create mode 100644 CVLByExample/UniswapHooks/lib/uniswap-hooks/lib/v4-periphery/src/lens/StateView.sol create mode 100644 CVLByExample/UniswapHooks/lib/uniswap-hooks/lib/v4-periphery/src/lens/V4Quoter.sol create mode 100644 CVLByExample/UniswapHooks/lib/uniswap-hooks/lib/v4-periphery/src/libraries/ActionConstants.sol create mode 100644 CVLByExample/UniswapHooks/lib/uniswap-hooks/lib/v4-periphery/src/libraries/Actions.sol create mode 100644 CVLByExample/UniswapHooks/lib/uniswap-hooks/lib/v4-periphery/src/libraries/AddressStringUtil.sol create mode 100644 CVLByExample/UniswapHooks/lib/uniswap-hooks/lib/v4-periphery/src/libraries/BipsLibrary.sol create mode 100644 CVLByExample/UniswapHooks/lib/uniswap-hooks/lib/v4-periphery/src/libraries/CalldataDecoder.sol create mode 100644 CVLByExample/UniswapHooks/lib/uniswap-hooks/lib/v4-periphery/src/libraries/CurrencyRatioSortOrder.sol create mode 100644 CVLByExample/UniswapHooks/lib/uniswap-hooks/lib/v4-periphery/src/libraries/Descriptor.sol create mode 100644 CVLByExample/UniswapHooks/lib/uniswap-hooks/lib/v4-periphery/src/libraries/ERC721PermitHash.sol create mode 100644 CVLByExample/UniswapHooks/lib/uniswap-hooks/lib/v4-periphery/src/libraries/HexStrings.sol create mode 100644 CVLByExample/UniswapHooks/lib/uniswap-hooks/lib/v4-periphery/src/libraries/LiquidityAmounts.sol create mode 100644 CVLByExample/UniswapHooks/lib/uniswap-hooks/lib/v4-periphery/src/libraries/Locker.sol create mode 100644 CVLByExample/UniswapHooks/lib/uniswap-hooks/lib/v4-periphery/src/libraries/PathKey.sol create mode 100644 CVLByExample/UniswapHooks/lib/uniswap-hooks/lib/v4-periphery/src/libraries/PositionConfig.sol create mode 100644 CVLByExample/UniswapHooks/lib/uniswap-hooks/lib/v4-periphery/src/libraries/PositionConfigId.sol create mode 100644 CVLByExample/UniswapHooks/lib/uniswap-hooks/lib/v4-periphery/src/libraries/PositionInfoLibrary.sol create mode 100644 CVLByExample/UniswapHooks/lib/uniswap-hooks/lib/v4-periphery/src/libraries/QuoterRevert.sol create mode 100644 CVLByExample/UniswapHooks/lib/uniswap-hooks/lib/v4-periphery/src/libraries/SVG.sol create mode 100644 CVLByExample/UniswapHooks/lib/uniswap-hooks/lib/v4-periphery/src/libraries/SafeCurrencyMetadata.sol create mode 100644 CVLByExample/UniswapHooks/lib/uniswap-hooks/lib/v4-periphery/src/libraries/SlippageCheck.sol create mode 100644 CVLByExample/UniswapHooks/lib/uniswap-hooks/lib/v4-periphery/src/libraries/VanityAddressLib.sol create mode 100644 CVLByExample/UniswapHooks/lib/uniswap-hooks/lib/v4-periphery/src/utils/BaseHook.sol create mode 100644 CVLByExample/UniswapHooks/lib/uniswap-hooks/lib/v4-periphery/src/utils/HookMiner.sol create mode 100644 CVLByExample/UniswapHooks/lib/uniswap-hooks/src/base/BaseAsyncSwap.sol create mode 100644 CVLByExample/UniswapHooks/lib/uniswap-hooks/src/base/BaseCustomAccounting.sol create mode 100644 CVLByExample/UniswapHooks/lib/uniswap-hooks/src/base/BaseCustomCurve.sol create mode 100644 CVLByExample/UniswapHooks/lib/uniswap-hooks/src/base/BaseHook.sol create mode 100644 CVLByExample/UniswapHooks/lib/uniswap-hooks/src/base/README.adoc create mode 100644 CVLByExample/UniswapHooks/lib/uniswap-hooks/src/fee/BaseDynamicAfterFee.sol create mode 100644 CVLByExample/UniswapHooks/lib/uniswap-hooks/src/fee/BaseDynamicFee.sol create mode 100644 CVLByExample/UniswapHooks/lib/uniswap-hooks/src/fee/BaseOverrideFee.sol create mode 100644 CVLByExample/UniswapHooks/lib/uniswap-hooks/src/fee/README.adoc create mode 100644 CVLByExample/UniswapHooks/lib/uniswap-hooks/src/general/AntiSandwichHook.sol create mode 100644 CVLByExample/UniswapHooks/lib/uniswap-hooks/src/general/LimitOrderHook.sol create mode 100644 CVLByExample/UniswapHooks/lib/uniswap-hooks/src/general/LiquidityPenaltyHook.sol create mode 100644 CVLByExample/UniswapHooks/lib/uniswap-hooks/src/general/README.adoc create mode 100644 CVLByExample/UniswapHooks/lib/uniswap-hooks/src/interfaces/IHookEvents.sol create mode 100644 CVLByExample/UniswapHooks/lib/uniswap-hooks/src/interfaces/README.adoc create mode 100644 CVLByExample/UniswapHooks/lib/uniswap-hooks/src/utils/CurrencySettler.sol create mode 100644 CVLByExample/UniswapHooks/lib/uniswap-hooks/src/utils/README.adoc diff --git a/CVLByExample/UniswapHooks/lib/uniswap-hooks/lib/v4-core/src/ERC6909.sol b/CVLByExample/UniswapHooks/lib/uniswap-hooks/lib/v4-core/src/ERC6909.sol new file mode 100644 index 00000000..3ba7e9b9 --- /dev/null +++ b/CVLByExample/UniswapHooks/lib/uniswap-hooks/lib/v4-core/src/ERC6909.sol @@ -0,0 +1,90 @@ +// SPDX-License-Identifier: MIT +pragma solidity ^0.8.0; + +import {IERC6909Claims} from "./interfaces/external/IERC6909Claims.sol"; + +/// @notice Minimalist and gas efficient standard ERC6909 implementation. +/// @author Solmate (https://github.com/transmissions11/solmate/blob/main/src/tokens/ERC6909.sol) +/// @dev Copied from the commit at 4b47a19038b798b4a33d9749d25e570443520647 +/// @dev This contract has been modified from the implementation at the above link. +abstract contract ERC6909 is IERC6909Claims { + /*////////////////////////////////////////////////////////////// + ERC6909 STORAGE + //////////////////////////////////////////////////////////////*/ + + mapping(address owner => mapping(address operator => bool isOperator)) public isOperator; + + mapping(address owner => mapping(uint256 id => uint256 balance)) public balanceOf; + + mapping(address owner => mapping(address spender => mapping(uint256 id => uint256 amount))) public allowance; + + /*////////////////////////////////////////////////////////////// + ERC6909 LOGIC + //////////////////////////////////////////////////////////////*/ + + function transfer(address receiver, uint256 id, uint256 amount) public virtual returns (bool) { + balanceOf[msg.sender][id] -= amount; + + balanceOf[receiver][id] += amount; + + emit Transfer(msg.sender, msg.sender, receiver, id, amount); + + return true; + } + + function transferFrom(address sender, address receiver, uint256 id, uint256 amount) public virtual returns (bool) { + if (msg.sender != sender && !isOperator[sender][msg.sender]) { + uint256 allowed = allowance[sender][msg.sender][id]; + if (allowed != type(uint256).max) allowance[sender][msg.sender][id] = allowed - amount; + } + + balanceOf[sender][id] -= amount; + + balanceOf[receiver][id] += amount; + + emit Transfer(msg.sender, sender, receiver, id, amount); + + return true; + } + + function approve(address spender, uint256 id, uint256 amount) public virtual returns (bool) { + allowance[msg.sender][spender][id] = amount; + + emit Approval(msg.sender, spender, id, amount); + + return true; + } + + function setOperator(address operator, bool approved) public virtual returns (bool) { + isOperator[msg.sender][operator] = approved; + + emit OperatorSet(msg.sender, operator, approved); + + return true; + } + + /*////////////////////////////////////////////////////////////// + ERC165 LOGIC + //////////////////////////////////////////////////////////////*/ + + function supportsInterface(bytes4 interfaceId) public view virtual returns (bool) { + return interfaceId == 0x01ffc9a7 // ERC165 Interface ID for ERC165 + || interfaceId == 0x0f632fb3; // ERC165 Interface ID for ERC6909 + } + + /*////////////////////////////////////////////////////////////// + INTERNAL MINT/BURN LOGIC + //////////////////////////////////////////////////////////////*/ + + function _mint(address receiver, uint256 id, uint256 amount) internal virtual { + balanceOf[receiver][id] += amount; + + emit Transfer(msg.sender, address(0), receiver, id, amount); + } + + function _burn(address sender, uint256 id, uint256 amount) internal virtual { + balanceOf[sender][id] -= amount; + + emit Transfer(msg.sender, sender, address(0), id, amount); + } +} diff --git a/CVLByExample/UniswapHooks/lib/uniswap-hooks/lib/v4-core/src/ERC6909Claims.sol b/CVLByExample/UniswapHooks/lib/uniswap-hooks/lib/v4-core/src/ERC6909Claims.sol new file mode 100644 index 00000000..fb0b212c --- /dev/null +++ b/CVLByExample/UniswapHooks/lib/uniswap-hooks/lib/v4-core/src/ERC6909Claims.sol @@ -0,0 +1,23 @@ +// SPDX-License-Identifier: MIT +pragma solidity ^0.8.0; + +import {ERC6909} from "./ERC6909.sol"; + +/// @notice ERC6909Claims inherits ERC6909 and implements an internal burnFrom function +abstract contract ERC6909Claims is ERC6909 { + /// @notice Burn `amount` tokens of token type `id` from `from`. + /// @dev if sender is not `from` they must be an operator or have sufficient allowance. + /// @param from The address to burn tokens from. + /// @param id The currency to burn. + /// @param amount The amount to burn. + function _burnFrom(address from, uint256 id, uint256 amount) internal { + address sender = msg.sender; + if (from != sender && !isOperator[from][sender]) { + uint256 senderAllowance = allowance[from][sender][id]; + if (senderAllowance != type(uint256).max) { + allowance[from][sender][id] = senderAllowance - amount; + } + } + _burn(from, id, amount); + } +} diff --git a/CVLByExample/UniswapHooks/lib/uniswap-hooks/lib/v4-core/src/Extsload.sol b/CVLByExample/UniswapHooks/lib/uniswap-hooks/lib/v4-core/src/Extsload.sol new file mode 100644 index 00000000..96ddc43d --- /dev/null +++ b/CVLByExample/UniswapHooks/lib/uniswap-hooks/lib/v4-core/src/Extsload.sol @@ -0,0 +1,64 @@ +// SPDX-License-Identifier: MIT +pragma solidity ^0.8.0; + +import {IExtsload} from "./interfaces/IExtsload.sol"; + +/// @notice Enables public storage access for efficient state retrieval by external contracts. +/// https://eips.ethereum.org/EIPS/eip-2330#rationale +abstract contract Extsload is IExtsload { + /// @inheritdoc IExtsload + function extsload(bytes32 slot) external view returns (bytes32) { + assembly ("memory-safe") { + mstore(0, sload(slot)) + return(0, 0x20) + } + } + + /// @inheritdoc IExtsload + function extsload(bytes32 startSlot, uint256 nSlots) external view returns (bytes32[] memory) { + assembly ("memory-safe") { + let memptr := mload(0x40) + let start := memptr + // A left bit-shift of 5 is equivalent to multiplying by 32 but costs less gas. + let length := shl(5, nSlots) + // The abi offset of dynamic array in the returndata is 32. + mstore(memptr, 0x20) + // Store the length of the array returned + mstore(add(memptr, 0x20), nSlots) + // update memptr to the first location to hold a result + memptr := add(memptr, 0x40) + let end := add(memptr, length) + for {} 1 {} { + mstore(memptr, sload(startSlot)) + memptr := add(memptr, 0x20) + startSlot := add(startSlot, 1) + if iszero(lt(memptr, end)) { break } + } + return(start, sub(end, start)) + } + } + + /// @inheritdoc IExtsload + function extsload(bytes32[] calldata slots) external view returns (bytes32[] memory) { + assembly ("memory-safe") { + let memptr := mload(0x40) + let start := memptr + // for abi encoding the response - the array will be found at 0x20 + mstore(memptr, 0x20) + // next we store the length of the return array + mstore(add(memptr, 0x20), slots.length) + // update memptr to the first location to hold an array entry + memptr := add(memptr, 0x40) + // A left bit-shift of 5 is equivalent to multiplying by 32 but costs less gas. + let end := add(memptr, shl(5, slots.length)) + let calldataptr := slots.offset + for {} 1 {} { + mstore(memptr, sload(calldataload(calldataptr))) + memptr := add(memptr, 0x20) + calldataptr := add(calldataptr, 0x20) + if iszero(lt(memptr, end)) { break } + } + return(start, sub(end, start)) + } + } +} diff --git a/CVLByExample/UniswapHooks/lib/uniswap-hooks/lib/v4-core/src/Exttload.sol b/CVLByExample/UniswapHooks/lib/uniswap-hooks/lib/v4-core/src/Exttload.sol new file mode 100644 index 00000000..ad24b75a --- /dev/null +++ b/CVLByExample/UniswapHooks/lib/uniswap-hooks/lib/v4-core/src/Exttload.sol @@ -0,0 +1,40 @@ +// SPDX-License-Identifier: MIT +pragma solidity ^0.8.24; + +import {IExttload} from "./interfaces/IExttload.sol"; + +/// @notice Enables public transient storage access for efficient state retrieval by external contracts. +/// https://eips.ethereum.org/EIPS/eip-2330#rationale +abstract contract Exttload is IExttload { + /// @inheritdoc IExttload + function exttload(bytes32 slot) external view returns (bytes32) { + assembly ("memory-safe") { + mstore(0, tload(slot)) + return(0, 0x20) + } + } + + /// @inheritdoc IExttload + function exttload(bytes32[] calldata slots) external view returns (bytes32[] memory) { + assembly ("memory-safe") { + let memptr := mload(0x40) + let start := memptr + // for abi encoding the response - the array will be found at 0x20 + mstore(memptr, 0x20) + // next we store the length of the return array + mstore(add(memptr, 0x20), slots.length) + // update memptr to the first location to hold an array entry + memptr := add(memptr, 0x40) + // A left bit-shift of 5 is equivalent to multiplying by 32 but costs less gas. + let end := add(memptr, shl(5, slots.length)) + let calldataptr := slots.offset + for {} 1 {} { + mstore(memptr, tload(calldataload(calldataptr))) + memptr := add(memptr, 0x20) + calldataptr := add(calldataptr, 0x20) + if iszero(lt(memptr, end)) { break } + } + return(start, sub(end, start)) + } + } +} diff --git a/CVLByExample/UniswapHooks/lib/uniswap-hooks/lib/v4-core/src/NoDelegateCall.sol b/CVLByExample/UniswapHooks/lib/uniswap-hooks/lib/v4-core/src/NoDelegateCall.sol new file mode 100644 index 00000000..5e6d9609 --- /dev/null +++ b/CVLByExample/UniswapHooks/lib/uniswap-hooks/lib/v4-core/src/NoDelegateCall.sol @@ -0,0 +1,33 @@ +// SPDX-License-Identifier: MIT +pragma solidity ^0.8.0; + +import {CustomRevert} from "./libraries/CustomRevert.sol"; + +/// @title Prevents delegatecall to a contract +/// @notice Base contract that provides a modifier for preventing delegatecall to methods in a child contract +abstract contract NoDelegateCall { + using CustomRevert for bytes4; + + error DelegateCallNotAllowed(); + + /// @dev The original address of this contract + address private immutable original; + + constructor() { + // Immutables are computed in the init code of the contract, and then inlined into the deployed bytecode. + // In other words, this variable won't change when it's checked at runtime. + original = address(this); + } + + /// @dev Private method is used instead of inlining into modifier because modifiers are copied into each method, + /// and the use of immutable means the address bytes are copied in every place the modifier is used. + function checkNotDelegateCall() private view { + if (address(this) != original) DelegateCallNotAllowed.selector.revertWith(); + } + + /// @notice Prevents delegatecall into the modified method + modifier noDelegateCall() { + checkNotDelegateCall(); + _; + } +} diff --git a/CVLByExample/UniswapHooks/lib/uniswap-hooks/lib/v4-core/src/PoolManager.sol b/CVLByExample/UniswapHooks/lib/uniswap-hooks/lib/v4-core/src/PoolManager.sol new file mode 100644 index 00000000..e2829951 --- /dev/null +++ b/CVLByExample/UniswapHooks/lib/uniswap-hooks/lib/v4-core/src/PoolManager.sol @@ -0,0 +1,395 @@ +// SPDX-License-Identifier: BUSL-1.1 +pragma solidity 0.8.26; + +import {Hooks} from "./libraries/Hooks.sol"; +import {Pool} from "./libraries/Pool.sol"; +import {SafeCast} from "./libraries/SafeCast.sol"; +import {Position} from "./libraries/Position.sol"; +import {LPFeeLibrary} from "./libraries/LPFeeLibrary.sol"; +import {Currency, CurrencyLibrary} from "./types/Currency.sol"; +import {PoolKey} from "./types/PoolKey.sol"; +import {TickMath} from "./libraries/TickMath.sol"; +import {NoDelegateCall} from "./NoDelegateCall.sol"; +import {IHooks} from "./interfaces/IHooks.sol"; +import {IPoolManager} from "./interfaces/IPoolManager.sol"; +import {IUnlockCallback} from "./interfaces/callback/IUnlockCallback.sol"; +import {ProtocolFees} from "./ProtocolFees.sol"; +import {ERC6909Claims} from "./ERC6909Claims.sol"; +import {PoolId} from "./types/PoolId.sol"; +import {ModifyLiquidityParams, SwapParams} from "./types/PoolOperation.sol"; +import {BalanceDelta, BalanceDeltaLibrary} from "./types/BalanceDelta.sol"; +import {BeforeSwapDelta} from "./types/BeforeSwapDelta.sol"; +import {Lock} from "./libraries/Lock.sol"; +import {CurrencyDelta} from "./libraries/CurrencyDelta.sol"; +import {NonzeroDeltaCount} from "./libraries/NonzeroDeltaCount.sol"; +import {CurrencyReserves} from "./libraries/CurrencyReserves.sol"; +import {Extsload} from "./Extsload.sol"; +import {Exttload} from "./Exttload.sol"; +import {CustomRevert} from "./libraries/CustomRevert.sol"; + +// 4 +// 44 +// 444 +// 444 4444 +// 4444 4444 4444 +// 4444 4444444 4444 4 +// 4444 44444444 4444 4 +// 44444 4444444 4444444444444444 444444 +// 4 44444 44444444 444444444444444444444 4444 +// 4 44444 4444444 4444444444444444444444 44444 +// 4 444444 4444444 44444444444444444444444 44 4 +// 44 44444 444444 444444444444444444444 4 4 +// 44 44444 44444 4444444444444444444 4 44 +// 44 4444 44 444444444444444 444 +// 444 4444 4444444 +// 4444444444444 44 4 +// 44444444444 444444 444444444 44 +// 444444 4444 4444 4444444444 44 +// 4444 44 44 4 44444444444 +// 44444 444444444 444444444444 4444 +// 44444 44444444 4444 44444444 444444 +// 44444 4444 444444444 44444444 +// 44444 4444 44444444 4444444444 +// 44444 4444 444444444 444444444444 +// 44444 4444 44444444 444444444444 +// 4444444 4444 44444444 4444444 +// 4444444 44444 44444444 4444444 +// 44444444 44444444444444444444444444444 4444 +// 4444444444 44444444444444444444444444444 444 +// 444444444444 444444444444444444444444444444 444 444 +// 44444444444444 444444444 44444 +// 44444 44444444444 444 44444444 444444 +// 44444 4444444444 4444444444 444444 44444444 444444444444 +// 444444444444444 4444 444444 4444444 44444444 444444444444 +// 444444444444444 444 444444 444444 44444444 44444444444 +// 4444444444444 4444 444444 4444 4444444444 +// 444444444444 4 44444 4444 444444444 +// 44444444444 444444 444 44444444 +// 44444444 444444 4444 4444444 +// 44444 444 44444 +// 44444 444 4 4444 +// 44444 444 44 444 +// 44444 444 4444 +// 444444 44444 444 +// 444444444 444 +// 44444 444 +// 444 + +/// @title PoolManager +/// @notice Holds the state for all pools +contract PoolManager is IPoolManager, ProtocolFees, NoDelegateCall, ERC6909Claims, Extsload, Exttload { + using SafeCast for *; + using Pool for *; + using Hooks for IHooks; + using CurrencyDelta for Currency; + using LPFeeLibrary for uint24; + using CurrencyReserves for Currency; + using CustomRevert for bytes4; + + int24 private constant MAX_TICK_SPACING = TickMath.MAX_TICK_SPACING; + + int24 private constant MIN_TICK_SPACING = TickMath.MIN_TICK_SPACING; + + mapping(PoolId id => Pool.State) internal _pools; + + /// @notice This will revert if the contract is locked + modifier onlyWhenUnlocked() { + if (!Lock.isUnlocked()) ManagerLocked.selector.revertWith(); + _; + } + + constructor(address initialOwner) ProtocolFees(initialOwner) {} + + /// @inheritdoc IPoolManager + function unlock(bytes calldata data) external override returns (bytes memory result) { + if (Lock.isUnlocked()) AlreadyUnlocked.selector.revertWith(); + + Lock.unlock(); + + // the caller does everything in this callback, including paying what they owe via calls to settle + result = IUnlockCallback(msg.sender).unlockCallback(data); + + if (NonzeroDeltaCount.read() != 0) CurrencyNotSettled.selector.revertWith(); + Lock.lock(); + } + + /// @inheritdoc IPoolManager + function initialize(PoolKey memory key, uint160 sqrtPriceX96) external noDelegateCall returns (int24 tick) { + // see TickBitmap.sol for overflow conditions that can arise from tick spacing being too large + if (key.tickSpacing > MAX_TICK_SPACING) TickSpacingTooLarge.selector.revertWith(key.tickSpacing); + if (key.tickSpacing < MIN_TICK_SPACING) TickSpacingTooSmall.selector.revertWith(key.tickSpacing); + if (key.currency0 >= key.currency1) { + CurrenciesOutOfOrderOrEqual.selector.revertWith( + Currency.unwrap(key.currency0), Currency.unwrap(key.currency1) + ); + } + if (!key.hooks.isValidHookAddress(key.fee)) Hooks.HookAddressNotValid.selector.revertWith(address(key.hooks)); + + uint24 lpFee = key.fee.getInitialLPFee(); + + key.hooks.beforeInitialize(key, sqrtPriceX96); + + PoolId id = key.toId(); + + tick = _pools[id].initialize(sqrtPriceX96, lpFee); + + // event is emitted before the afterInitialize call to ensure events are always emitted in order + // emit all details of a pool key. poolkeys are not saved in storage and must always be provided by the caller + // the key's fee may be a static fee or a sentinel to denote a dynamic fee. + emit Initialize(id, key.currency0, key.currency1, key.fee, key.tickSpacing, key.hooks, sqrtPriceX96, tick); + + key.hooks.afterInitialize(key, sqrtPriceX96, tick); + } + + /// @inheritdoc IPoolManager + function modifyLiquidity(PoolKey memory key, ModifyLiquidityParams memory params, bytes calldata hookData) + external + onlyWhenUnlocked + noDelegateCall + returns (BalanceDelta callerDelta, BalanceDelta feesAccrued) + { + PoolId id = key.toId(); + { + Pool.State storage pool = _getPool(id); + pool.checkPoolInitialized(); + + key.hooks.beforeModifyLiquidity(key, params, hookData); + + BalanceDelta principalDelta; + (principalDelta, feesAccrued) = pool.modifyLiquidity( + Pool.ModifyLiquidityParams({ + owner: msg.sender, + tickLower: params.tickLower, + tickUpper: params.tickUpper, + liquidityDelta: params.liquidityDelta.toInt128(), + tickSpacing: key.tickSpacing, + salt: params.salt + }) + ); + + // fee delta and principal delta are both accrued to the caller + callerDelta = principalDelta + feesAccrued; + } + + // event is emitted before the afterModifyLiquidity call to ensure events are always emitted in order + emit ModifyLiquidity(id, msg.sender, params.tickLower, params.tickUpper, params.liquidityDelta, params.salt); + + BalanceDelta hookDelta; + (callerDelta, hookDelta) = key.hooks.afterModifyLiquidity(key, params, callerDelta, feesAccrued, hookData); + + // if the hook doesn't have the flag to be able to return deltas, hookDelta will always be 0 + if (hookDelta != BalanceDeltaLibrary.ZERO_DELTA) _accountPoolBalanceDelta(key, hookDelta, address(key.hooks)); + + _accountPoolBalanceDelta(key, callerDelta, msg.sender); + } + + /// @inheritdoc IPoolManager + function swap(PoolKey memory key, SwapParams memory params, bytes calldata hookData) + external + onlyWhenUnlocked + noDelegateCall + returns (BalanceDelta swapDelta) + { + if (params.amountSpecified == 0) SwapAmountCannotBeZero.selector.revertWith(); + PoolId id = key.toId(); + Pool.State storage pool = _getPool(id); + pool.checkPoolInitialized(); + + BeforeSwapDelta beforeSwapDelta; + { + int256 amountToSwap; + uint24 lpFeeOverride; + (amountToSwap, beforeSwapDelta, lpFeeOverride) = key.hooks.beforeSwap(key, params, hookData); + + // execute swap, account protocol fees, and emit swap event + // _swap is needed to avoid stack too deep error + swapDelta = _swap( + pool, + id, + Pool.SwapParams({ + tickSpacing: key.tickSpacing, + zeroForOne: params.zeroForOne, + amountSpecified: amountToSwap, + sqrtPriceLimitX96: params.sqrtPriceLimitX96, + lpFeeOverride: lpFeeOverride + }), + params.zeroForOne ? key.currency0 : key.currency1 // input token + ); + } + + BalanceDelta hookDelta; + (swapDelta, hookDelta) = key.hooks.afterSwap(key, params, swapDelta, hookData, beforeSwapDelta); + + // if the hook doesn't have the flag to be able to return deltas, hookDelta will always be 0 + if (hookDelta != BalanceDeltaLibrary.ZERO_DELTA) _accountPoolBalanceDelta(key, hookDelta, address(key.hooks)); + + _accountPoolBalanceDelta(key, swapDelta, msg.sender); + } + + /// @notice Internal swap function to execute a swap, take protocol fees on input token, and emit the swap event + function _swap(Pool.State storage pool, PoolId id, Pool.SwapParams memory params, Currency inputCurrency) + internal + returns (BalanceDelta) + { + (BalanceDelta delta, uint256 amountToProtocol, uint24 swapFee, Pool.SwapResult memory result) = + pool.swap(params); + + // the fee is on the input currency + if (amountToProtocol > 0) _updateProtocolFees(inputCurrency, amountToProtocol); + + // event is emitted before the afterSwap call to ensure events are always emitted in order + emit Swap( + id, + msg.sender, + delta.amount0(), + delta.amount1(), + result.sqrtPriceX96, + result.liquidity, + result.tick, + swapFee + ); + + return delta; + } + + /// @inheritdoc IPoolManager + function donate(PoolKey memory key, uint256 amount0, uint256 amount1, bytes calldata hookData) + external + onlyWhenUnlocked + noDelegateCall + returns (BalanceDelta delta) + { + PoolId poolId = key.toId(); + Pool.State storage pool = _getPool(poolId); + pool.checkPoolInitialized(); + + key.hooks.beforeDonate(key, amount0, amount1, hookData); + + delta = pool.donate(amount0, amount1); + + _accountPoolBalanceDelta(key, delta, msg.sender); + + // event is emitted before the afterDonate call to ensure events are always emitted in order + emit Donate(poolId, msg.sender, amount0, amount1); + + key.hooks.afterDonate(key, amount0, amount1, hookData); + } + + /// @inheritdoc IPoolManager + function sync(Currency currency) external { + // address(0) is used for the native currency + if (currency.isAddressZero()) { + // The reserves balance is not used for native settling, so we only need to reset the currency. + CurrencyReserves.resetCurrency(); + } else { + uint256 balance = currency.balanceOfSelf(); + CurrencyReserves.syncCurrencyAndReserves(currency, balance); + } + } + + /// @inheritdoc IPoolManager + function take(Currency currency, address to, uint256 amount) external onlyWhenUnlocked { + unchecked { + // negation must be safe as amount is not negative + _accountDelta(currency, -(amount.toInt128()), msg.sender); + currency.transfer(to, amount); + } + } + + /// @inheritdoc IPoolManager + function settle() external payable onlyWhenUnlocked returns (uint256) { + return _settle(msg.sender); + } + + /// @inheritdoc IPoolManager + function settleFor(address recipient) external payable onlyWhenUnlocked returns (uint256) { + return _settle(recipient); + } + + /// @inheritdoc IPoolManager + function clear(Currency currency, uint256 amount) external onlyWhenUnlocked { + int256 current = currency.getDelta(msg.sender); + // Because input is `uint256`, only positive amounts can be cleared. + int128 amountDelta = amount.toInt128(); + if (amountDelta != current) MustClearExactPositiveDelta.selector.revertWith(); + // negation must be safe as amountDelta is positive + unchecked { + _accountDelta(currency, -(amountDelta), msg.sender); + } + } + + /// @inheritdoc IPoolManager + function mint(address to, uint256 id, uint256 amount) external onlyWhenUnlocked { + unchecked { + Currency currency = CurrencyLibrary.fromId(id); + // negation must be safe as amount is not negative + _accountDelta(currency, -(amount.toInt128()), msg.sender); + _mint(to, currency.toId(), amount); + } + } + + /// @inheritdoc IPoolManager + function burn(address from, uint256 id, uint256 amount) external onlyWhenUnlocked { + Currency currency = CurrencyLibrary.fromId(id); + _accountDelta(currency, amount.toInt128(), msg.sender); + _burnFrom(from, currency.toId(), amount); + } + + /// @inheritdoc IPoolManager + function updateDynamicLPFee(PoolKey memory key, uint24 newDynamicLPFee) external { + if (!key.fee.isDynamicFee() || msg.sender != address(key.hooks)) { + UnauthorizedDynamicLPFeeUpdate.selector.revertWith(); + } + newDynamicLPFee.validate(); + PoolId id = key.toId(); + _pools[id].setLPFee(newDynamicLPFee); + } + + // if settling native, integrators should still call `sync` first to avoid DoS attack vectors + function _settle(address recipient) internal returns (uint256 paid) { + Currency currency = CurrencyReserves.getSyncedCurrency(); + + // if not previously synced, or the syncedCurrency slot has been reset, expects native currency to be settled + if (currency.isAddressZero()) { + paid = msg.value; + } else { + if (msg.value > 0) NonzeroNativeValue.selector.revertWith(); + // Reserves are guaranteed to be set because currency and reserves are always set together + uint256 reservesBefore = CurrencyReserves.getSyncedReserves(); + uint256 reservesNow = currency.balanceOfSelf(); + paid = reservesNow - reservesBefore; + CurrencyReserves.resetCurrency(); + } + + _accountDelta(currency, paid.toInt128(), recipient); + } + + /// @notice Adds a balance delta in a currency for a target address + function _accountDelta(Currency currency, int128 delta, address target) internal { + if (delta == 0) return; + + (int256 previous, int256 next) = currency.applyDelta(target, delta); + + if (next == 0) { + NonzeroDeltaCount.decrement(); + } else if (previous == 0) { + NonzeroDeltaCount.increment(); + } + } + + /// @notice Accounts the deltas of 2 currencies to a target address + function _accountPoolBalanceDelta(PoolKey memory key, BalanceDelta delta, address target) internal { + _accountDelta(key.currency0, delta.amount0(), target); + _accountDelta(key.currency1, delta.amount1(), target); + } + + /// @notice Implementation of the _getPool function defined in ProtocolFees + function _getPool(PoolId id) internal view override returns (Pool.State storage) { + return _pools[id]; + } + + /// @notice Implementation of the _isUnlocked function defined in ProtocolFees + function _isUnlocked() internal view override returns (bool) { + return Lock.isUnlocked(); + } +} diff --git a/CVLByExample/UniswapHooks/lib/uniswap-hooks/lib/v4-core/src/ProtocolFees.sol b/CVLByExample/UniswapHooks/lib/uniswap-hooks/lib/v4-core/src/ProtocolFees.sol new file mode 100644 index 00000000..36d29d66 --- /dev/null +++ b/CVLByExample/UniswapHooks/lib/uniswap-hooks/lib/v4-core/src/ProtocolFees.sol @@ -0,0 +1,71 @@ +// SPDX-License-Identifier: MIT +pragma solidity ^0.8.0; + +import {Currency} from "./types/Currency.sol"; +import {CurrencyReserves} from "./libraries/CurrencyReserves.sol"; +import {IProtocolFees} from "./interfaces/IProtocolFees.sol"; +import {PoolKey} from "./types/PoolKey.sol"; +import {ProtocolFeeLibrary} from "./libraries/ProtocolFeeLibrary.sol"; +import {Owned} from "solmate/src/auth/Owned.sol"; +import {PoolId} from "./types/PoolId.sol"; +import {Pool} from "./libraries/Pool.sol"; +import {CustomRevert} from "./libraries/CustomRevert.sol"; + +/// @notice Contract handling the setting and accrual of protocol fees +abstract contract ProtocolFees is IProtocolFees, Owned { + using ProtocolFeeLibrary for uint24; + using Pool for Pool.State; + using CustomRevert for bytes4; + + /// @inheritdoc IProtocolFees + mapping(Currency currency => uint256 amount) public protocolFeesAccrued; + + /// @inheritdoc IProtocolFees + address public protocolFeeController; + + constructor(address initialOwner) Owned(initialOwner) {} + + /// @inheritdoc IProtocolFees + function setProtocolFeeController(address controller) external onlyOwner { + protocolFeeController = controller; + emit ProtocolFeeControllerUpdated(controller); + } + + /// @inheritdoc IProtocolFees + function setProtocolFee(PoolKey memory key, uint24 newProtocolFee) external { + if (msg.sender != protocolFeeController) InvalidCaller.selector.revertWith(); + if (!newProtocolFee.isValidProtocolFee()) ProtocolFeeTooLarge.selector.revertWith(newProtocolFee); + PoolId id = key.toId(); + _getPool(id).setProtocolFee(newProtocolFee); + emit ProtocolFeeUpdated(id, newProtocolFee); + } + + /// @inheritdoc IProtocolFees + function collectProtocolFees(address recipient, Currency currency, uint256 amount) + external + returns (uint256 amountCollected) + { + if (msg.sender != protocolFeeController) InvalidCaller.selector.revertWith(); + if (!currency.isAddressZero() && CurrencyReserves.getSyncedCurrency() == currency) { + // prevent transfer between the sync and settle balanceOfs (native settle uses msg.value) + ProtocolFeeCurrencySynced.selector.revertWith(); + } + + amountCollected = (amount == 0) ? protocolFeesAccrued[currency] : amount; + protocolFeesAccrued[currency] -= amountCollected; + currency.transfer(recipient, amountCollected); + } + + /// @dev abstract internal function to allow the ProtocolFees contract to access the lock + function _isUnlocked() internal virtual returns (bool); + + /// @dev abstract internal function to allow the ProtocolFees contract to access pool state + /// @dev this is overridden in PoolManager.sol to give access to the _pools mapping + function _getPool(PoolId id) internal virtual returns (Pool.State storage); + + function _updateProtocolFees(Currency currency, uint256 amount) internal { + unchecked { + protocolFeesAccrued[currency] += amount; + } + } +} diff --git a/CVLByExample/UniswapHooks/lib/uniswap-hooks/lib/v4-core/src/interfaces/IExtsload.sol b/CVLByExample/UniswapHooks/lib/uniswap-hooks/lib/v4-core/src/interfaces/IExtsload.sol new file mode 100644 index 00000000..600402d7 --- /dev/null +++ b/CVLByExample/UniswapHooks/lib/uniswap-hooks/lib/v4-core/src/interfaces/IExtsload.sol @@ -0,0 +1,21 @@ +// SPDX-License-Identifier: MIT +pragma solidity ^0.8.0; + +/// @notice Interface for functions to access any storage slot in a contract +interface IExtsload { + /// @notice Called by external contracts to access granular pool state + /// @param slot Key of slot to sload + /// @return value The value of the slot as bytes32 + function extsload(bytes32 slot) external view returns (bytes32 value); + + /// @notice Called by external contracts to access granular pool state + /// @param startSlot Key of slot to start sloading from + /// @param nSlots Number of slots to load into return value + /// @return values List of loaded values. + function extsload(bytes32 startSlot, uint256 nSlots) external view returns (bytes32[] memory values); + + /// @notice Called by external contracts to access sparse pool state + /// @param slots List of slots to SLOAD from. + /// @return values List of loaded values. + function extsload(bytes32[] calldata slots) external view returns (bytes32[] memory values); +} diff --git a/CVLByExample/UniswapHooks/lib/uniswap-hooks/lib/v4-core/src/interfaces/IExttload.sol b/CVLByExample/UniswapHooks/lib/uniswap-hooks/lib/v4-core/src/interfaces/IExttload.sol new file mode 100644 index 00000000..1e0482e8 --- /dev/null +++ b/CVLByExample/UniswapHooks/lib/uniswap-hooks/lib/v4-core/src/interfaces/IExttload.sol @@ -0,0 +1,15 @@ +// SPDX-License-Identifier: MIT +pragma solidity ^0.8.24; + +/// @notice Interface for functions to access any transient storage slot in a contract +interface IExttload { + /// @notice Called by external contracts to access transient storage of the contract + /// @param slot Key of slot to tload + /// @return value The value of the slot as bytes32 + function exttload(bytes32 slot) external view returns (bytes32 value); + + /// @notice Called by external contracts to access sparse transient pool state + /// @param slots List of slots to tload + /// @return values List of loaded values + function exttload(bytes32[] calldata slots) external view returns (bytes32[] memory values); +} diff --git a/CVLByExample/UniswapHooks/lib/uniswap-hooks/lib/v4-core/src/interfaces/IHooks.sol b/CVLByExample/UniswapHooks/lib/uniswap-hooks/lib/v4-core/src/interfaces/IHooks.sol new file mode 100644 index 00000000..5a44a918 --- /dev/null +++ b/CVLByExample/UniswapHooks/lib/uniswap-hooks/lib/v4-core/src/interfaces/IHooks.sol @@ -0,0 +1,152 @@ +// SPDX-License-Identifier: MIT +pragma solidity ^0.8.0; + +import {PoolKey} from "../types/PoolKey.sol"; +import {BalanceDelta} from "../types/BalanceDelta.sol"; +import {ModifyLiquidityParams, SwapParams} from "../types/PoolOperation.sol"; +import {BeforeSwapDelta} from "../types/BeforeSwapDelta.sol"; + +/// @notice V4 decides whether to invoke specific hooks by inspecting the least significant bits +/// of the address that the hooks contract is deployed to. +/// For example, a hooks contract deployed to address: 0x0000000000000000000000000000000000002400 +/// has the lowest bits '10 0100 0000 0000' which would cause the 'before initialize' and 'after add liquidity' hooks to be used. +/// See the Hooks library for the full spec. +/// @dev Should only be callable by the v4 PoolManager. +interface IHooks { + /// @notice The hook called before the state of a pool is initialized + /// @param sender The initial msg.sender for the initialize call + /// @param key The key for the pool being initialized + /// @param sqrtPriceX96 The sqrt(price) of the pool as a Q64.96 + /// @return bytes4 The function selector for the hook + function beforeInitialize(address sender, PoolKey calldata key, uint160 sqrtPriceX96) external returns (bytes4); + + /// @notice The hook called after the state of a pool is initialized + /// @param sender The initial msg.sender for the initialize call + /// @param key The key for the pool being initialized + /// @param sqrtPriceX96 The sqrt(price) of the pool as a Q64.96 + /// @param tick The current tick after the state of a pool is initialized + /// @return bytes4 The function selector for the hook + function afterInitialize(address sender, PoolKey calldata key, uint160 sqrtPriceX96, int24 tick) + external + returns (bytes4); + + /// @notice The hook called before liquidity is added + /// @param sender The initial msg.sender for the add liquidity call + /// @param key The key for the pool + /// @param params The parameters for adding liquidity + /// @param hookData Arbitrary data handed into the PoolManager by the liquidity provider to be passed on to the hook + /// @return bytes4 The function selector for the hook + function beforeAddLiquidity( + address sender, + PoolKey calldata key, + ModifyLiquidityParams calldata params, + bytes calldata hookData + ) external returns (bytes4); + + /// @notice The hook called after liquidity is added + /// @param sender The initial msg.sender for the add liquidity call + /// @param key The key for the pool + /// @param params The parameters for adding liquidity + /// @param delta The caller's balance delta after adding liquidity; the sum of principal delta, fees accrued, and hook delta + /// @param feesAccrued The fees accrued since the last time fees were collected from this position + /// @param hookData Arbitrary data handed into the PoolManager by the liquidity provider to be passed on to the hook + /// @return bytes4 The function selector for the hook + /// @return BalanceDelta The hook's delta in token0 and token1. Positive: the hook is owed/took currency, negative: the hook owes/sent currency + function afterAddLiquidity( + address sender, + PoolKey calldata key, + ModifyLiquidityParams calldata params, + BalanceDelta delta, + BalanceDelta feesAccrued, + bytes calldata hookData + ) external returns (bytes4, BalanceDelta); + + /// @notice The hook called before liquidity is removed + /// @param sender The initial msg.sender for the remove liquidity call + /// @param key The key for the pool + /// @param params The parameters for removing liquidity + /// @param hookData Arbitrary data handed into the PoolManager by the liquidity provider to be be passed on to the hook + /// @return bytes4 The function selector for the hook + function beforeRemoveLiquidity( + address sender, + PoolKey calldata key, + ModifyLiquidityParams calldata params, + bytes calldata hookData + ) external returns (bytes4); + + /// @notice The hook called after liquidity is removed + /// @param sender The initial msg.sender for the remove liquidity call + /// @param key The key for the pool + /// @param params The parameters for removing liquidity + /// @param delta The caller's balance delta after removing liquidity; the sum of principal delta, fees accrued, and hook delta + /// @param feesAccrued The fees accrued since the last time fees were collected from this position + /// @param hookData Arbitrary data handed into the PoolManager by the liquidity provider to be be passed on to the hook + /// @return bytes4 The function selector for the hook + /// @return BalanceDelta The hook's delta in token0 and token1. Positive: the hook is owed/took currency, negative: the hook owes/sent currency + function afterRemoveLiquidity( + address sender, + PoolKey calldata key, + ModifyLiquidityParams calldata params, + BalanceDelta delta, + BalanceDelta feesAccrued, + bytes calldata hookData + ) external returns (bytes4, BalanceDelta); + + /// @notice The hook called before a swap + /// @param sender The initial msg.sender for the swap call + /// @param key The key for the pool + /// @param params The parameters for the swap + /// @param hookData Arbitrary data handed into the PoolManager by the swapper to be be passed on to the hook + /// @return bytes4 The function selector for the hook + /// @return BeforeSwapDelta The hook's delta in specified and unspecified currencies. Positive: the hook is owed/took currency, negative: the hook owes/sent currency + /// @return uint24 Optionally override the lp fee, only used if three conditions are met: 1. the Pool has a dynamic fee, 2. the value's 2nd highest bit is set (23rd bit, 0x400000), and 3. the value is less than or equal to the maximum fee (1 million) + function beforeSwap(address sender, PoolKey calldata key, SwapParams calldata params, bytes calldata hookData) + external + returns (bytes4, BeforeSwapDelta, uint24); + + /// @notice The hook called after a swap + /// @param sender The initial msg.sender for the swap call + /// @param key The key for the pool + /// @param params The parameters for the swap + /// @param delta The amount owed to the caller (positive) or owed to the pool (negative) + /// @param hookData Arbitrary data handed into the PoolManager by the swapper to be be passed on to the hook + /// @return bytes4 The function selector for the hook + /// @return int128 The hook's delta in unspecified currency. Positive: the hook is owed/took currency, negative: the hook owes/sent currency + function afterSwap( + address sender, + PoolKey calldata key, + SwapParams calldata params, + BalanceDelta delta, + bytes calldata hookData + ) external returns (bytes4, int128); + + /// @notice The hook called before donate + /// @param sender The initial msg.sender for the donate call + /// @param key The key for the pool + /// @param amount0 The amount of token0 being donated + /// @param amount1 The amount of token1 being donated + /// @param hookData Arbitrary data handed into the PoolManager by the donor to be be passed on to the hook + /// @return bytes4 The function selector for the hook + function beforeDonate( + address sender, + PoolKey calldata key, + uint256 amount0, + uint256 amount1, + bytes calldata hookData + ) external returns (bytes4); + + /// @notice The hook called after donate + /// @param sender The initial msg.sender for the donate call + /// @param key The key for the pool + /// @param amount0 The amount of token0 being donated + /// @param amount1 The amount of token1 being donated + /// @param hookData Arbitrary data handed into the PoolManager by the donor to be be passed on to the hook + /// @return bytes4 The function selector for the hook + function afterDonate( + address sender, + PoolKey calldata key, + uint256 amount0, + uint256 amount1, + bytes calldata hookData + ) external returns (bytes4); +} diff --git a/CVLByExample/UniswapHooks/lib/uniswap-hooks/lib/v4-core/src/interfaces/IPoolManager.sol b/CVLByExample/UniswapHooks/lib/uniswap-hooks/lib/v4-core/src/interfaces/IPoolManager.sol new file mode 100644 index 00000000..7cd319ae --- /dev/null +++ b/CVLByExample/UniswapHooks/lib/uniswap-hooks/lib/v4-core/src/interfaces/IPoolManager.sol @@ -0,0 +1,217 @@ +// SPDX-License-Identifier: MIT +pragma solidity ^0.8.24; + +import {Currency} from "../types/Currency.sol"; +import {PoolKey} from "../types/PoolKey.sol"; +import {IHooks} from "./IHooks.sol"; +import {IERC6909Claims} from "./external/IERC6909Claims.sol"; +import {IProtocolFees} from "./IProtocolFees.sol"; +import {BalanceDelta} from "../types/BalanceDelta.sol"; +import {PoolId} from "../types/PoolId.sol"; +import {IExtsload} from "./IExtsload.sol"; +import {IExttload} from "./IExttload.sol"; +import {ModifyLiquidityParams, SwapParams} from "../types/PoolOperation.sol"; + +/// @notice Interface for the PoolManager +interface IPoolManager is IProtocolFees, IERC6909Claims, IExtsload, IExttload { + /// @notice Thrown when a currency is not netted out after the contract is unlocked + error CurrencyNotSettled(); + + /// @notice Thrown when trying to interact with a non-initialized pool + error PoolNotInitialized(); + + /// @notice Thrown when unlock is called, but the contract is already unlocked + error AlreadyUnlocked(); + + /// @notice Thrown when a function is called that requires the contract to be unlocked, but it is not + error ManagerLocked(); + + /// @notice Pools are limited to type(int16).max tickSpacing in #initialize, to prevent overflow + error TickSpacingTooLarge(int24 tickSpacing); + + /// @notice Pools must have a positive non-zero tickSpacing passed to #initialize + error TickSpacingTooSmall(int24 tickSpacing); + + /// @notice PoolKey must have currencies where address(currency0) < address(currency1) + error CurrenciesOutOfOrderOrEqual(address currency0, address currency1); + + /// @notice Thrown when a call to updateDynamicLPFee is made by an address that is not the hook, + /// or on a pool that does not have a dynamic swap fee. + error UnauthorizedDynamicLPFeeUpdate(); + + /// @notice Thrown when trying to swap amount of 0 + error SwapAmountCannotBeZero(); + + ///@notice Thrown when native currency is passed to a non native settlement + error NonzeroNativeValue(); + + /// @notice Thrown when `clear` is called with an amount that is not exactly equal to the open currency delta. + error MustClearExactPositiveDelta(); + + /// @notice Emitted when a new pool is initialized + /// @param id The abi encoded hash of the pool key struct for the new pool + /// @param currency0 The first currency of the pool by address sort order + /// @param currency1 The second currency of the pool by address sort order + /// @param fee The fee collected upon every swap in the pool, denominated in hundredths of a bip + /// @param tickSpacing The minimum number of ticks between initialized ticks + /// @param hooks The hooks contract address for the pool, or address(0) if none + /// @param sqrtPriceX96 The price of the pool on initialization + /// @param tick The initial tick of the pool corresponding to the initialized price + event Initialize( + PoolId indexed id, + Currency indexed currency0, + Currency indexed currency1, + uint24 fee, + int24 tickSpacing, + IHooks hooks, + uint160 sqrtPriceX96, + int24 tick + ); + + /// @notice Emitted when a liquidity position is modified + /// @param id The abi encoded hash of the pool key struct for the pool that was modified + /// @param sender The address that modified the pool + /// @param tickLower The lower tick of the position + /// @param tickUpper The upper tick of the position + /// @param liquidityDelta The amount of liquidity that was added or removed + /// @param salt The extra data to make positions unique + event ModifyLiquidity( + PoolId indexed id, address indexed sender, int24 tickLower, int24 tickUpper, int256 liquidityDelta, bytes32 salt + ); + + /// @notice Emitted for swaps between currency0 and currency1 + /// @param id The abi encoded hash of the pool key struct for the pool that was modified + /// @param sender The address that initiated the swap call, and that received the callback + /// @param amount0 The delta of the currency0 balance of the pool + /// @param amount1 The delta of the currency1 balance of the pool + /// @param sqrtPriceX96 The sqrt(price) of the pool after the swap, as a Q64.96 + /// @param liquidity The liquidity of the pool after the swap + /// @param tick The log base 1.0001 of the price of the pool after the swap + /// @param fee The swap fee in hundredths of a bip + event Swap( + PoolId indexed id, + address indexed sender, + int128 amount0, + int128 amount1, + uint160 sqrtPriceX96, + uint128 liquidity, + int24 tick, + uint24 fee + ); + + /// @notice Emitted for donations + /// @param id The abi encoded hash of the pool key struct for the pool that was donated to + /// @param sender The address that initiated the donate call + /// @param amount0 The amount donated in currency0 + /// @param amount1 The amount donated in currency1 + event Donate(PoolId indexed id, address indexed sender, uint256 amount0, uint256 amount1); + + /// @notice All interactions on the contract that account deltas require unlocking. A caller that calls `unlock` must implement + /// `IUnlockCallback(msg.sender).unlockCallback(data)`, where they interact with the remaining functions on this contract. + /// @dev The only functions callable without an unlocking are `initialize` and `updateDynamicLPFee` + /// @param data Any data to pass to the callback, via `IUnlockCallback(msg.sender).unlockCallback(data)` + /// @return The data returned by the call to `IUnlockCallback(msg.sender).unlockCallback(data)` + function unlock(bytes calldata data) external returns (bytes memory); + + /// @notice Initialize the state for a given pool ID + /// @dev A swap fee totaling MAX_SWAP_FEE (100%) makes exact output swaps impossible since the input is entirely consumed by the fee + /// @param key The pool key for the pool to initialize + /// @param sqrtPriceX96 The initial square root price + /// @return tick The initial tick of the pool + function initialize(PoolKey memory key, uint160 sqrtPriceX96) external returns (int24 tick); + + /// @notice Modify the liquidity for the given pool + /// @dev Poke by calling with a zero liquidityDelta + /// @param key The pool to modify liquidity in + /// @param params The parameters for modifying the liquidity + /// @param hookData The data to pass through to the add/removeLiquidity hooks + /// @return callerDelta The balance delta of the caller of modifyLiquidity. This is the total of both principal, fee deltas, and hook deltas if applicable + /// @return feesAccrued The balance delta of the fees generated in the liquidity range. Returned for informational purposes + /// @dev Note that feesAccrued can be artificially inflated by a malicious actor and integrators should be careful using the value + /// For pools with a single liquidity position, actors can donate to themselves to inflate feeGrowthGlobal (and consequently feesAccrued) + /// atomically donating and collecting fees in the same unlockCallback may make the inflated value more extreme + function modifyLiquidity(PoolKey memory key, ModifyLiquidityParams memory params, bytes calldata hookData) + external + returns (BalanceDelta callerDelta, BalanceDelta feesAccrued); + + /// @notice Swap against the given pool + /// @param key The pool to swap in + /// @param params The parameters for swapping + /// @param hookData The data to pass through to the swap hooks + /// @return swapDelta The balance delta of the address swapping + /// @dev Swapping on low liquidity pools may cause unexpected swap amounts when liquidity available is less than amountSpecified. + /// Additionally note that if interacting with hooks that have the BEFORE_SWAP_RETURNS_DELTA_FLAG or AFTER_SWAP_RETURNS_DELTA_FLAG + /// the hook may alter the swap input/output. Integrators should perform checks on the returned swapDelta. + function swap(PoolKey memory key, SwapParams memory params, bytes calldata hookData) + external + returns (BalanceDelta swapDelta); + + /// @notice Donate the given currency amounts to the in-range liquidity providers of a pool + /// @dev Calls to donate can be frontrun adding just-in-time liquidity, with the aim of receiving a portion donated funds. + /// Donors should keep this in mind when designing donation mechanisms. + /// @dev This function donates to in-range LPs at slot0.tick. In certain edge-cases of the swap algorithm, the `sqrtPrice` of + /// a pool can be at the lower boundary of tick `n`, but the `slot0.tick` of the pool is already `n - 1`. In this case a call to + /// `donate` would donate to tick `n - 1` (slot0.tick) not tick `n` (getTickAtSqrtPrice(slot0.sqrtPriceX96)). + /// Read the comments in `Pool.swap()` for more information about this. + /// @param key The key of the pool to donate to + /// @param amount0 The amount of currency0 to donate + /// @param amount1 The amount of currency1 to donate + /// @param hookData The data to pass through to the donate hooks + /// @return BalanceDelta The delta of the caller after the donate + function donate(PoolKey memory key, uint256 amount0, uint256 amount1, bytes calldata hookData) + external + returns (BalanceDelta); + + /// @notice Writes the current ERC20 balance of the specified currency to transient storage + /// This is used to checkpoint balances for the manager and derive deltas for the caller. + /// @dev This MUST be called before any ERC20 tokens are sent into the contract, but can be skipped + /// for native tokens because the amount to settle is determined by the sent value. + /// However, if an ERC20 token has been synced and not settled, and the caller instead wants to settle + /// native funds, this function can be called with the native currency to then be able to settle the native currency + function sync(Currency currency) external; + + /// @notice Called by the user to net out some value owed to the user + /// @dev Will revert if the requested amount is not available, consider using `mint` instead + /// @dev Can also be used as a mechanism for free flash loans + /// @param currency The currency to withdraw from the pool manager + /// @param to The address to withdraw to + /// @param amount The amount of currency to withdraw + function take(Currency currency, address to, uint256 amount) external; + + /// @notice Called by the user to pay what is owed + /// @return paid The amount of currency settled + function settle() external payable returns (uint256 paid); + + /// @notice Called by the user to pay on behalf of another address + /// @param recipient The address to credit for the payment + /// @return paid The amount of currency settled + function settleFor(address recipient) external payable returns (uint256 paid); + + /// @notice WARNING - Any currency that is cleared, will be non-retrievable, and locked in the contract permanently. + /// A call to clear will zero out a positive balance WITHOUT a corresponding transfer. + /// @dev This could be used to clear a balance that is considered dust. + /// Additionally, the amount must be the exact positive balance. This is to enforce that the caller is aware of the amount being cleared. + function clear(Currency currency, uint256 amount) external; + + /// @notice Called by the user to move value into ERC6909 balance + /// @param to The address to mint the tokens to + /// @param id The currency address to mint to ERC6909s, as a uint256 + /// @param amount The amount of currency to mint + /// @dev The id is converted to a uint160 to correspond to a currency address + /// If the upper 12 bytes are not 0, they will be 0-ed out + function mint(address to, uint256 id, uint256 amount) external; + + /// @notice Called by the user to move value from ERC6909 balance + /// @param from The address to burn the tokens from + /// @param id The currency address to burn from ERC6909s, as a uint256 + /// @param amount The amount of currency to burn + /// @dev The id is converted to a uint160 to correspond to a currency address + /// If the upper 12 bytes are not 0, they will be 0-ed out + function burn(address from, uint256 id, uint256 amount) external; + + /// @notice Updates the pools lp fees for the a pool that has enabled dynamic lp fees. + /// @dev A swap fee totaling MAX_SWAP_FEE (100%) makes exact output swaps impossible since the input is entirely consumed by the fee + /// @param key The key of the pool to update dynamic LP fees for + /// @param newDynamicLPFee The new dynamic pool LP fee + function updateDynamicLPFee(PoolKey memory key, uint24 newDynamicLPFee) external; +} diff --git a/CVLByExample/UniswapHooks/lib/uniswap-hooks/lib/v4-core/src/interfaces/IProtocolFees.sol b/CVLByExample/UniswapHooks/lib/uniswap-hooks/lib/v4-core/src/interfaces/IProtocolFees.sol new file mode 100644 index 00000000..2c2af594 --- /dev/null +++ b/CVLByExample/UniswapHooks/lib/uniswap-hooks/lib/v4-core/src/interfaces/IProtocolFees.sol @@ -0,0 +1,52 @@ +// SPDX-License-Identifier: MIT +pragma solidity ^0.8.0; + +import {Currency} from "../types/Currency.sol"; +import {PoolId} from "../types/PoolId.sol"; +import {PoolKey} from "../types/PoolKey.sol"; + +/// @notice Interface for all protocol-fee related functions in the pool manager +interface IProtocolFees { + /// @notice Thrown when protocol fee is set too high + error ProtocolFeeTooLarge(uint24 fee); + + /// @notice Thrown when collectProtocolFees or setProtocolFee is not called by the controller. + error InvalidCaller(); + + /// @notice Thrown when collectProtocolFees is attempted on a token that is synced. + error ProtocolFeeCurrencySynced(); + + /// @notice Emitted when the protocol fee controller address is updated in setProtocolFeeController. + event ProtocolFeeControllerUpdated(address indexed protocolFeeController); + + /// @notice Emitted when the protocol fee is updated for a pool. + event ProtocolFeeUpdated(PoolId indexed id, uint24 protocolFee); + + /// @notice Given a currency address, returns the protocol fees accrued in that currency + /// @param currency The currency to check + /// @return amount The amount of protocol fees accrued in the currency + function protocolFeesAccrued(Currency currency) external view returns (uint256 amount); + + /// @notice Sets the protocol fee for the given pool + /// @param key The key of the pool to set a protocol fee for + /// @param newProtocolFee The fee to set + function setProtocolFee(PoolKey memory key, uint24 newProtocolFee) external; + + /// @notice Sets the protocol fee controller + /// @param controller The new protocol fee controller + function setProtocolFeeController(address controller) external; + + /// @notice Collects the protocol fees for a given recipient and currency, returning the amount collected + /// @dev This will revert if the contract is unlocked + /// @param recipient The address to receive the protocol fees + /// @param currency The currency to withdraw + /// @param amount The amount of currency to withdraw + /// @return amountCollected The amount of currency successfully withdrawn + function collectProtocolFees(address recipient, Currency currency, uint256 amount) + external + returns (uint256 amountCollected); + + /// @notice Returns the current protocol fee controller address + /// @return address The current protocol fee controller address + function protocolFeeController() external view returns (address); +} diff --git a/CVLByExample/UniswapHooks/lib/uniswap-hooks/lib/v4-core/src/interfaces/callback/IUnlockCallback.sol b/CVLByExample/UniswapHooks/lib/uniswap-hooks/lib/v4-core/src/interfaces/callback/IUnlockCallback.sol new file mode 100644 index 00000000..7957de5c --- /dev/null +++ b/CVLByExample/UniswapHooks/lib/uniswap-hooks/lib/v4-core/src/interfaces/callback/IUnlockCallback.sol @@ -0,0 +1,10 @@ +// SPDX-License-Identifier: MIT +pragma solidity ^0.8.0; + +/// @notice Interface for the callback executed when an address unlocks the pool manager +interface IUnlockCallback { + /// @notice Called by the pool manager on `msg.sender` when the manager is unlocked + /// @param data The data that was passed to the call to unlock + /// @return Any data that you want to be returned from the unlock call + function unlockCallback(bytes calldata data) external returns (bytes memory); +} diff --git a/CVLByExample/UniswapHooks/lib/uniswap-hooks/lib/v4-core/src/interfaces/external/IERC20Minimal.sol b/CVLByExample/UniswapHooks/lib/uniswap-hooks/lib/v4-core/src/interfaces/external/IERC20Minimal.sol new file mode 100644 index 00000000..11e1db4e --- /dev/null +++ b/CVLByExample/UniswapHooks/lib/uniswap-hooks/lib/v4-core/src/interfaces/external/IERC20Minimal.sol @@ -0,0 +1,48 @@ +// SPDX-License-Identifier: MIT +pragma solidity ^0.8.0; + +/// @title Minimal ERC20 interface for Uniswap +/// @notice Contains a subset of the full ERC20 interface that is used in Uniswap V3 +interface IERC20Minimal { + /// @notice Returns an account's balance in the token + /// @param account The account for which to look up the number of tokens it has, i.e. its balance + /// @return The number of tokens held by the account + function balanceOf(address account) external view returns (uint256); + + /// @notice Transfers the amount of token from the `msg.sender` to the recipient + /// @param recipient The account that will receive the amount transferred + /// @param amount The number of tokens to send from the sender to the recipient + /// @return Returns true for a successful transfer, false for an unsuccessful transfer + function transfer(address recipient, uint256 amount) external returns (bool); + + /// @notice Returns the current allowance given to a spender by an owner + /// @param owner The account of the token owner + /// @param spender The account of the token spender + /// @return The current allowance granted by `owner` to `spender` + function allowance(address owner, address spender) external view returns (uint256); + + /// @notice Sets the allowance of a spender from the `msg.sender` to the value `amount` + /// @param spender The account which will be allowed to spend a given amount of the owners tokens + /// @param amount The amount of tokens allowed to be used by `spender` + /// @return Returns true for a successful approval, false for unsuccessful + function approve(address spender, uint256 amount) external returns (bool); + + /// @notice Transfers `amount` tokens from `sender` to `recipient` up to the allowance given to the `msg.sender` + /// @param sender The account from which the transfer will be initiated + /// @param recipient The recipient of the transfer + /// @param amount The amount of the transfer + /// @return Returns true for a successful transfer, false for unsuccessful + function transferFrom(address sender, address recipient, uint256 amount) external returns (bool); + + /// @notice Event emitted when tokens are transferred from one address to another, either via `#transfer` or `#transferFrom`. + /// @param from The account from which the tokens were sent, i.e. the balance decreased + /// @param to The account to which the tokens were sent, i.e. the balance increased + /// @param value The amount of tokens that were transferred + event Transfer(address indexed from, address indexed to, uint256 value); + + /// @notice Event emitted when the approval amount for the spender of a given owner's tokens changes. + /// @param owner The account that approved spending of its tokens + /// @param spender The account for which the spending allowance was modified + /// @param value The new allowance from the owner to the spender + event Approval(address indexed owner, address indexed spender, uint256 value); +} diff --git a/CVLByExample/UniswapHooks/lib/uniswap-hooks/lib/v4-core/src/interfaces/external/IERC6909Claims.sol b/CVLByExample/UniswapHooks/lib/uniswap-hooks/lib/v4-core/src/interfaces/external/IERC6909Claims.sol new file mode 100644 index 00000000..f32ac97f --- /dev/null +++ b/CVLByExample/UniswapHooks/lib/uniswap-hooks/lib/v4-core/src/interfaces/external/IERC6909Claims.sol @@ -0,0 +1,66 @@ +// SPDX-License-Identifier: MIT +pragma solidity ^0.8.0; + +/// @notice Interface for claims over a contract balance, wrapped as a ERC6909 +interface IERC6909Claims { + /*////////////////////////////////////////////////////////////// + EVENTS + //////////////////////////////////////////////////////////////*/ + + event OperatorSet(address indexed owner, address indexed operator, bool approved); + + event Approval(address indexed owner, address indexed spender, uint256 indexed id, uint256 amount); + + event Transfer(address caller, address indexed from, address indexed to, uint256 indexed id, uint256 amount); + + /*////////////////////////////////////////////////////////////// + FUNCTIONS + //////////////////////////////////////////////////////////////*/ + + /// @notice Owner balance of an id. + /// @param owner The address of the owner. + /// @param id The id of the token. + /// @return amount The balance of the token. + function balanceOf(address owner, uint256 id) external view returns (uint256 amount); + + /// @notice Spender allowance of an id. + /// @param owner The address of the owner. + /// @param spender The address of the spender. + /// @param id The id of the token. + /// @return amount The allowance of the token. + function allowance(address owner, address spender, uint256 id) external view returns (uint256 amount); + + /// @notice Checks if a spender is approved by an owner as an operator + /// @param owner The address of the owner. + /// @param spender The address of the spender. + /// @return approved The approval status. + function isOperator(address owner, address spender) external view returns (bool approved); + + /// @notice Transfers an amount of an id from the caller to a receiver. + /// @param receiver The address of the receiver. + /// @param id The id of the token. + /// @param amount The amount of the token. + /// @return bool True, always, unless the function reverts + function transfer(address receiver, uint256 id, uint256 amount) external returns (bool); + + /// @notice Transfers an amount of an id from a sender to a receiver. + /// @param sender The address of the sender. + /// @param receiver The address of the receiver. + /// @param id The id of the token. + /// @param amount The amount of the token. + /// @return bool True, always, unless the function reverts + function transferFrom(address sender, address receiver, uint256 id, uint256 amount) external returns (bool); + + /// @notice Approves an amount of an id to a spender. + /// @param spender The address of the spender. + /// @param id The id of the token. + /// @param amount The amount of the token. + /// @return bool True, always + function approve(address spender, uint256 id, uint256 amount) external returns (bool); + + /// @notice Sets or removes an operator for the caller. + /// @param operator The address of the operator. + /// @param approved The approval status. + /// @return bool True, always + function setOperator(address operator, bool approved) external returns (bool); +} diff --git a/CVLByExample/UniswapHooks/lib/uniswap-hooks/lib/v4-core/src/libraries/BitMath.sol b/CVLByExample/UniswapHooks/lib/uniswap-hooks/lib/v4-core/src/libraries/BitMath.sol new file mode 100644 index 00000000..6c20495b --- /dev/null +++ b/CVLByExample/UniswapHooks/lib/uniswap-hooks/lib/v4-core/src/libraries/BitMath.sol @@ -0,0 +1,49 @@ +// SPDX-License-Identifier: MIT +pragma solidity ^0.8.0; + +/// @title BitMath +/// @dev This library provides functionality for computing bit properties of an unsigned integer +/// @author Solady (https://github.com/Vectorized/solady/blob/8200a70e8dc2a77ecb074fc2e99a2a0d36547522/src/utils/LibBit.sol) +library BitMath { + /// @notice Returns the index of the most significant bit of the number, + /// where the least significant bit is at index 0 and the most significant bit is at index 255 + /// @param x the value for which to compute the most significant bit, must be greater than 0 + /// @return r the index of the most significant bit + function mostSignificantBit(uint256 x) internal pure returns (uint8 r) { + require(x > 0); + + assembly ("memory-safe") { + r := shl(7, lt(0xffffffffffffffffffffffffffffffff, x)) + r := or(r, shl(6, lt(0xffffffffffffffff, shr(r, x)))) + r := or(r, shl(5, lt(0xffffffff, shr(r, x)))) + r := or(r, shl(4, lt(0xffff, shr(r, x)))) + r := or(r, shl(3, lt(0xff, shr(r, x)))) + // forgefmt: disable-next-item + r := or(r, byte(and(0x1f, shr(shr(r, x), 0x8421084210842108cc6318c6db6d54be)), + 0x0706060506020500060203020504000106050205030304010505030400000000)) + } + } + + /// @notice Returns the index of the least significant bit of the number, + /// where the least significant bit is at index 0 and the most significant bit is at index 255 + /// @param x the value for which to compute the least significant bit, must be greater than 0 + /// @return r the index of the least significant bit + function leastSignificantBit(uint256 x) internal pure returns (uint8 r) { + require(x > 0); + + assembly ("memory-safe") { + // Isolate the least significant bit. + x := and(x, sub(0, x)) + // For the upper 3 bits of the result, use a De Bruijn-like lookup. + // Credit to adhusson: https://blog.adhusson.com/cheap-find-first-set-evm/ + // forgefmt: disable-next-item + r := shl(5, shr(252, shl(shl(2, shr(250, mul(x, + 0xb6db6db6ddddddddd34d34d349249249210842108c6318c639ce739cffffffff))), + 0x8040405543005266443200005020610674053026020000107506200176117077))) + // For the lower 5 bits of the result, use a De Bruijn lookup. + // forgefmt: disable-next-item + r := or(r, byte(and(div(0xd76453e0, shr(r, x)), 0x1f), + 0x001f0d1e100c1d070f090b19131c1706010e11080a1a141802121b1503160405)) + } + } +} diff --git a/CVLByExample/UniswapHooks/lib/uniswap-hooks/lib/v4-core/src/libraries/CurrencyDelta.sol b/CVLByExample/UniswapHooks/lib/uniswap-hooks/lib/v4-core/src/libraries/CurrencyDelta.sol new file mode 100644 index 00000000..a1049067 --- /dev/null +++ b/CVLByExample/UniswapHooks/lib/uniswap-hooks/lib/v4-core/src/libraries/CurrencyDelta.sol @@ -0,0 +1,42 @@ +// SPDX-License-Identifier: BUSL-1.1 +pragma solidity ^0.8.24; + +import {Currency} from "../types/Currency.sol"; + +/// @title a library to store callers' currency deltas in transient storage +/// @dev this library implements the equivalent of a mapping, as transient storage can only be accessed in assembly +library CurrencyDelta { + /// @notice calculates which storage slot a delta should be stored in for a given account and currency + function _computeSlot(address target, Currency currency) internal pure returns (bytes32 hashSlot) { + assembly ("memory-safe") { + mstore(0, and(target, 0xffffffffffffffffffffffffffffffffffffffff)) + mstore(32, and(currency, 0xffffffffffffffffffffffffffffffffffffffff)) + hashSlot := keccak256(0, 64) + } + } + + function getDelta(Currency currency, address target) internal view returns (int256 delta) { + bytes32 hashSlot = _computeSlot(target, currency); + assembly ("memory-safe") { + delta := tload(hashSlot) + } + } + + /// @notice applies a new currency delta for a given account and currency + /// @return previous The prior value + /// @return next The modified result + function applyDelta(Currency currency, address target, int128 delta) + internal + returns (int256 previous, int256 next) + { + bytes32 hashSlot = _computeSlot(target, currency); + + assembly ("memory-safe") { + previous := tload(hashSlot) + } + next = previous + delta; + assembly ("memory-safe") { + tstore(hashSlot, next) + } + } +} diff --git a/CVLByExample/UniswapHooks/lib/uniswap-hooks/lib/v4-core/src/libraries/CurrencyReserves.sol b/CVLByExample/UniswapHooks/lib/uniswap-hooks/lib/v4-core/src/libraries/CurrencyReserves.sol new file mode 100644 index 00000000..3a3309c3 --- /dev/null +++ b/CVLByExample/UniswapHooks/lib/uniswap-hooks/lib/v4-core/src/libraries/CurrencyReserves.sol @@ -0,0 +1,39 @@ +// SPDX-License-Identifier: BUSL-1.1 +pragma solidity ^0.8.24; + +import {Currency} from "../types/Currency.sol"; +import {CustomRevert} from "./CustomRevert.sol"; + +library CurrencyReserves { + using CustomRevert for bytes4; + + /// bytes32(uint256(keccak256("ReservesOf")) - 1) + bytes32 constant RESERVES_OF_SLOT = 0x1e0745a7db1623981f0b2a5d4232364c00787266eb75ad546f190e6cebe9bd95; + /// bytes32(uint256(keccak256("Currency")) - 1) + bytes32 constant CURRENCY_SLOT = 0x27e098c505d44ec3574004bca052aabf76bd35004c182099d8c575fb238593b9; + + function getSyncedCurrency() internal view returns (Currency currency) { + assembly ("memory-safe") { + currency := tload(CURRENCY_SLOT) + } + } + + function resetCurrency() internal { + assembly ("memory-safe") { + tstore(CURRENCY_SLOT, 0) + } + } + + function syncCurrencyAndReserves(Currency currency, uint256 value) internal { + assembly ("memory-safe") { + tstore(CURRENCY_SLOT, and(currency, 0xffffffffffffffffffffffffffffffffffffffff)) + tstore(RESERVES_OF_SLOT, value) + } + } + + function getSyncedReserves() internal view returns (uint256 value) { + assembly ("memory-safe") { + value := tload(RESERVES_OF_SLOT) + } + } +} diff --git a/CVLByExample/UniswapHooks/lib/uniswap-hooks/lib/v4-core/src/libraries/CustomRevert.sol b/CVLByExample/UniswapHooks/lib/uniswap-hooks/lib/v4-core/src/libraries/CustomRevert.sol new file mode 100644 index 00000000..5d707330 --- /dev/null +++ b/CVLByExample/UniswapHooks/lib/uniswap-hooks/lib/v4-core/src/libraries/CustomRevert.sol @@ -0,0 +1,120 @@ +// SPDX-License-Identifier: MIT +pragma solidity ^0.8.0; + +/// @title Library for reverting with custom errors efficiently +/// @notice Contains functions for reverting with custom errors with different argument types efficiently +/// @dev To use this library, declare `using CustomRevert for bytes4;` and replace `revert CustomError()` with +/// `CustomError.selector.revertWith()` +/// @dev The functions may tamper with the free memory pointer but it is fine since the call context is exited immediately +library CustomRevert { + /// @dev ERC-7751 error for wrapping bubbled up reverts + error WrappedError(address target, bytes4 selector, bytes reason, bytes details); + + /// @dev Reverts with the selector of a custom error in the scratch space + function revertWith(bytes4 selector) internal pure { + assembly ("memory-safe") { + mstore(0, selector) + revert(0, 0x04) + } + } + + /// @dev Reverts with a custom error with an address argument in the scratch space + function revertWith(bytes4 selector, address addr) internal pure { + assembly ("memory-safe") { + mstore(0, selector) + mstore(0x04, and(addr, 0xffffffffffffffffffffffffffffffffffffffff)) + revert(0, 0x24) + } + } + + /// @dev Reverts with a custom error with an int24 argument in the scratch space + function revertWith(bytes4 selector, int24 value) internal pure { + assembly ("memory-safe") { + mstore(0, selector) + mstore(0x04, signextend(2, value)) + revert(0, 0x24) + } + } + + /// @dev Reverts with a custom error with a uint160 argument in the scratch space + function revertWith(bytes4 selector, uint160 value) internal pure { + assembly ("memory-safe") { + mstore(0, selector) + mstore(0x04, and(value, 0xffffffffffffffffffffffffffffffffffffffff)) + revert(0, 0x24) + } + } + + /// @dev Reverts with a custom error with two int24 arguments + function revertWith(bytes4 selector, int24 value1, int24 value2) internal pure { + assembly ("memory-safe") { + let fmp := mload(0x40) + mstore(fmp, selector) + mstore(add(fmp, 0x04), signextend(2, value1)) + mstore(add(fmp, 0x24), signextend(2, value2)) + revert(fmp, 0x44) + } + } + + /// @dev Reverts with a custom error with two uint160 arguments + function revertWith(bytes4 selector, uint160 value1, uint160 value2) internal pure { + assembly ("memory-safe") { + let fmp := mload(0x40) + mstore(fmp, selector) + mstore(add(fmp, 0x04), and(value1, 0xffffffffffffffffffffffffffffffffffffffff)) + mstore(add(fmp, 0x24), and(value2, 0xffffffffffffffffffffffffffffffffffffffff)) + revert(fmp, 0x44) + } + } + + /// @dev Reverts with a custom error with two address arguments + function revertWith(bytes4 selector, address value1, address value2) internal pure { + assembly ("memory-safe") { + let fmp := mload(0x40) + mstore(fmp, selector) + mstore(add(fmp, 0x04), and(value1, 0xffffffffffffffffffffffffffffffffffffffff)) + mstore(add(fmp, 0x24), and(value2, 0xffffffffffffffffffffffffffffffffffffffff)) + revert(fmp, 0x44) + } + } + + /// @notice bubble up the revert message returned by a call and revert with a wrapped ERC-7751 error + /// @dev this method can be vulnerable to revert data bombs + function bubbleUpAndRevertWith( + address revertingContract, + bytes4 revertingFunctionSelector, + bytes4 additionalContext + ) internal pure { + bytes4 wrappedErrorSelector = WrappedError.selector; + assembly ("memory-safe") { + // Ensure the size of the revert data is a multiple of 32 bytes + let encodedDataSize := mul(div(add(returndatasize(), 31), 32), 32) + + let fmp := mload(0x40) + + // Encode wrapped error selector, address, function selector, offset, additional context, size, revert reason + mstore(fmp, wrappedErrorSelector) + mstore(add(fmp, 0x04), and(revertingContract, 0xffffffffffffffffffffffffffffffffffffffff)) + mstore( + add(fmp, 0x24), + and(revertingFunctionSelector, 0xffffffff00000000000000000000000000000000000000000000000000000000) + ) + // offset revert reason + mstore(add(fmp, 0x44), 0x80) + // offset additional context + mstore(add(fmp, 0x64), add(0xa0, encodedDataSize)) + // size revert reason + mstore(add(fmp, 0x84), returndatasize()) + // revert reason + returndatacopy(add(fmp, 0xa4), 0, returndatasize()) + // size additional context + mstore(add(fmp, add(0xa4, encodedDataSize)), 0x04) + // additional context + mstore( + add(fmp, add(0xc4, encodedDataSize)), + and(additionalContext, 0xffffffff00000000000000000000000000000000000000000000000000000000) + ) + revert(fmp, add(0xe4, encodedDataSize)) + } + } +} diff --git a/CVLByExample/UniswapHooks/lib/uniswap-hooks/lib/v4-core/src/libraries/FixedPoint128.sol b/CVLByExample/UniswapHooks/lib/uniswap-hooks/lib/v4-core/src/libraries/FixedPoint128.sol new file mode 100644 index 00000000..ef69ae80 --- /dev/null +++ b/CVLByExample/UniswapHooks/lib/uniswap-hooks/lib/v4-core/src/libraries/FixedPoint128.sol @@ -0,0 +1,8 @@ +// SPDX-License-Identifier: MIT +pragma solidity ^0.8.0; + +/// @title FixedPoint128 +/// @notice A library for handling binary fixed point numbers, see https://en.wikipedia.org/wiki/Q_(number_format) +library FixedPoint128 { + uint256 internal constant Q128 = 0x100000000000000000000000000000000; +} diff --git a/CVLByExample/UniswapHooks/lib/uniswap-hooks/lib/v4-core/src/libraries/FixedPoint96.sol b/CVLByExample/UniswapHooks/lib/uniswap-hooks/lib/v4-core/src/libraries/FixedPoint96.sol new file mode 100644 index 00000000..8e704cb3 --- /dev/null +++ b/CVLByExample/UniswapHooks/lib/uniswap-hooks/lib/v4-core/src/libraries/FixedPoint96.sol @@ -0,0 +1,10 @@ +// SPDX-License-Identifier: MIT +pragma solidity ^0.8.0; + +/// @title FixedPoint96 +/// @notice A library for handling binary fixed point numbers, see https://en.wikipedia.org/wiki/Q_(number_format) +/// @dev Used in SqrtPriceMath.sol +library FixedPoint96 { + uint8 internal constant RESOLUTION = 96; + uint256 internal constant Q96 = 0x1000000000000000000000000; +} diff --git a/CVLByExample/UniswapHooks/lib/uniswap-hooks/lib/v4-core/src/libraries/FullMath.sol b/CVLByExample/UniswapHooks/lib/uniswap-hooks/lib/v4-core/src/libraries/FullMath.sol new file mode 100644 index 00000000..2dc13788 --- /dev/null +++ b/CVLByExample/UniswapHooks/lib/uniswap-hooks/lib/v4-core/src/libraries/FullMath.sol @@ -0,0 +1,117 @@ +// SPDX-License-Identifier: MIT +pragma solidity ^0.8.0; + +/// @title Contains 512-bit math functions +/// @notice Facilitates multiplication and division that can have overflow of an intermediate value without any loss of precision +/// @dev Handles "phantom overflow" i.e., allows multiplication and division where an intermediate value overflows 256 bits +library FullMath { + /// @notice Calculates floor(a×b÷denominator) with full precision. Throws if result overflows a uint256 or denominator == 0 + /// @param a The multiplicand + /// @param b The multiplier + /// @param denominator The divisor + /// @return result The 256-bit result + /// @dev Credit to Remco Bloemen under MIT license https://xn--2-umb.com/21/muldiv + function mulDiv(uint256 a, uint256 b, uint256 denominator) internal pure returns (uint256 result) { + unchecked { + // 512-bit multiply [prod1 prod0] = a * b + // Compute the product mod 2**256 and mod 2**256 - 1 + // then use the Chinese Remainder Theorem to reconstruct + // the 512 bit result. The result is stored in two 256 + // variables such that product = prod1 * 2**256 + prod0 + uint256 prod0 = a * b; // Least significant 256 bits of the product + uint256 prod1; // Most significant 256 bits of the product + assembly ("memory-safe") { + let mm := mulmod(a, b, not(0)) + prod1 := sub(sub(mm, prod0), lt(mm, prod0)) + } + + // Make sure the result is less than 2**256. + // Also prevents denominator == 0 + require(denominator > prod1); + + // Handle non-overflow cases, 256 by 256 division + if (prod1 == 0) { + assembly ("memory-safe") { + result := div(prod0, denominator) + } + return result; + } + + /////////////////////////////////////////////// + // 512 by 256 division. + /////////////////////////////////////////////// + + // Make division exact by subtracting the remainder from [prod1 prod0] + // Compute remainder using mulmod + uint256 remainder; + assembly ("memory-safe") { + remainder := mulmod(a, b, denominator) + } + // Subtract 256 bit number from 512 bit number + assembly ("memory-safe") { + prod1 := sub(prod1, gt(remainder, prod0)) + prod0 := sub(prod0, remainder) + } + + // Factor powers of two out of denominator + // Compute largest power of two divisor of denominator. + // Always >= 1. + uint256 twos = (0 - denominator) & denominator; + // Divide denominator by power of two + assembly ("memory-safe") { + denominator := div(denominator, twos) + } + + // Divide [prod1 prod0] by the factors of two + assembly ("memory-safe") { + prod0 := div(prod0, twos) + } + // Shift in bits from prod1 into prod0. For this we need + // to flip `twos` such that it is 2**256 / twos. + // If twos is zero, then it becomes one + assembly ("memory-safe") { + twos := add(div(sub(0, twos), twos), 1) + } + prod0 |= prod1 * twos; + + // Invert denominator mod 2**256 + // Now that denominator is an odd number, it has an inverse + // modulo 2**256 such that denominator * inv = 1 mod 2**256. + // Compute the inverse by starting with a seed that is correct + // correct for four bits. That is, denominator * inv = 1 mod 2**4 + uint256 inv = (3 * denominator) ^ 2; + // Now use Newton-Raphson iteration to improve the precision. + // Thanks to Hensel's lifting lemma, this also works in modular + // arithmetic, doubling the correct bits in each step. + inv *= 2 - denominator * inv; // inverse mod 2**8 + inv *= 2 - denominator * inv; // inverse mod 2**16 + inv *= 2 - denominator * inv; // inverse mod 2**32 + inv *= 2 - denominator * inv; // inverse mod 2**64 + inv *= 2 - denominator * inv; // inverse mod 2**128 + inv *= 2 - denominator * inv; // inverse mod 2**256 + + // Because the division is now exact we can divide by multiplying + // with the modular inverse of denominator. This will give us the + // correct result modulo 2**256. Since the preconditions guarantee + // that the outcome is less than 2**256, this is the final result. + // We don't need to compute the high bits of the result and prod1 + // is no longer required. + result = prod0 * inv; + return result; + } + } + + /// @notice Calculates ceil(a×b÷denominator) with full precision. Throws if result overflows a uint256 or denominator == 0 + /// @param a The multiplicand + /// @param b The multiplier + /// @param denominator The divisor + /// @return result The 256-bit result + function mulDivRoundingUp(uint256 a, uint256 b, uint256 denominator) internal pure returns (uint256 result) { + unchecked { + result = mulDiv(a, b, denominator); + if (mulmod(a, b, denominator) != 0) { + require(++result > 0); + } + } + } +} diff --git a/CVLByExample/UniswapHooks/lib/uniswap-hooks/lib/v4-core/src/libraries/Hooks.sol b/CVLByExample/UniswapHooks/lib/uniswap-hooks/lib/v4-core/src/libraries/Hooks.sol new file mode 100644 index 00000000..15e5a38c --- /dev/null +++ b/CVLByExample/UniswapHooks/lib/uniswap-hooks/lib/v4-core/src/libraries/Hooks.sol @@ -0,0 +1,340 @@ +// SPDX-License-Identifier: MIT +pragma solidity ^0.8.0; + +import {PoolKey} from "../types/PoolKey.sol"; +import {IHooks} from "../interfaces/IHooks.sol"; +import {SafeCast} from "./SafeCast.sol"; +import {LPFeeLibrary} from "./LPFeeLibrary.sol"; +import {BalanceDelta, toBalanceDelta, BalanceDeltaLibrary} from "../types/BalanceDelta.sol"; +import {BeforeSwapDelta, BeforeSwapDeltaLibrary} from "../types/BeforeSwapDelta.sol"; +import {IPoolManager} from "../interfaces/IPoolManager.sol"; +import {ModifyLiquidityParams, SwapParams} from "../types/PoolOperation.sol"; +import {ParseBytes} from "./ParseBytes.sol"; +import {CustomRevert} from "./CustomRevert.sol"; + +/// @notice V4 decides whether to invoke specific hooks by inspecting the least significant bits +/// of the address that the hooks contract is deployed to. +/// For example, a hooks contract deployed to address: 0x0000000000000000000000000000000000002400 +/// has the lowest bits '10 0100 0000 0000' which would cause the 'before initialize' and 'after add liquidity' hooks to be used. +library Hooks { + using LPFeeLibrary for uint24; + using Hooks for IHooks; + using SafeCast for int256; + using BeforeSwapDeltaLibrary for BeforeSwapDelta; + using ParseBytes for bytes; + using CustomRevert for bytes4; + + uint160 internal constant ALL_HOOK_MASK = uint160((1 << 14) - 1); + + uint160 internal constant BEFORE_INITIALIZE_FLAG = 1 << 13; + uint160 internal constant AFTER_INITIALIZE_FLAG = 1 << 12; + + uint160 internal constant BEFORE_ADD_LIQUIDITY_FLAG = 1 << 11; + uint160 internal constant AFTER_ADD_LIQUIDITY_FLAG = 1 << 10; + + uint160 internal constant BEFORE_REMOVE_LIQUIDITY_FLAG = 1 << 9; + uint160 internal constant AFTER_REMOVE_LIQUIDITY_FLAG = 1 << 8; + + uint160 internal constant BEFORE_SWAP_FLAG = 1 << 7; + uint160 internal constant AFTER_SWAP_FLAG = 1 << 6; + + uint160 internal constant BEFORE_DONATE_FLAG = 1 << 5; + uint160 internal constant AFTER_DONATE_FLAG = 1 << 4; + + uint160 internal constant BEFORE_SWAP_RETURNS_DELTA_FLAG = 1 << 3; + uint160 internal constant AFTER_SWAP_RETURNS_DELTA_FLAG = 1 << 2; + uint160 internal constant AFTER_ADD_LIQUIDITY_RETURNS_DELTA_FLAG = 1 << 1; + uint160 internal constant AFTER_REMOVE_LIQUIDITY_RETURNS_DELTA_FLAG = 1 << 0; + + struct Permissions { + bool beforeInitialize; + bool afterInitialize; + bool beforeAddLiquidity; + bool afterAddLiquidity; + bool beforeRemoveLiquidity; + bool afterRemoveLiquidity; + bool beforeSwap; + bool afterSwap; + bool beforeDonate; + bool afterDonate; + bool beforeSwapReturnDelta; + bool afterSwapReturnDelta; + bool afterAddLiquidityReturnDelta; + bool afterRemoveLiquidityReturnDelta; + } + + /// @notice Thrown if the address will not lead to the specified hook calls being called + /// @param hooks The address of the hooks contract + error HookAddressNotValid(address hooks); + + /// @notice Hook did not return its selector + error InvalidHookResponse(); + + /// @notice Additional context for ERC-7751 wrapped error when a hook call fails + error HookCallFailed(); + + /// @notice The hook's delta changed the swap from exactIn to exactOut or vice versa + error HookDeltaExceedsSwapAmount(); + + /// @notice Utility function intended to be used in hook constructors to ensure + /// the deployed hooks address causes the intended hooks to be called + /// @param permissions The hooks that are intended to be called + /// @dev permissions param is memory as the function will be called from constructors + function validateHookPermissions(IHooks self, Permissions memory permissions) internal pure { + if ( + permissions.beforeInitialize != self.hasPermission(BEFORE_INITIALIZE_FLAG) + || permissions.afterInitialize != self.hasPermission(AFTER_INITIALIZE_FLAG) + || permissions.beforeAddLiquidity != self.hasPermission(BEFORE_ADD_LIQUIDITY_FLAG) + || permissions.afterAddLiquidity != self.hasPermission(AFTER_ADD_LIQUIDITY_FLAG) + || permissions.beforeRemoveLiquidity != self.hasPermission(BEFORE_REMOVE_LIQUIDITY_FLAG) + || permissions.afterRemoveLiquidity != self.hasPermission(AFTER_REMOVE_LIQUIDITY_FLAG) + || permissions.beforeSwap != self.hasPermission(BEFORE_SWAP_FLAG) + || permissions.afterSwap != self.hasPermission(AFTER_SWAP_FLAG) + || permissions.beforeDonate != self.hasPermission(BEFORE_DONATE_FLAG) + || permissions.afterDonate != self.hasPermission(AFTER_DONATE_FLAG) + || permissions.beforeSwapReturnDelta != self.hasPermission(BEFORE_SWAP_RETURNS_DELTA_FLAG) + || permissions.afterSwapReturnDelta != self.hasPermission(AFTER_SWAP_RETURNS_DELTA_FLAG) + || permissions.afterAddLiquidityReturnDelta != self.hasPermission(AFTER_ADD_LIQUIDITY_RETURNS_DELTA_FLAG) + || permissions.afterRemoveLiquidityReturnDelta + != self.hasPermission(AFTER_REMOVE_LIQUIDITY_RETURNS_DELTA_FLAG) + ) { + HookAddressNotValid.selector.revertWith(address(self)); + } + } + + /// @notice Ensures that the hook address includes at least one hook flag or dynamic fees, or is the 0 address + /// @param self The hook to verify + /// @param fee The fee of the pool the hook is used with + /// @return bool True if the hook address is valid + function isValidHookAddress(IHooks self, uint24 fee) internal pure returns (bool) { + // The hook can only have a flag to return a hook delta on an action if it also has the corresponding action flag + if (!self.hasPermission(BEFORE_SWAP_FLAG) && self.hasPermission(BEFORE_SWAP_RETURNS_DELTA_FLAG)) return false; + if (!self.hasPermission(AFTER_SWAP_FLAG) && self.hasPermission(AFTER_SWAP_RETURNS_DELTA_FLAG)) return false; + if (!self.hasPermission(AFTER_ADD_LIQUIDITY_FLAG) && self.hasPermission(AFTER_ADD_LIQUIDITY_RETURNS_DELTA_FLAG)) + { + return false; + } + if ( + !self.hasPermission(AFTER_REMOVE_LIQUIDITY_FLAG) + && self.hasPermission(AFTER_REMOVE_LIQUIDITY_RETURNS_DELTA_FLAG) + ) return false; + + // If there is no hook contract set, then fee cannot be dynamic + // If a hook contract is set, it must have at least 1 flag set, or have a dynamic fee + return address(self) == address(0) + ? !fee.isDynamicFee() + : (uint160(address(self)) & ALL_HOOK_MASK > 0 || fee.isDynamicFee()); + } + + /// @notice performs a hook call using the given calldata on the given hook that doesn't return a delta + /// @return result The complete data returned by the hook + function callHook(IHooks self, bytes memory data) internal returns (bytes memory result) { + bool success; + assembly ("memory-safe") { + success := call(gas(), self, 0, add(data, 0x20), mload(data), 0, 0) + } + // Revert with FailedHookCall, containing any error message to bubble up + if (!success) CustomRevert.bubbleUpAndRevertWith(address(self), bytes4(data), HookCallFailed.selector); + + // The call was successful, fetch the returned data + assembly ("memory-safe") { + // allocate result byte array from the free memory pointer + result := mload(0x40) + // store new free memory pointer at the end of the array padded to 32 bytes + mstore(0x40, add(result, and(add(returndatasize(), 0x3f), not(0x1f)))) + // store length in memory + mstore(result, returndatasize()) + // copy return data to result + returndatacopy(add(result, 0x20), 0, returndatasize()) + } + + // Length must be at least 32 to contain the selector. Check expected selector and returned selector match. + if (result.length < 32 || result.parseSelector() != data.parseSelector()) { + InvalidHookResponse.selector.revertWith(); + } + } + + /// @notice performs a hook call using the given calldata on the given hook + /// @return int256 The delta returned by the hook + function callHookWithReturnDelta(IHooks self, bytes memory data, bool parseReturn) internal returns (int256) { + bytes memory result = callHook(self, data); + + // If this hook wasn't meant to return something, default to 0 delta + if (!parseReturn) return 0; + + // A length of 64 bytes is required to return a bytes4, and a 32 byte delta + if (result.length != 64) InvalidHookResponse.selector.revertWith(); + return result.parseReturnDelta(); + } + + /// @notice modifier to prevent calling a hook if they initiated the action + modifier noSelfCall(IHooks self) { + if (msg.sender != address(self)) { + _; + } + } + + /// @notice calls beforeInitialize hook if permissioned and validates return value + function beforeInitialize(IHooks self, PoolKey memory key, uint160 sqrtPriceX96) internal noSelfCall(self) { + if (self.hasPermission(BEFORE_INITIALIZE_FLAG)) { + self.callHook(abi.encodeCall(IHooks.beforeInitialize, (msg.sender, key, sqrtPriceX96))); + } + } + + /// @notice calls afterInitialize hook if permissioned and validates return value + function afterInitialize(IHooks self, PoolKey memory key, uint160 sqrtPriceX96, int24 tick) + internal + noSelfCall(self) + { + if (self.hasPermission(AFTER_INITIALIZE_FLAG)) { + self.callHook(abi.encodeCall(IHooks.afterInitialize, (msg.sender, key, sqrtPriceX96, tick))); + } + } + + /// @notice calls beforeModifyLiquidity hook if permissioned and validates return value + function beforeModifyLiquidity( + IHooks self, + PoolKey memory key, + ModifyLiquidityParams memory params, + bytes calldata hookData + ) internal noSelfCall(self) { + if (params.liquidityDelta > 0 && self.hasPermission(BEFORE_ADD_LIQUIDITY_FLAG)) { + self.callHook(abi.encodeCall(IHooks.beforeAddLiquidity, (msg.sender, key, params, hookData))); + } else if (params.liquidityDelta <= 0 && self.hasPermission(BEFORE_REMOVE_LIQUIDITY_FLAG)) { + self.callHook(abi.encodeCall(IHooks.beforeRemoveLiquidity, (msg.sender, key, params, hookData))); + } + } + + /// @notice calls afterModifyLiquidity hook if permissioned and validates return value + function afterModifyLiquidity( + IHooks self, + PoolKey memory key, + ModifyLiquidityParams memory params, + BalanceDelta delta, + BalanceDelta feesAccrued, + bytes calldata hookData + ) internal returns (BalanceDelta callerDelta, BalanceDelta hookDelta) { + if (msg.sender == address(self)) return (delta, BalanceDeltaLibrary.ZERO_DELTA); + + callerDelta = delta; + if (params.liquidityDelta > 0) { + if (self.hasPermission(AFTER_ADD_LIQUIDITY_FLAG)) { + hookDelta = BalanceDelta.wrap( + self.callHookWithReturnDelta( + abi.encodeCall( + IHooks.afterAddLiquidity, (msg.sender, key, params, delta, feesAccrued, hookData) + ), + self.hasPermission(AFTER_ADD_LIQUIDITY_RETURNS_DELTA_FLAG) + ) + ); + callerDelta = callerDelta - hookDelta; + } + } else { + if (self.hasPermission(AFTER_REMOVE_LIQUIDITY_FLAG)) { + hookDelta = BalanceDelta.wrap( + self.callHookWithReturnDelta( + abi.encodeCall( + IHooks.afterRemoveLiquidity, (msg.sender, key, params, delta, feesAccrued, hookData) + ), + self.hasPermission(AFTER_REMOVE_LIQUIDITY_RETURNS_DELTA_FLAG) + ) + ); + callerDelta = callerDelta - hookDelta; + } + } + } + + /// @notice calls beforeSwap hook if permissioned and validates return value + function beforeSwap(IHooks self, PoolKey memory key, SwapParams memory params, bytes calldata hookData) + internal + returns (int256 amountToSwap, BeforeSwapDelta hookReturn, uint24 lpFeeOverride) + { + amountToSwap = params.amountSpecified; + if (msg.sender == address(self)) return (amountToSwap, BeforeSwapDeltaLibrary.ZERO_DELTA, lpFeeOverride); + + if (self.hasPermission(BEFORE_SWAP_FLAG)) { + bytes memory result = callHook(self, abi.encodeCall(IHooks.beforeSwap, (msg.sender, key, params, hookData))); + + // A length of 96 bytes is required to return a bytes4, a 32 byte delta, and an LP fee + if (result.length != 96) InvalidHookResponse.selector.revertWith(); + + // dynamic fee pools that want to override the cache fee, return a valid fee with the override flag. If override flag + // is set but an invalid fee is returned, the transaction will revert. Otherwise the current LP fee will be used + if (key.fee.isDynamicFee()) lpFeeOverride = result.parseFee(); + + // skip this logic for the case where the hook return is 0 + if (self.hasPermission(BEFORE_SWAP_RETURNS_DELTA_FLAG)) { + hookReturn = BeforeSwapDelta.wrap(result.parseReturnDelta()); + + // any return in unspecified is passed to the afterSwap hook for handling + int128 hookDeltaSpecified = hookReturn.getSpecifiedDelta(); + + // Update the swap amount according to the hook's return, and check that the swap type doesn't change (exact input/output) + if (hookDeltaSpecified != 0) { + bool exactInput = amountToSwap < 0; + amountToSwap += hookDeltaSpecified; + if (exactInput ? amountToSwap > 0 : amountToSwap < 0) { + HookDeltaExceedsSwapAmount.selector.revertWith(); + } + } + } + } + } + + /// @notice calls afterSwap hook if permissioned and validates return value + function afterSwap( + IHooks self, + PoolKey memory key, + SwapParams memory params, + BalanceDelta swapDelta, + bytes calldata hookData, + BeforeSwapDelta beforeSwapHookReturn + ) internal returns (BalanceDelta, BalanceDelta) { + if (msg.sender == address(self)) return (swapDelta, BalanceDeltaLibrary.ZERO_DELTA); + + int128 hookDeltaSpecified = beforeSwapHookReturn.getSpecifiedDelta(); + int128 hookDeltaUnspecified = beforeSwapHookReturn.getUnspecifiedDelta(); + + if (self.hasPermission(AFTER_SWAP_FLAG)) { + hookDeltaUnspecified += self.callHookWithReturnDelta( + abi.encodeCall(IHooks.afterSwap, (msg.sender, key, params, swapDelta, hookData)), + self.hasPermission(AFTER_SWAP_RETURNS_DELTA_FLAG) + ).toInt128(); + } + + BalanceDelta hookDelta; + if (hookDeltaUnspecified != 0 || hookDeltaSpecified != 0) { + hookDelta = (params.amountSpecified < 0 == params.zeroForOne) + ? toBalanceDelta(hookDeltaSpecified, hookDeltaUnspecified) + : toBalanceDelta(hookDeltaUnspecified, hookDeltaSpecified); + + // the caller has to pay for (or receive) the hook's delta + swapDelta = swapDelta - hookDelta; + } + return (swapDelta, hookDelta); + } + + /// @notice calls beforeDonate hook if permissioned and validates return value + function beforeDonate(IHooks self, PoolKey memory key, uint256 amount0, uint256 amount1, bytes calldata hookData) + internal + noSelfCall(self) + { + if (self.hasPermission(BEFORE_DONATE_FLAG)) { + self.callHook(abi.encodeCall(IHooks.beforeDonate, (msg.sender, key, amount0, amount1, hookData))); + } + } + + /// @notice calls afterDonate hook if permissioned and validates return value + function afterDonate(IHooks self, PoolKey memory key, uint256 amount0, uint256 amount1, bytes calldata hookData) + internal + noSelfCall(self) + { + if (self.hasPermission(AFTER_DONATE_FLAG)) { + self.callHook(abi.encodeCall(IHooks.afterDonate, (msg.sender, key, amount0, amount1, hookData))); + } + } + + function hasPermission(IHooks self, uint160 flag) internal pure returns (bool) { + return uint160(address(self)) & flag != 0; + } +} diff --git a/CVLByExample/UniswapHooks/lib/uniswap-hooks/lib/v4-core/src/libraries/LPFeeLibrary.sol b/CVLByExample/UniswapHooks/lib/uniswap-hooks/lib/v4-core/src/libraries/LPFeeLibrary.sol new file mode 100644 index 00000000..5a68f963 --- /dev/null +++ b/CVLByExample/UniswapHooks/lib/uniswap-hooks/lib/v4-core/src/libraries/LPFeeLibrary.sol @@ -0,0 +1,79 @@ +// SPDX-License-Identifier: MIT +pragma solidity ^0.8.0; + +import {CustomRevert} from "./CustomRevert.sol"; + +/// @notice Library of helper functions for a pools LP fee +library LPFeeLibrary { + using LPFeeLibrary for uint24; + using CustomRevert for bytes4; + + /// @notice Thrown when the static or dynamic fee on a pool exceeds 100%. + error LPFeeTooLarge(uint24 fee); + + /// @notice An lp fee of exactly 0b1000000... signals a dynamic fee pool. This isn't a valid static fee as it is > MAX_LP_FEE + uint24 public constant DYNAMIC_FEE_FLAG = 0x800000; + + /// @notice the second bit of the fee returned by beforeSwap is used to signal if the stored LP fee should be overridden in this swap + // only dynamic-fee pools can return a fee via the beforeSwap hook + uint24 public constant OVERRIDE_FEE_FLAG = 0x400000; + + /// @notice mask to remove the override fee flag from a fee returned by the beforeSwaphook + uint24 public constant REMOVE_OVERRIDE_MASK = 0xBFFFFF; + + /// @notice the lp fee is represented in hundredths of a bip, so the max is 100% + uint24 public constant MAX_LP_FEE = 1000000; + + /// @notice returns true if a pool's LP fee signals that the pool has a dynamic fee + /// @param self The fee to check + /// @return bool True of the fee is dynamic + function isDynamicFee(uint24 self) internal pure returns (bool) { + return self == DYNAMIC_FEE_FLAG; + } + + /// @notice returns true if an LP fee is valid, aka not above the maximum permitted fee + /// @param self The fee to check + /// @return bool True of the fee is valid + function isValid(uint24 self) internal pure returns (bool) { + return self <= MAX_LP_FEE; + } + + /// @notice validates whether an LP fee is larger than the maximum, and reverts if invalid + /// @param self The fee to validate + function validate(uint24 self) internal pure { + if (!self.isValid()) LPFeeTooLarge.selector.revertWith(self); + } + + /// @notice gets and validates the initial LP fee for a pool. Dynamic fee pools have an initial fee of 0. + /// @dev if a dynamic fee pool wants a non-0 initial fee, it should call `updateDynamicLPFee` in the afterInitialize hook + /// @param self The fee to get the initial LP from + /// @return initialFee 0 if the fee is dynamic, otherwise the fee (if valid) + function getInitialLPFee(uint24 self) internal pure returns (uint24) { + // the initial fee for a dynamic fee pool is 0 + if (self.isDynamicFee()) return 0; + self.validate(); + return self; + } + + /// @notice returns true if the fee has the override flag set (2nd highest bit of the uint24) + /// @param self The fee to check + /// @return bool True of the fee has the override flag set + function isOverride(uint24 self) internal pure returns (bool) { + return self & OVERRIDE_FEE_FLAG != 0; + } + + /// @notice returns a fee with the override flag removed + /// @param self The fee to remove the override flag from + /// @return fee The fee without the override flag set + function removeOverrideFlag(uint24 self) internal pure returns (uint24) { + return self & REMOVE_OVERRIDE_MASK; + } + + /// @notice Removes the override flag and validates the fee (reverts if the fee is too large) + /// @param self The fee to remove the override flag from, and then validate + /// @return fee The fee without the override flag set (if valid) + function removeOverrideFlagAndValidate(uint24 self) internal pure returns (uint24 fee) { + fee = self.removeOverrideFlag(); + fee.validate(); + } +} diff --git a/CVLByExample/UniswapHooks/lib/uniswap-hooks/lib/v4-core/src/libraries/LiquidityMath.sol b/CVLByExample/UniswapHooks/lib/uniswap-hooks/lib/v4-core/src/libraries/LiquidityMath.sol new file mode 100644 index 00000000..ce5cfcd4 --- /dev/null +++ b/CVLByExample/UniswapHooks/lib/uniswap-hooks/lib/v4-core/src/libraries/LiquidityMath.sol @@ -0,0 +1,20 @@ +// SPDX-License-Identifier: MIT +pragma solidity ^0.8.0; + +/// @title Math library for liquidity +library LiquidityMath { + /// @notice Add a signed liquidity delta to liquidity and revert if it overflows or underflows + /// @param x The liquidity before change + /// @param y The delta by which liquidity should be changed + /// @return z The liquidity delta + function addDelta(uint128 x, int128 y) internal pure returns (uint128 z) { + assembly ("memory-safe") { + z := add(and(x, 0xffffffffffffffffffffffffffffffff), signextend(15, y)) + if shr(128, z) { + // revert SafeCastOverflow() + mstore(0, 0x93dafdf1) + revert(0x1c, 0x04) + } + } + } +} diff --git a/CVLByExample/UniswapHooks/lib/uniswap-hooks/lib/v4-core/src/libraries/Lock.sol b/CVLByExample/UniswapHooks/lib/uniswap-hooks/lib/v4-core/src/libraries/Lock.sol new file mode 100644 index 00000000..cea38bf7 --- /dev/null +++ b/CVLByExample/UniswapHooks/lib/uniswap-hooks/lib/v4-core/src/libraries/Lock.sol @@ -0,0 +1,28 @@ +// SPDX-License-Identifier: BUSL-1.1 +pragma solidity ^0.8.24; + +/// @notice This is a temporary library that allows us to use transient storage (tstore/tload) +/// TODO: This library can be deleted when we have the transient keyword support in solidity. +library Lock { + // The slot holding the unlocked state, transiently. bytes32(uint256(keccak256("Unlocked")) - 1) + bytes32 internal constant IS_UNLOCKED_SLOT = 0xc090fc4683624cfc3884e9d8de5eca132f2d0ec062aff75d43c0465d5ceeab23; + + function unlock() internal { + assembly ("memory-safe") { + // unlock + tstore(IS_UNLOCKED_SLOT, true) + } + } + + function lock() internal { + assembly ("memory-safe") { + tstore(IS_UNLOCKED_SLOT, false) + } + } + + function isUnlocked() internal view returns (bool unlocked) { + assembly ("memory-safe") { + unlocked := tload(IS_UNLOCKED_SLOT) + } + } +} diff --git a/CVLByExample/UniswapHooks/lib/uniswap-hooks/lib/v4-core/src/libraries/NonzeroDeltaCount.sol b/CVLByExample/UniswapHooks/lib/uniswap-hooks/lib/v4-core/src/libraries/NonzeroDeltaCount.sol new file mode 100644 index 00000000..3221df0c --- /dev/null +++ b/CVLByExample/UniswapHooks/lib/uniswap-hooks/lib/v4-core/src/libraries/NonzeroDeltaCount.sol @@ -0,0 +1,35 @@ +// SPDX-License-Identifier: BUSL-1.1 +pragma solidity ^0.8.24; + +/// @notice This is a temporary library that allows us to use transient storage (tstore/tload) +/// for the nonzero delta count. +/// TODO: This library can be deleted when we have the transient keyword support in solidity. +library NonzeroDeltaCount { + // The slot holding the number of nonzero deltas. bytes32(uint256(keccak256("NonzeroDeltaCount")) - 1) + bytes32 internal constant NONZERO_DELTA_COUNT_SLOT = + 0x7d4b3164c6e45b97e7d87b7125a44c5828d005af88f9d751cfd78729c5d99a0b; + + function read() internal view returns (uint256 count) { + assembly ("memory-safe") { + count := tload(NONZERO_DELTA_COUNT_SLOT) + } + } + + function increment() internal { + assembly ("memory-safe") { + let count := tload(NONZERO_DELTA_COUNT_SLOT) + count := add(count, 1) + tstore(NONZERO_DELTA_COUNT_SLOT, count) + } + } + + /// @notice Potential to underflow. Ensure checks are performed by integrating contracts to ensure this does not happen. + /// Current usage ensures this will not happen because we call decrement with known boundaries (only up to the number of times we call increment). + function decrement() internal { + assembly ("memory-safe") { + let count := tload(NONZERO_DELTA_COUNT_SLOT) + count := sub(count, 1) + tstore(NONZERO_DELTA_COUNT_SLOT, count) + } + } +} diff --git a/CVLByExample/UniswapHooks/lib/uniswap-hooks/lib/v4-core/src/libraries/ParseBytes.sol b/CVLByExample/UniswapHooks/lib/uniswap-hooks/lib/v4-core/src/libraries/ParseBytes.sol new file mode 100644 index 00000000..3503fe4a --- /dev/null +++ b/CVLByExample/UniswapHooks/lib/uniswap-hooks/lib/v4-core/src/libraries/ParseBytes.sol @@ -0,0 +1,29 @@ +// SPDX-License-Identifier: MIT + +pragma solidity ^0.8.0; + +/// @notice Parses bytes returned from hooks and the byte selector used to check return selectors from hooks. +/// @dev parseSelector also is used to parse the expected selector +/// For parsing hook returns, note that all hooks return either bytes4 or (bytes4, 32-byte-delta) or (bytes4, 32-byte-delta, uint24). +library ParseBytes { + function parseSelector(bytes memory result) internal pure returns (bytes4 selector) { + // equivalent: (selector,) = abi.decode(result, (bytes4, int256)); + assembly ("memory-safe") { + selector := mload(add(result, 0x20)) + } + } + + function parseFee(bytes memory result) internal pure returns (uint24 lpFee) { + // equivalent: (,, lpFee) = abi.decode(result, (bytes4, int256, uint24)); + assembly ("memory-safe") { + lpFee := mload(add(result, 0x60)) + } + } + + function parseReturnDelta(bytes memory result) internal pure returns (int256 hookReturn) { + // equivalent: (, hookReturnDelta) = abi.decode(result, (bytes4, int256)); + assembly ("memory-safe") { + hookReturn := mload(add(result, 0x40)) + } + } +} diff --git a/CVLByExample/UniswapHooks/lib/uniswap-hooks/lib/v4-core/src/libraries/Pool.sol b/CVLByExample/UniswapHooks/lib/uniswap-hooks/lib/v4-core/src/libraries/Pool.sol new file mode 100644 index 00000000..65a77a20 --- /dev/null +++ b/CVLByExample/UniswapHooks/lib/uniswap-hooks/lib/v4-core/src/libraries/Pool.sol @@ -0,0 +1,613 @@ +// SPDX-License-Identifier: BUSL-1.1 +pragma solidity ^0.8.0; + +import {SafeCast} from "./SafeCast.sol"; +import {TickBitmap} from "./TickBitmap.sol"; +import {Position} from "./Position.sol"; +import {UnsafeMath} from "./UnsafeMath.sol"; +import {FixedPoint128} from "./FixedPoint128.sol"; +import {TickMath} from "./TickMath.sol"; +import {SqrtPriceMath} from "./SqrtPriceMath.sol"; +import {SwapMath} from "./SwapMath.sol"; +import {BalanceDelta, toBalanceDelta, BalanceDeltaLibrary} from "../types/BalanceDelta.sol"; +import {Slot0} from "../types/Slot0.sol"; +import {ProtocolFeeLibrary} from "./ProtocolFeeLibrary.sol"; +import {LiquidityMath} from "./LiquidityMath.sol"; +import {LPFeeLibrary} from "./LPFeeLibrary.sol"; +import {CustomRevert} from "./CustomRevert.sol"; + +/// @notice a library with all actions that can be performed on a pool +library Pool { + using SafeCast for *; + using TickBitmap for mapping(int16 => uint256); + using Position for mapping(bytes32 => Position.State); + using Position for Position.State; + using Pool for State; + using ProtocolFeeLibrary for *; + using LPFeeLibrary for uint24; + using CustomRevert for bytes4; + + /// @notice Thrown when tickLower is not below tickUpper + /// @param tickLower The invalid tickLower + /// @param tickUpper The invalid tickUpper + error TicksMisordered(int24 tickLower, int24 tickUpper); + + /// @notice Thrown when tickLower is less than min tick + /// @param tickLower The invalid tickLower + error TickLowerOutOfBounds(int24 tickLower); + + /// @notice Thrown when tickUpper exceeds max tick + /// @param tickUpper The invalid tickUpper + error TickUpperOutOfBounds(int24 tickUpper); + + /// @notice For the tick spacing, the tick has too much liquidity + error TickLiquidityOverflow(int24 tick); + + /// @notice Thrown when trying to initialize an already initialized pool + error PoolAlreadyInitialized(); + + /// @notice Thrown when trying to interact with a non-initialized pool + error PoolNotInitialized(); + + /// @notice Thrown when sqrtPriceLimitX96 on a swap has already exceeded its limit + /// @param sqrtPriceCurrentX96 The invalid, already surpassed sqrtPriceLimitX96 + /// @param sqrtPriceLimitX96 The surpassed price limit + error PriceLimitAlreadyExceeded(uint160 sqrtPriceCurrentX96, uint160 sqrtPriceLimitX96); + + /// @notice Thrown when sqrtPriceLimitX96 lies outside of valid tick/price range + /// @param sqrtPriceLimitX96 The invalid, out-of-bounds sqrtPriceLimitX96 + error PriceLimitOutOfBounds(uint160 sqrtPriceLimitX96); + + /// @notice Thrown by donate if there is currently 0 liquidity, since the fees will not go to any liquidity providers + error NoLiquidityToReceiveFees(); + + /// @notice Thrown when trying to swap with max lp fee and specifying an output amount + error InvalidFeeForExactOut(); + + // info stored for each initialized individual tick + struct TickInfo { + // the total position liquidity that references this tick + uint128 liquidityGross; + // amount of net liquidity added (subtracted) when tick is crossed from left to right (right to left), + int128 liquidityNet; + // fee growth per unit of liquidity on the _other_ side of this tick (relative to the current tick) + // only has relative meaning, not absolute — the value depends on when the tick is initialized + uint256 feeGrowthOutside0X128; + uint256 feeGrowthOutside1X128; + } + + /// @notice The state of a pool + /// @dev Note that feeGrowthGlobal can be artificially inflated + /// For pools with a single liquidity position, actors can donate to themselves to freely inflate feeGrowthGlobal + /// atomically donating and collecting fees in the same unlockCallback may make the inflated value more extreme + struct State { + Slot0 slot0; + uint256 feeGrowthGlobal0X128; + uint256 feeGrowthGlobal1X128; + uint128 liquidity; + mapping(int24 tick => TickInfo) ticks; + mapping(int16 wordPos => uint256) tickBitmap; + mapping(bytes32 positionKey => Position.State) positions; + } + + /// @dev Common checks for valid tick inputs. + function checkTicks(int24 tickLower, int24 tickUpper) private pure { + if (tickLower >= tickUpper) TicksMisordered.selector.revertWith(tickLower, tickUpper); + if (tickLower < TickMath.MIN_TICK) TickLowerOutOfBounds.selector.revertWith(tickLower); + if (tickUpper > TickMath.MAX_TICK) TickUpperOutOfBounds.selector.revertWith(tickUpper); + } + + function initialize(State storage self, uint160 sqrtPriceX96, uint24 lpFee) internal returns (int24 tick) { + if (self.slot0.sqrtPriceX96() != 0) PoolAlreadyInitialized.selector.revertWith(); + + tick = TickMath.getTickAtSqrtPrice(sqrtPriceX96); + + // the initial protocolFee is 0 so doesn't need to be set + self.slot0 = Slot0.wrap(bytes32(0)).setSqrtPriceX96(sqrtPriceX96).setTick(tick).setLpFee(lpFee); + } + + function setProtocolFee(State storage self, uint24 protocolFee) internal { + self.checkPoolInitialized(); + self.slot0 = self.slot0.setProtocolFee(protocolFee); + } + + /// @notice Only dynamic fee pools may update the lp fee. + function setLPFee(State storage self, uint24 lpFee) internal { + self.checkPoolInitialized(); + self.slot0 = self.slot0.setLpFee(lpFee); + } + + struct ModifyLiquidityParams { + // the address that owns the position + address owner; + // the lower and upper tick of the position + int24 tickLower; + int24 tickUpper; + // any change in liquidity + int128 liquidityDelta; + // the spacing between ticks + int24 tickSpacing; + // used to distinguish positions of the same owner, at the same tick range + bytes32 salt; + } + + struct ModifyLiquidityState { + bool flippedLower; + uint128 liquidityGrossAfterLower; + bool flippedUpper; + uint128 liquidityGrossAfterUpper; + } + + /// @notice Effect changes to a position in a pool + /// @dev PoolManager checks that the pool is initialized before calling + /// @param params the position details and the change to the position's liquidity to effect + /// @return delta the deltas of the token balances of the pool, from the liquidity change + /// @return feeDelta the fees generated by the liquidity range + function modifyLiquidity(State storage self, ModifyLiquidityParams memory params) + internal + returns (BalanceDelta delta, BalanceDelta feeDelta) + { + int128 liquidityDelta = params.liquidityDelta; + int24 tickLower = params.tickLower; + int24 tickUpper = params.tickUpper; + checkTicks(tickLower, tickUpper); + + { + ModifyLiquidityState memory state; + + // if we need to update the ticks, do it + if (liquidityDelta != 0) { + (state.flippedLower, state.liquidityGrossAfterLower) = + updateTick(self, tickLower, liquidityDelta, false); + (state.flippedUpper, state.liquidityGrossAfterUpper) = updateTick(self, tickUpper, liquidityDelta, true); + + // `>` and `>=` are logically equivalent here but `>=` is cheaper + if (liquidityDelta >= 0) { + uint128 maxLiquidityPerTick = tickSpacingToMaxLiquidityPerTick(params.tickSpacing); + if (state.liquidityGrossAfterLower > maxLiquidityPerTick) { + TickLiquidityOverflow.selector.revertWith(tickLower); + } + if (state.liquidityGrossAfterUpper > maxLiquidityPerTick) { + TickLiquidityOverflow.selector.revertWith(tickUpper); + } + } + + if (state.flippedLower) { + self.tickBitmap.flipTick(tickLower, params.tickSpacing); + } + if (state.flippedUpper) { + self.tickBitmap.flipTick(tickUpper, params.tickSpacing); + } + } + + { + (uint256 feeGrowthInside0X128, uint256 feeGrowthInside1X128) = + getFeeGrowthInside(self, tickLower, tickUpper); + + Position.State storage position = self.positions.get(params.owner, tickLower, tickUpper, params.salt); + (uint256 feesOwed0, uint256 feesOwed1) = + position.update(liquidityDelta, feeGrowthInside0X128, feeGrowthInside1X128); + + // Fees earned from LPing are calculated, and returned + feeDelta = toBalanceDelta(feesOwed0.toInt128(), feesOwed1.toInt128()); + } + + // clear any tick data that is no longer needed + if (liquidityDelta < 0) { + if (state.flippedLower) { + clearTick(self, tickLower); + } + if (state.flippedUpper) { + clearTick(self, tickUpper); + } + } + } + + if (liquidityDelta != 0) { + Slot0 _slot0 = self.slot0; + (int24 tick, uint160 sqrtPriceX96) = (_slot0.tick(), _slot0.sqrtPriceX96()); + if (tick < tickLower) { + // current tick is below the passed range; liquidity can only become in range by crossing from left to + // right, when we'll need _more_ currency0 (it's becoming more valuable) so user must provide it + delta = toBalanceDelta( + SqrtPriceMath.getAmount0Delta( + TickMath.getSqrtPriceAtTick(tickLower), TickMath.getSqrtPriceAtTick(tickUpper), liquidityDelta + ).toInt128(), + 0 + ); + } else if (tick < tickUpper) { + delta = toBalanceDelta( + SqrtPriceMath.getAmount0Delta(sqrtPriceX96, TickMath.getSqrtPriceAtTick(tickUpper), liquidityDelta) + .toInt128(), + SqrtPriceMath.getAmount1Delta(TickMath.getSqrtPriceAtTick(tickLower), sqrtPriceX96, liquidityDelta) + .toInt128() + ); + + self.liquidity = LiquidityMath.addDelta(self.liquidity, liquidityDelta); + } else { + // current tick is above the passed range; liquidity can only become in range by crossing from right to + // left, when we'll need _more_ currency1 (it's becoming more valuable) so user must provide it + delta = toBalanceDelta( + 0, + SqrtPriceMath.getAmount1Delta( + TickMath.getSqrtPriceAtTick(tickLower), TickMath.getSqrtPriceAtTick(tickUpper), liquidityDelta + ).toInt128() + ); + } + } + } + + // Tracks the state of a pool throughout a swap, and returns these values at the end of the swap + struct SwapResult { + // the current sqrt(price) + uint160 sqrtPriceX96; + // the tick associated with the current price + int24 tick; + // the current liquidity in range + uint128 liquidity; + } + + struct StepComputations { + // the price at the beginning of the step + uint160 sqrtPriceStartX96; + // the next tick to swap to from the current tick in the swap direction + int24 tickNext; + // whether tickNext is initialized or not + bool initialized; + // sqrt(price) for the next tick (1/0) + uint160 sqrtPriceNextX96; + // how much is being swapped in in this step + uint256 amountIn; + // how much is being swapped out + uint256 amountOut; + // how much fee is being paid in + uint256 feeAmount; + // the global fee growth of the input token. updated in storage at the end of swap + uint256 feeGrowthGlobalX128; + } + + struct SwapParams { + int256 amountSpecified; + int24 tickSpacing; + bool zeroForOne; + uint160 sqrtPriceLimitX96; + uint24 lpFeeOverride; + } + + /// @notice Executes a swap against the state, and returns the amount deltas of the pool + /// @dev PoolManager checks that the pool is initialized before calling + function swap(State storage self, SwapParams memory params) + internal + returns (BalanceDelta swapDelta, uint256 amountToProtocol, uint24 swapFee, SwapResult memory result) + { + Slot0 slot0Start = self.slot0; + bool zeroForOne = params.zeroForOne; + + uint256 protocolFee = + zeroForOne ? slot0Start.protocolFee().getZeroForOneFee() : slot0Start.protocolFee().getOneForZeroFee(); + + // the amount remaining to be swapped in/out of the input/output asset. initially set to the amountSpecified + int256 amountSpecifiedRemaining = params.amountSpecified; + // the amount swapped out/in of the output/input asset. initially set to 0 + int256 amountCalculated = 0; + // initialize to the current sqrt(price) + result.sqrtPriceX96 = slot0Start.sqrtPriceX96(); + // initialize to the current tick + result.tick = slot0Start.tick(); + // initialize to the current liquidity + result.liquidity = self.liquidity; + + // if the beforeSwap hook returned a valid fee override, use that as the LP fee, otherwise load from storage + // lpFee, swapFee, and protocolFee are all in pips + { + uint24 lpFee = params.lpFeeOverride.isOverride() + ? params.lpFeeOverride.removeOverrideFlagAndValidate() + : slot0Start.lpFee(); + + swapFee = protocolFee == 0 ? lpFee : uint16(protocolFee).calculateSwapFee(lpFee); + } + + // a swap fee totaling MAX_SWAP_FEE (100%) makes exact output swaps impossible since the input is entirely consumed by the fee + if (swapFee >= SwapMath.MAX_SWAP_FEE) { + // if exactOutput + if (params.amountSpecified > 0) { + InvalidFeeForExactOut.selector.revertWith(); + } + } + + // swapFee is the pool's fee in pips (LP fee + protocol fee) + // when the amount swapped is 0, there is no protocolFee applied and the fee amount paid to the protocol is set to 0 + if (params.amountSpecified == 0) return (BalanceDeltaLibrary.ZERO_DELTA, 0, swapFee, result); + + if (zeroForOne) { + if (params.sqrtPriceLimitX96 >= slot0Start.sqrtPriceX96()) { + PriceLimitAlreadyExceeded.selector.revertWith(slot0Start.sqrtPriceX96(), params.sqrtPriceLimitX96); + } + // Swaps can never occur at MIN_TICK, only at MIN_TICK + 1, except at initialization of a pool + // Under certain circumstances outlined below, the tick will preemptively reach MIN_TICK without swapping there + if (params.sqrtPriceLimitX96 <= TickMath.MIN_SQRT_PRICE) { + PriceLimitOutOfBounds.selector.revertWith(params.sqrtPriceLimitX96); + } + } else { + if (params.sqrtPriceLimitX96 <= slot0Start.sqrtPriceX96()) { + PriceLimitAlreadyExceeded.selector.revertWith(slot0Start.sqrtPriceX96(), params.sqrtPriceLimitX96); + } + if (params.sqrtPriceLimitX96 >= TickMath.MAX_SQRT_PRICE) { + PriceLimitOutOfBounds.selector.revertWith(params.sqrtPriceLimitX96); + } + } + + StepComputations memory step; + step.feeGrowthGlobalX128 = zeroForOne ? self.feeGrowthGlobal0X128 : self.feeGrowthGlobal1X128; + + // continue swapping as long as we haven't used the entire input/output and haven't reached the price limit + while (!(amountSpecifiedRemaining == 0 || result.sqrtPriceX96 == params.sqrtPriceLimitX96)) { + step.sqrtPriceStartX96 = result.sqrtPriceX96; + + (step.tickNext, step.initialized) = + self.tickBitmap.nextInitializedTickWithinOneWord(result.tick, params.tickSpacing, zeroForOne); + + // ensure that we do not overshoot the min/max tick, as the tick bitmap is not aware of these bounds + if (step.tickNext <= TickMath.MIN_TICK) { + step.tickNext = TickMath.MIN_TICK; + } + if (step.tickNext >= TickMath.MAX_TICK) { + step.tickNext = TickMath.MAX_TICK; + } + + // get the price for the next tick + step.sqrtPriceNextX96 = TickMath.getSqrtPriceAtTick(step.tickNext); + + // compute values to swap to the target tick, price limit, or point where input/output amount is exhausted + (result.sqrtPriceX96, step.amountIn, step.amountOut, step.feeAmount) = SwapMath.computeSwapStep( + result.sqrtPriceX96, + SwapMath.getSqrtPriceTarget(zeroForOne, step.sqrtPriceNextX96, params.sqrtPriceLimitX96), + result.liquidity, + amountSpecifiedRemaining, + swapFee + ); + + // if exactOutput + if (params.amountSpecified > 0) { + unchecked { + amountSpecifiedRemaining -= step.amountOut.toInt256(); + } + amountCalculated -= (step.amountIn + step.feeAmount).toInt256(); + } else { + // safe because we test that amountSpecified > amountIn + feeAmount in SwapMath + unchecked { + amountSpecifiedRemaining += (step.amountIn + step.feeAmount).toInt256(); + } + amountCalculated += step.amountOut.toInt256(); + } + + // if the protocol fee is on, calculate how much is owed, decrement feeAmount, and increment protocolFee + if (protocolFee > 0) { + unchecked { + // step.amountIn does not include the swap fee, as it's already been taken from it, + // so add it back to get the total amountIn and use that to calculate the amount of fees owed to the protocol + // cannot overflow due to limits on the size of protocolFee and params.amountSpecified + // this rounds down to favor LPs over the protocol + uint256 delta = (swapFee == protocolFee) + ? step.feeAmount // lp fee is 0, so the entire fee is owed to the protocol instead + : (step.amountIn + step.feeAmount) * protocolFee / ProtocolFeeLibrary.PIPS_DENOMINATOR; + // subtract it from the total fee and add it to the protocol fee + step.feeAmount -= delta; + amountToProtocol += delta; + } + } + + // update global fee tracker + if (result.liquidity > 0) { + unchecked { + // FullMath.mulDiv isn't needed as the numerator can't overflow uint256 since tokens have a max supply of type(uint128).max + step.feeGrowthGlobalX128 += + UnsafeMath.simpleMulDiv(step.feeAmount, FixedPoint128.Q128, result.liquidity); + } + } + + // Shift tick if we reached the next price, and preemptively decrement for zeroForOne swaps to tickNext - 1. + // If the swap doesn't continue (if amountRemaining == 0 or sqrtPriceLimit is met), slot0.tick will be 1 less + // than getTickAtSqrtPrice(slot0.sqrtPrice). This doesn't affect swaps, but donation calls should verify both + // price and tick to reward the correct LPs. + if (result.sqrtPriceX96 == step.sqrtPriceNextX96) { + // if the tick is initialized, run the tick transition + if (step.initialized) { + (uint256 feeGrowthGlobal0X128, uint256 feeGrowthGlobal1X128) = zeroForOne + ? (step.feeGrowthGlobalX128, self.feeGrowthGlobal1X128) + : (self.feeGrowthGlobal0X128, step.feeGrowthGlobalX128); + int128 liquidityNet = + Pool.crossTick(self, step.tickNext, feeGrowthGlobal0X128, feeGrowthGlobal1X128); + // if we're moving leftward, we interpret liquidityNet as the opposite sign + // safe because liquidityNet cannot be type(int128).min + unchecked { + if (zeroForOne) liquidityNet = -liquidityNet; + } + + result.liquidity = LiquidityMath.addDelta(result.liquidity, liquidityNet); + } + + unchecked { + result.tick = zeroForOne ? step.tickNext - 1 : step.tickNext; + } + } else if (result.sqrtPriceX96 != step.sqrtPriceStartX96) { + // recompute unless we're on a lower tick boundary (i.e. already transitioned ticks), and haven't moved + result.tick = TickMath.getTickAtSqrtPrice(result.sqrtPriceX96); + } + } + + self.slot0 = slot0Start.setTick(result.tick).setSqrtPriceX96(result.sqrtPriceX96); + + // update liquidity if it changed + if (self.liquidity != result.liquidity) self.liquidity = result.liquidity; + + // update fee growth global + if (!zeroForOne) { + self.feeGrowthGlobal1X128 = step.feeGrowthGlobalX128; + } else { + self.feeGrowthGlobal0X128 = step.feeGrowthGlobalX128; + } + + unchecked { + // "if currency1 is specified" + if (zeroForOne != (params.amountSpecified < 0)) { + swapDelta = toBalanceDelta( + amountCalculated.toInt128(), (params.amountSpecified - amountSpecifiedRemaining).toInt128() + ); + } else { + swapDelta = toBalanceDelta( + (params.amountSpecified - amountSpecifiedRemaining).toInt128(), amountCalculated.toInt128() + ); + } + } + } + + /// @notice Donates the given amount of currency0 and currency1 to the pool + function donate(State storage state, uint256 amount0, uint256 amount1) internal returns (BalanceDelta delta) { + uint128 liquidity = state.liquidity; + if (liquidity == 0) NoLiquidityToReceiveFees.selector.revertWith(); + unchecked { + // negation safe as amount0 and amount1 are always positive + delta = toBalanceDelta(-(amount0.toInt128()), -(amount1.toInt128())); + // FullMath.mulDiv is unnecessary because the numerator is bounded by type(int128).max * Q128, which is less than type(uint256).max + if (amount0 > 0) { + state.feeGrowthGlobal0X128 += UnsafeMath.simpleMulDiv(amount0, FixedPoint128.Q128, liquidity); + } + if (amount1 > 0) { + state.feeGrowthGlobal1X128 += UnsafeMath.simpleMulDiv(amount1, FixedPoint128.Q128, liquidity); + } + } + } + + /// @notice Retrieves fee growth data + /// @param self The Pool state struct + /// @param tickLower The lower tick boundary of the position + /// @param tickUpper The upper tick boundary of the position + /// @return feeGrowthInside0X128 The all-time fee growth in token0, per unit of liquidity, inside the position's tick boundaries + /// @return feeGrowthInside1X128 The all-time fee growth in token1, per unit of liquidity, inside the position's tick boundaries + function getFeeGrowthInside(State storage self, int24 tickLower, int24 tickUpper) + internal + view + returns (uint256 feeGrowthInside0X128, uint256 feeGrowthInside1X128) + { + TickInfo storage lower = self.ticks[tickLower]; + TickInfo storage upper = self.ticks[tickUpper]; + int24 tickCurrent = self.slot0.tick(); + + unchecked { + if (tickCurrent < tickLower) { + feeGrowthInside0X128 = lower.feeGrowthOutside0X128 - upper.feeGrowthOutside0X128; + feeGrowthInside1X128 = lower.feeGrowthOutside1X128 - upper.feeGrowthOutside1X128; + } else if (tickCurrent >= tickUpper) { + feeGrowthInside0X128 = upper.feeGrowthOutside0X128 - lower.feeGrowthOutside0X128; + feeGrowthInside1X128 = upper.feeGrowthOutside1X128 - lower.feeGrowthOutside1X128; + } else { + feeGrowthInside0X128 = + self.feeGrowthGlobal0X128 - lower.feeGrowthOutside0X128 - upper.feeGrowthOutside0X128; + feeGrowthInside1X128 = + self.feeGrowthGlobal1X128 - lower.feeGrowthOutside1X128 - upper.feeGrowthOutside1X128; + } + } + } + + /// @notice Updates a tick and returns true if the tick was flipped from initialized to uninitialized, or vice versa + /// @param self The mapping containing all tick information for initialized ticks + /// @param tick The tick that will be updated + /// @param liquidityDelta A new amount of liquidity to be added (subtracted) when tick is crossed from left to right (right to left) + /// @param upper true for updating a position's upper tick, or false for updating a position's lower tick + /// @return flipped Whether the tick was flipped from initialized to uninitialized, or vice versa + /// @return liquidityGrossAfter The total amount of liquidity for all positions that references the tick after the update + function updateTick(State storage self, int24 tick, int128 liquidityDelta, bool upper) + internal + returns (bool flipped, uint128 liquidityGrossAfter) + { + TickInfo storage info = self.ticks[tick]; + + uint128 liquidityGrossBefore = info.liquidityGross; + int128 liquidityNetBefore = info.liquidityNet; + + liquidityGrossAfter = LiquidityMath.addDelta(liquidityGrossBefore, liquidityDelta); + + flipped = (liquidityGrossAfter == 0) != (liquidityGrossBefore == 0); + + if (liquidityGrossBefore == 0) { + // by convention, we assume that all growth before a tick was initialized happened _below_ the tick + if (tick <= self.slot0.tick()) { + info.feeGrowthOutside0X128 = self.feeGrowthGlobal0X128; + info.feeGrowthOutside1X128 = self.feeGrowthGlobal1X128; + } + } + + // when the lower (upper) tick is crossed left to right, liquidity must be added (removed) + // when the lower (upper) tick is crossed right to left, liquidity must be removed (added) + int128 liquidityNet = upper ? liquidityNetBefore - liquidityDelta : liquidityNetBefore + liquidityDelta; + assembly ("memory-safe") { + // liquidityGrossAfter and liquidityNet are packed in the first slot of `info` + // So we can store them with a single sstore by packing them ourselves first + sstore( + info.slot, + // bitwise OR to pack liquidityGrossAfter and liquidityNet + or( + // Put liquidityGrossAfter in the lower bits, clearing out the upper bits + and(liquidityGrossAfter, 0xffffffffffffffffffffffffffffffff), + // Shift liquidityNet to put it in the upper bits (no need for signextend since we're shifting left) + shl(128, liquidityNet) + ) + ) + } + } + + /// @notice Derives max liquidity per tick from given tick spacing + /// @dev Executed when adding liquidity + /// @param tickSpacing The amount of required tick separation, realized in multiples of `tickSpacing` + /// e.g., a tickSpacing of 3 requires ticks to be initialized every 3rd tick i.e., ..., -6, -3, 0, 3, 6, ... + /// @return result The max liquidity per tick + function tickSpacingToMaxLiquidityPerTick(int24 tickSpacing) internal pure returns (uint128 result) { + // Equivalent to: + // int24 minTick = (TickMath.MIN_TICK / tickSpacing); + // if (TickMath.MIN_TICK % tickSpacing != 0) minTick--; + // int24 maxTick = (TickMath.MAX_TICK / tickSpacing); + // uint24 numTicks = maxTick - minTick + 1; + // return type(uint128).max / numTicks; + int24 MAX_TICK = TickMath.MAX_TICK; + int24 MIN_TICK = TickMath.MIN_TICK; + // tick spacing will never be 0 since TickMath.MIN_TICK_SPACING is 1 + assembly ("memory-safe") { + tickSpacing := signextend(2, tickSpacing) + let minTick := sub(sdiv(MIN_TICK, tickSpacing), slt(smod(MIN_TICK, tickSpacing), 0)) + let maxTick := sdiv(MAX_TICK, tickSpacing) + let numTicks := add(sub(maxTick, minTick), 1) + result := div(sub(shl(128, 1), 1), numTicks) + } + } + + /// @notice Reverts if the given pool has not been initialized + function checkPoolInitialized(State storage self) internal view { + if (self.slot0.sqrtPriceX96() == 0) PoolNotInitialized.selector.revertWith(); + } + + /// @notice Clears tick data + /// @param self The mapping containing all initialized tick information for initialized ticks + /// @param tick The tick that will be cleared + function clearTick(State storage self, int24 tick) internal { + delete self.ticks[tick]; + } + + /// @notice Transitions to next tick as needed by price movement + /// @param self The Pool state struct + /// @param tick The destination tick of the transition + /// @param feeGrowthGlobal0X128 The all-time global fee growth, per unit of liquidity, in token0 + /// @param feeGrowthGlobal1X128 The all-time global fee growth, per unit of liquidity, in token1 + /// @return liquidityNet The amount of liquidity added (subtracted) when tick is crossed from left to right (right to left) + function crossTick(State storage self, int24 tick, uint256 feeGrowthGlobal0X128, uint256 feeGrowthGlobal1X128) + internal + returns (int128 liquidityNet) + { + unchecked { + TickInfo storage info = self.ticks[tick]; + info.feeGrowthOutside0X128 = feeGrowthGlobal0X128 - info.feeGrowthOutside0X128; + info.feeGrowthOutside1X128 = feeGrowthGlobal1X128 - info.feeGrowthOutside1X128; + liquidityNet = info.liquidityNet; + } + } +} diff --git a/CVLByExample/UniswapHooks/lib/uniswap-hooks/lib/v4-core/src/libraries/Position.sol b/CVLByExample/UniswapHooks/lib/uniswap-hooks/lib/v4-core/src/libraries/Position.sol new file mode 100644 index 00000000..40821ae3 --- /dev/null +++ b/CVLByExample/UniswapHooks/lib/uniswap-hooks/lib/v4-core/src/libraries/Position.sol @@ -0,0 +1,103 @@ +// SPDX-License-Identifier: BUSL-1.1 +pragma solidity ^0.8.0; + +import {FullMath} from "./FullMath.sol"; +import {FixedPoint128} from "./FixedPoint128.sol"; +import {LiquidityMath} from "./LiquidityMath.sol"; +import {CustomRevert} from "./CustomRevert.sol"; + +/// @title Position +/// @notice Positions represent an owner address' liquidity between a lower and upper tick boundary +/// @dev Positions store additional state for tracking fees owed to the position +library Position { + using CustomRevert for bytes4; + + /// @notice Cannot update a position with no liquidity + error CannotUpdateEmptyPosition(); + + // info stored for each user's position + struct State { + // the amount of liquidity owned by this position + uint128 liquidity; + // fee growth per unit of liquidity as of the last update to liquidity or fees owed + uint256 feeGrowthInside0LastX128; + uint256 feeGrowthInside1LastX128; + } + + /// @notice Returns the State struct of a position, given an owner and position boundaries + /// @param self The mapping containing all user positions + /// @param owner The address of the position owner + /// @param tickLower The lower tick boundary of the position + /// @param tickUpper The upper tick boundary of the position + /// @param salt A unique value to differentiate between multiple positions in the same range + /// @return position The position info struct of the given owners' position + function get(mapping(bytes32 => State) storage self, address owner, int24 tickLower, int24 tickUpper, bytes32 salt) + internal + view + returns (State storage position) + { + bytes32 positionKey = calculatePositionKey(owner, tickLower, tickUpper, salt); + position = self[positionKey]; + } + + /// @notice A helper function to calculate the position key + /// @param owner The address of the position owner + /// @param tickLower the lower tick boundary of the position + /// @param tickUpper the upper tick boundary of the position + /// @param salt A unique value to differentiate between multiple positions in the same range, by the same owner. Passed in by the caller. + function calculatePositionKey(address owner, int24 tickLower, int24 tickUpper, bytes32 salt) + internal + pure + returns (bytes32 positionKey) + { + // positionKey = keccak256(abi.encodePacked(owner, tickLower, tickUpper, salt)) + assembly ("memory-safe") { + let fmp := mload(0x40) + mstore(add(fmp, 0x26), salt) // [0x26, 0x46) + mstore(add(fmp, 0x06), tickUpper) // [0x23, 0x26) + mstore(add(fmp, 0x03), tickLower) // [0x20, 0x23) + mstore(fmp, owner) // [0x0c, 0x20) + positionKey := keccak256(add(fmp, 0x0c), 0x3a) // len is 58 bytes + + // now clean the memory we used + mstore(add(fmp, 0x40), 0) // fmp+0x40 held salt + mstore(add(fmp, 0x20), 0) // fmp+0x20 held tickLower, tickUpper, salt + mstore(fmp, 0) // fmp held owner + } + } + + /// @notice Credits accumulated fees to a user's position + /// @param self The individual position to update + /// @param liquidityDelta The change in pool liquidity as a result of the position update + /// @param feeGrowthInside0X128 The all-time fee growth in currency0, per unit of liquidity, inside the position's tick boundaries + /// @param feeGrowthInside1X128 The all-time fee growth in currency1, per unit of liquidity, inside the position's tick boundaries + /// @return feesOwed0 The amount of currency0 owed to the position owner + /// @return feesOwed1 The amount of currency1 owed to the position owner + function update( + State storage self, + int128 liquidityDelta, + uint256 feeGrowthInside0X128, + uint256 feeGrowthInside1X128 + ) internal returns (uint256 feesOwed0, uint256 feesOwed1) { + uint128 liquidity = self.liquidity; + + if (liquidityDelta == 0) { + // disallow pokes for 0 liquidity positions + if (liquidity == 0) CannotUpdateEmptyPosition.selector.revertWith(); + } else { + self.liquidity = LiquidityMath.addDelta(liquidity, liquidityDelta); + } + + // calculate accumulated fees. overflow in the subtraction of fee growth is expected + unchecked { + feesOwed0 = + FullMath.mulDiv(feeGrowthInside0X128 - self.feeGrowthInside0LastX128, liquidity, FixedPoint128.Q128); + feesOwed1 = + FullMath.mulDiv(feeGrowthInside1X128 - self.feeGrowthInside1LastX128, liquidity, FixedPoint128.Q128); + } + + // update the position + self.feeGrowthInside0LastX128 = feeGrowthInside0X128; + self.feeGrowthInside1LastX128 = feeGrowthInside1X128; + } +} diff --git a/CVLByExample/UniswapHooks/lib/uniswap-hooks/lib/v4-core/src/libraries/ProtocolFeeLibrary.sol b/CVLByExample/UniswapHooks/lib/uniswap-hooks/lib/v4-core/src/libraries/ProtocolFeeLibrary.sol new file mode 100644 index 00000000..c155b86a --- /dev/null +++ b/CVLByExample/UniswapHooks/lib/uniswap-hooks/lib/v4-core/src/libraries/ProtocolFeeLibrary.sol @@ -0,0 +1,47 @@ +// SPDX-License-Identifier: MIT +pragma solidity ^0.8.0; + +/// @notice library of functions related to protocol fees +library ProtocolFeeLibrary { + /// @notice Max protocol fee is 0.1% (1000 pips) + /// @dev Increasing these values could lead to overflow in Pool.swap + uint16 public constant MAX_PROTOCOL_FEE = 1000; + + /// @notice Thresholds used for optimized bounds checks on protocol fees + uint24 internal constant FEE_0_THRESHOLD = 1001; + uint24 internal constant FEE_1_THRESHOLD = 1001 << 12; + + /// @notice the protocol fee is represented in hundredths of a bip + uint256 internal constant PIPS_DENOMINATOR = 1_000_000; + + function getZeroForOneFee(uint24 self) internal pure returns (uint16) { + return uint16(self & 0xfff); + } + + function getOneForZeroFee(uint24 self) internal pure returns (uint16) { + return uint16(self >> 12); + } + + function isValidProtocolFee(uint24 self) internal pure returns (bool valid) { + // Equivalent to: getZeroForOneFee(self) <= MAX_PROTOCOL_FEE && getOneForZeroFee(self) <= MAX_PROTOCOL_FEE + assembly ("memory-safe") { + let isZeroForOneFeeOk := lt(and(self, 0xfff), FEE_0_THRESHOLD) + let isOneForZeroFeeOk := lt(and(self, 0xfff000), FEE_1_THRESHOLD) + valid := and(isZeroForOneFeeOk, isOneForZeroFeeOk) + } + } + + // The protocol fee is taken from the input amount first and then the LP fee is taken from the remaining + // The swap fee is capped at 100% + // Equivalent to protocolFee + lpFee(1_000_000 - protocolFee) / 1_000_000 (rounded up) + /// @dev here `self` is just a single direction's protocol fee, not a packed type of 2 protocol fees + function calculateSwapFee(uint16 self, uint24 lpFee) internal pure returns (uint24 swapFee) { + // protocolFee + lpFee - (protocolFee * lpFee / 1_000_000) + assembly ("memory-safe") { + self := and(self, 0xfff) + lpFee := and(lpFee, 0xffffff) + let numerator := mul(self, lpFee) + swapFee := sub(add(self, lpFee), div(numerator, PIPS_DENOMINATOR)) + } + } +} diff --git a/CVLByExample/UniswapHooks/lib/uniswap-hooks/lib/v4-core/src/libraries/SafeCast.sol b/CVLByExample/UniswapHooks/lib/uniswap-hooks/lib/v4-core/src/libraries/SafeCast.sol new file mode 100644 index 00000000..118b02cd --- /dev/null +++ b/CVLByExample/UniswapHooks/lib/uniswap-hooks/lib/v4-core/src/libraries/SafeCast.sol @@ -0,0 +1,60 @@ +// SPDX-License-Identifier: MIT +pragma solidity ^0.8.0; + +import {CustomRevert} from "./CustomRevert.sol"; + +/// @title Safe casting methods +/// @notice Contains methods for safely casting between types +library SafeCast { + using CustomRevert for bytes4; + + error SafeCastOverflow(); + + /// @notice Cast a uint256 to a uint160, revert on overflow + /// @param x The uint256 to be downcasted + /// @return y The downcasted integer, now type uint160 + function toUint160(uint256 x) internal pure returns (uint160 y) { + y = uint160(x); + if (y != x) SafeCastOverflow.selector.revertWith(); + } + + /// @notice Cast a uint256 to a uint128, revert on overflow + /// @param x The uint256 to be downcasted + /// @return y The downcasted integer, now type uint128 + function toUint128(uint256 x) internal pure returns (uint128 y) { + y = uint128(x); + if (x != y) SafeCastOverflow.selector.revertWith(); + } + + /// @notice Cast a int128 to a uint128, revert on overflow or underflow + /// @param x The int128 to be casted + /// @return y The casted integer, now type uint128 + function toUint128(int128 x) internal pure returns (uint128 y) { + if (x < 0) SafeCastOverflow.selector.revertWith(); + y = uint128(x); + } + + /// @notice Cast a int256 to a int128, revert on overflow or underflow + /// @param x The int256 to be downcasted + /// @return y The downcasted integer, now type int128 + function toInt128(int256 x) internal pure returns (int128 y) { + y = int128(x); + if (y != x) SafeCastOverflow.selector.revertWith(); + } + + /// @notice Cast a uint256 to a int256, revert on overflow + /// @param x The uint256 to be casted + /// @return y The casted integer, now type int256 + function toInt256(uint256 x) internal pure returns (int256 y) { + y = int256(x); + if (y < 0) SafeCastOverflow.selector.revertWith(); + } + + /// @notice Cast a uint256 to a int128, revert on overflow + /// @param x The uint256 to be downcasted + /// @return The downcasted integer, now type int128 + function toInt128(uint256 x) internal pure returns (int128) { + if (x >= 1 << 127) SafeCastOverflow.selector.revertWith(); + return int128(int256(x)); + } +} diff --git a/CVLByExample/UniswapHooks/lib/uniswap-hooks/lib/v4-core/src/libraries/SqrtPriceMath.sol b/CVLByExample/UniswapHooks/lib/uniswap-hooks/lib/v4-core/src/libraries/SqrtPriceMath.sol new file mode 100644 index 00000000..0b706ba1 --- /dev/null +++ b/CVLByExample/UniswapHooks/lib/uniswap-hooks/lib/v4-core/src/libraries/SqrtPriceMath.sol @@ -0,0 +1,289 @@ +// SPDX-License-Identifier: MIT +pragma solidity ^0.8.0; + +import {SafeCast} from "./SafeCast.sol"; + +import {FullMath} from "./FullMath.sol"; +import {UnsafeMath} from "./UnsafeMath.sol"; +import {FixedPoint96} from "./FixedPoint96.sol"; + +/// @title Functions based on Q64.96 sqrt price and liquidity +/// @notice Contains the math that uses square root of price as a Q64.96 and liquidity to compute deltas +library SqrtPriceMath { + using SafeCast for uint256; + + error InvalidPriceOrLiquidity(); + error InvalidPrice(); + error NotEnoughLiquidity(); + error PriceOverflow(); + + /// @notice Gets the next sqrt price given a delta of currency0 + /// @dev Always rounds up, because in the exact output case (increasing price) we need to move the price at least + /// far enough to get the desired output amount, and in the exact input case (decreasing price) we need to move the + /// price less in order to not send too much output. + /// The most precise formula for this is liquidity * sqrtPX96 / (liquidity +- amount * sqrtPX96), + /// if this is impossible because of overflow, we calculate liquidity / (liquidity / sqrtPX96 +- amount). + /// @param sqrtPX96 The starting price, i.e. before accounting for the currency0 delta + /// @param liquidity The amount of usable liquidity + /// @param amount How much of currency0 to add or remove from virtual reserves + /// @param add Whether to add or remove the amount of currency0 + /// @return The price after adding or removing amount, depending on add + function getNextSqrtPriceFromAmount0RoundingUp(uint160 sqrtPX96, uint128 liquidity, uint256 amount, bool add) + internal + pure + returns (uint160) + { + // we short circuit amount == 0 because the result is otherwise not guaranteed to equal the input price + if (amount == 0) return sqrtPX96; + uint256 numerator1 = uint256(liquidity) << FixedPoint96.RESOLUTION; + + if (add) { + unchecked { + uint256 product = amount * sqrtPX96; + if (product / amount == sqrtPX96) { + uint256 denominator = numerator1 + product; + if (denominator >= numerator1) { + // always fits in 160 bits + return uint160(FullMath.mulDivRoundingUp(numerator1, sqrtPX96, denominator)); + } + } + } + // denominator is checked for overflow + return uint160(UnsafeMath.divRoundingUp(numerator1, (numerator1 / sqrtPX96) + amount)); + } else { + unchecked { + uint256 product = amount * sqrtPX96; + // if the product overflows, we know the denominator underflows + // in addition, we must check that the denominator does not underflow + // equivalent: if (product / amount != sqrtPX96 || numerator1 <= product) revert PriceOverflow(); + assembly ("memory-safe") { + if iszero( + and( + eq(div(product, amount), and(sqrtPX96, 0xffffffffffffffffffffffffffffffffffffffff)), + gt(numerator1, product) + ) + ) { + mstore(0, 0xf5c787f1) // selector for PriceOverflow() + revert(0x1c, 0x04) + } + } + uint256 denominator = numerator1 - product; + return FullMath.mulDivRoundingUp(numerator1, sqrtPX96, denominator).toUint160(); + } + } + } + + /// @notice Gets the next sqrt price given a delta of currency1 + /// @dev Always rounds down, because in the exact output case (decreasing price) we need to move the price at least + /// far enough to get the desired output amount, and in the exact input case (increasing price) we need to move the + /// price less in order to not send too much output. + /// The formula we compute is within <1 wei of the lossless version: sqrtPX96 +- amount / liquidity + /// @param sqrtPX96 The starting price, i.e., before accounting for the currency1 delta + /// @param liquidity The amount of usable liquidity + /// @param amount How much of currency1 to add, or remove, from virtual reserves + /// @param add Whether to add, or remove, the amount of currency1 + /// @return The price after adding or removing `amount` + function getNextSqrtPriceFromAmount1RoundingDown(uint160 sqrtPX96, uint128 liquidity, uint256 amount, bool add) + internal + pure + returns (uint160) + { + // if we're adding (subtracting), rounding down requires rounding the quotient down (up) + // in both cases, avoid a mulDiv for most inputs + if (add) { + uint256 quotient = ( + amount <= type(uint160).max + ? (amount << FixedPoint96.RESOLUTION) / liquidity + : FullMath.mulDiv(amount, FixedPoint96.Q96, liquidity) + ); + + return (uint256(sqrtPX96) + quotient).toUint160(); + } else { + uint256 quotient = ( + amount <= type(uint160).max + ? UnsafeMath.divRoundingUp(amount << FixedPoint96.RESOLUTION, liquidity) + : FullMath.mulDivRoundingUp(amount, FixedPoint96.Q96, liquidity) + ); + + // equivalent: if (sqrtPX96 <= quotient) revert NotEnoughLiquidity(); + assembly ("memory-safe") { + if iszero(gt(and(sqrtPX96, 0xffffffffffffffffffffffffffffffffffffffff), quotient)) { + mstore(0, 0x4323a555) // selector for NotEnoughLiquidity() + revert(0x1c, 0x04) + } + } + // always fits 160 bits + unchecked { + return uint160(sqrtPX96 - quotient); + } + } + } + + /// @notice Gets the next sqrt price given an input amount of currency0 or currency1 + /// @dev Throws if price or liquidity are 0, or if the next price is out of bounds + /// @param sqrtPX96 The starting price, i.e., before accounting for the input amount + /// @param liquidity The amount of usable liquidity + /// @param amountIn How much of currency0, or currency1, is being swapped in + /// @param zeroForOne Whether the amount in is currency0 or currency1 + /// @return uint160 The price after adding the input amount to currency0 or currency1 + function getNextSqrtPriceFromInput(uint160 sqrtPX96, uint128 liquidity, uint256 amountIn, bool zeroForOne) + internal + pure + returns (uint160) + { + // equivalent: if (sqrtPX96 == 0 || liquidity == 0) revert InvalidPriceOrLiquidity(); + assembly ("memory-safe") { + if or( + iszero(and(sqrtPX96, 0xffffffffffffffffffffffffffffffffffffffff)), + iszero(and(liquidity, 0xffffffffffffffffffffffffffffffff)) + ) { + mstore(0, 0x4f2461b8) // selector for InvalidPriceOrLiquidity() + revert(0x1c, 0x04) + } + } + + // round to make sure that we don't pass the target price + return zeroForOne + ? getNextSqrtPriceFromAmount0RoundingUp(sqrtPX96, liquidity, amountIn, true) + : getNextSqrtPriceFromAmount1RoundingDown(sqrtPX96, liquidity, amountIn, true); + } + + /// @notice Gets the next sqrt price given an output amount of currency0 or currency1 + /// @dev Throws if price or liquidity are 0 or the next price is out of bounds + /// @param sqrtPX96 The starting price before accounting for the output amount + /// @param liquidity The amount of usable liquidity + /// @param amountOut How much of currency0, or currency1, is being swapped out + /// @param zeroForOne Whether the amount out is currency1 or currency0 + /// @return uint160 The price after removing the output amount of currency0 or currency1 + function getNextSqrtPriceFromOutput(uint160 sqrtPX96, uint128 liquidity, uint256 amountOut, bool zeroForOne) + internal + pure + returns (uint160) + { + // equivalent: if (sqrtPX96 == 0 || liquidity == 0) revert InvalidPriceOrLiquidity(); + assembly ("memory-safe") { + if or( + iszero(and(sqrtPX96, 0xffffffffffffffffffffffffffffffffffffffff)), + iszero(and(liquidity, 0xffffffffffffffffffffffffffffffff)) + ) { + mstore(0, 0x4f2461b8) // selector for InvalidPriceOrLiquidity() + revert(0x1c, 0x04) + } + } + + // round to make sure that we pass the target price + return zeroForOne + ? getNextSqrtPriceFromAmount1RoundingDown(sqrtPX96, liquidity, amountOut, false) + : getNextSqrtPriceFromAmount0RoundingUp(sqrtPX96, liquidity, amountOut, false); + } + + /// @notice Gets the amount0 delta between two prices + /// @dev Calculates liquidity / sqrt(lower) - liquidity / sqrt(upper), + /// i.e. liquidity * (sqrt(upper) - sqrt(lower)) / (sqrt(upper) * sqrt(lower)) + /// @param sqrtPriceAX96 A sqrt price + /// @param sqrtPriceBX96 Another sqrt price + /// @param liquidity The amount of usable liquidity + /// @param roundUp Whether to round the amount up or down + /// @return uint256 Amount of currency0 required to cover a position of size liquidity between the two passed prices + function getAmount0Delta(uint160 sqrtPriceAX96, uint160 sqrtPriceBX96, uint128 liquidity, bool roundUp) + internal + pure + returns (uint256) + { + unchecked { + if (sqrtPriceAX96 > sqrtPriceBX96) (sqrtPriceAX96, sqrtPriceBX96) = (sqrtPriceBX96, sqrtPriceAX96); + + // equivalent: if (sqrtPriceAX96 == 0) revert InvalidPrice(); + assembly ("memory-safe") { + if iszero(and(sqrtPriceAX96, 0xffffffffffffffffffffffffffffffffffffffff)) { + mstore(0, 0x00bfc921) // selector for InvalidPrice() + revert(0x1c, 0x04) + } + } + + uint256 numerator1 = uint256(liquidity) << FixedPoint96.RESOLUTION; + uint256 numerator2 = sqrtPriceBX96 - sqrtPriceAX96; + + return roundUp + ? UnsafeMath.divRoundingUp(FullMath.mulDivRoundingUp(numerator1, numerator2, sqrtPriceBX96), sqrtPriceAX96) + : FullMath.mulDiv(numerator1, numerator2, sqrtPriceBX96) / sqrtPriceAX96; + } + } + + /// @notice Equivalent to: `a >= b ? a - b : b - a` + function absDiff(uint160 a, uint160 b) internal pure returns (uint256 res) { + assembly ("memory-safe") { + let diff := + sub(and(a, 0xffffffffffffffffffffffffffffffffffffffff), and(b, 0xffffffffffffffffffffffffffffffffffffffff)) + // mask = 0 if a >= b else -1 (all 1s) + let mask := sar(255, diff) + // if a >= b, res = a - b = 0 ^ (a - b) + // if a < b, res = b - a = ~~(b - a) = ~(-(b - a) - 1) = ~(a - b - 1) = (-1) ^ (a - b - 1) + // either way, res = mask ^ (a - b + mask) + res := xor(mask, add(mask, diff)) + } + } + + /// @notice Gets the amount1 delta between two prices + /// @dev Calculates liquidity * (sqrt(upper) - sqrt(lower)) + /// @param sqrtPriceAX96 A sqrt price + /// @param sqrtPriceBX96 Another sqrt price + /// @param liquidity The amount of usable liquidity + /// @param roundUp Whether to round the amount up, or down + /// @return amount1 Amount of currency1 required to cover a position of size liquidity between the two passed prices + function getAmount1Delta(uint160 sqrtPriceAX96, uint160 sqrtPriceBX96, uint128 liquidity, bool roundUp) + internal + pure + returns (uint256 amount1) + { + uint256 numerator = absDiff(sqrtPriceAX96, sqrtPriceBX96); + uint256 denominator = FixedPoint96.Q96; + uint256 _liquidity = uint256(liquidity); + + /** + * Equivalent to: + * amount1 = roundUp + * ? FullMath.mulDivRoundingUp(liquidity, sqrtPriceBX96 - sqrtPriceAX96, FixedPoint96.Q96) + * : FullMath.mulDiv(liquidity, sqrtPriceBX96 - sqrtPriceAX96, FixedPoint96.Q96); + * Cannot overflow because `type(uint128).max * type(uint160).max >> 96 < (1 << 192)`. + */ + amount1 = FullMath.mulDiv(_liquidity, numerator, denominator); + assembly ("memory-safe") { + amount1 := add(amount1, and(gt(mulmod(_liquidity, numerator, denominator), 0), roundUp)) + } + } + + /// @notice Helper that gets signed currency0 delta + /// @param sqrtPriceAX96 A sqrt price + /// @param sqrtPriceBX96 Another sqrt price + /// @param liquidity The change in liquidity for which to compute the amount0 delta + /// @return int256 Amount of currency0 corresponding to the passed liquidityDelta between the two prices + function getAmount0Delta(uint160 sqrtPriceAX96, uint160 sqrtPriceBX96, int128 liquidity) + internal + pure + returns (int256) + { + unchecked { + return liquidity < 0 + ? getAmount0Delta(sqrtPriceAX96, sqrtPriceBX96, uint128(-liquidity), false).toInt256() + : -getAmount0Delta(sqrtPriceAX96, sqrtPriceBX96, uint128(liquidity), true).toInt256(); + } + } + + /// @notice Helper that gets signed currency1 delta + /// @param sqrtPriceAX96 A sqrt price + /// @param sqrtPriceBX96 Another sqrt price + /// @param liquidity The change in liquidity for which to compute the amount1 delta + /// @return int256 Amount of currency1 corresponding to the passed liquidityDelta between the two prices + function getAmount1Delta(uint160 sqrtPriceAX96, uint160 sqrtPriceBX96, int128 liquidity) + internal + pure + returns (int256) + { + unchecked { + return liquidity < 0 + ? getAmount1Delta(sqrtPriceAX96, sqrtPriceBX96, uint128(-liquidity), false).toInt256() + : -getAmount1Delta(sqrtPriceAX96, sqrtPriceBX96, uint128(liquidity), true).toInt256(); + } + } +} diff --git a/CVLByExample/UniswapHooks/lib/uniswap-hooks/lib/v4-core/src/libraries/StateLibrary.sol b/CVLByExample/UniswapHooks/lib/uniswap-hooks/lib/v4-core/src/libraries/StateLibrary.sol new file mode 100644 index 00000000..a8cd6213 --- /dev/null +++ b/CVLByExample/UniswapHooks/lib/uniswap-hooks/lib/v4-core/src/libraries/StateLibrary.sol @@ -0,0 +1,349 @@ +// SPDX-License-Identifier: MIT +pragma solidity ^0.8.0; + +import {PoolId} from "../types/PoolId.sol"; +import {IPoolManager} from "../interfaces/IPoolManager.sol"; +import {Position} from "./Position.sol"; + +/// @notice A helper library to provide state getters that use extsload +library StateLibrary { + /// @notice index of pools mapping in the PoolManager + bytes32 public constant POOLS_SLOT = bytes32(uint256(6)); + + /// @notice index of feeGrowthGlobal0X128 in Pool.State + uint256 public constant FEE_GROWTH_GLOBAL0_OFFSET = 1; + + // feeGrowthGlobal1X128 offset in Pool.State = 2 + + /// @notice index of liquidity in Pool.State + uint256 public constant LIQUIDITY_OFFSET = 3; + + /// @notice index of TicksInfo mapping in Pool.State: mapping(int24 => TickInfo) ticks; + uint256 public constant TICKS_OFFSET = 4; + + /// @notice index of tickBitmap mapping in Pool.State + uint256 public constant TICK_BITMAP_OFFSET = 5; + + /// @notice index of Position.State mapping in Pool.State: mapping(bytes32 => Position.State) positions; + uint256 public constant POSITIONS_OFFSET = 6; + + /** + * @notice Get Slot0 of the pool: sqrtPriceX96, tick, protocolFee, lpFee + * @dev Corresponds to pools[poolId].slot0 + * @param manager The pool manager contract. + * @param poolId The ID of the pool. + * @return sqrtPriceX96 The square root of the price of the pool, in Q96 precision. + * @return tick The current tick of the pool. + * @return protocolFee The protocol fee of the pool. + * @return lpFee The swap fee of the pool. + */ + function getSlot0(IPoolManager manager, PoolId poolId) + internal + view + returns (uint160 sqrtPriceX96, int24 tick, uint24 protocolFee, uint24 lpFee) + { + // slot key of Pool.State value: `pools[poolId]` + bytes32 stateSlot = _getPoolStateSlot(poolId); + + bytes32 data = manager.extsload(stateSlot); + + // 24 bits |24bits|24bits |24 bits|160 bits + // 0x000000 |000bb8|000000 |ffff75 |0000000000000000fe3aa841ba359daa0ea9eff7 + // ---------- | fee |protocolfee | tick | sqrtPriceX96 + assembly ("memory-safe") { + // bottom 160 bits of data + sqrtPriceX96 := and(data, 0xFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFF) + // next 24 bits of data + tick := signextend(2, shr(160, data)) + // next 24 bits of data + protocolFee := and(shr(184, data), 0xFFFFFF) + // last 24 bits of data + lpFee := and(shr(208, data), 0xFFFFFF) + } + } + + /** + * @notice Retrieves the tick information of a pool at a specific tick. + * @dev Corresponds to pools[poolId].ticks[tick] + * @param manager The pool manager contract. + * @param poolId The ID of the pool. + * @param tick The tick to retrieve information for. + * @return liquidityGross The total position liquidity that references this tick + * @return liquidityNet The amount of net liquidity added (subtracted) when tick is crossed from left to right (right to left) + * @return feeGrowthOutside0X128 fee growth per unit of liquidity on the _other_ side of this tick (relative to the current tick) + * @return feeGrowthOutside1X128 fee growth per unit of liquidity on the _other_ side of this tick (relative to the current tick) + */ + function getTickInfo(IPoolManager manager, PoolId poolId, int24 tick) + internal + view + returns ( + uint128 liquidityGross, + int128 liquidityNet, + uint256 feeGrowthOutside0X128, + uint256 feeGrowthOutside1X128 + ) + { + bytes32 slot = _getTickInfoSlot(poolId, tick); + + // read all 3 words of the TickInfo struct + bytes32[] memory data = manager.extsload(slot, 3); + assembly ("memory-safe") { + let firstWord := mload(add(data, 32)) + liquidityNet := sar(128, firstWord) + liquidityGross := and(firstWord, 0xFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFF) + feeGrowthOutside0X128 := mload(add(data, 64)) + feeGrowthOutside1X128 := mload(add(data, 96)) + } + } + + /** + * @notice Retrieves the liquidity information of a pool at a specific tick. + * @dev Corresponds to pools[poolId].ticks[tick].liquidityGross and pools[poolId].ticks[tick].liquidityNet. A more gas efficient version of getTickInfo + * @param manager The pool manager contract. + * @param poolId The ID of the pool. + * @param tick The tick to retrieve liquidity for. + * @return liquidityGross The total position liquidity that references this tick + * @return liquidityNet The amount of net liquidity added (subtracted) when tick is crossed from left to right (right to left) + */ + function getTickLiquidity(IPoolManager manager, PoolId poolId, int24 tick) + internal + view + returns (uint128 liquidityGross, int128 liquidityNet) + { + bytes32 slot = _getTickInfoSlot(poolId, tick); + + bytes32 value = manager.extsload(slot); + assembly ("memory-safe") { + liquidityNet := sar(128, value) + liquidityGross := and(value, 0xFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFF) + } + } + + /** + * @notice Retrieves the fee growth outside a tick range of a pool + * @dev Corresponds to pools[poolId].ticks[tick].feeGrowthOutside0X128 and pools[poolId].ticks[tick].feeGrowthOutside1X128. A more gas efficient version of getTickInfo + * @param manager The pool manager contract. + * @param poolId The ID of the pool. + * @param tick The tick to retrieve fee growth for. + * @return feeGrowthOutside0X128 fee growth per unit of liquidity on the _other_ side of this tick (relative to the current tick) + * @return feeGrowthOutside1X128 fee growth per unit of liquidity on the _other_ side of this tick (relative to the current tick) + */ + function getTickFeeGrowthOutside(IPoolManager manager, PoolId poolId, int24 tick) + internal + view + returns (uint256 feeGrowthOutside0X128, uint256 feeGrowthOutside1X128) + { + bytes32 slot = _getTickInfoSlot(poolId, tick); + + // offset by 1 word, since the first word is liquidityGross + liquidityNet + bytes32[] memory data = manager.extsload(bytes32(uint256(slot) + 1), 2); + assembly ("memory-safe") { + feeGrowthOutside0X128 := mload(add(data, 32)) + feeGrowthOutside1X128 := mload(add(data, 64)) + } + } + + /** + * @notice Retrieves the global fee growth of a pool. + * @dev Corresponds to pools[poolId].feeGrowthGlobal0X128 and pools[poolId].feeGrowthGlobal1X128 + * @param manager The pool manager contract. + * @param poolId The ID of the pool. + * @return feeGrowthGlobal0 The global fee growth for token0. + * @return feeGrowthGlobal1 The global fee growth for token1. + * @dev Note that feeGrowthGlobal can be artificially inflated + * For pools with a single liquidity position, actors can donate to themselves to freely inflate feeGrowthGlobal + * atomically donating and collecting fees in the same unlockCallback may make the inflated value more extreme + */ + function getFeeGrowthGlobals(IPoolManager manager, PoolId poolId) + internal + view + returns (uint256 feeGrowthGlobal0, uint256 feeGrowthGlobal1) + { + // slot key of Pool.State value: `pools[poolId]` + bytes32 stateSlot = _getPoolStateSlot(poolId); + + // Pool.State, `uint256 feeGrowthGlobal0X128` + bytes32 slot_feeGrowthGlobal0X128 = bytes32(uint256(stateSlot) + FEE_GROWTH_GLOBAL0_OFFSET); + + // read the 2 words of feeGrowthGlobal + bytes32[] memory data = manager.extsload(slot_feeGrowthGlobal0X128, 2); + assembly ("memory-safe") { + feeGrowthGlobal0 := mload(add(data, 32)) + feeGrowthGlobal1 := mload(add(data, 64)) + } + } + + /** + * @notice Retrieves total the liquidity of a pool. + * @dev Corresponds to pools[poolId].liquidity + * @param manager The pool manager contract. + * @param poolId The ID of the pool. + * @return liquidity The liquidity of the pool. + */ + function getLiquidity(IPoolManager manager, PoolId poolId) internal view returns (uint128 liquidity) { + // slot key of Pool.State value: `pools[poolId]` + bytes32 stateSlot = _getPoolStateSlot(poolId); + + // Pool.State: `uint128 liquidity` + bytes32 slot = bytes32(uint256(stateSlot) + LIQUIDITY_OFFSET); + + liquidity = uint128(uint256(manager.extsload(slot))); + } + + /** + * @notice Retrieves the tick bitmap of a pool at a specific tick. + * @dev Corresponds to pools[poolId].tickBitmap[tick] + * @param manager The pool manager contract. + * @param poolId The ID of the pool. + * @param tick The tick to retrieve the bitmap for. + * @return tickBitmap The bitmap of the tick. + */ + function getTickBitmap(IPoolManager manager, PoolId poolId, int16 tick) + internal + view + returns (uint256 tickBitmap) + { + // slot key of Pool.State value: `pools[poolId]` + bytes32 stateSlot = _getPoolStateSlot(poolId); + + // Pool.State: `mapping(int16 => uint256) tickBitmap;` + bytes32 tickBitmapMapping = bytes32(uint256(stateSlot) + TICK_BITMAP_OFFSET); + + // slot id of the mapping key: `pools[poolId].tickBitmap[tick] + bytes32 slot = keccak256(abi.encodePacked(int256(tick), tickBitmapMapping)); + + tickBitmap = uint256(manager.extsload(slot)); + } + + /** + * @notice Retrieves the position information of a pool without needing to calculate the `positionId`. + * @dev Corresponds to pools[poolId].positions[positionId] + * @param poolId The ID of the pool. + * @param owner The owner of the liquidity position. + * @param tickLower The lower tick of the liquidity range. + * @param tickUpper The upper tick of the liquidity range. + * @param salt The bytes32 randomness to further distinguish position state. + * @return liquidity The liquidity of the position. + * @return feeGrowthInside0LastX128 The fee growth inside the position for token0. + * @return feeGrowthInside1LastX128 The fee growth inside the position for token1. + */ + function getPositionInfo( + IPoolManager manager, + PoolId poolId, + address owner, + int24 tickLower, + int24 tickUpper, + bytes32 salt + ) internal view returns (uint128 liquidity, uint256 feeGrowthInside0LastX128, uint256 feeGrowthInside1LastX128) { + // positionKey = keccak256(abi.encodePacked(owner, tickLower, tickUpper, salt)) + bytes32 positionKey = Position.calculatePositionKey(owner, tickLower, tickUpper, salt); + + (liquidity, feeGrowthInside0LastX128, feeGrowthInside1LastX128) = getPositionInfo(manager, poolId, positionKey); + } + + /** + * @notice Retrieves the position information of a pool at a specific position ID. + * @dev Corresponds to pools[poolId].positions[positionId] + * @param manager The pool manager contract. + * @param poolId The ID of the pool. + * @param positionId The ID of the position. + * @return liquidity The liquidity of the position. + * @return feeGrowthInside0LastX128 The fee growth inside the position for token0. + * @return feeGrowthInside1LastX128 The fee growth inside the position for token1. + */ + function getPositionInfo(IPoolManager manager, PoolId poolId, bytes32 positionId) + internal + view + returns (uint128 liquidity, uint256 feeGrowthInside0LastX128, uint256 feeGrowthInside1LastX128) + { + bytes32 slot = _getPositionInfoSlot(poolId, positionId); + + // read all 3 words of the Position.State struct + bytes32[] memory data = manager.extsload(slot, 3); + + assembly ("memory-safe") { + liquidity := mload(add(data, 32)) + feeGrowthInside0LastX128 := mload(add(data, 64)) + feeGrowthInside1LastX128 := mload(add(data, 96)) + } + } + + /** + * @notice Retrieves the liquidity of a position. + * @dev Corresponds to pools[poolId].positions[positionId].liquidity. More gas efficient for just retrieiving liquidity as compared to getPositionInfo + * @param manager The pool manager contract. + * @param poolId The ID of the pool. + * @param positionId The ID of the position. + * @return liquidity The liquidity of the position. + */ + function getPositionLiquidity(IPoolManager manager, PoolId poolId, bytes32 positionId) + internal + view + returns (uint128 liquidity) + { + bytes32 slot = _getPositionInfoSlot(poolId, positionId); + liquidity = uint128(uint256(manager.extsload(slot))); + } + + /** + * @notice Calculate the fee growth inside a tick range of a pool + * @dev pools[poolId].feeGrowthInside0LastX128 in Position.State is cached and can become stale. This function will calculate the up to date feeGrowthInside + * @param manager The pool manager contract. + * @param poolId The ID of the pool. + * @param tickLower The lower tick of the range. + * @param tickUpper The upper tick of the range. + * @return feeGrowthInside0X128 The fee growth inside the tick range for token0. + * @return feeGrowthInside1X128 The fee growth inside the tick range for token1. + */ + function getFeeGrowthInside(IPoolManager manager, PoolId poolId, int24 tickLower, int24 tickUpper) + internal + view + returns (uint256 feeGrowthInside0X128, uint256 feeGrowthInside1X128) + { + (uint256 feeGrowthGlobal0X128, uint256 feeGrowthGlobal1X128) = getFeeGrowthGlobals(manager, poolId); + + (uint256 lowerFeeGrowthOutside0X128, uint256 lowerFeeGrowthOutside1X128) = + getTickFeeGrowthOutside(manager, poolId, tickLower); + (uint256 upperFeeGrowthOutside0X128, uint256 upperFeeGrowthOutside1X128) = + getTickFeeGrowthOutside(manager, poolId, tickUpper); + (, int24 tickCurrent,,) = getSlot0(manager, poolId); + unchecked { + if (tickCurrent < tickLower) { + feeGrowthInside0X128 = lowerFeeGrowthOutside0X128 - upperFeeGrowthOutside0X128; + feeGrowthInside1X128 = lowerFeeGrowthOutside1X128 - upperFeeGrowthOutside1X128; + } else if (tickCurrent >= tickUpper) { + feeGrowthInside0X128 = upperFeeGrowthOutside0X128 - lowerFeeGrowthOutside0X128; + feeGrowthInside1X128 = upperFeeGrowthOutside1X128 - lowerFeeGrowthOutside1X128; + } else { + feeGrowthInside0X128 = feeGrowthGlobal0X128 - lowerFeeGrowthOutside0X128 - upperFeeGrowthOutside0X128; + feeGrowthInside1X128 = feeGrowthGlobal1X128 - lowerFeeGrowthOutside1X128 - upperFeeGrowthOutside1X128; + } + } + } + + function _getPoolStateSlot(PoolId poolId) internal pure returns (bytes32) { + return keccak256(abi.encodePacked(PoolId.unwrap(poolId), POOLS_SLOT)); + } + + function _getTickInfoSlot(PoolId poolId, int24 tick) internal pure returns (bytes32) { + // slot key of Pool.State value: `pools[poolId]` + bytes32 stateSlot = _getPoolStateSlot(poolId); + + // Pool.State: `mapping(int24 => TickInfo) ticks` + bytes32 ticksMappingSlot = bytes32(uint256(stateSlot) + TICKS_OFFSET); + + // slot key of the tick key: `pools[poolId].ticks[tick] + return keccak256(abi.encodePacked(int256(tick), ticksMappingSlot)); + } + + function _getPositionInfoSlot(PoolId poolId, bytes32 positionId) internal pure returns (bytes32) { + // slot key of Pool.State value: `pools[poolId]` + bytes32 stateSlot = _getPoolStateSlot(poolId); + + // Pool.State: `mapping(bytes32 => Position.State) positions;` + bytes32 positionMapping = bytes32(uint256(stateSlot) + POSITIONS_OFFSET); + + // slot of the mapping key: `pools[poolId].positions[positionId] + return keccak256(abi.encodePacked(positionId, positionMapping)); + } +} diff --git a/CVLByExample/UniswapHooks/lib/uniswap-hooks/lib/v4-core/src/libraries/SwapMath.sol b/CVLByExample/UniswapHooks/lib/uniswap-hooks/lib/v4-core/src/libraries/SwapMath.sol new file mode 100644 index 00000000..92599e5d --- /dev/null +++ b/CVLByExample/UniswapHooks/lib/uniswap-hooks/lib/v4-core/src/libraries/SwapMath.sol @@ -0,0 +1,108 @@ +// SPDX-License-Identifier: MIT +pragma solidity ^0.8.0; + +import {FullMath} from "./FullMath.sol"; +import {SqrtPriceMath} from "./SqrtPriceMath.sol"; + +/// @title Computes the result of a swap within ticks +/// @notice Contains methods for computing the result of a swap within a single tick price range, i.e., a single tick. +library SwapMath { + /// @notice the swap fee is represented in hundredths of a bip, so the max is 100% + /// @dev the swap fee is the total fee on a swap, including both LP and Protocol fee + uint256 internal constant MAX_SWAP_FEE = 1e6; + + /// @notice Computes the sqrt price target for the next swap step + /// @param zeroForOne The direction of the swap, true for currency0 to currency1, false for currency1 to currency0 + /// @param sqrtPriceNextX96 The Q64.96 sqrt price for the next initialized tick + /// @param sqrtPriceLimitX96 The Q64.96 sqrt price limit. If zero for one, the price cannot be less than this value + /// after the swap. If one for zero, the price cannot be greater than this value after the swap + /// @return sqrtPriceTargetX96 The price target for the next swap step + function getSqrtPriceTarget(bool zeroForOne, uint160 sqrtPriceNextX96, uint160 sqrtPriceLimitX96) + internal + pure + returns (uint160 sqrtPriceTargetX96) + { + assembly ("memory-safe") { + // a flag to toggle between sqrtPriceNextX96 and sqrtPriceLimitX96 + // when zeroForOne == true, nextOrLimit reduces to sqrtPriceNextX96 >= sqrtPriceLimitX96 + // sqrtPriceTargetX96 = max(sqrtPriceNextX96, sqrtPriceLimitX96) + // when zeroForOne == false, nextOrLimit reduces to sqrtPriceNextX96 < sqrtPriceLimitX96 + // sqrtPriceTargetX96 = min(sqrtPriceNextX96, sqrtPriceLimitX96) + sqrtPriceNextX96 := and(sqrtPriceNextX96, 0xffffffffffffffffffffffffffffffffffffffff) + sqrtPriceLimitX96 := and(sqrtPriceLimitX96, 0xffffffffffffffffffffffffffffffffffffffff) + let nextOrLimit := xor(lt(sqrtPriceNextX96, sqrtPriceLimitX96), and(zeroForOne, 0x1)) + let symDiff := xor(sqrtPriceNextX96, sqrtPriceLimitX96) + sqrtPriceTargetX96 := xor(sqrtPriceLimitX96, mul(symDiff, nextOrLimit)) + } + } + + /// @notice Computes the result of swapping some amount in, or amount out, given the parameters of the swap + /// @dev If the swap's amountSpecified is negative, the combined fee and input amount will never exceed the absolute value of the remaining amount. + /// @param sqrtPriceCurrentX96 The current sqrt price of the pool + /// @param sqrtPriceTargetX96 The price that cannot be exceeded, from which the direction of the swap is inferred + /// @param liquidity The usable liquidity + /// @param amountRemaining How much input or output amount is remaining to be swapped in/out + /// @param feePips The fee taken from the input amount, expressed in hundredths of a bip + /// @return sqrtPriceNextX96 The price after swapping the amount in/out, not to exceed the price target + /// @return amountIn The amount to be swapped in, of either currency0 or currency1, based on the direction of the swap + /// @return amountOut The amount to be received, of either currency0 or currency1, based on the direction of the swap + /// @return feeAmount The amount of input that will be taken as a fee + /// @dev feePips must be no larger than MAX_SWAP_FEE for this function. We ensure that before setting a fee using LPFeeLibrary.isValid. + function computeSwapStep( + uint160 sqrtPriceCurrentX96, + uint160 sqrtPriceTargetX96, + uint128 liquidity, + int256 amountRemaining, + uint24 feePips + ) internal pure returns (uint160 sqrtPriceNextX96, uint256 amountIn, uint256 amountOut, uint256 feeAmount) { + unchecked { + uint256 _feePips = feePips; // upcast once and cache + bool zeroForOne = sqrtPriceCurrentX96 >= sqrtPriceTargetX96; + bool exactIn = amountRemaining < 0; + + if (exactIn) { + uint256 amountRemainingLessFee = + FullMath.mulDiv(uint256(-amountRemaining), MAX_SWAP_FEE - _feePips, MAX_SWAP_FEE); + amountIn = zeroForOne + ? SqrtPriceMath.getAmount0Delta(sqrtPriceTargetX96, sqrtPriceCurrentX96, liquidity, true) + : SqrtPriceMath.getAmount1Delta(sqrtPriceCurrentX96, sqrtPriceTargetX96, liquidity, true); + if (amountRemainingLessFee >= amountIn) { + // `amountIn` is capped by the target price + sqrtPriceNextX96 = sqrtPriceTargetX96; + feeAmount = _feePips == MAX_SWAP_FEE + ? amountIn // amountIn is always 0 here, as amountRemainingLessFee == 0 and amountRemainingLessFee >= amountIn + : FullMath.mulDivRoundingUp(amountIn, _feePips, MAX_SWAP_FEE - _feePips); + } else { + // exhaust the remaining amount + amountIn = amountRemainingLessFee; + sqrtPriceNextX96 = SqrtPriceMath.getNextSqrtPriceFromInput( + sqrtPriceCurrentX96, liquidity, amountRemainingLessFee, zeroForOne + ); + // we didn't reach the target, so take the remainder of the maximum input as fee + feeAmount = uint256(-amountRemaining) - amountIn; + } + amountOut = zeroForOne + ? SqrtPriceMath.getAmount1Delta(sqrtPriceNextX96, sqrtPriceCurrentX96, liquidity, false) + : SqrtPriceMath.getAmount0Delta(sqrtPriceCurrentX96, sqrtPriceNextX96, liquidity, false); + } else { + amountOut = zeroForOne + ? SqrtPriceMath.getAmount1Delta(sqrtPriceTargetX96, sqrtPriceCurrentX96, liquidity, false) + : SqrtPriceMath.getAmount0Delta(sqrtPriceCurrentX96, sqrtPriceTargetX96, liquidity, false); + if (uint256(amountRemaining) >= amountOut) { + // `amountOut` is capped by the target price + sqrtPriceNextX96 = sqrtPriceTargetX96; + } else { + // cap the output amount to not exceed the remaining output amount + amountOut = uint256(amountRemaining); + sqrtPriceNextX96 = + SqrtPriceMath.getNextSqrtPriceFromOutput(sqrtPriceCurrentX96, liquidity, amountOut, zeroForOne); + } + amountIn = zeroForOne + ? SqrtPriceMath.getAmount0Delta(sqrtPriceNextX96, sqrtPriceCurrentX96, liquidity, true) + : SqrtPriceMath.getAmount1Delta(sqrtPriceCurrentX96, sqrtPriceNextX96, liquidity, true); + // `feePips` cannot be `MAX_SWAP_FEE` for exact out + feeAmount = FullMath.mulDivRoundingUp(amountIn, _feePips, MAX_SWAP_FEE - _feePips); + } + } + } +} diff --git a/CVLByExample/UniswapHooks/lib/uniswap-hooks/lib/v4-core/src/libraries/TickBitmap.sol b/CVLByExample/UniswapHooks/lib/uniswap-hooks/lib/v4-core/src/libraries/TickBitmap.sol new file mode 100644 index 00000000..15104a19 --- /dev/null +++ b/CVLByExample/UniswapHooks/lib/uniswap-hooks/lib/v4-core/src/libraries/TickBitmap.sol @@ -0,0 +1,122 @@ +// SPDX-License-Identifier: MIT +pragma solidity ^0.8.0; + +import {BitMath} from "./BitMath.sol"; + +/// @title Packed tick initialized state library +/// @notice Stores a packed mapping of tick index to its initialized state +/// @dev The mapping uses int16 for keys since ticks are represented as int24 and there are 256 (2^8) values per word. +library TickBitmap { + /// @notice Thrown when the tick is not enumerated by the tick spacing + /// @param tick the invalid tick + /// @param tickSpacing The tick spacing of the pool + error TickMisaligned(int24 tick, int24 tickSpacing); + + /// @dev round towards negative infinity + function compress(int24 tick, int24 tickSpacing) internal pure returns (int24 compressed) { + // compressed = tick / tickSpacing; + // if (tick < 0 && tick % tickSpacing != 0) compressed--; + assembly ("memory-safe") { + tick := signextend(2, tick) + tickSpacing := signextend(2, tickSpacing) + compressed := + sub( + sdiv(tick, tickSpacing), + // if (tick < 0 && tick % tickSpacing != 0) then tick % tickSpacing < 0, vice versa + slt(smod(tick, tickSpacing), 0) + ) + } + } + + /// @notice Computes the position in the mapping where the initialized bit for a tick lives + /// @param tick The tick for which to compute the position + /// @return wordPos The key in the mapping containing the word in which the bit is stored + /// @return bitPos The bit position in the word where the flag is stored + function position(int24 tick) internal pure returns (int16 wordPos, uint8 bitPos) { + assembly ("memory-safe") { + // signed arithmetic shift right + wordPos := sar(8, signextend(2, tick)) + bitPos := and(tick, 0xff) + } + } + + /// @notice Flips the initialized state for a given tick from false to true, or vice versa + /// @param self The mapping in which to flip the tick + /// @param tick The tick to flip + /// @param tickSpacing The spacing between usable ticks + function flipTick(mapping(int16 => uint256) storage self, int24 tick, int24 tickSpacing) internal { + // Equivalent to the following Solidity: + // if (tick % tickSpacing != 0) revert TickMisaligned(tick, tickSpacing); + // (int16 wordPos, uint8 bitPos) = position(tick / tickSpacing); + // uint256 mask = 1 << bitPos; + // self[wordPos] ^= mask; + assembly ("memory-safe") { + tick := signextend(2, tick) + tickSpacing := signextend(2, tickSpacing) + // ensure that the tick is spaced + if smod(tick, tickSpacing) { + let fmp := mload(0x40) + mstore(fmp, 0xd4d8f3e6) // selector for TickMisaligned(int24,int24) + mstore(add(fmp, 0x20), tick) + mstore(add(fmp, 0x40), tickSpacing) + revert(add(fmp, 0x1c), 0x44) + } + tick := sdiv(tick, tickSpacing) + // calculate the storage slot corresponding to the tick + // wordPos = tick >> 8 + mstore(0, sar(8, tick)) + mstore(0x20, self.slot) + // the slot of self[wordPos] is keccak256(abi.encode(wordPos, self.slot)) + let slot := keccak256(0, 0x40) + // mask = 1 << bitPos = 1 << (tick % 256) + // self[wordPos] ^= mask + sstore(slot, xor(sload(slot), shl(and(tick, 0xff), 1))) + } + } + + /// @notice Returns the next initialized tick contained in the same word (or adjacent word) as the tick that is either + /// to the left (less than or equal to) or right (greater than) of the given tick + /// @param self The mapping in which to compute the next initialized tick + /// @param tick The starting tick + /// @param tickSpacing The spacing between usable ticks + /// @param lte Whether to search for the next initialized tick to the left (less than or equal to the starting tick) + /// @return next The next initialized or uninitialized tick up to 256 ticks away from the current tick + /// @return initialized Whether the next tick is initialized, as the function only searches within up to 256 ticks + function nextInitializedTickWithinOneWord( + mapping(int16 => uint256) storage self, + int24 tick, + int24 tickSpacing, + bool lte + ) internal view returns (int24 next, bool initialized) { + unchecked { + int24 compressed = compress(tick, tickSpacing); + + if (lte) { + (int16 wordPos, uint8 bitPos) = position(compressed); + // all the 1s at or to the right of the current bitPos + uint256 mask = type(uint256).max >> (uint256(type(uint8).max) - bitPos); + uint256 masked = self[wordPos] & mask; + + // if there are no initialized ticks to the right of or at the current tick, return rightmost in the word + initialized = masked != 0; + // overflow/underflow is possible, but prevented externally by limiting both tickSpacing and tick + next = initialized + ? (compressed - int24(uint24(bitPos - BitMath.mostSignificantBit(masked)))) * tickSpacing + : (compressed - int24(uint24(bitPos))) * tickSpacing; + } else { + // start from the word of the next tick, since the current tick state doesn't matter + (int16 wordPos, uint8 bitPos) = position(++compressed); + // all the 1s at or to the left of the bitPos + uint256 mask = ~((1 << bitPos) - 1); + uint256 masked = self[wordPos] & mask; + + // if there are no initialized ticks to the left of the current tick, return leftmost in the word + initialized = masked != 0; + // overflow/underflow is possible, but prevented externally by limiting both tickSpacing and tick + next = initialized + ? (compressed + int24(uint24(BitMath.leastSignificantBit(masked) - bitPos))) * tickSpacing + : (compressed + int24(uint24(type(uint8).max - bitPos))) * tickSpacing; + } + } + } +} diff --git a/CVLByExample/UniswapHooks/lib/uniswap-hooks/lib/v4-core/src/libraries/TickMath.sol b/CVLByExample/UniswapHooks/lib/uniswap-hooks/lib/v4-core/src/libraries/TickMath.sol new file mode 100644 index 00000000..e5dc4adc --- /dev/null +++ b/CVLByExample/UniswapHooks/lib/uniswap-hooks/lib/v4-core/src/libraries/TickMath.sol @@ -0,0 +1,238 @@ +// SPDX-License-Identifier: MIT +pragma solidity ^0.8.0; + +import {BitMath} from "./BitMath.sol"; +import {CustomRevert} from "./CustomRevert.sol"; + +/// @title Math library for computing sqrt prices from ticks and vice versa +/// @notice Computes sqrt price for ticks of size 1.0001, i.e. sqrt(1.0001^tick) as fixed point Q64.96 numbers. Supports +/// prices between 2**-128 and 2**128 +library TickMath { + using CustomRevert for bytes4; + + /// @notice Thrown when the tick passed to #getSqrtPriceAtTick is not between MIN_TICK and MAX_TICK + error InvalidTick(int24 tick); + /// @notice Thrown when the price passed to #getTickAtSqrtPrice does not correspond to a price between MIN_TICK and MAX_TICK + error InvalidSqrtPrice(uint160 sqrtPriceX96); + + /// @dev The minimum tick that may be passed to #getSqrtPriceAtTick computed from log base 1.0001 of 2**-128 + /// @dev If ever MIN_TICK and MAX_TICK are not centered around 0, the absTick logic in getSqrtPriceAtTick cannot be used + int24 internal constant MIN_TICK = -887272; + /// @dev The maximum tick that may be passed to #getSqrtPriceAtTick computed from log base 1.0001 of 2**128 + /// @dev If ever MIN_TICK and MAX_TICK are not centered around 0, the absTick logic in getSqrtPriceAtTick cannot be used + int24 internal constant MAX_TICK = 887272; + + /// @dev The minimum tick spacing value drawn from the range of type int16 that is greater than 0, i.e. min from the range [1, 32767] + int24 internal constant MIN_TICK_SPACING = 1; + /// @dev The maximum tick spacing value drawn from the range of type int16, i.e. max from the range [1, 32767] + int24 internal constant MAX_TICK_SPACING = type(int16).max; + + /// @dev The minimum value that can be returned from #getSqrtPriceAtTick. Equivalent to getSqrtPriceAtTick(MIN_TICK) + uint160 internal constant MIN_SQRT_PRICE = 4295128739; + /// @dev The maximum value that can be returned from #getSqrtPriceAtTick. Equivalent to getSqrtPriceAtTick(MAX_TICK) + uint160 internal constant MAX_SQRT_PRICE = 1461446703485210103287273052203988822378723970342; + /// @dev A threshold used for optimized bounds check, equals `MAX_SQRT_PRICE - MIN_SQRT_PRICE - 1` + uint160 internal constant MAX_SQRT_PRICE_MINUS_MIN_SQRT_PRICE_MINUS_ONE = + 1461446703485210103287273052203988822378723970342 - 4295128739 - 1; + + /// @notice Given a tickSpacing, compute the maximum usable tick + function maxUsableTick(int24 tickSpacing) internal pure returns (int24) { + unchecked { + return (MAX_TICK / tickSpacing) * tickSpacing; + } + } + + /// @notice Given a tickSpacing, compute the minimum usable tick + function minUsableTick(int24 tickSpacing) internal pure returns (int24) { + unchecked { + return (MIN_TICK / tickSpacing) * tickSpacing; + } + } + + /// @notice Calculates sqrt(1.0001^tick) * 2^96 + /// @dev Throws if |tick| > max tick + /// @param tick The input tick for the above formula + /// @return sqrtPriceX96 A Fixed point Q64.96 number representing the sqrt of the price of the two assets (currency1/currency0) + /// at the given tick + function getSqrtPriceAtTick(int24 tick) internal pure returns (uint160 sqrtPriceX96) { + unchecked { + uint256 absTick; + assembly ("memory-safe") { + tick := signextend(2, tick) + // mask = 0 if tick >= 0 else -1 (all 1s) + let mask := sar(255, tick) + // if tick >= 0, |tick| = tick = 0 ^ tick + // if tick < 0, |tick| = ~~|tick| = ~(-|tick| - 1) = ~(tick - 1) = (-1) ^ (tick - 1) + // either way, |tick| = mask ^ (tick + mask) + absTick := xor(mask, add(mask, tick)) + } + + if (absTick > uint256(int256(MAX_TICK))) InvalidTick.selector.revertWith(tick); + + // The tick is decomposed into bits, and for each bit with index i that is set, the product of 1/sqrt(1.0001^(2^i)) + // is calculated (using Q128.128). The constants used for this calculation are rounded to the nearest integer + + // Equivalent to: + // price = absTick & 0x1 != 0 ? 0xfffcb933bd6fad37aa2d162d1a594001 : 0x100000000000000000000000000000000; + // or price = int(2**128 / sqrt(1.0001)) if (absTick & 0x1) else 1 << 128 + uint256 price; + assembly ("memory-safe") { + price := xor(shl(128, 1), mul(xor(shl(128, 1), 0xfffcb933bd6fad37aa2d162d1a594001), and(absTick, 0x1))) + } + if (absTick & 0x2 != 0) price = (price * 0xfff97272373d413259a46990580e213a) >> 128; + if (absTick & 0x4 != 0) price = (price * 0xfff2e50f5f656932ef12357cf3c7fdcc) >> 128; + if (absTick & 0x8 != 0) price = (price * 0xffe5caca7e10e4e61c3624eaa0941cd0) >> 128; + if (absTick & 0x10 != 0) price = (price * 0xffcb9843d60f6159c9db58835c926644) >> 128; + if (absTick & 0x20 != 0) price = (price * 0xff973b41fa98c081472e6896dfb254c0) >> 128; + if (absTick & 0x40 != 0) price = (price * 0xff2ea16466c96a3843ec78b326b52861) >> 128; + if (absTick & 0x80 != 0) price = (price * 0xfe5dee046a99a2a811c461f1969c3053) >> 128; + if (absTick & 0x100 != 0) price = (price * 0xfcbe86c7900a88aedcffc83b479aa3a4) >> 128; + if (absTick & 0x200 != 0) price = (price * 0xf987a7253ac413176f2b074cf7815e54) >> 128; + if (absTick & 0x400 != 0) price = (price * 0xf3392b0822b70005940c7a398e4b70f3) >> 128; + if (absTick & 0x800 != 0) price = (price * 0xe7159475a2c29b7443b29c7fa6e889d9) >> 128; + if (absTick & 0x1000 != 0) price = (price * 0xd097f3bdfd2022b8845ad8f792aa5825) >> 128; + if (absTick & 0x2000 != 0) price = (price * 0xa9f746462d870fdf8a65dc1f90e061e5) >> 128; + if (absTick & 0x4000 != 0) price = (price * 0x70d869a156d2a1b890bb3df62baf32f7) >> 128; + if (absTick & 0x8000 != 0) price = (price * 0x31be135f97d08fd981231505542fcfa6) >> 128; + if (absTick & 0x10000 != 0) price = (price * 0x9aa508b5b7a84e1c677de54f3e99bc9) >> 128; + if (absTick & 0x20000 != 0) price = (price * 0x5d6af8dedb81196699c329225ee604) >> 128; + if (absTick & 0x40000 != 0) price = (price * 0x2216e584f5fa1ea926041bedfe98) >> 128; + if (absTick & 0x80000 != 0) price = (price * 0x48a170391f7dc42444e8fa2) >> 128; + + assembly ("memory-safe") { + // if (tick > 0) price = type(uint256).max / price; + if sgt(tick, 0) { price := div(not(0), price) } + + // this divides by 1<<32 rounding up to go from a Q128.128 to a Q128.96. + // we then downcast because we know the result always fits within 160 bits due to our tick input constraint + // we round up in the division so getTickAtSqrtPrice of the output price is always consistent + // `sub(shl(32, 1), 1)` is `type(uint32).max` + // `price + type(uint32).max` will not overflow because `price` fits in 192 bits + sqrtPriceX96 := shr(32, add(price, sub(shl(32, 1), 1))) + } + } + } + + /// @notice Calculates the greatest tick value such that getSqrtPriceAtTick(tick) <= sqrtPriceX96 + /// @dev Throws in case sqrtPriceX96 < MIN_SQRT_PRICE, as MIN_SQRT_PRICE is the lowest value getSqrtPriceAtTick may + /// ever return. + /// @param sqrtPriceX96 The sqrt price for which to compute the tick as a Q64.96 + /// @return tick The greatest tick for which the getSqrtPriceAtTick(tick) is less than or equal to the input sqrtPriceX96 + function getTickAtSqrtPrice(uint160 sqrtPriceX96) internal pure returns (int24 tick) { + unchecked { + // Equivalent: if (sqrtPriceX96 < MIN_SQRT_PRICE || sqrtPriceX96 >= MAX_SQRT_PRICE) revert InvalidSqrtPrice(); + // second inequality must be >= because the price can never reach the price at the max tick + // if sqrtPriceX96 < MIN_SQRT_PRICE, the `sub` underflows and `gt` is true + // if sqrtPriceX96 >= MAX_SQRT_PRICE, sqrtPriceX96 - MIN_SQRT_PRICE > MAX_SQRT_PRICE - MIN_SQRT_PRICE - 1 + if ((sqrtPriceX96 - MIN_SQRT_PRICE) > MAX_SQRT_PRICE_MINUS_MIN_SQRT_PRICE_MINUS_ONE) { + InvalidSqrtPrice.selector.revertWith(sqrtPriceX96); + } + + uint256 price = uint256(sqrtPriceX96) << 32; + + uint256 r = price; + uint256 msb = BitMath.mostSignificantBit(r); + + if (msb >= 128) r = price >> (msb - 127); + else r = price << (127 - msb); + + int256 log_2 = (int256(msb) - 128) << 64; + + assembly ("memory-safe") { + r := shr(127, mul(r, r)) + let f := shr(128, r) + log_2 := or(log_2, shl(63, f)) + r := shr(f, r) + } + assembly ("memory-safe") { + r := shr(127, mul(r, r)) + let f := shr(128, r) + log_2 := or(log_2, shl(62, f)) + r := shr(f, r) + } + assembly ("memory-safe") { + r := shr(127, mul(r, r)) + let f := shr(128, r) + log_2 := or(log_2, shl(61, f)) + r := shr(f, r) + } + assembly ("memory-safe") { + r := shr(127, mul(r, r)) + let f := shr(128, r) + log_2 := or(log_2, shl(60, f)) + r := shr(f, r) + } + assembly ("memory-safe") { + r := shr(127, mul(r, r)) + let f := shr(128, r) + log_2 := or(log_2, shl(59, f)) + r := shr(f, r) + } + assembly ("memory-safe") { + r := shr(127, mul(r, r)) + let f := shr(128, r) + log_2 := or(log_2, shl(58, f)) + r := shr(f, r) + } + assembly ("memory-safe") { + r := shr(127, mul(r, r)) + let f := shr(128, r) + log_2 := or(log_2, shl(57, f)) + r := shr(f, r) + } + assembly ("memory-safe") { + r := shr(127, mul(r, r)) + let f := shr(128, r) + log_2 := or(log_2, shl(56, f)) + r := shr(f, r) + } + assembly ("memory-safe") { + r := shr(127, mul(r, r)) + let f := shr(128, r) + log_2 := or(log_2, shl(55, f)) + r := shr(f, r) + } + assembly ("memory-safe") { + r := shr(127, mul(r, r)) + let f := shr(128, r) + log_2 := or(log_2, shl(54, f)) + r := shr(f, r) + } + assembly ("memory-safe") { + r := shr(127, mul(r, r)) + let f := shr(128, r) + log_2 := or(log_2, shl(53, f)) + r := shr(f, r) + } + assembly ("memory-safe") { + r := shr(127, mul(r, r)) + let f := shr(128, r) + log_2 := or(log_2, shl(52, f)) + r := shr(f, r) + } + assembly ("memory-safe") { + r := shr(127, mul(r, r)) + let f := shr(128, r) + log_2 := or(log_2, shl(51, f)) + r := shr(f, r) + } + assembly ("memory-safe") { + r := shr(127, mul(r, r)) + let f := shr(128, r) + log_2 := or(log_2, shl(50, f)) + } + + int256 log_sqrt10001 = log_2 * 255738958999603826347141; // Q22.128 number + + // Magic number represents the ceiling of the maximum value of the error when approximating log_sqrt10001(x) + int24 tickLow = int24((log_sqrt10001 - 3402992956809132418596140100660247210) >> 128); + + // Magic number represents the minimum value of the error when approximating log_sqrt10001(x), when + // sqrtPrice is from the range (2^-64, 2^64). This is safe as MIN_SQRT_PRICE is more than 2^-64. If MIN_SQRT_PRICE + // is changed, this may need to be changed too + int24 tickHi = int24((log_sqrt10001 + 291339464771989622907027621153398088495) >> 128); + + tick = tickLow == tickHi ? tickLow : getSqrtPriceAtTick(tickHi) <= sqrtPriceX96 ? tickHi : tickLow; + } + } +} diff --git a/CVLByExample/UniswapHooks/lib/uniswap-hooks/lib/v4-core/src/libraries/TransientStateLibrary.sol b/CVLByExample/UniswapHooks/lib/uniswap-hooks/lib/v4-core/src/libraries/TransientStateLibrary.sol new file mode 100644 index 00000000..0544716b --- /dev/null +++ b/CVLByExample/UniswapHooks/lib/uniswap-hooks/lib/v4-core/src/libraries/TransientStateLibrary.sol @@ -0,0 +1,49 @@ +// SPDX-License-Identifier: MIT +pragma solidity ^0.8.24; + +import {IPoolManager} from "../interfaces/IPoolManager.sol"; +import {Currency} from "../types/Currency.sol"; +import {CurrencyReserves} from "./CurrencyReserves.sol"; +import {NonzeroDeltaCount} from "./NonzeroDeltaCount.sol"; +import {Lock} from "./Lock.sol"; + +/// @notice A helper library to provide state getters that use exttload +library TransientStateLibrary { + /// @notice returns the reserves for the synced currency + /// @param manager The pool manager contract. + + /// @return uint256 The reserves of the currency. + /// @dev returns 0 if the reserves are not synced or value is 0. + /// Checks the synced currency to only return valid reserve values (after a sync and before a settle). + function getSyncedReserves(IPoolManager manager) internal view returns (uint256) { + if (getSyncedCurrency(manager).isAddressZero()) return 0; + return uint256(manager.exttload(CurrencyReserves.RESERVES_OF_SLOT)); + } + + function getSyncedCurrency(IPoolManager manager) internal view returns (Currency) { + return Currency.wrap(address(uint160(uint256(manager.exttload(CurrencyReserves.CURRENCY_SLOT))))); + } + + /// @notice Returns the number of nonzero deltas open on the PoolManager that must be zeroed out before the contract is locked + function getNonzeroDeltaCount(IPoolManager manager) internal view returns (uint256) { + return uint256(manager.exttload(NonzeroDeltaCount.NONZERO_DELTA_COUNT_SLOT)); + } + + /// @notice Get the current delta for a caller in the given currency + /// @param target The credited account address + /// @param currency The currency for which to lookup the delta + function currencyDelta(IPoolManager manager, address target, Currency currency) internal view returns (int256) { + bytes32 key; + assembly ("memory-safe") { + mstore(0, and(target, 0xffffffffffffffffffffffffffffffffffffffff)) + mstore(32, and(currency, 0xffffffffffffffffffffffffffffffffffffffff)) + key := keccak256(0, 64) + } + return int256(uint256(manager.exttload(key))); + } + + /// @notice Returns whether the contract is unlocked or not + function isUnlocked(IPoolManager manager) internal view returns (bool) { + return manager.exttload(Lock.IS_UNLOCKED_SLOT) != 0x0; + } +} diff --git a/CVLByExample/UniswapHooks/lib/uniswap-hooks/lib/v4-core/src/libraries/UnsafeMath.sol b/CVLByExample/UniswapHooks/lib/uniswap-hooks/lib/v4-core/src/libraries/UnsafeMath.sol new file mode 100644 index 00000000..ed88beca --- /dev/null +++ b/CVLByExample/UniswapHooks/lib/uniswap-hooks/lib/v4-core/src/libraries/UnsafeMath.sol @@ -0,0 +1,29 @@ +// SPDX-License-Identifier: MIT +pragma solidity ^0.8.0; + +/// @title Math functions that do not check inputs or outputs +/// @notice Contains methods that perform common math functions but do not do any overflow or underflow checks +library UnsafeMath { + /// @notice Returns ceil(x / y) + /// @dev division by 0 will return 0, and should be checked externally + /// @param x The dividend + /// @param y The divisor + /// @return z The quotient, ceil(x / y) + function divRoundingUp(uint256 x, uint256 y) internal pure returns (uint256 z) { + assembly ("memory-safe") { + z := add(div(x, y), gt(mod(x, y), 0)) + } + } + + /// @notice Calculates floor(a×b÷denominator) + /// @dev division by 0 will return 0, and should be checked externally + /// @param a The multiplicand + /// @param b The multiplier + /// @param denominator The divisor + /// @return result The 256-bit result, floor(a×b÷denominator) + function simpleMulDiv(uint256 a, uint256 b, uint256 denominator) internal pure returns (uint256 result) { + assembly ("memory-safe") { + result := div(mul(a, b), denominator) + } + } +} diff --git a/CVLByExample/UniswapHooks/lib/uniswap-hooks/lib/v4-core/src/test/ActionsRouter.sol b/CVLByExample/UniswapHooks/lib/uniswap-hooks/lib/v4-core/src/test/ActionsRouter.sol new file mode 100644 index 00000000..b1e2bed5 --- /dev/null +++ b/CVLByExample/UniswapHooks/lib/uniswap-hooks/lib/v4-core/src/test/ActionsRouter.sol @@ -0,0 +1,170 @@ +// SPDX-License-Identifier: UNLICENSED +pragma solidity ^0.8.24; + +import "forge-std/Test.sol"; +import {IPoolManager} from "../interfaces/IPoolManager.sol"; +import {IUnlockCallback} from "../interfaces/callback/IUnlockCallback.sol"; +import {Currency} from "../types/Currency.sol"; +import {MockERC20} from "solmate/src/test/utils/mocks/MockERC20.sol"; +import {StateLibrary} from "../libraries/StateLibrary.sol"; +import {TransientStateLibrary} from "../libraries/TransientStateLibrary.sol"; + +// Supported Actions. +enum Actions { + SETTLE, + SETTLE_NATIVE, + SETTLE_FOR, + TAKE, + PRANK_TAKE_FROM, + SYNC, + MINT, + CLEAR, + ASSERT_BALANCE_EQUALS, + ASSERT_RESERVES_EQUALS, + ASSERT_DELTA_EQUALS, + ASSERT_NONZERO_DELTA_COUNT_EQUALS, + TRANSFER_FROM, + COLLECT_PROTOCOL_FEES +} +// TODO: Add other actions as needed. +// BURN, +// MODIFY_POSITION, +// INITIALIZE, +// DONATE + +/// @notice A router that handles an arbitrary input of actions. +/// TODO: Can continue to add functions per action. +contract ActionsRouter is IUnlockCallback, Test { + using StateLibrary for IPoolManager; + using TransientStateLibrary for IPoolManager; + + error ActionNotSupported(); + + // error thrown so that incorrectly formatted tests don't pass silently + error CheckParameters(); + + IPoolManager manager; + + constructor(IPoolManager _manager) { + manager = _manager; + } + + function unlockCallback(bytes calldata data) external returns (bytes memory) { + (Actions[] memory actions, bytes[] memory params) = abi.decode(data, (Actions[], bytes[])); + if (actions.length != params.length || actions.length == 0) revert CheckParameters(); + for (uint256 i = 0; i < actions.length; i++) { + Actions action = actions[i]; + bytes memory param = params[i]; + if (action == Actions.SETTLE) { + _settle(); + } else if (action == Actions.SETTLE_NATIVE) { + _settleNative(param); + } else if (action == Actions.SETTLE_FOR) { + _settleFor(param); + } else if (action == Actions.TAKE) { + _take(param); + } else if (action == Actions.PRANK_TAKE_FROM) { + _prankTakeFrom(param); + } else if (action == Actions.SYNC) { + _sync(param); + } else if (action == Actions.MINT) { + _mint(param); + } else if (action == Actions.CLEAR) { + _clear(param); + } else if (action == Actions.ASSERT_BALANCE_EQUALS) { + _assertBalanceEquals(param); + } else if (action == Actions.ASSERT_RESERVES_EQUALS) { + _assertReservesEquals(param); + } else if (action == Actions.ASSERT_DELTA_EQUALS) { + _assertDeltaEquals(param); + } else if (action == Actions.ASSERT_NONZERO_DELTA_COUNT_EQUALS) { + _assertNonzeroDeltaCountEquals(param); + } else if (action == Actions.TRANSFER_FROM) { + _transferFrom(param); + } else if (action == Actions.COLLECT_PROTOCOL_FEES) { + _collectProtocolFees(param); + } + } + return ""; + } + + function executeActions(Actions[] memory actions, bytes[] memory params) external payable { + manager.unlock(abi.encode(actions, params)); + } + + function _settle() internal { + manager.settle(); + } + + function _settleNative(bytes memory params) internal { + uint256 amount = abi.decode(params, (uint256)); + manager.settle{value: amount}(); + } + + function _settleFor(bytes memory params) internal { + address recipient = abi.decode(params, (address)); + manager.settleFor(recipient); + } + + function _take(bytes memory params) internal { + (Currency currency, address recipient, int128 amount) = abi.decode(params, (Currency, address, int128)); + manager.take(currency, recipient, uint128(amount)); + } + + function _prankTakeFrom(bytes memory params) internal { + (Currency currency, address from, address recipient, uint256 amount) = + abi.decode(params, (Currency, address, address, uint256)); + vm.prank(from); + manager.take(currency, recipient, amount); + } + + function _sync(bytes memory params) internal { + Currency currency = Currency.wrap(abi.decode(params, (address))); + manager.sync(currency); + } + + function _mint(bytes memory params) internal { + (address recipient, Currency currency, uint256 amount) = abi.decode(params, (address, Currency, uint256)); + manager.mint(recipient, currency.toId(), amount); + } + + function _clear(bytes memory params) internal { + (Currency currency, uint256 amount, bool measureGas, string memory gasSnapName) = + abi.decode(params, (Currency, uint256, bool, string)); + + manager.clear(currency, amount); + if (measureGas) vm.snapshotGasLastCall(gasSnapName); + } + + function _assertBalanceEquals(bytes memory params) internal view { + (Currency currency, address user, uint256 expectedBalance) = abi.decode(params, (Currency, address, uint256)); + assertEq(currency.balanceOf(user), expectedBalance, "usertoken value incorrect"); + } + + function _assertReservesEquals(bytes memory params) internal view { + uint256 expectedReserves = abi.decode(params, (uint256)); + assertEq(manager.getSyncedReserves(), expectedReserves, "reserves value incorrect"); + } + + function _assertDeltaEquals(bytes memory params) internal view { + (Currency currency, address caller, int256 expectedDelta) = abi.decode(params, (Currency, address, int256)); + + assertEq(manager.currencyDelta(caller, currency), expectedDelta, "delta value incorrect"); + } + + function _assertNonzeroDeltaCountEquals(bytes memory params) internal view { + (uint256 expectedCount) = abi.decode(params, (uint256)); + assertEq(manager.getNonzeroDeltaCount(), expectedCount, "nonzero delta count incorrect"); + } + + function _transferFrom(bytes memory params) internal { + (Currency currency, address from, address recipient, uint256 amount) = + abi.decode(params, (Currency, address, address, uint256)); + MockERC20(Currency.unwrap(currency)).transferFrom(from, recipient, uint256(amount)); + } + + function _collectProtocolFees(bytes memory params) internal { + (address to, Currency currency, uint256 amount) = abi.decode(params, (address, Currency, uint256)); + manager.collectProtocolFees(to, currency, amount); + } +} diff --git a/CVLByExample/UniswapHooks/lib/uniswap-hooks/lib/v4-core/src/test/BaseTestHooks.sol b/CVLByExample/UniswapHooks/lib/uniswap-hooks/lib/v4-core/src/test/BaseTestHooks.sol new file mode 100644 index 00000000..a902cb3c --- /dev/null +++ b/CVLByExample/UniswapHooks/lib/uniswap-hooks/lib/v4-core/src/test/BaseTestHooks.sol @@ -0,0 +1,109 @@ +// SPDX-License-Identifier: UNLICENSED +pragma solidity ^0.8.24; + +import {IHooks} from "../interfaces/IHooks.sol"; +import {PoolKey} from "../types/PoolKey.sol"; +import {ModifyLiquidityParams, SwapParams} from "../types/PoolOperation.sol"; +import {BalanceDelta} from "../types/BalanceDelta.sol"; +import {BeforeSwapDelta} from "../types/BeforeSwapDelta.sol"; +import {IPoolManager} from "../interfaces/IPoolManager.sol"; + +contract BaseTestHooks is IHooks { + error HookNotImplemented(); + + function beforeInitialize(address, /* sender **/ PoolKey calldata, /* key **/ uint160 /* sqrtPriceX96 **/ ) + external + virtual + returns (bytes4) + { + revert HookNotImplemented(); + } + + function afterInitialize( + address, /* sender **/ + PoolKey calldata, /* key **/ + uint160, /* sqrtPriceX96 **/ + int24 /* tick **/ + ) external virtual returns (bytes4) { + revert HookNotImplemented(); + } + + function beforeAddLiquidity( + address, /* sender **/ + PoolKey calldata, /* key **/ + ModifyLiquidityParams calldata, /* params **/ + bytes calldata /* hookData **/ + ) external virtual returns (bytes4) { + revert HookNotImplemented(); + } + + function afterAddLiquidity( + address, /* sender **/ + PoolKey calldata, /* key **/ + ModifyLiquidityParams calldata, /* params **/ + BalanceDelta, /* delta **/ + BalanceDelta, /* feeDelta **/ + bytes calldata /* hookData **/ + ) external virtual returns (bytes4, BalanceDelta) { + revert HookNotImplemented(); + } + + function beforeRemoveLiquidity( + address, /* sender **/ + PoolKey calldata, /* key **/ + ModifyLiquidityParams calldata, /* params **/ + bytes calldata /* hookData **/ + ) external virtual returns (bytes4) { + revert HookNotImplemented(); + } + + function afterRemoveLiquidity( + address, /* sender **/ + PoolKey calldata, /* key **/ + ModifyLiquidityParams calldata, /* params **/ + BalanceDelta, /* delta **/ + BalanceDelta, /* feeDelta **/ + bytes calldata /* hookData **/ + ) external virtual returns (bytes4, BalanceDelta) { + revert HookNotImplemented(); + } + + function beforeSwap( + address, /* sender **/ + PoolKey calldata, /* key **/ + SwapParams calldata, /* params **/ + bytes calldata /* hookData **/ + ) external virtual returns (bytes4, BeforeSwapDelta, uint24) { + revert HookNotImplemented(); + } + + function afterSwap( + address, /* sender **/ + PoolKey calldata, /* key **/ + SwapParams calldata, /* params **/ + BalanceDelta, /* delta **/ + bytes calldata /* hookData **/ + ) external virtual returns (bytes4, int128) { + revert HookNotImplemented(); + } + + function beforeDonate( + address, /* sender **/ + PoolKey calldata, /* key **/ + uint256, /* amount0 **/ + uint256, /* amount1 **/ + bytes calldata /* hookData **/ + ) external virtual returns (bytes4) { + revert HookNotImplemented(); + } + + function afterDonate( + address, /* sender **/ + PoolKey calldata, /* key **/ + uint256, /* amount0 **/ + uint256, /* amount1 **/ + bytes calldata /* hookData **/ + ) external virtual returns (bytes4) { + revert HookNotImplemented(); + } +} diff --git a/CVLByExample/UniswapHooks/lib/uniswap-hooks/lib/v4-core/src/test/CurrencyTest.sol b/CVLByExample/UniswapHooks/lib/uniswap-hooks/lib/v4-core/src/test/CurrencyTest.sol new file mode 100644 index 00000000..d0f73de6 --- /dev/null +++ b/CVLByExample/UniswapHooks/lib/uniswap-hooks/lib/v4-core/src/test/CurrencyTest.sol @@ -0,0 +1,30 @@ +// SPDX-License-Identifier: UNLICENSED +pragma solidity ^0.8.20; + +import {Currency, CurrencyLibrary} from "../types/Currency.sol"; + +contract CurrencyTest { + function transfer(Currency currency, address to, uint256 amount) external { + currency.transfer(to, amount); + } + + function balanceOfSelf(Currency currency) external view returns (uint256) { + return currency.balanceOfSelf(); + } + + function balanceOf(Currency currency, address owner) external view returns (uint256) { + return currency.balanceOf(owner); + } + + function isAddressZero(Currency currency) external pure returns (bool) { + return currency.isAddressZero(); + } + + function toId(Currency currency) external pure returns (uint256) { + return currency.toId(); + } + + function fromId(uint256 id) external pure returns (Currency) { + return CurrencyLibrary.fromId(id); + } +} diff --git a/CVLByExample/UniswapHooks/lib/uniswap-hooks/lib/v4-core/src/test/CustomCurveHook.sol b/CVLByExample/UniswapHooks/lib/uniswap-hooks/lib/v4-core/src/test/CustomCurveHook.sol new file mode 100644 index 00000000..58bd1bc7 --- /dev/null +++ b/CVLByExample/UniswapHooks/lib/uniswap-hooks/lib/v4-core/src/test/CustomCurveHook.sol @@ -0,0 +1,71 @@ +// SPDX-License-Identifier: UNLICENSED +pragma solidity ^0.8.20; + +import {Hooks} from "../libraries/Hooks.sol"; +import {IHooks} from "../interfaces/IHooks.sol"; +import {IPoolManager} from "../interfaces/IPoolManager.sol"; +import {PoolKey} from "../types/PoolKey.sol"; +import {ModifyLiquidityParams, SwapParams} from "../types/PoolOperation.sol"; +import {BeforeSwapDelta, toBeforeSwapDelta} from "../types/BeforeSwapDelta.sol"; +import {BalanceDelta} from "../types/BalanceDelta.sol"; +import {Currency} from "../types/Currency.sol"; +import {CurrencySettler} from "../../test/utils/CurrencySettler.sol"; +import {BaseTestHooks} from "./BaseTestHooks.sol"; +import {Currency} from "../types/Currency.sol"; + +contract CustomCurveHook is BaseTestHooks { + using Hooks for IHooks; + using CurrencySettler for Currency; + + error AddLiquidityDirectToHook(); + + IPoolManager immutable manager; + + constructor(IPoolManager _manager) { + manager = _manager; + } + + modifier onlyPoolManager() { + require(msg.sender == address(manager)); + _; + } + + function beforeSwap( + address, /* sender **/ + PoolKey calldata key, + SwapParams calldata params, + bytes calldata /* hookData **/ + ) external override onlyPoolManager returns (bytes4, BeforeSwapDelta, uint24) { + (Currency inputCurrency, Currency outputCurrency, uint256 amount) = _getInputOutputAndAmount(key, params); + + // this "custom curve" is a line, 1-1 + // take the full input amount, and give the full output amount + manager.take(inputCurrency, address(this), amount); + outputCurrency.settle(manager, address(this), amount, false); + + // return -amountSpecified as specified to no-op the concentrated liquidity swap + BeforeSwapDelta hookDelta = toBeforeSwapDelta(int128(-params.amountSpecified), int128(params.amountSpecified)); + return (IHooks.beforeSwap.selector, hookDelta, 0); + } + + function afterAddLiquidity( + address, /* sender **/ + PoolKey calldata, /* key **/ + ModifyLiquidityParams calldata, /* params **/ + BalanceDelta, /* delta **/ + BalanceDelta, /* feeDelta **/ + bytes calldata /* hookData **/ + ) external view override onlyPoolManager returns (bytes4, BalanceDelta) { + revert AddLiquidityDirectToHook(); + } + + function _getInputOutputAndAmount(PoolKey calldata key, SwapParams calldata params) + internal + pure + returns (Currency input, Currency output, uint256 amount) + { + (input, output) = params.zeroForOne ? (key.currency0, key.currency1) : (key.currency1, key.currency0); + + amount = params.amountSpecified < 0 ? uint256(-params.amountSpecified) : uint256(params.amountSpecified); + } +} diff --git a/CVLByExample/UniswapHooks/lib/uniswap-hooks/lib/v4-core/src/test/DeltaReturningHook.sol b/CVLByExample/UniswapHooks/lib/uniswap-hooks/lib/v4-core/src/test/DeltaReturningHook.sol new file mode 100644 index 00000000..ac9202f4 --- /dev/null +++ b/CVLByExample/UniswapHooks/lib/uniswap-hooks/lib/v4-core/src/test/DeltaReturningHook.sol @@ -0,0 +1,100 @@ +// SPDX-License-Identifier: UNLICENSED +pragma solidity ^0.8.20; + +import {Hooks} from "../libraries/Hooks.sol"; +import {IHooks} from "../interfaces/IHooks.sol"; +import {IPoolManager} from "../interfaces/IPoolManager.sol"; +import {CurrencySettler} from "../../test/utils/CurrencySettler.sol"; +import {PoolKey} from "../types/PoolKey.sol"; +import {SwapParams} from "../types/PoolOperation.sol"; +import {BalanceDelta} from "../types/BalanceDelta.sol"; +import {Currency} from "../types/Currency.sol"; +import {BaseTestHooks} from "./BaseTestHooks.sol"; +import {Currency} from "../types/Currency.sol"; +import {BeforeSwapDelta, toBeforeSwapDelta} from "../types/BeforeSwapDelta.sol"; + +contract DeltaReturningHook is BaseTestHooks { + using Hooks for IHooks; + using CurrencySettler for Currency; + + IPoolManager immutable manager; + + int128 deltaSpecified; + int128 deltaUnspecifiedBeforeSwap; + int128 deltaUnspecifiedAfterSwap; + + constructor(IPoolManager _manager) { + manager = _manager; + } + + modifier onlyPoolManager() { + require(msg.sender == address(manager)); + _; + } + + function setDeltaSpecified(int128 delta) external { + deltaSpecified = delta; + } + + function setDeltaUnspecifiedBeforeSwap(int128 delta) external { + deltaUnspecifiedBeforeSwap = delta; + } + + function setDeltaUnspecifiedAfterSwap(int128 delta) external { + deltaUnspecifiedAfterSwap = delta; + } + + function beforeSwap( + address, /* sender **/ + PoolKey calldata key, + SwapParams calldata params, + bytes calldata /* hookData **/ + ) external override onlyPoolManager returns (bytes4, BeforeSwapDelta, uint24) { + (Currency specifiedCurrency, Currency unspecifiedCurrency) = _sortCurrencies(key, params); + + if (deltaSpecified != 0) _settleOrTake(specifiedCurrency, deltaSpecified); + if (deltaUnspecifiedBeforeSwap != 0) _settleOrTake(unspecifiedCurrency, deltaUnspecifiedBeforeSwap); + + BeforeSwapDelta beforeSwapDelta = toBeforeSwapDelta(deltaSpecified, deltaUnspecifiedBeforeSwap); + + return (IHooks.beforeSwap.selector, beforeSwapDelta, 0); + } + + function afterSwap( + address, /* sender **/ + PoolKey calldata key, + SwapParams calldata params, + BalanceDelta, /* delta **/ + bytes calldata /* hookData **/ + ) external override onlyPoolManager returns (bytes4, int128) { + (, Currency unspecifiedCurrency) = _sortCurrencies(key, params); + _settleOrTake(unspecifiedCurrency, deltaUnspecifiedAfterSwap); + + return (IHooks.afterSwap.selector, deltaUnspecifiedAfterSwap); + } + + function _sortCurrencies(PoolKey calldata key, SwapParams calldata params) + internal + pure + returns (Currency specified, Currency unspecified) + { + (specified, unspecified) = (params.zeroForOne == (params.amountSpecified < 0)) + ? (key.currency0, key.currency1) + : (key.currency1, key.currency0); + } + + function _settleOrTake(Currency currency, int128 delta) internal { + // positive amount means positive delta for the hook, so it can take + // negative it should settle + if (delta > 0) { + currency.take(manager, address(this), uint128(delta), false); + } else { + uint256 amount = uint256(-int256(delta)); + if (currency.isAddressZero()) { + manager.settle{value: amount}(); + } else { + currency.settle(manager, address(this), amount, false); + } + } + } +} diff --git a/CVLByExample/UniswapHooks/lib/uniswap-hooks/lib/v4-core/src/test/DynamicFeesTestHook.sol b/CVLByExample/UniswapHooks/lib/uniswap-hooks/lib/v4-core/src/test/DynamicFeesTestHook.sol new file mode 100644 index 00000000..27354572 --- /dev/null +++ b/CVLByExample/UniswapHooks/lib/uniswap-hooks/lib/v4-core/src/test/DynamicFeesTestHook.sol @@ -0,0 +1,40 @@ +// SPDX-License-Identifier: UNLICENSED +pragma solidity ^0.8.24; + +import {BaseTestHooks} from "./BaseTestHooks.sol"; +import {PoolKey} from "../types/PoolKey.sol"; +import {SwapParams} from "../types/PoolOperation.sol"; +import {IPoolManager} from "../interfaces/IPoolManager.sol"; +import {IHooks} from "../interfaces/IHooks.sol"; +import {BeforeSwapDelta, BeforeSwapDeltaLibrary} from "../types/BeforeSwapDelta.sol"; + +contract DynamicFeesTestHook is BaseTestHooks { + uint24 internal fee; + IPoolManager manager; + + function setManager(IPoolManager _manager) external { + manager = _manager; + } + + function setFee(uint24 _fee) external { + fee = _fee; + } + + function afterInitialize(address, PoolKey calldata key, uint160, int24) external override returns (bytes4) { + manager.updateDynamicLPFee(key, fee); + return IHooks.afterInitialize.selector; + } + + function beforeSwap(address, PoolKey calldata key, SwapParams calldata, bytes calldata) + external + override + returns (bytes4, BeforeSwapDelta, uint24) + { + manager.updateDynamicLPFee(key, fee); + return (IHooks.beforeSwap.selector, BeforeSwapDeltaLibrary.ZERO_DELTA, 0); + } + + function forcePoolFeeUpdate(PoolKey calldata _key, uint24 _fee) external { + manager.updateDynamicLPFee(_key, _fee); + } +} diff --git a/CVLByExample/UniswapHooks/lib/uniswap-hooks/lib/v4-core/src/test/DynamicReturnFeeTestHook.sol b/CVLByExample/UniswapHooks/lib/uniswap-hooks/lib/v4-core/src/test/DynamicReturnFeeTestHook.sol new file mode 100644 index 00000000..4ac37954 --- /dev/null +++ b/CVLByExample/UniswapHooks/lib/uniswap-hooks/lib/v4-core/src/test/DynamicReturnFeeTestHook.sol @@ -0,0 +1,39 @@ +// SPDX-License-Identifier: UNLICENSED +pragma solidity ^0.8.24; + +import {BaseTestHooks} from "./BaseTestHooks.sol"; +import {PoolKey} from "../types/PoolKey.sol"; +import {SwapParams} from "../types/PoolOperation.sol"; +import {IPoolManager} from "../interfaces/IPoolManager.sol"; +import {IHooks} from "../interfaces/IHooks.sol"; +import {BeforeSwapDelta, BeforeSwapDeltaLibrary} from "../types/BeforeSwapDelta.sol"; +import {LPFeeLibrary} from "../libraries/LPFeeLibrary.sol"; + +contract DynamicReturnFeeTestHook is BaseTestHooks { + using LPFeeLibrary for uint24; + + uint24 internal fee; + IPoolManager manager; + + function setManager(IPoolManager _manager) external { + manager = _manager; + } + + function setFee(uint24 _fee) external { + fee = _fee; + } + + function beforeSwap(address, PoolKey calldata, SwapParams calldata, bytes calldata) + external + view + override + returns (bytes4, BeforeSwapDelta, uint24) + { + // attach the fee flag to `fee` to enable overriding the pool's stored fee + return (IHooks.beforeSwap.selector, BeforeSwapDeltaLibrary.ZERO_DELTA, fee | LPFeeLibrary.OVERRIDE_FEE_FLAG); + } + + function forcePoolFeeUpdate(PoolKey calldata _key, uint24 _fee) external { + manager.updateDynamicLPFee(_key, _fee); + } +} diff --git a/CVLByExample/UniswapHooks/lib/uniswap-hooks/lib/v4-core/src/test/EmptyRevertContract.sol b/CVLByExample/UniswapHooks/lib/uniswap-hooks/lib/v4-core/src/test/EmptyRevertContract.sol new file mode 100644 index 00000000..1acf833c --- /dev/null +++ b/CVLByExample/UniswapHooks/lib/uniswap-hooks/lib/v4-core/src/test/EmptyRevertContract.sol @@ -0,0 +1,9 @@ +// SPDX-License-Identifier: UNLICENSED +pragma solidity ^0.8.24; + +contract EmptyRevertContract { + // a contract to simulate reverting with no returndata, to test that our error catching works + fallback() external { + revert(); + } +} diff --git a/CVLByExample/UniswapHooks/lib/uniswap-hooks/lib/v4-core/src/test/EmptyTestHooks.sol b/CVLByExample/UniswapHooks/lib/uniswap-hooks/lib/v4-core/src/test/EmptyTestHooks.sol new file mode 100644 index 00000000..67161697 --- /dev/null +++ b/CVLByExample/UniswapHooks/lib/uniswap-hooks/lib/v4-core/src/test/EmptyTestHooks.sol @@ -0,0 +1,119 @@ +// SPDX-License-Identifier: UNLICENSED +pragma solidity ^0.8.24; + +import {Hooks} from "../libraries/Hooks.sol"; +import {IHooks} from "../interfaces/IHooks.sol"; +import {IPoolManager} from "../interfaces/IPoolManager.sol"; +import {ModifyLiquidityParams, SwapParams} from "../types/PoolOperation.sol"; +import {PoolKey} from "../types/PoolKey.sol"; +import {BalanceDelta, BalanceDeltaLibrary} from "../types/BalanceDelta.sol"; +import {BeforeSwapDelta, BeforeSwapDeltaLibrary} from "../types/BeforeSwapDelta.sol"; + +contract EmptyTestHooks is IHooks { + using Hooks for IHooks; + + constructor() { + IHooks(this).validateHookPermissions( + Hooks.Permissions({ + beforeInitialize: true, + afterInitialize: true, + beforeAddLiquidity: true, + afterAddLiquidity: true, + beforeRemoveLiquidity: true, + afterRemoveLiquidity: true, + beforeSwap: true, + afterSwap: true, + beforeDonate: true, + afterDonate: true, + beforeSwapReturnDelta: true, + afterSwapReturnDelta: true, + afterAddLiquidityReturnDelta: true, + afterRemoveLiquidityReturnDelta: true + }) + ); + } + + function beforeInitialize(address, PoolKey calldata, uint160) external pure override returns (bytes4) { + return IHooks.beforeInitialize.selector; + } + + function afterInitialize(address, PoolKey calldata, uint160, int24) external pure override returns (bytes4) { + return IHooks.afterInitialize.selector; + } + + function beforeAddLiquidity(address, PoolKey calldata, ModifyLiquidityParams calldata, bytes calldata) + external + pure + override + returns (bytes4) + { + return IHooks.beforeAddLiquidity.selector; + } + + function afterAddLiquidity( + address, + PoolKey calldata, + ModifyLiquidityParams calldata, + BalanceDelta, + BalanceDelta, + bytes calldata + ) external pure override returns (bytes4, BalanceDelta) { + return (IHooks.afterAddLiquidity.selector, BalanceDeltaLibrary.ZERO_DELTA); + } + + function beforeRemoveLiquidity(address, PoolKey calldata, ModifyLiquidityParams calldata, bytes calldata) + external + pure + override + returns (bytes4) + { + return IHooks.beforeRemoveLiquidity.selector; + } + + function afterRemoveLiquidity( + address, + PoolKey calldata, + ModifyLiquidityParams calldata, + BalanceDelta, + BalanceDelta, + bytes calldata + ) external pure override returns (bytes4, BalanceDelta) { + return (IHooks.afterRemoveLiquidity.selector, BalanceDeltaLibrary.ZERO_DELTA); + } + + function beforeSwap(address, PoolKey calldata, SwapParams calldata, bytes calldata) + external + pure + override + returns (bytes4, BeforeSwapDelta, uint24) + { + return (IHooks.beforeSwap.selector, BeforeSwapDeltaLibrary.ZERO_DELTA, 0); + } + + function afterSwap(address, PoolKey calldata, SwapParams calldata, BalanceDelta, bytes calldata) + external + pure + override + returns (bytes4, int128) + { + return (IHooks.afterSwap.selector, 0); + } + + function beforeDonate(address, PoolKey calldata, uint256, uint256, bytes calldata) + external + pure + override + returns (bytes4) + { + return IHooks.beforeDonate.selector; + } + + function afterDonate(address, PoolKey calldata, uint256, uint256, bytes calldata) + external + pure + override + returns (bytes4) + { + return IHooks.afterDonate.selector; + } +} diff --git a/CVLByExample/UniswapHooks/lib/uniswap-hooks/lib/v4-core/src/test/FeeTakingHook.sol b/CVLByExample/UniswapHooks/lib/uniswap-hooks/lib/v4-core/src/test/FeeTakingHook.sol new file mode 100644 index 00000000..b88f4d79 --- /dev/null +++ b/CVLByExample/UniswapHooks/lib/uniswap-hooks/lib/v4-core/src/test/FeeTakingHook.sol @@ -0,0 +1,91 @@ +// SPDX-License-Identifier: UNLICENSED +pragma solidity ^0.8.20; + +import {Hooks} from "../libraries/Hooks.sol"; +import {SafeCast} from "../libraries/SafeCast.sol"; +import {IHooks} from "../interfaces/IHooks.sol"; +import {IPoolManager} from "../interfaces/IPoolManager.sol"; +import {ModifyLiquidityParams, SwapParams} from "../types/PoolOperation.sol"; +import {PoolKey} from "../types/PoolKey.sol"; +import {BalanceDelta, toBalanceDelta} from "../types/BalanceDelta.sol"; +import {Currency} from "../types/Currency.sol"; +import {BaseTestHooks} from "./BaseTestHooks.sol"; + +contract FeeTakingHook is BaseTestHooks { + using Hooks for IHooks; + using SafeCast for uint256; + using SafeCast for int128; + + IPoolManager immutable manager; + + constructor(IPoolManager _manager) { + manager = _manager; + } + + modifier onlyPoolManager() { + require(msg.sender == address(manager)); + _; + } + + uint128 public constant LIQUIDITY_FEE = 543; // 543/10000 = 5.43% + uint128 public constant SWAP_FEE_BIPS = 123; // 123/10000 = 1.23% + uint128 public constant TOTAL_BIPS = 10000; + + function afterSwap( + address, /* sender **/ + PoolKey calldata key, + SwapParams calldata params, + BalanceDelta delta, + bytes calldata /* hookData **/ + ) external override onlyPoolManager returns (bytes4, int128) { + // fee will be in the unspecified token of the swap + bool specifiedTokenIs0 = (params.amountSpecified < 0 == params.zeroForOne); + (Currency feeCurrency, int128 swapAmount) = + (specifiedTokenIs0) ? (key.currency1, delta.amount1()) : (key.currency0, delta.amount0()); + // if fee is on output, get the absolute output amount + if (swapAmount < 0) swapAmount = -swapAmount; + + uint256 feeAmount = uint128(swapAmount) * SWAP_FEE_BIPS / TOTAL_BIPS; + manager.take(feeCurrency, address(this), feeAmount); + + return (IHooks.afterSwap.selector, feeAmount.toInt128()); + } + + function afterRemoveLiquidity( + address, /* sender **/ + PoolKey calldata key, + ModifyLiquidityParams calldata, /* params **/ + BalanceDelta delta, + BalanceDelta, + bytes calldata /* hookData **/ + ) external override onlyPoolManager returns (bytes4, BalanceDelta) { + assert(delta.amount0() >= 0 && delta.amount1() >= 0); + + uint128 feeAmount0 = uint128(delta.amount0()) * LIQUIDITY_FEE / TOTAL_BIPS; + uint128 feeAmount1 = uint128(delta.amount1()) * LIQUIDITY_FEE / TOTAL_BIPS; + + manager.take(key.currency0, address(this), feeAmount0); + manager.take(key.currency1, address(this), feeAmount1); + + return (IHooks.afterRemoveLiquidity.selector, toBalanceDelta(int128(feeAmount0), int128(feeAmount1))); + } + + function afterAddLiquidity( + address, /* sender **/ + PoolKey calldata key, + ModifyLiquidityParams calldata, /* params **/ + BalanceDelta delta, + BalanceDelta, + bytes calldata /* hookData **/ + ) external override onlyPoolManager returns (bytes4, BalanceDelta) { + assert(delta.amount0() <= 0 && delta.amount1() <= 0); + + uint128 feeAmount0 = uint128(-delta.amount0()) * LIQUIDITY_FEE / TOTAL_BIPS; + uint128 feeAmount1 = uint128(-delta.amount1()) * LIQUIDITY_FEE / TOTAL_BIPS; + + manager.take(key.currency0, address(this), feeAmount0); + manager.take(key.currency1, address(this), feeAmount1); + + return (IHooks.afterAddLiquidity.selector, toBalanceDelta(int128(feeAmount0), int128(feeAmount1))); + } +} diff --git a/CVLByExample/UniswapHooks/lib/uniswap-hooks/lib/v4-core/src/test/Fuzzers.sol b/CVLByExample/UniswapHooks/lib/uniswap-hooks/lib/v4-core/src/test/Fuzzers.sol new file mode 100644 index 00000000..5050cc36 --- /dev/null +++ b/CVLByExample/UniswapHooks/lib/uniswap-hooks/lib/v4-core/src/test/Fuzzers.sol @@ -0,0 +1,184 @@ +// SPDX-License-Identifier: MIT +pragma solidity ^0.8.24; + +import {Vm} from "forge-std/Vm.sol"; +import {StdUtils} from "forge-std/StdUtils.sol"; + +import {IPoolManager} from "../interfaces/IPoolManager.sol"; +import {ModifyLiquidityParams} from "../types/PoolOperation.sol"; +import {PoolKey} from "../types/PoolKey.sol"; +import {BalanceDelta} from "../types/BalanceDelta.sol"; +import {TickMath} from "../libraries/TickMath.sol"; +import {Pool} from "../libraries/Pool.sol"; +import {PoolModifyLiquidityTest} from "./PoolModifyLiquidityTest.sol"; +import {LiquidityAmounts} from "../../test/utils/LiquidityAmounts.sol"; +import {SafeCast} from "../../src/libraries/SafeCast.sol"; + +contract Fuzzers is StdUtils { + using SafeCast for uint256; + + Vm internal constant _vm = Vm(address(uint160(uint256(keccak256("hevm cheat code"))))); + + function boundLiquidityDelta(PoolKey memory key, int256 liquidityDeltaUnbounded, int256 liquidityMaxByAmount) + internal + pure + returns (int256) + { + int256 liquidityMaxPerTick = int256(uint256(Pool.tickSpacingToMaxLiquidityPerTick(key.tickSpacing))); + + // Finally bound the seeded liquidity by either the max per tick, or by the amount allowed in the position range. + int256 liquidityMax = liquidityMaxByAmount > liquidityMaxPerTick ? liquidityMaxPerTick : liquidityMaxByAmount; + _vm.assume(liquidityMax != 0); + return bound(liquidityDeltaUnbounded, 1, liquidityMax); + } + + // Uses tickSpacingToMaxLiquidityPerTick/2 as one of the possible bounds. + // Potentially adjust this value to be more strict for positions that touch the same tick. + function boundLiquidityDeltaTightly( + PoolKey memory key, + int256 liquidityDeltaUnbounded, + int256 liquidityMaxByAmount, + uint256 maxPositions + ) internal pure returns (int256) { + // Divide by half to bound liquidity more. TODO: Probably a better way to do this. + int256 liquidityMaxTightBound = + int256(uint256(Pool.tickSpacingToMaxLiquidityPerTick(key.tickSpacing)) / maxPositions); + + // Finally bound the seeded liquidity by either the max per tick, or by the amount allowed in the position range. + int256 liquidityMax = + liquidityMaxByAmount > liquidityMaxTightBound ? liquidityMaxTightBound : liquidityMaxByAmount; + _vm.assume(liquidityMax != 0); + return bound(liquidityDeltaUnbounded, 1, liquidityMax); + } + + function getLiquidityDeltaFromAmounts(int24 tickLower, int24 tickUpper, uint160 sqrtPriceX96) + internal + pure + returns (int256) + { + // First get the maximum amount0 and maximum amount1 that can be deposited at this range. + (uint256 maxAmount0, uint256 maxAmount1) = LiquidityAmounts.getAmountsForLiquidity( + sqrtPriceX96, + TickMath.getSqrtPriceAtTick(tickLower), + TickMath.getSqrtPriceAtTick(tickUpper), + uint128(type(int128).max) + ); + + // Compare the max amounts (defined by the range of the position) to the max amount constrained by the type container. + // The true maximum should be the minimum of the two. + // (ie If the position range allows a deposit of more then int128.max in any token, then here we cap it at int128.max.) + + uint256 amount0 = uint256(type(uint128).max / 2); + uint256 amount1 = uint256(type(uint128).max / 2); + + maxAmount0 = maxAmount0 > amount0 ? amount0 : maxAmount0; + maxAmount1 = maxAmount1 > amount1 ? amount1 : maxAmount1; + + int256 liquidityMaxByAmount = uint256( + LiquidityAmounts.getLiquidityForAmounts( + sqrtPriceX96, + TickMath.getSqrtPriceAtTick(tickLower), + TickMath.getSqrtPriceAtTick(tickUpper), + maxAmount0, + maxAmount1 + ) + ).toInt256(); + + return liquidityMaxByAmount; + } + + function boundTicks(int24 tickLower, int24 tickUpper, int24 tickSpacing) internal pure returns (int24, int24) { + tickLower = int24( + bound( + int256(tickLower), + int256(TickMath.minUsableTick(tickSpacing)), + int256(TickMath.maxUsableTick(tickSpacing)) + ) + ); + tickUpper = int24( + bound( + int256(tickUpper), + int256(TickMath.minUsableTick(tickSpacing)), + int256(TickMath.maxUsableTick(tickSpacing)) + ) + ); + + // round down ticks + tickLower = (tickLower / tickSpacing) * tickSpacing; + tickUpper = (tickUpper / tickSpacing) * tickSpacing; + + (tickLower, tickUpper) = tickLower < tickUpper ? (tickLower, tickUpper) : (tickUpper, tickLower); + + if (tickLower == tickUpper) { + if (tickLower != TickMath.minUsableTick(tickSpacing)) tickLower = tickLower - tickSpacing; + else tickUpper = tickUpper + tickSpacing; + } + + return (tickLower, tickUpper); + } + + function boundTicks(PoolKey memory key, int24 tickLower, int24 tickUpper) internal pure returns (int24, int24) { + return boundTicks(tickLower, tickUpper, key.tickSpacing); + } + + function createRandomSqrtPriceX96(int24 tickSpacing, int256 seed) internal pure returns (uint160) { + int256 min = int256(TickMath.minUsableTick(tickSpacing)); + int256 max = int256(TickMath.maxUsableTick(tickSpacing)); + int256 randomTick = bound(seed, min + 1, max - 1); + return TickMath.getSqrtPriceAtTick(int24(randomTick)); + } + + /// @dev Obtain fuzzed and bounded parameters for creating liquidity + /// @param key The pool key + /// @param params IPoolManager.ModifyLiquidityParams Note that these parameters are unbounded + /// @param sqrtPriceX96 The current sqrt price + function createFuzzyLiquidityParams(PoolKey memory key, ModifyLiquidityParams memory params, uint160 sqrtPriceX96) + internal + pure + returns (ModifyLiquidityParams memory result) + { + (result.tickLower, result.tickUpper) = boundTicks(key, params.tickLower, params.tickUpper); + int256 liquidityDeltaFromAmounts = + getLiquidityDeltaFromAmounts(result.tickLower, result.tickUpper, sqrtPriceX96); + result.liquidityDelta = boundLiquidityDelta(key, params.liquidityDelta, liquidityDeltaFromAmounts); + } + + // Creates liquidity parameters with a stricter bound. Should be used if multiple positions being initialized on the pool, with potential for tick overlap. + function createFuzzyLiquidityParamsWithTightBound( + PoolKey memory key, + ModifyLiquidityParams memory params, + uint160 sqrtPriceX96, + uint256 maxPositions + ) internal pure returns (ModifyLiquidityParams memory result) { + (result.tickLower, result.tickUpper) = boundTicks(key, params.tickLower, params.tickUpper); + int256 liquidityDeltaFromAmounts = + getLiquidityDeltaFromAmounts(result.tickLower, result.tickUpper, sqrtPriceX96); + + result.liquidityDelta = + boundLiquidityDeltaTightly(key, params.liquidityDelta, liquidityDeltaFromAmounts, maxPositions); + } + + function createFuzzyLiquidity( + PoolModifyLiquidityTest modifyLiquidityRouter, + PoolKey memory key, + ModifyLiquidityParams memory params, + uint160 sqrtPriceX96, + bytes memory hookData + ) internal returns (ModifyLiquidityParams memory result, BalanceDelta delta) { + result = createFuzzyLiquidityParams(key, params, sqrtPriceX96); + delta = modifyLiquidityRouter.modifyLiquidity(key, result, hookData); + } + + // There exists possible positions in the pool, so we tighten the boundaries of liquidity. + function createFuzzyLiquidityWithTightBound( + PoolModifyLiquidityTest modifyLiquidityRouter, + PoolKey memory key, + ModifyLiquidityParams memory params, + uint160 sqrtPriceX96, + bytes memory hookData, + uint256 maxPositions + ) internal returns (ModifyLiquidityParams memory result, BalanceDelta delta) { + result = createFuzzyLiquidityParamsWithTightBound(key, params, sqrtPriceX96, maxPositions); + delta = modifyLiquidityRouter.modifyLiquidity(key, result, hookData); + } +} diff --git a/CVLByExample/UniswapHooks/lib/uniswap-hooks/lib/v4-core/src/test/HooksTest.sol b/CVLByExample/UniswapHooks/lib/uniswap-hooks/lib/v4-core/src/test/HooksTest.sol new file mode 100644 index 00000000..1da6edf8 --- /dev/null +++ b/CVLByExample/UniswapHooks/lib/uniswap-hooks/lib/v4-core/src/test/HooksTest.sol @@ -0,0 +1,73 @@ +// SPDX-License-Identifier: UNLICENSED +pragma solidity ^0.8.20; + +import {Hooks} from "../libraries/Hooks.sol"; +import {IHooks} from "../interfaces/IHooks.sol"; + +contract HooksTest { + using Hooks for IHooks; + + function validateHookPermissions(address hookAddress, Hooks.Permissions calldata params) external pure { + IHooks(hookAddress).validateHookPermissions(params); + } + + function isValidHookAddress(address hookAddress, uint24 fee) external pure returns (bool) { + return IHooks(hookAddress).isValidHookAddress(fee); + } + + function shouldCallBeforeInitialize(address hookAddress) external pure returns (bool) { + return IHooks(hookAddress).hasPermission(Hooks.BEFORE_INITIALIZE_FLAG); + } + + function shouldCallAfterInitialize(address hookAddress) external pure returns (bool) { + return IHooks(hookAddress).hasPermission(Hooks.AFTER_INITIALIZE_FLAG); + } + + function shouldCallBeforeSwap(address hookAddress) external pure returns (bool) { + return IHooks(hookAddress).hasPermission(Hooks.BEFORE_SWAP_FLAG); + } + + function shouldCallAfterSwap(address hookAddress) external pure returns (bool) { + return IHooks(hookAddress).hasPermission(Hooks.AFTER_SWAP_FLAG); + } + + function shouldCallBeforeAddLiquidity(address hookAddress) external pure returns (bool) { + return IHooks(hookAddress).hasPermission(Hooks.BEFORE_ADD_LIQUIDITY_FLAG); + } + + function shouldCallAfterAddLiquidity(address hookAddress) external pure returns (bool) { + return IHooks(hookAddress).hasPermission(Hooks.AFTER_ADD_LIQUIDITY_FLAG); + } + + function shouldCallBeforeRemoveLiquidity(address hookAddress) external pure returns (bool) { + return IHooks(hookAddress).hasPermission(Hooks.BEFORE_REMOVE_LIQUIDITY_FLAG); + } + + function shouldCallAfterRemoveLiquidity(address hookAddress) external pure returns (bool) { + return IHooks(hookAddress).hasPermission(Hooks.AFTER_REMOVE_LIQUIDITY_FLAG); + } + + function shouldCallBeforeDonate(address hookAddress) external pure returns (bool) { + return IHooks(hookAddress).hasPermission(Hooks.BEFORE_DONATE_FLAG); + } + + function shouldCallAfterDonate(address hookAddress) external pure returns (bool) { + return IHooks(hookAddress).hasPermission(Hooks.AFTER_DONATE_FLAG); + } + + function getGasCostOfShouldCall(address hookAddress) external view returns (uint256) { + uint256 gasBefore = gasleft(); + IHooks(hookAddress).hasPermission(Hooks.BEFORE_SWAP_FLAG); + return gasBefore - gasleft(); + } + + function getGasCostOfValidateHookAddress(address hookAddress, Hooks.Permissions calldata params) + external + view + returns (uint256) + { + uint256 gasBefore = gasleft(); + IHooks(hookAddress).validateHookPermissions(params); + return gasBefore - gasleft(); + } +} diff --git a/CVLByExample/UniswapHooks/lib/uniswap-hooks/lib/v4-core/src/test/LPFeeTakingHook.sol b/CVLByExample/UniswapHooks/lib/uniswap-hooks/lib/v4-core/src/test/LPFeeTakingHook.sol new file mode 100644 index 00000000..0c09861c --- /dev/null +++ b/CVLByExample/UniswapHooks/lib/uniswap-hooks/lib/v4-core/src/test/LPFeeTakingHook.sol @@ -0,0 +1,65 @@ +// SPDX-License-Identifier: UNLICENSED +pragma solidity ^0.8.20; + +import {Hooks} from "../libraries/Hooks.sol"; +import {SafeCast} from "../libraries/SafeCast.sol"; +import {IHooks} from "../interfaces/IHooks.sol"; +import {IPoolManager} from "../interfaces/IPoolManager.sol"; +import {ModifyLiquidityParams} from "../types/PoolOperation.sol"; +import {PoolKey} from "../types/PoolKey.sol"; +import {BalanceDelta, toBalanceDelta} from "../types/BalanceDelta.sol"; +import {Currency} from "../types/Currency.sol"; +import {BaseTestHooks} from "./BaseTestHooks.sol"; + +/// @notice a hook that takes all of the LP fee revenue +/// @dev an example test hook to validate the data is provided correctly +contract LPFeeTakingHook is BaseTestHooks { + using Hooks for IHooks; + using SafeCast for uint256; + using SafeCast for int128; + + IPoolManager immutable manager; + + constructor(IPoolManager _manager) { + manager = _manager; + } + + modifier onlyPoolManager() { + require(msg.sender == address(manager)); + _; + } + + function afterRemoveLiquidity( + address, /* sender **/ + PoolKey calldata key, + ModifyLiquidityParams calldata, /* params **/ + BalanceDelta, + BalanceDelta feeDelta, + bytes calldata /* hookData **/ + ) external override onlyPoolManager returns (bytes4, BalanceDelta) { + uint128 feeAmount0 = uint128(feeDelta.amount0()); + uint128 feeAmount1 = uint128(feeDelta.amount1()); + + if (0 < feeAmount0) manager.take(key.currency0, address(this), feeAmount0); + if (0 < feeAmount1) manager.take(key.currency1, address(this), feeAmount1); + + return (IHooks.afterRemoveLiquidity.selector, toBalanceDelta(int128(feeAmount0), int128(feeAmount1))); + } + + function afterAddLiquidity( + address, /* sender **/ + PoolKey calldata key, + ModifyLiquidityParams calldata, /* params **/ + BalanceDelta, + BalanceDelta feeDelta, + bytes calldata /* hookData **/ + ) external override onlyPoolManager returns (bytes4, BalanceDelta) { + uint128 feeAmount0 = uint128(feeDelta.amount0()); + uint128 feeAmount1 = uint128(feeDelta.amount1()); + + if (0 < feeAmount0) manager.take(key.currency0, address(this), feeAmount0); + if (0 < feeAmount1) manager.take(key.currency1, address(this), feeAmount1); + + return (IHooks.afterAddLiquidity.selector, toBalanceDelta(int128(feeAmount0), int128(feeAmount1))); + } +} diff --git a/CVLByExample/UniswapHooks/lib/uniswap-hooks/lib/v4-core/src/test/LiquidityMathTest.sol b/CVLByExample/UniswapHooks/lib/uniswap-hooks/lib/v4-core/src/test/LiquidityMathTest.sol new file mode 100644 index 00000000..6893917e --- /dev/null +++ b/CVLByExample/UniswapHooks/lib/uniswap-hooks/lib/v4-core/src/test/LiquidityMathTest.sol @@ -0,0 +1,10 @@ +// SPDX-License-Identifier: UNLICENSED +pragma solidity ^0.8.20; + +import {LiquidityMath} from "../libraries/LiquidityMath.sol"; + +contract LiquidityMathTest { + function addDelta(uint128 x, int128 y) external pure returns (uint128 z) { + return LiquidityMath.addDelta(x, y); + } +} diff --git a/CVLByExample/UniswapHooks/lib/uniswap-hooks/lib/v4-core/src/test/MockContract.sol b/CVLByExample/UniswapHooks/lib/uniswap-hooks/lib/v4-core/src/test/MockContract.sol new file mode 100644 index 00000000..450b4a08 --- /dev/null +++ b/CVLByExample/UniswapHooks/lib/uniswap-hooks/lib/v4-core/src/test/MockContract.sol @@ -0,0 +1,57 @@ +// SPDX-License-Identifier: UNLICENSED +pragma solidity ^0.8.20; + +import {Proxy} from "@openzeppelin/contracts/proxy/Proxy.sol"; + +/// @notice Mock contract that tracks the number of calls to various functions by selector +/// @dev allows for proxying to an implementation contract +/// if real logic or return values are needed +contract MockContract is Proxy { + mapping(bytes32 => uint256) public calls; + mapping(bytes32 => mapping(bytes => uint256)) public callParams; + + /// @notice If set, delegatecall to implementation after tracking call + address internal impl; + + function timesCalledSelector(bytes32 selector) public view returns (uint256) { + return calls[selector]; + } + + function timesCalled(string calldata fnSig) public view returns (uint256) { + bytes32 selector = bytes32(uint256(keccak256(bytes(fnSig))) & (type(uint256).max << 224)); + return calls[selector]; + } + + function calledWithSelector(bytes32 selector, bytes calldata params) public view returns (bool) { + return callParams[selector][params[1:]] > 0; // Drop 0x byte string prefix + } + + function calledWith(string calldata fnSig, bytes calldata params) public view returns (bool) { + bytes32 selector = bytes32(uint256(keccak256(bytes(fnSig))) & (type(uint256).max << 224)); + return callParams[selector][params[1:]] > 0; // Drop 0x byte string prefix + } + + /// @notice exposes implementation contract address + function _implementation() internal view override returns (address) { + return impl; + } + + function setImplementation(address _impl) external { + impl = _impl; + } + + /// @notice Captures calls by selector + function _beforeFallback() internal { + bytes32 selector = bytes32(msg.data[:5]); + bytes memory params = msg.data[5:]; + calls[selector]++; + callParams[selector][params]++; + } + + function _fallback() internal override { + _beforeFallback(); + super._fallback(); + } + + receive() external payable {} +} diff --git a/CVLByExample/UniswapHooks/lib/uniswap-hooks/lib/v4-core/src/test/MockERC6909Claims.sol b/CVLByExample/UniswapHooks/lib/uniswap-hooks/lib/v4-core/src/test/MockERC6909Claims.sol new file mode 100644 index 00000000..17e6499c --- /dev/null +++ b/CVLByExample/UniswapHooks/lib/uniswap-hooks/lib/v4-core/src/test/MockERC6909Claims.sol @@ -0,0 +1,22 @@ +// SPDX-License-Identifier: UNLICENSED +pragma solidity ^0.8.20; + +import {ERC6909Claims} from "../ERC6909Claims.sol"; + +/// @notice Mock contract for testing ERC6909Claims +contract MockERC6909Claims is ERC6909Claims { + /// @notice mocked mint logic + function mint(address to, uint256 id, uint256 amount) public { + _mint(to, id, amount); + } + + /// @notice mocked burn logic + function burn(uint256 id, uint256 amount) public { + _burn(msg.sender, id, amount); + } + + /// @notice mocked burn logic without checking sender allowance + function burnFrom(address from, uint256 id, uint256 amount) public { + _burnFrom(from, id, amount); + } +} diff --git a/CVLByExample/UniswapHooks/lib/uniswap-hooks/lib/v4-core/src/test/MockHooks.sol b/CVLByExample/UniswapHooks/lib/uniswap-hooks/lib/v4-core/src/test/MockHooks.sol new file mode 100644 index 00000000..185a91c0 --- /dev/null +++ b/CVLByExample/UniswapHooks/lib/uniswap-hooks/lib/v4-core/src/test/MockHooks.sol @@ -0,0 +1,140 @@ +// SPDX-License-Identifier: UNLICENSED +pragma solidity ^0.8.24; + +import {Hooks} from "../libraries/Hooks.sol"; +import {IHooks} from "../interfaces/IHooks.sol"; +import {IPoolManager} from "../interfaces/IPoolManager.sol"; +import {ModifyLiquidityParams, SwapParams} from "../types/PoolOperation.sol"; +import {PoolKey} from "../types/PoolKey.sol"; +import {BalanceDelta, BalanceDeltaLibrary} from "../types/BalanceDelta.sol"; +import {PoolId} from "../types/PoolId.sol"; +import {BeforeSwapDelta, BeforeSwapDeltaLibrary} from "../types/BeforeSwapDelta.sol"; + +contract MockHooks is IHooks { + using Hooks for IHooks; + + bytes public beforeInitializeData; + bytes public afterInitializeData; + bytes public beforeAddLiquidityData; + bytes public afterAddLiquidityData; + bytes public beforeRemoveLiquidityData; + bytes public afterRemoveLiquidityData; + bytes public beforeSwapData; + bytes public afterSwapData; + bytes public beforeDonateData; + bytes public afterDonateData; + + mapping(bytes4 => bytes4) public returnValues; + + mapping(PoolId => uint16) public lpFees; + + function beforeInitialize(address, PoolKey calldata, uint160) external override returns (bytes4) { + beforeInitializeData = new bytes(123); + bytes4 selector = MockHooks.beforeInitialize.selector; + return returnValues[selector] == bytes4(0) ? selector : returnValues[selector]; + } + + function afterInitialize(address, PoolKey calldata, uint160, int24) external override returns (bytes4) { + afterInitializeData = new bytes(123); + bytes4 selector = MockHooks.afterInitialize.selector; + return returnValues[selector] == bytes4(0) ? selector : returnValues[selector]; + } + + function beforeAddLiquidity(address, PoolKey calldata, ModifyLiquidityParams calldata, bytes calldata hookData) + external + override + returns (bytes4) + { + beforeAddLiquidityData = hookData; + bytes4 selector = MockHooks.beforeAddLiquidity.selector; + return returnValues[selector] == bytes4(0) ? selector : returnValues[selector]; + } + + function afterAddLiquidity( + address, + PoolKey calldata, + ModifyLiquidityParams calldata, + BalanceDelta, + BalanceDelta, + bytes calldata hookData + ) external override returns (bytes4, BalanceDelta) { + afterAddLiquidityData = hookData; + bytes4 selector = MockHooks.afterAddLiquidity.selector; + return (returnValues[selector] == bytes4(0) ? selector : returnValues[selector], BalanceDeltaLibrary.ZERO_DELTA); + } + + function beforeRemoveLiquidity(address, PoolKey calldata, ModifyLiquidityParams calldata, bytes calldata hookData) + external + override + returns (bytes4) + { + beforeRemoveLiquidityData = hookData; + bytes4 selector = MockHooks.beforeRemoveLiquidity.selector; + return returnValues[selector] == bytes4(0) ? selector : returnValues[selector]; + } + + function afterRemoveLiquidity( + address, + PoolKey calldata, + ModifyLiquidityParams calldata, + BalanceDelta, + BalanceDelta, + bytes calldata hookData + ) external override returns (bytes4, BalanceDelta) { + afterRemoveLiquidityData = hookData; + bytes4 selector = MockHooks.afterRemoveLiquidity.selector; + return (returnValues[selector] == bytes4(0) ? selector : returnValues[selector], BalanceDeltaLibrary.ZERO_DELTA); + } + + function beforeSwap(address, PoolKey calldata, SwapParams calldata, bytes calldata hookData) + external + override + returns (bytes4, BeforeSwapDelta, uint24) + { + beforeSwapData = hookData; + bytes4 selector = MockHooks.beforeSwap.selector; + return ( + returnValues[selector] == bytes4(0) ? selector : returnValues[selector], + BeforeSwapDeltaLibrary.ZERO_DELTA, + 0 + ); + } + + function afterSwap(address, PoolKey calldata, SwapParams calldata, BalanceDelta, bytes calldata hookData) + external + override + returns (bytes4, int128) + { + afterSwapData = hookData; + bytes4 selector = MockHooks.afterSwap.selector; + return (returnValues[selector] == bytes4(0) ? selector : returnValues[selector], 0); + } + + function beforeDonate(address, PoolKey calldata, uint256, uint256, bytes calldata hookData) + external + override + returns (bytes4) + { + beforeDonateData = hookData; + bytes4 selector = MockHooks.beforeDonate.selector; + return returnValues[selector] == bytes4(0) ? selector : returnValues[selector]; + } + + function afterDonate(address, PoolKey calldata, uint256, uint256, bytes calldata hookData) + external + override + returns (bytes4) + { + afterDonateData = hookData; + bytes4 selector = MockHooks.afterDonate.selector; + return returnValues[selector] == bytes4(0) ? selector : returnValues[selector]; + } + + function setReturnValue(bytes4 key, bytes4 value) external { + returnValues[key] = value; + } + + function setlpFee(PoolKey calldata key, uint16 value) external { + lpFees[key.toId()] = value; + } +} diff --git a/CVLByExample/UniswapHooks/lib/uniswap-hooks/lib/v4-core/src/test/NativeERC20.sol b/CVLByExample/UniswapHooks/lib/uniswap-hooks/lib/v4-core/src/test/NativeERC20.sol new file mode 100644 index 00000000..ce1d6d31 --- /dev/null +++ b/CVLByExample/UniswapHooks/lib/uniswap-hooks/lib/v4-core/src/test/NativeERC20.sol @@ -0,0 +1,50 @@ +// SPDX-License-Identifier: GPL-3.0-or-later +pragma solidity ^0.8.0; + +import {Test} from "forge-std/Test.sol"; + +/// @dev This token contract simulates the ERC20 representation of a native token where on `transfer` and `transferFrom` the native balances are modified using a precompile +contract NativeERC20 is Test { + string public name = "NativeERC20"; + string public symbol = "NERC20"; + uint8 public decimals = 18; + + event Approval(address indexed src, address indexed guy, uint256 wad); + event Transfer(address indexed src, address indexed dst, uint256 wad); + + mapping(address => mapping(address => uint256)) public allowance; + + function totalSupply() public view returns (uint256) { + return address(this).balance; + } + + function approve(address guy, uint256 wad) public returns (bool) { + allowance[msg.sender][guy] = wad; + emit Approval(msg.sender, guy, wad); + return true; + } + + function transfer(address dst, uint256 wad) public returns (bool) { + return transferFrom(msg.sender, dst, wad); + } + + function transferFrom(address src, address dst, uint256 wad) public returns (bool) { + require(src.balance >= wad); + + if (src != msg.sender && allowance[src][msg.sender] != type(uint256).max) { + require(allowance[src][msg.sender] >= wad, ""); + allowance[src][msg.sender] -= wad; + } + + vm.deal(src, src.balance - wad); + vm.deal(dst, dst.balance + wad); + + emit Transfer(src, dst, wad); + + return true; + } + + function balanceOf(address account) external view returns (uint256) { + return account.balance; + } +} diff --git a/CVLByExample/UniswapHooks/lib/uniswap-hooks/lib/v4-core/src/test/NoDelegateCallTest.sol b/CVLByExample/UniswapHooks/lib/uniswap-hooks/lib/v4-core/src/test/NoDelegateCallTest.sol new file mode 100644 index 00000000..05979082 --- /dev/null +++ b/CVLByExample/UniswapHooks/lib/uniswap-hooks/lib/v4-core/src/test/NoDelegateCallTest.sol @@ -0,0 +1,32 @@ +// SPDX-License-Identifier: UNLICENSED +pragma solidity ^0.8.20; + +import {NoDelegateCall} from "../NoDelegateCall.sol"; + +contract NoDelegateCallTest is NoDelegateCall { + function canBeDelegateCalled() public view returns (uint256) { + return block.timestamp / 5; + } + + function cannotBeDelegateCalled() public view noDelegateCall returns (uint256) { + return block.timestamp / 5; + } + + function getGasCostOfCanBeDelegateCalled() external view returns (uint256) { + uint256 gasBefore = gasleft(); + canBeDelegateCalled(); + return gasBefore - gasleft(); + } + + function getGasCostOfCannotBeDelegateCalled() external view returns (uint256) { + uint256 gasBefore = gasleft(); + cannotBeDelegateCalled(); + return gasBefore - gasleft(); + } + + function callsIntoNoDelegateCallFunction() external view { + noDelegateCallPrivate(); + } + + function noDelegateCallPrivate() private view noDelegateCall {} +} diff --git a/CVLByExample/UniswapHooks/lib/uniswap-hooks/lib/v4-core/src/test/PoolClaimsTest.sol b/CVLByExample/UniswapHooks/lib/uniswap-hooks/lib/v4-core/src/test/PoolClaimsTest.sol new file mode 100644 index 00000000..bb1e3f46 --- /dev/null +++ b/CVLByExample/UniswapHooks/lib/uniswap-hooks/lib/v4-core/src/test/PoolClaimsTest.sol @@ -0,0 +1,50 @@ +// SPDX-License-Identifier: UNLICENSED +pragma solidity ^0.8.24; + +import {Currency} from "../types/Currency.sol"; +import {BalanceDelta} from "../types/BalanceDelta.sol"; +import {IPoolManager} from "../interfaces/IPoolManager.sol"; +import {PoolTestBase} from "./PoolTestBase.sol"; +import {SafeCast} from "../libraries/SafeCast.sol"; +import {CurrencySettler} from "../../test/utils/CurrencySettler.sol"; + +contract PoolClaimsTest is PoolTestBase { + using CurrencySettler for Currency; + using SafeCast for uint256; + + constructor(IPoolManager _manager) PoolTestBase(_manager) {} + + struct CallbackData { + address sender; + address user; + Currency currency; + uint256 amount; + bool deposit; + } + + /// @notice Convert ERC20 into a claimable 6909 + function deposit(Currency currency, address user, uint256 amount) external payable { + manager.unlock(abi.encode(CallbackData(msg.sender, user, currency, amount, true))); + } + + /// @notice Redeem claimable 6909 for ERC20 + function withdraw(Currency currency, address user, uint256 amount) external payable { + manager.unlock(abi.encode(CallbackData(msg.sender, user, currency, amount, false))); + } + + function unlockCallback(bytes calldata rawData) external returns (bytes memory) { + require(msg.sender == address(manager)); + + CallbackData memory data = abi.decode(rawData, (CallbackData)); + + if (data.deposit) { + data.currency.take(manager, data.user, data.amount, true); // mint 6909 + data.currency.settle(manager, data.user, data.amount, false); // transfer ERC20 + } else { + data.currency.settle(manager, data.user, data.amount, true); // burn 6909 + data.currency.take(manager, data.user, data.amount, false); // claim ERC20 + } + + return abi.encode(0); + } +} diff --git a/CVLByExample/UniswapHooks/lib/uniswap-hooks/lib/v4-core/src/test/PoolDonateTest.sol b/CVLByExample/UniswapHooks/lib/uniswap-hooks/lib/v4-core/src/test/PoolDonateTest.sol new file mode 100644 index 00000000..79d417e1 --- /dev/null +++ b/CVLByExample/UniswapHooks/lib/uniswap-hooks/lib/v4-core/src/test/PoolDonateTest.sol @@ -0,0 +1,68 @@ +// SPDX-License-Identifier: UNLICENSED +pragma solidity ^0.8.24; + +import {Currency, CurrencyLibrary} from "../types/Currency.sol"; +import {IPoolManager} from "../interfaces/IPoolManager.sol"; +import {PoolKey} from "../types/PoolKey.sol"; +import {BalanceDelta} from "../types/BalanceDelta.sol"; +import {PoolTestBase} from "./PoolTestBase.sol"; +import {IHooks} from "../interfaces/IHooks.sol"; +import {Hooks} from "../libraries/Hooks.sol"; +import {CurrencySettler} from "../../test/utils/CurrencySettler.sol"; + +contract PoolDonateTest is PoolTestBase { + using CurrencySettler for Currency; + using Hooks for IHooks; + + constructor(IPoolManager _manager) PoolTestBase(_manager) {} + + struct CallbackData { + address sender; + PoolKey key; + uint256 amount0; + uint256 amount1; + bytes hookData; + } + + function donate(PoolKey memory key, uint256 amount0, uint256 amount1, bytes memory hookData) + external + payable + returns (BalanceDelta delta) + { + delta = abi.decode( + manager.unlock(abi.encode(CallbackData(msg.sender, key, amount0, amount1, hookData))), (BalanceDelta) + ); + + uint256 ethBalance = address(this).balance; + if (ethBalance > 0) { + CurrencyLibrary.ADDRESS_ZERO.transfer(msg.sender, ethBalance); + } + } + + function unlockCallback(bytes calldata rawData) external returns (bytes memory) { + require(msg.sender == address(manager)); + + CallbackData memory data = abi.decode(rawData, (CallbackData)); + + (,, int256 deltaBefore0) = _fetchBalances(data.key.currency0, data.sender, address(this)); + (,, int256 deltaBefore1) = _fetchBalances(data.key.currency1, data.sender, address(this)); + + require(deltaBefore0 == 0, "deltaBefore0 is not 0"); + require(deltaBefore1 == 0, "deltaBefore1 is not 0"); + + BalanceDelta delta = manager.donate(data.key, data.amount0, data.amount1, data.hookData); + + (,, int256 deltaAfter0) = _fetchBalances(data.key.currency0, data.sender, address(this)); + (,, int256 deltaAfter1) = _fetchBalances(data.key.currency1, data.sender, address(this)); + + require(deltaAfter0 == -int256(data.amount0), "deltaAfter0 is not equal to -int256(data.amount0)"); + require(deltaAfter1 == -int256(data.amount1), "deltaAfter1 is not equal to -int256(data.amount1)"); + + if (deltaAfter0 < 0) data.key.currency0.settle(manager, data.sender, uint256(-deltaAfter0), false); + if (deltaAfter1 < 0) data.key.currency1.settle(manager, data.sender, uint256(-deltaAfter1), false); + if (deltaAfter0 > 0) data.key.currency0.take(manager, data.sender, uint256(deltaAfter0), false); + if (deltaAfter1 > 0) data.key.currency1.take(manager, data.sender, uint256(deltaAfter1), false); + + return abi.encode(delta); + } +} diff --git a/CVLByExample/UniswapHooks/lib/uniswap-hooks/lib/v4-core/src/test/PoolEmptyUnlockTest.sol b/CVLByExample/UniswapHooks/lib/uniswap-hooks/lib/v4-core/src/test/PoolEmptyUnlockTest.sol new file mode 100644 index 00000000..d0eca3b5 --- /dev/null +++ b/CVLByExample/UniswapHooks/lib/uniswap-hooks/lib/v4-core/src/test/PoolEmptyUnlockTest.sol @@ -0,0 +1,25 @@ +// SPDX-License-Identifier: UNLICENSED +pragma solidity ^0.8.24; + +import {IPoolManager} from "../interfaces/IPoolManager.sol"; +import {IUnlockCallback} from "../interfaces/callback/IUnlockCallback.sol"; + +contract PoolEmptyUnlockTest is IUnlockCallback { + event UnlockCallback(); + + IPoolManager manager; + + constructor(IPoolManager _manager) { + manager = _manager; + } + + function unlock() external { + manager.unlock(""); + } + + /// @notice Called by the pool manager on `msg.sender` when the manager is unlocked + function unlockCallback(bytes calldata) external override returns (bytes memory) { + emit UnlockCallback(); + return ""; + } +} diff --git a/CVLByExample/UniswapHooks/lib/uniswap-hooks/lib/v4-core/src/test/PoolModifyLiquidityTest.sol b/CVLByExample/UniswapHooks/lib/uniswap-hooks/lib/v4-core/src/test/PoolModifyLiquidityTest.sol new file mode 100644 index 00000000..5f9b46e9 --- /dev/null +++ b/CVLByExample/UniswapHooks/lib/uniswap-hooks/lib/v4-core/src/test/PoolModifyLiquidityTest.sol @@ -0,0 +1,96 @@ +// SPDX-License-Identifier: UNLICENSED +pragma solidity ^0.8.24; + +import {CurrencyLibrary, Currency} from "../types/Currency.sol"; +import {IPoolManager} from "../interfaces/IPoolManager.sol"; +import {BalanceDelta} from "../types/BalanceDelta.sol"; +import {PoolKey} from "../types/PoolKey.sol"; +import {ModifyLiquidityParams} from "../types/PoolOperation.sol"; +import {PoolTestBase} from "./PoolTestBase.sol"; +import {IHooks} from "../interfaces/IHooks.sol"; +import {Hooks} from "../libraries/Hooks.sol"; +import {LPFeeLibrary} from "../libraries/LPFeeLibrary.sol"; +import {CurrencySettler} from "../../test/utils/CurrencySettler.sol"; +import {StateLibrary} from "../libraries/StateLibrary.sol"; + +contract PoolModifyLiquidityTest is PoolTestBase { + using CurrencySettler for Currency; + using Hooks for IHooks; + using LPFeeLibrary for uint24; + using StateLibrary for IPoolManager; + + constructor(IPoolManager _manager) PoolTestBase(_manager) {} + + struct CallbackData { + address sender; + PoolKey key; + ModifyLiquidityParams params; + bytes hookData; + bool settleUsingBurn; + bool takeClaims; + } + + function modifyLiquidity(PoolKey memory key, ModifyLiquidityParams memory params, bytes memory hookData) + external + payable + returns (BalanceDelta delta) + { + delta = modifyLiquidity(key, params, hookData, false, false); + } + + function modifyLiquidity( + PoolKey memory key, + ModifyLiquidityParams memory params, + bytes memory hookData, + bool settleUsingBurn, + bool takeClaims + ) public payable returns (BalanceDelta delta) { + delta = abi.decode( + manager.unlock(abi.encode(CallbackData(msg.sender, key, params, hookData, settleUsingBurn, takeClaims))), + (BalanceDelta) + ); + + uint256 ethBalance = address(this).balance; + if (ethBalance > 0) { + CurrencyLibrary.ADDRESS_ZERO.transfer(msg.sender, ethBalance); + } + } + + function unlockCallback(bytes calldata rawData) external returns (bytes memory) { + require(msg.sender == address(manager)); + + CallbackData memory data = abi.decode(rawData, (CallbackData)); + + (uint128 liquidityBefore,,) = manager.getPositionInfo( + data.key.toId(), address(this), data.params.tickLower, data.params.tickUpper, data.params.salt + ); + + (BalanceDelta delta,) = manager.modifyLiquidity(data.key, data.params, data.hookData); + + (uint128 liquidityAfter,,) = manager.getPositionInfo( + data.key.toId(), address(this), data.params.tickLower, data.params.tickUpper, data.params.salt + ); + + (,, int256 delta0) = _fetchBalances(data.key.currency0, data.sender, address(this)); + (,, int256 delta1) = _fetchBalances(data.key.currency1, data.sender, address(this)); + + require( + int128(liquidityBefore) + data.params.liquidityDelta == int128(liquidityAfter), "liquidity change incorrect" + ); + + if (data.params.liquidityDelta < 0) { + assert(delta0 > 0 || delta1 > 0); + assert(!(delta0 < 0 || delta1 < 0)); + } else if (data.params.liquidityDelta > 0) { + assert(delta0 < 0 || delta1 < 0); + assert(!(delta0 > 0 || delta1 > 0)); + } + + if (delta0 < 0) data.key.currency0.settle(manager, data.sender, uint256(-delta0), data.settleUsingBurn); + if (delta1 < 0) data.key.currency1.settle(manager, data.sender, uint256(-delta1), data.settleUsingBurn); + if (delta0 > 0) data.key.currency0.take(manager, data.sender, uint256(delta0), data.takeClaims); + if (delta1 > 0) data.key.currency1.take(manager, data.sender, uint256(delta1), data.takeClaims); + + return abi.encode(delta); + } +} diff --git a/CVLByExample/UniswapHooks/lib/uniswap-hooks/lib/v4-core/src/test/PoolModifyLiquidityTestNoChecks.sol b/CVLByExample/UniswapHooks/lib/uniswap-hooks/lib/v4-core/src/test/PoolModifyLiquidityTestNoChecks.sol new file mode 100644 index 00000000..c4c0240e --- /dev/null +++ b/CVLByExample/UniswapHooks/lib/uniswap-hooks/lib/v4-core/src/test/PoolModifyLiquidityTestNoChecks.sol @@ -0,0 +1,74 @@ +// SPDX-License-Identifier: UNLICENSED +pragma solidity ^0.8.24; + +import {CurrencyLibrary, Currency} from "../types/Currency.sol"; +import {IPoolManager} from "../interfaces/IPoolManager.sol"; +import {BalanceDelta} from "../types/BalanceDelta.sol"; +import {ModifyLiquidityParams} from "../types/PoolOperation.sol"; +import {PoolKey} from "../types/PoolKey.sol"; +import {PoolTestBase} from "./PoolTestBase.sol"; +import {IHooks} from "../interfaces/IHooks.sol"; +import {Hooks} from "../libraries/Hooks.sol"; +import {LPFeeLibrary} from "../libraries/LPFeeLibrary.sol"; +import {CurrencySettler} from "../../test/utils/CurrencySettler.sol"; + +contract PoolModifyLiquidityTestNoChecks is PoolTestBase { + using CurrencySettler for Currency; + using Hooks for IHooks; + using LPFeeLibrary for uint24; + + constructor(IPoolManager _manager) PoolTestBase(_manager) {} + + struct CallbackData { + address sender; + PoolKey key; + ModifyLiquidityParams params; + bytes hookData; + bool settleUsingBurn; + bool takeClaims; + } + + function modifyLiquidity(PoolKey memory key, ModifyLiquidityParams memory params, bytes memory hookData) + external + payable + returns (BalanceDelta delta) + { + delta = modifyLiquidity(key, params, hookData, false, false); + } + + function modifyLiquidity( + PoolKey memory key, + ModifyLiquidityParams memory params, + bytes memory hookData, + bool settleUsingBurn, + bool takeClaims + ) public payable returns (BalanceDelta delta) { + delta = abi.decode( + manager.unlock(abi.encode(CallbackData(msg.sender, key, params, hookData, settleUsingBurn, takeClaims))), + (BalanceDelta) + ); + + uint256 ethBalance = address(this).balance; + if (ethBalance > 0) { + CurrencyLibrary.ADDRESS_ZERO.transfer(msg.sender, ethBalance); + } + } + + function unlockCallback(bytes calldata rawData) external returns (bytes memory) { + require(msg.sender == address(manager)); + + CallbackData memory data = abi.decode(rawData, (CallbackData)); + + (BalanceDelta delta,) = manager.modifyLiquidity(data.key, data.params, data.hookData); + + int256 delta0 = delta.amount0(); + int256 delta1 = delta.amount1(); + + if (delta0 < 0) data.key.currency0.settle(manager, data.sender, uint256(-delta0), data.settleUsingBurn); + if (delta1 < 0) data.key.currency1.settle(manager, data.sender, uint256(-delta1), data.settleUsingBurn); + if (delta0 > 0) data.key.currency0.take(manager, data.sender, uint256(delta0), data.takeClaims); + if (delta1 > 0) data.key.currency1.take(manager, data.sender, uint256(delta1), data.takeClaims); + + return abi.encode(delta); + } +} diff --git a/CVLByExample/UniswapHooks/lib/uniswap-hooks/lib/v4-core/src/test/PoolNestedActionsTest.sol b/CVLByExample/UniswapHooks/lib/uniswap-hooks/lib/v4-core/src/test/PoolNestedActionsTest.sol new file mode 100644 index 00000000..ec6634f9 --- /dev/null +++ b/CVLByExample/UniswapHooks/lib/uniswap-hooks/lib/v4-core/src/test/PoolNestedActionsTest.sol @@ -0,0 +1,236 @@ +// SPDX-License-Identifier: UNLICENSED +pragma solidity ^0.8.20; + +import {IPoolManager} from "../interfaces/IPoolManager.sol"; +import {IUnlockCallback} from "../interfaces/callback/IUnlockCallback.sol"; +import {PoolTestBase} from "./PoolTestBase.sol"; +import {ModifyLiquidityParams, SwapParams} from "../types/PoolOperation.sol"; +import {PoolKey} from "../types/PoolKey.sol"; +import {Constants} from "../../test/utils/Constants.sol"; +import {Test} from "forge-std/Test.sol"; +import {BalanceDelta} from "../types/BalanceDelta.sol"; +import {Currency} from "../types/Currency.sol"; +import {PoolId} from "../types/PoolId.sol"; +import {CurrencySettler} from "../../test/utils/CurrencySettler.sol"; +import {StateLibrary} from "../libraries/StateLibrary.sol"; +import {TransientStateLibrary} from "../libraries/TransientStateLibrary.sol"; + +enum Action { + NESTED_SELF_UNLOCK, + NESTED_EXECUTOR_UNLOCK, + SWAP_AND_SETTLE, + DONATE_AND_SETTLE, + ADD_LIQUIDITY_AND_SETTLE, + REMOVE_LIQUIDITY_AND_SETTLE, + INITIALIZE +} + +contract PoolNestedActionsTest is Test, IUnlockCallback { + using StateLibrary for IPoolManager; + using TransientStateLibrary for IPoolManager; + + IPoolManager manager; + NestedActionExecutor public executor; + address user; + + constructor(IPoolManager _manager) { + manager = _manager; + user = msg.sender; + executor = new NestedActionExecutor(manager, user); + } + + function unlock(bytes calldata data) external { + manager.unlock(data); + } + + /// @notice Called by the pool manager on `msg.sender` when the manager is unlocked + function unlockCallback(bytes calldata data) external override returns (bytes memory) { + Action[] memory actions = abi.decode(data, (Action[])); + if (actions.length == 1 && actions[0] == Action.NESTED_SELF_UNLOCK) { + _nestedUnlock(); + } else { + executor.execute(actions); + } + return ""; + } + + function _nestedUnlock() internal { + bool unlocked = manager.isUnlocked(); + assertEq(unlocked, true); + vm.expectRevert(abi.encodeWithSelector(IPoolManager.AlreadyUnlocked.selector)); + manager.unlock(""); + unlocked = manager.isUnlocked(); + assertEq(unlocked, true); + } +} + +contract NestedActionExecutor is Test, PoolTestBase { + using StateLibrary for IPoolManager; + using TransientStateLibrary for IPoolManager; + using CurrencySettler for Currency; + + PoolKey internal key; + address user; + + error KeyNotSet(); + + ModifyLiquidityParams internal ADD_LIQUIDITY_PARAMS = + ModifyLiquidityParams({tickLower: -120, tickUpper: 120, liquidityDelta: 1e18, salt: 0}); + + ModifyLiquidityParams internal REMOVE_LIQUIDITY_PARAMS = + ModifyLiquidityParams({tickLower: -120, tickUpper: 120, liquidityDelta: -1e18, salt: 0}); + + SwapParams internal SWAP_PARAMS = + SwapParams({zeroForOne: true, amountSpecified: -100, sqrtPriceLimitX96: Constants.SQRT_PRICE_1_2}); + + uint256 internal DONATE_AMOUNT0 = 12345e6; + uint256 internal DONATE_AMOUNT1 = 98765e4; + + constructor(IPoolManager _manager, address _user) PoolTestBase(_manager) { + user = _user; + } + + function setKey(PoolKey memory _key) external { + key = _key; + } + + function execute(Action[] memory actions) public { + if (Currency.unwrap(key.currency0) == address(0)) revert KeyNotSet(); + for (uint256 i = 0; i < actions.length; i++) { + Action action = actions[i]; + if (action == Action.NESTED_EXECUTOR_UNLOCK) _nestedUnlock(); + else if (action == Action.SWAP_AND_SETTLE) _swap(msg.sender); + else if (action == Action.ADD_LIQUIDITY_AND_SETTLE) _addLiquidity(msg.sender); + else if (action == Action.REMOVE_LIQUIDITY_AND_SETTLE) _removeLiquidity(msg.sender); + else if (action == Action.DONATE_AND_SETTLE) _donate(msg.sender); + else if (action == Action.INITIALIZE) _initialize(); + } + } + + function _nestedUnlock() internal { + bool unlocked = manager.isUnlocked(); + assertEq(unlocked, true); + vm.expectRevert(abi.encodeWithSelector(IPoolManager.AlreadyUnlocked.selector)); + manager.unlock(""); + unlocked = manager.isUnlocked(); + assertEq(unlocked, true); + } + + function _swap(address caller) internal { + bool unlocked = manager.isUnlocked(); + assertEq(unlocked, true); + (,, int256 deltaCallerBefore0) = _fetchBalances(key.currency0, user, caller); + (,, int256 deltaCallerBefore1) = _fetchBalances(key.currency1, user, caller); + (,, int256 deltaThisBefore0) = _fetchBalances(key.currency0, user, address(this)); + (,, int256 deltaThisBefore1) = _fetchBalances(key.currency1, user, address(this)); + + BalanceDelta delta = manager.swap(key, SWAP_PARAMS, ""); + + (,, int256 deltaCallerAfter0) = _fetchBalances(key.currency0, user, caller); + (,, int256 deltaCallerAfter1) = _fetchBalances(key.currency1, user, caller); + (,, int256 deltaThisAfter0) = _fetchBalances(key.currency0, user, address(this)); + (,, int256 deltaThisAfter1) = _fetchBalances(key.currency1, user, address(this)); + + assertEq(deltaCallerBefore0, deltaCallerAfter0, "Caller delta 0"); + assertEq(deltaCallerBefore1, deltaCallerAfter1, "Caller delta 1"); + assertEq(deltaThisBefore0 + SWAP_PARAMS.amountSpecified, deltaThisAfter0, "Executor delta 0"); + assertEq(deltaThisBefore1 + 98, deltaThisAfter1, "Executor delta 1"); + assertEq(delta.amount0(), deltaThisAfter0, "Swap delta 0"); + assertEq(delta.amount1(), deltaThisAfter1, "Swap delta 1"); + + key.currency0.settle(manager, user, uint256(-deltaThisAfter0), false); + key.currency1.take(manager, user, uint256(deltaThisAfter1), false); + } + + function _addLiquidity(address caller) internal { + bool unlocked = manager.isUnlocked(); + assertEq(unlocked, true); + (,, int256 deltaCallerBefore0) = _fetchBalances(key.currency0, user, caller); + (,, int256 deltaCallerBefore1) = _fetchBalances(key.currency1, user, caller); + (,, int256 deltaThisBefore0) = _fetchBalances(key.currency0, user, address(this)); + (,, int256 deltaThisBefore1) = _fetchBalances(key.currency1, user, address(this)); + + (BalanceDelta delta,) = manager.modifyLiquidity(key, ADD_LIQUIDITY_PARAMS, ""); + + (,, int256 deltaCallerAfter0) = _fetchBalances(key.currency0, user, caller); + (,, int256 deltaCallerAfter1) = _fetchBalances(key.currency1, user, caller); + (,, int256 deltaThisAfter0) = _fetchBalances(key.currency0, user, address(this)); + (,, int256 deltaThisAfter1) = _fetchBalances(key.currency1, user, address(this)); + + assertEq(deltaCallerBefore0, deltaCallerAfter0, "Caller delta 0"); + assertEq(deltaCallerBefore1, deltaCallerAfter1, "Caller delta 1"); + assertEq(deltaThisBefore0 + delta.amount0(), deltaThisAfter0, "Executor delta 0"); + assertEq(deltaThisBefore1 + delta.amount1(), deltaThisAfter1, "Executor delta 1"); + + key.currency0.settle(manager, user, uint256(-deltaThisAfter0), false); + key.currency1.settle(manager, user, uint256(-deltaThisAfter1), false); + } + + // cannot remove non-existent liquidity - need to perform an add before this removal + function _removeLiquidity(address caller) internal { + bool unlocked = manager.isUnlocked(); + assertEq(unlocked, true); + (,, int256 deltaCallerBefore0) = _fetchBalances(key.currency0, user, caller); + (,, int256 deltaCallerBefore1) = _fetchBalances(key.currency1, user, caller); + (,, int256 deltaThisBefore0) = _fetchBalances(key.currency0, user, address(this)); + (,, int256 deltaThisBefore1) = _fetchBalances(key.currency1, user, address(this)); + + (BalanceDelta delta,) = manager.modifyLiquidity(key, REMOVE_LIQUIDITY_PARAMS, ""); + + (,, int256 deltaCallerAfter0) = _fetchBalances(key.currency0, user, caller); + (,, int256 deltaCallerAfter1) = _fetchBalances(key.currency1, user, caller); + (,, int256 deltaThisAfter0) = _fetchBalances(key.currency0, user, address(this)); + (,, int256 deltaThisAfter1) = _fetchBalances(key.currency1, user, address(this)); + + assertEq(deltaCallerBefore0, deltaCallerAfter0, "Caller delta 0"); + assertEq(deltaCallerBefore1, deltaCallerAfter1, "Caller delta 1"); + assertEq(deltaThisBefore0 + delta.amount0(), deltaThisAfter0, "Executor delta 0"); + assertEq(deltaThisBefore1 + delta.amount1(), deltaThisAfter1, "Executor delta 1"); + + key.currency0.take(manager, user, uint256(deltaThisAfter0), false); + key.currency1.take(manager, user, uint256(deltaThisAfter1), false); + } + + function _donate(address caller) internal { + bool unlocked = manager.isUnlocked(); + assertEq(unlocked, true); + (,, int256 deltaCallerBefore0) = _fetchBalances(key.currency0, user, caller); + (,, int256 deltaCallerBefore1) = _fetchBalances(key.currency1, user, caller); + (,, int256 deltaThisBefore0) = _fetchBalances(key.currency0, user, address(this)); + (,, int256 deltaThisBefore1) = _fetchBalances(key.currency1, user, address(this)); + + BalanceDelta delta = manager.donate(key, DONATE_AMOUNT0, DONATE_AMOUNT1, ""); + + (,, int256 deltaCallerAfter0) = _fetchBalances(key.currency0, user, caller); + (,, int256 deltaCallerAfter1) = _fetchBalances(key.currency1, user, caller); + (,, int256 deltaThisAfter0) = _fetchBalances(key.currency0, user, address(this)); + (,, int256 deltaThisAfter1) = _fetchBalances(key.currency1, user, address(this)); + + assertEq(deltaCallerBefore0, deltaCallerAfter0, "Caller delta 0"); + assertEq(deltaCallerBefore1, deltaCallerAfter1, "Caller delta 1"); + assertEq(deltaThisBefore0 - int256(DONATE_AMOUNT0), deltaThisAfter0, "Executor delta 0"); + assertEq(deltaThisBefore1 - int256(DONATE_AMOUNT1), deltaThisAfter1, "Executor delta 1"); + assertEq(-delta.amount0(), int256(DONATE_AMOUNT0), "Donate delta 0"); + assertEq(-delta.amount1(), int256(DONATE_AMOUNT1), "Donate delta 1"); + + key.currency0.settle(manager, user, uint256(-deltaThisAfter0), false); + key.currency1.settle(manager, user, uint256(-deltaThisAfter1), false); + } + + function _initialize() internal { + bool unlocked = manager.isUnlocked(); + assertEq(unlocked, true); + key.tickSpacing = 50; + PoolId id = key.toId(); + (uint256 price,,,) = manager.getSlot0(id); + assertEq(price, 0); + manager.initialize(key, Constants.SQRT_PRICE_1_2); + (price,,,) = manager.getSlot0(id); + assertEq(price, Constants.SQRT_PRICE_1_2); + } + + // This will never actually be used - its just to allow us to use the PoolTestBase helper contact + function unlockCallback(bytes calldata) external pure override returns (bytes memory) { + return ""; + } +} diff --git a/CVLByExample/UniswapHooks/lib/uniswap-hooks/lib/v4-core/src/test/PoolSwapTest.sol b/CVLByExample/UniswapHooks/lib/uniswap-hooks/lib/v4-core/src/test/PoolSwapTest.sol new file mode 100644 index 00000000..238c79a8 --- /dev/null +++ b/CVLByExample/UniswapHooks/lib/uniswap-hooks/lib/v4-core/src/test/PoolSwapTest.sol @@ -0,0 +1,117 @@ +// SPDX-License-Identifier: UNLICENSED +pragma solidity ^0.8.24; + +import {CurrencyLibrary, Currency} from "../types/Currency.sol"; +import {IPoolManager} from "../interfaces/IPoolManager.sol"; +import {BalanceDelta} from "../types/BalanceDelta.sol"; +import {PoolKey} from "../types/PoolKey.sol"; +import {SwapParams} from "../types/PoolOperation.sol"; +import {IHooks} from "../interfaces/IHooks.sol"; +import {Hooks} from "../libraries/Hooks.sol"; +import {PoolTestBase} from "./PoolTestBase.sol"; +import {CurrencySettler} from "../../test/utils/CurrencySettler.sol"; + +contract PoolSwapTest is PoolTestBase { + using CurrencySettler for Currency; + using Hooks for IHooks; + + constructor(IPoolManager _manager) PoolTestBase(_manager) {} + + error NoSwapOccurred(); + + struct CallbackData { + address sender; + TestSettings testSettings; + PoolKey key; + SwapParams params; + bytes hookData; + } + + struct TestSettings { + bool takeClaims; + bool settleUsingBurn; + } + + function swap(PoolKey memory key, SwapParams memory params, TestSettings memory testSettings, bytes memory hookData) + external + payable + returns (BalanceDelta delta) + { + delta = abi.decode( + manager.unlock(abi.encode(CallbackData(msg.sender, testSettings, key, params, hookData))), (BalanceDelta) + ); + + uint256 ethBalance = address(this).balance; + if (ethBalance > 0) CurrencyLibrary.ADDRESS_ZERO.transfer(msg.sender, ethBalance); + } + + function unlockCallback(bytes calldata rawData) external returns (bytes memory) { + require(msg.sender == address(manager)); + + CallbackData memory data = abi.decode(rawData, (CallbackData)); + + (,, int256 deltaBefore0) = _fetchBalances(data.key.currency0, data.sender, address(this)); + (,, int256 deltaBefore1) = _fetchBalances(data.key.currency1, data.sender, address(this)); + + require(deltaBefore0 == 0, "deltaBefore0 is not equal to 0"); + require(deltaBefore1 == 0, "deltaBefore1 is not equal to 0"); + + BalanceDelta delta = manager.swap(data.key, data.params, data.hookData); + + (,, int256 deltaAfter0) = _fetchBalances(data.key.currency0, data.sender, address(this)); + (,, int256 deltaAfter1) = _fetchBalances(data.key.currency1, data.sender, address(this)); + + if (data.params.zeroForOne) { + if (data.params.amountSpecified < 0) { + // exact input, 0 for 1 + require( + deltaAfter0 >= data.params.amountSpecified, + "deltaAfter0 is not greater than or equal to data.params.amountSpecified" + ); + require(delta.amount0() == deltaAfter0, "delta.amount0() is not equal to deltaAfter0"); + require(deltaAfter1 >= 0, "deltaAfter1 is not greater than or equal to 0"); + } else { + // exact output, 0 for 1 + require(deltaAfter0 <= 0, "deltaAfter0 is not less than or equal to zero"); + require(delta.amount1() == deltaAfter1, "delta.amount1() is not equal to deltaAfter1"); + require( + deltaAfter1 <= data.params.amountSpecified, + "deltaAfter1 is not less than or equal to data.params.amountSpecified" + ); + } + } else { + if (data.params.amountSpecified < 0) { + // exact input, 1 for 0 + require( + deltaAfter1 >= data.params.amountSpecified, + "deltaAfter1 is not greater than or equal to data.params.amountSpecified" + ); + require(delta.amount1() == deltaAfter1, "delta.amount1() is not equal to deltaAfter1"); + require(deltaAfter0 >= 0, "deltaAfter0 is not greater than or equal to 0"); + } else { + // exact output, 1 for 0 + require(deltaAfter1 <= 0, "deltaAfter1 is not less than or equal to 0"); + require(delta.amount0() == deltaAfter0, "delta.amount0() is not equal to deltaAfter0"); + require( + deltaAfter0 <= data.params.amountSpecified, + "deltaAfter0 is not less than or equal to data.params.amountSpecified" + ); + } + } + + if (deltaAfter0 < 0) { + data.key.currency0.settle(manager, data.sender, uint256(-deltaAfter0), data.testSettings.settleUsingBurn); + } + if (deltaAfter1 < 0) { + data.key.currency1.settle(manager, data.sender, uint256(-deltaAfter1), data.testSettings.settleUsingBurn); + } + if (deltaAfter0 > 0) { + data.key.currency0.take(manager, data.sender, uint256(deltaAfter0), data.testSettings.takeClaims); + } + if (deltaAfter1 > 0) { + data.key.currency1.take(manager, data.sender, uint256(deltaAfter1), data.testSettings.takeClaims); + } + + return abi.encode(delta); + } +} diff --git a/CVLByExample/UniswapHooks/lib/uniswap-hooks/lib/v4-core/src/test/PoolTakeTest.sol b/CVLByExample/UniswapHooks/lib/uniswap-hooks/lib/v4-core/src/test/PoolTakeTest.sol new file mode 100644 index 00000000..bf66e62c --- /dev/null +++ b/CVLByExample/UniswapHooks/lib/uniswap-hooks/lib/v4-core/src/test/PoolTakeTest.sol @@ -0,0 +1,61 @@ +// SPDX-License-Identifier: UNLICENSED +pragma solidity ^0.8.24; + +import {Currency} from "../types/Currency.sol"; +import {IPoolManager} from "../interfaces/IPoolManager.sol"; +import {PoolKey} from "../types/PoolKey.sol"; +import {PoolTestBase} from "./PoolTestBase.sol"; +import {SafeCast} from "../libraries/SafeCast.sol"; +import {CurrencySettler} from "../../test/utils/CurrencySettler.sol"; + +contract PoolTakeTest is PoolTestBase { + using CurrencySettler for Currency; + using SafeCast for uint256; + + constructor(IPoolManager _manager) PoolTestBase(_manager) {} + + struct CallbackData { + address sender; + PoolKey key; + uint256 amount0; + uint256 amount1; + } + + function take(PoolKey memory key, uint256 amount0, uint256 amount1) external payable { + manager.unlock(abi.encode(CallbackData(msg.sender, key, amount0, amount1))); + } + + function unlockCallback(bytes calldata rawData) external returns (bytes memory) { + require(msg.sender == address(manager)); + + CallbackData memory data = abi.decode(rawData, (CallbackData)); + + if (data.amount0 > 0) _testTake(data.key.currency0, data.sender, data.amount0); + if (data.amount1 > 0) _testTake(data.key.currency1, data.sender, data.amount1); + + return abi.encode(0); + } + + function _testTake(Currency currency, address sender, uint256 amount) internal { + (uint256 userBalBefore, uint256 pmBalBefore, int256 deltaBefore) = + _fetchBalances(currency, sender, address(this)); + require(deltaBefore == 0, "deltaBefore is not equal to 0"); + + currency.take(manager, sender, amount, false); + + (uint256 userBalAfter, uint256 pmBalAfter, int256 deltaAfter) = _fetchBalances(currency, sender, address(this)); + + require(deltaAfter == -amount.toInt128(), "deltaAfter is not equal to -amount.toInt128()"); + + require( + userBalAfter - userBalBefore == amount, + "the difference between userBalAfter and userBalBefore is not equal to amount" + ); + require( + pmBalBefore - pmBalAfter == amount, + "the difference between pmBalBefore and pmBalAfter is not equal to amount" + ); + + currency.settle(manager, sender, amount, false); + } +} diff --git a/CVLByExample/UniswapHooks/lib/uniswap-hooks/lib/v4-core/src/test/PoolTestBase.sol b/CVLByExample/UniswapHooks/lib/uniswap-hooks/lib/v4-core/src/test/PoolTestBase.sol new file mode 100644 index 00000000..f73bb6a1 --- /dev/null +++ b/CVLByExample/UniswapHooks/lib/uniswap-hooks/lib/v4-core/src/test/PoolTestBase.sol @@ -0,0 +1,31 @@ +// SPDX-License-Identifier: UNLICENSED +pragma solidity ^0.8.24; + +import {Currency} from "../types/Currency.sol"; + +import {IUnlockCallback} from "../interfaces/callback/IUnlockCallback.sol"; +import {IPoolManager} from "../interfaces/IPoolManager.sol"; + +import {StateLibrary} from "../libraries/StateLibrary.sol"; +import {TransientStateLibrary} from "../libraries/TransientStateLibrary.sol"; + +abstract contract PoolTestBase is IUnlockCallback { + using StateLibrary for IPoolManager; + using TransientStateLibrary for IPoolManager; + + IPoolManager public immutable manager; + + constructor(IPoolManager _manager) { + manager = _manager; + } + + function _fetchBalances(Currency currency, address user, address deltaHolder) + internal + view + returns (uint256 userBalance, uint256 poolBalance, int256 delta) + { + userBalance = currency.balanceOf(user); + poolBalance = currency.balanceOf(address(manager)); + delta = manager.currencyDelta(deltaHolder, currency); + } +} diff --git a/CVLByExample/UniswapHooks/lib/uniswap-hooks/lib/v4-core/src/test/ProtocolFeesImplementation.sol b/CVLByExample/UniswapHooks/lib/uniswap-hooks/lib/v4-core/src/test/ProtocolFeesImplementation.sol new file mode 100644 index 00000000..f26ac849 --- /dev/null +++ b/CVLByExample/UniswapHooks/lib/uniswap-hooks/lib/v4-core/src/test/ProtocolFeesImplementation.sol @@ -0,0 +1,38 @@ +// SPDX-License-Identifier: UNLICENSED +pragma solidity ^0.8.20; + +import {ProtocolFees} from "../ProtocolFees.sol"; +import {PoolKey} from "../types/PoolKey.sol"; +import {Currency} from "../types/Currency.sol"; +import {PoolId} from "../types/PoolId.sol"; +import {Pool} from "../libraries/Pool.sol"; +import {Slot0} from "../types/Slot0.sol"; + +contract ProtocolFeesImplementation is ProtocolFees { + mapping(PoolId id => Pool.State) internal _pools; + bool internal isUnlocked; + + constructor() ProtocolFees(msg.sender) {} + + // Used to set the price of a pool to pretend that the pool has been initialized in order to successfully set a protocol fee + function setPrice(PoolKey memory key, uint160 sqrtPriceX96) public { + Pool.State storage pool = _getPool(key.toId()); + pool.slot0 = pool.slot0.setSqrtPriceX96(sqrtPriceX96); + } + + function _getPool(PoolId id) internal view override returns (Pool.State storage) { + return _pools[id]; + } + + function setIsUnlocked(bool newValue) public { + isUnlocked = newValue; + } + + function _isUnlocked() internal view override returns (bool) { + return isUnlocked; + } + + function updateProtocolFees(Currency currency, uint256 amount) public { + ProtocolFees._updateProtocolFees(currency, amount); + } +} diff --git a/CVLByExample/UniswapHooks/lib/uniswap-hooks/lib/v4-core/src/test/ProxyPoolManager.sol b/CVLByExample/UniswapHooks/lib/uniswap-hooks/lib/v4-core/src/test/ProxyPoolManager.sol new file mode 100644 index 00000000..bdf651fe --- /dev/null +++ b/CVLByExample/UniswapHooks/lib/uniswap-hooks/lib/v4-core/src/test/ProxyPoolManager.sol @@ -0,0 +1,222 @@ +// SPDX-License-Identifier: BUSL-1.1 +pragma solidity ^0.8.24; + +import {Hooks} from "../libraries/Hooks.sol"; +import {Pool} from "../libraries/Pool.sol"; +import {SafeCast} from "../libraries/SafeCast.sol"; +import {Position} from "../libraries/Position.sol"; +import {LPFeeLibrary} from "../libraries/LPFeeLibrary.sol"; +import {Currency} from "../types/Currency.sol"; +import {PoolKey} from "../types/PoolKey.sol"; +import {ModifyLiquidityParams, SwapParams} from "../types/PoolOperation.sol"; +import {TickMath} from "../libraries/TickMath.sol"; +import {NoDelegateCall} from "../NoDelegateCall.sol"; +import {IHooks} from "../interfaces/IHooks.sol"; +import {IPoolManager} from "../interfaces/IPoolManager.sol"; +import {IUnlockCallback} from "../interfaces/callback/IUnlockCallback.sol"; +import {ProtocolFees} from "../ProtocolFees.sol"; +import {ERC6909Claims} from "../ERC6909Claims.sol"; +import {PoolId} from "../types/PoolId.sol"; +import {BalanceDelta} from "../types/BalanceDelta.sol"; +import {Lock} from "../libraries/Lock.sol"; +import {CurrencyDelta} from "../libraries/CurrencyDelta.sol"; +import {NonzeroDeltaCount} from "../libraries/NonzeroDeltaCount.sol"; +import {CurrencyReserves} from "../libraries/CurrencyReserves.sol"; +import {Extsload} from "../Extsload.sol"; +import {Exttload} from "../Exttload.sol"; +import {CustomRevert} from "../libraries/CustomRevert.sol"; + +/// @notice A proxy pool manager that delegates calls to the real/delegate pool manager +contract ProxyPoolManager is IPoolManager, ProtocolFees, NoDelegateCall, ERC6909Claims, Extsload, Exttload { + using SafeCast for *; + using Pool for *; + using Hooks for IHooks; + using CurrencyDelta for Currency; + using LPFeeLibrary for uint24; + using CurrencyReserves for Currency; + using CustomRevert for bytes4; + + int24 private constant MAX_TICK_SPACING = TickMath.MAX_TICK_SPACING; + + int24 private constant MIN_TICK_SPACING = TickMath.MIN_TICK_SPACING; + + mapping(PoolId id => Pool.State) internal _pools; + + address internal immutable _delegateManager; + + constructor(address delegateManager) ProtocolFees(msg.sender) { + _delegateManager = delegateManager; + } + + /// @notice This will revert if the contract is locked + modifier onlyWhenUnlocked() { + if (!Lock.isUnlocked()) ManagerLocked.selector.revertWith(); + _; + } + + /// @inheritdoc IPoolManager + function unlock(bytes calldata data) external noDelegateCall returns (bytes memory result) { + if (Lock.isUnlocked()) AlreadyUnlocked.selector.revertWith(); + + Lock.unlock(); + + // the caller does everything in this callback, including paying what they owe via calls to settle + result = IUnlockCallback(msg.sender).unlockCallback(data); + + if (NonzeroDeltaCount.read() != 0) CurrencyNotSettled.selector.revertWith(); + Lock.lock(); + } + + /// @inheritdoc IPoolManager + function initialize(PoolKey memory key, uint160 sqrtPriceX96) external noDelegateCall returns (int24 tick) { + // see TickBitmap.sol for overflow conditions that can arise from tick spacing being too large + if (key.tickSpacing > MAX_TICK_SPACING) TickSpacingTooLarge.selector.revertWith(key.tickSpacing); + if (key.tickSpacing < MIN_TICK_SPACING) TickSpacingTooSmall.selector.revertWith(key.tickSpacing); + if (key.currency0 >= key.currency1) { + CurrenciesOutOfOrderOrEqual.selector.revertWith( + Currency.unwrap(key.currency0), Currency.unwrap(key.currency1) + ); + } + if (!key.hooks.isValidHookAddress(key.fee)) Hooks.HookAddressNotValid.selector.revertWith(address(key.hooks)); + + uint24 lpFee = key.fee.getInitialLPFee(); + + key.hooks.beforeInitialize(key, sqrtPriceX96); + + PoolId id = key.toId(); + + tick = _pools[id].initialize(sqrtPriceX96, lpFee); + + key.hooks.afterInitialize(key, sqrtPriceX96, tick); + + // emit all details of a pool key. poolkeys are not saved in storage and must always be provided by the caller + // the key's fee may be a static fee or a sentinel to denote a dynamic fee. + emit Initialize(id, key.currency0, key.currency1, key.fee, key.tickSpacing, key.hooks, sqrtPriceX96, tick); + } + + /// @inheritdoc IPoolManager + function modifyLiquidity(PoolKey memory key, ModifyLiquidityParams memory params, bytes calldata hookData) + external + onlyWhenUnlocked + noDelegateCall + returns (BalanceDelta callerDelta, BalanceDelta feesAccrued) + { + bytes memory result = _delegateCall( + _delegateManager, abi.encodeWithSelector(this.modifyLiquidity.selector, key, params, hookData) + ); + + return abi.decode(result, (BalanceDelta, BalanceDelta)); + } + + /// @inheritdoc IPoolManager + function swap(PoolKey memory key, SwapParams memory params, bytes calldata hookData) + external + onlyWhenUnlocked + noDelegateCall + returns (BalanceDelta swapDelta) + { + bytes memory result = + _delegateCall(_delegateManager, abi.encodeWithSelector(this.swap.selector, key, params, hookData)); + + return abi.decode(result, (BalanceDelta)); + } + + /// @inheritdoc IPoolManager + function donate(PoolKey memory key, uint256 amount0, uint256 amount1, bytes calldata hookData) + external + onlyWhenUnlocked + noDelegateCall + returns (BalanceDelta delta) + { + bytes memory result = _delegateCall( + _delegateManager, abi.encodeWithSelector(this.donate.selector, key, amount0, amount1, hookData) + ); + + return abi.decode(result, (BalanceDelta)); + } + + /// @inheritdoc IPoolManager + function sync(Currency currency) public { + // address(0) is used for the native currency + if (currency.isAddressZero()) { + // The reserves balance is not used for native settling, so we only need to reset the currency. + CurrencyReserves.resetCurrency(); + } else { + uint256 balance = currency.balanceOfSelf(); + CurrencyReserves.syncCurrencyAndReserves(currency, balance); + } + } + + /// @inheritdoc IPoolManager + function take(Currency currency, address to, uint256 amount) external onlyWhenUnlocked noDelegateCall { + _delegateCall(_delegateManager, abi.encodeWithSelector(this.take.selector, currency, to, amount)); + } + + /// @inheritdoc IPoolManager + function settle() external payable onlyWhenUnlocked noDelegateCall returns (uint256 paid) { + bytes memory result = _delegateCall(_delegateManager, abi.encodeWithSelector(this.settle.selector)); + + return abi.decode(result, (uint256)); + } + + /// @inheritdoc IPoolManager + function settleFor(address recipient) external payable onlyWhenUnlocked noDelegateCall returns (uint256 paid) { + bytes memory result = + _delegateCall(_delegateManager, abi.encodeWithSelector(this.settleFor.selector, recipient)); + + return abi.decode(result, (uint256)); + } + + /// @inheritdoc IPoolManager + function clear(Currency currency, uint256 amount) external onlyWhenUnlocked { + _delegateCall(_delegateManager, abi.encodeWithSelector(this.clear.selector, currency, amount)); + } + + /// @inheritdoc IPoolManager + function mint(address to, uint256 id, uint256 amount) external onlyWhenUnlocked noDelegateCall { + _delegateCall(_delegateManager, abi.encodeWithSelector(this.mint.selector, to, id, amount)); + } + + /// @inheritdoc IPoolManager + function burn(address from, uint256 id, uint256 amount) external onlyWhenUnlocked noDelegateCall { + _delegateCall(_delegateManager, abi.encodeWithSelector(this.burn.selector, from, id, amount)); + } + + /// @inheritdoc IPoolManager + function updateDynamicLPFee(PoolKey memory key, uint24 newDynamicLPFee) external { + if (!key.fee.isDynamicFee() || msg.sender != address(key.hooks)) { + UnauthorizedDynamicLPFeeUpdate.selector.revertWith(); + } + newDynamicLPFee.validate(); + PoolId id = key.toId(); + _pools[id].setLPFee(newDynamicLPFee); + } + + /// @notice Make a delegate call, bubble up any error or return the result + function _delegateCall(address target, bytes memory data) internal returns (bytes memory result) { + (bool success, bytes memory returnData) = target.delegatecall(data); + + if (!success) { + if (returnData.length == 0) { + revert(); + } else { + assembly { + let size := mload(returnData) + revert(add(32, returnData), size) + } + } + } + + return returnData; + } + + /// @notice Implementation of the _getPool function defined in ProtocolFees + function _getPool(PoolId id) internal view override returns (Pool.State storage) { + return _pools[id]; + } + + /// @notice Implementation of the _isUnlocked function defined in ProtocolFees + function _isUnlocked() internal view override returns (bool) { + return Lock.isUnlocked(); + } +} diff --git a/CVLByExample/UniswapHooks/lib/uniswap-hooks/lib/v4-core/src/test/SkipCallsTestHook.sol b/CVLByExample/UniswapHooks/lib/uniswap-hooks/lib/v4-core/src/test/SkipCallsTestHook.sol new file mode 100644 index 00000000..7291af07 --- /dev/null +++ b/CVLByExample/UniswapHooks/lib/uniswap-hooks/lib/v4-core/src/test/SkipCallsTestHook.sol @@ -0,0 +1,193 @@ +// SPDX-License-Identifier: UNLICENSED +pragma solidity ^0.8.24; + +import {Hooks} from "../libraries/Hooks.sol"; +import {BaseTestHooks} from "./BaseTestHooks.sol"; +import {IHooks} from "../interfaces/IHooks.sol"; +import {IPoolManager} from "../interfaces/IPoolManager.sol"; +import {ModifyLiquidityParams, SwapParams} from "../types/PoolOperation.sol"; +import {PoolKey} from "../types/PoolKey.sol"; +import {BalanceDelta, BalanceDeltaLibrary} from "../types/BalanceDelta.sol"; +import {Currency} from "../types/Currency.sol"; +import {Test} from "forge-std/Test.sol"; +import {CurrencySettler} from "../../test/utils/CurrencySettler.sol"; +import {StateLibrary} from "../libraries/StateLibrary.sol"; +import {TransientStateLibrary} from "../libraries/TransientStateLibrary.sol"; +import {BeforeSwapDelta, BeforeSwapDeltaLibrary} from "../types/BeforeSwapDelta.sol"; + +contract SkipCallsTestHook is BaseTestHooks, Test { + using CurrencySettler for Currency; + using Hooks for IHooks; + using StateLibrary for IPoolManager; + using TransientStateLibrary for IPoolManager; + + uint256 public counter; + IPoolManager manager; + + function setManager(IPoolManager _manager) external { + manager = _manager; + } + + function beforeInitialize(address, PoolKey calldata key, uint160 sqrtPriceX96) external override returns (bytes4) { + counter++; + _initialize(key, sqrtPriceX96); + return IHooks.beforeInitialize.selector; + } + + function afterInitialize(address, PoolKey calldata key, uint160 sqrtPriceX96, int24) + external + override + returns (bytes4) + { + counter++; + _initialize(key, sqrtPriceX96); + return IHooks.afterInitialize.selector; + } + + function beforeAddLiquidity( + address, + PoolKey calldata key, + ModifyLiquidityParams calldata params, + bytes calldata hookData + ) external override returns (bytes4) { + counter++; + _addLiquidity(key, params, hookData); + return IHooks.beforeAddLiquidity.selector; + } + + function afterAddLiquidity( + address, + PoolKey calldata key, + ModifyLiquidityParams calldata params, + BalanceDelta, + BalanceDelta, + bytes calldata hookData + ) external override returns (bytes4, BalanceDelta) { + counter++; + _addLiquidity(key, params, hookData); + return (IHooks.afterAddLiquidity.selector, BalanceDeltaLibrary.ZERO_DELTA); + } + + function beforeRemoveLiquidity( + address, + PoolKey calldata key, + ModifyLiquidityParams calldata params, + bytes calldata hookData + ) external override returns (bytes4) { + counter++; + _removeLiquidity(key, params, hookData); + return IHooks.beforeRemoveLiquidity.selector; + } + + function afterRemoveLiquidity( + address, + PoolKey calldata key, + ModifyLiquidityParams calldata params, + BalanceDelta, + BalanceDelta, + bytes calldata hookData + ) external override returns (bytes4, BalanceDelta) { + counter++; + _removeLiquidity(key, params, hookData); + return (IHooks.afterRemoveLiquidity.selector, BalanceDeltaLibrary.ZERO_DELTA); + } + + function beforeSwap(address, PoolKey calldata key, SwapParams calldata params, bytes calldata hookData) + external + override + returns (bytes4, BeforeSwapDelta, uint24) + { + counter++; + _swap(key, params, hookData); + return (IHooks.beforeSwap.selector, BeforeSwapDeltaLibrary.ZERO_DELTA, 0); + } + + function afterSwap(address, PoolKey calldata key, SwapParams calldata params, BalanceDelta, bytes calldata hookData) + external + override + returns (bytes4, int128) + { + counter++; + _swap(key, params, hookData); + return (IHooks.afterSwap.selector, 0); + } + + function beforeDonate(address, PoolKey calldata key, uint256 amt0, uint256 amt1, bytes calldata hookData) + external + override + returns (bytes4) + { + counter++; + _donate(key, amt0, amt1, hookData); + return IHooks.beforeDonate.selector; + } + + function afterDonate(address, PoolKey calldata key, uint256 amt0, uint256 amt1, bytes calldata hookData) + external + override + returns (bytes4) + { + counter++; + _donate(key, amt0, amt1, hookData); + return IHooks.afterDonate.selector; + } + + function _initialize(PoolKey memory key, uint160 sqrtPriceX96) public { + // initialize a new pool with different fee + key.fee = 2000; + IPoolManager(manager).initialize(key, sqrtPriceX96); + } + + function _swap(PoolKey calldata key, SwapParams memory params, bytes calldata hookData) public { + IPoolManager(manager).swap(key, params, hookData); + address payer = abi.decode(hookData, (address)); + int256 delta0 = IPoolManager(manager).currencyDelta(address(this), key.currency0); + assertEq(delta0, params.amountSpecified); + int256 delta1 = IPoolManager(manager).currencyDelta(address(this), key.currency1); + assert(delta1 > 0); + key.currency0.settle(manager, payer, uint256(-delta0), false); + key.currency1.take(manager, payer, uint256(delta1), false); + } + + function _addLiquidity(PoolKey calldata key, ModifyLiquidityParams memory params, bytes calldata hookData) public { + IPoolManager(manager).modifyLiquidity(key, params, hookData); + address payer = abi.decode(hookData, (address)); + int256 delta0 = IPoolManager(manager).currencyDelta(address(this), key.currency0); + int256 delta1 = IPoolManager(manager).currencyDelta(address(this), key.currency1); + + assert(delta0 < 0 || delta1 < 0); + assert(!(delta0 > 0 || delta1 > 0)); + + key.currency0.settle(manager, payer, uint256(-delta0), false); + key.currency1.settle(manager, payer, uint256(-delta1), false); + } + + function _removeLiquidity(PoolKey calldata key, ModifyLiquidityParams memory params, bytes calldata hookData) + public + { + // first hook needs to add liquidity for itself + ModifyLiquidityParams memory newParams = + ModifyLiquidityParams({tickLower: -120, tickUpper: 120, liquidityDelta: 1e18, salt: 0}); + IPoolManager(manager).modifyLiquidity(key, newParams, hookData); + // hook removes liquidity + IPoolManager(manager).modifyLiquidity(key, params, hookData); + address payer = abi.decode(hookData, (address)); + int256 delta0 = IPoolManager(manager).currencyDelta(address(this), key.currency0); + int256 delta1 = IPoolManager(manager).currencyDelta(address(this), key.currency1); + + assert(delta0 < 0 || delta1 < 0); + assert(!(delta0 > 0 || delta1 > 0)); + + key.currency0.settle(manager, payer, uint256(-delta0), false); + key.currency1.settle(manager, payer, uint256(-delta1), false); + } + + function _donate(PoolKey calldata key, uint256 amt0, uint256 amt1, bytes calldata hookData) public { + IPoolManager(manager).donate(key, amt0, amt1, hookData); + address payer = abi.decode(hookData, (address)); + int256 delta0 = IPoolManager(manager).currencyDelta(address(this), key.currency0); + int256 delta1 = IPoolManager(manager).currencyDelta(address(this), key.currency1); + key.currency0.settle(manager, payer, uint256(-delta0), false); + key.currency1.settle(manager, payer, uint256(-delta1), false); + } +} diff --git a/CVLByExample/UniswapHooks/lib/uniswap-hooks/lib/v4-core/src/test/SqrtPriceMathEchidnaTest.sol b/CVLByExample/UniswapHooks/lib/uniswap-hooks/lib/v4-core/src/test/SqrtPriceMathEchidnaTest.sol new file mode 100644 index 00000000..8deb8d16 --- /dev/null +++ b/CVLByExample/UniswapHooks/lib/uniswap-hooks/lib/v4-core/src/test/SqrtPriceMathEchidnaTest.sol @@ -0,0 +1,198 @@ +// SPDX-License-Identifier: UNLICENSED +pragma solidity ^0.8.20; + +import {FullMath} from "../libraries/FullMath.sol"; +import {SqrtPriceMath} from "../libraries/SqrtPriceMath.sol"; +import {FixedPoint96} from "../libraries/FixedPoint96.sol"; + +contract SqrtPriceMathEchidnaTest { + function mulDivRoundingUpInvariants(uint256 x, uint256 y, uint256 z) external pure { + unchecked { + require(z > 0); + uint256 notRoundedUp = FullMath.mulDiv(x, y, z); + uint256 roundedUp = FullMath.mulDivRoundingUp(x, y, z); + assert(roundedUp >= notRoundedUp); + assert(roundedUp - notRoundedUp < 2); + if (roundedUp - notRoundedUp == 1) { + assert(mulmod(x, y, z) > 0); + } else { + assert(mulmod(x, y, z) == 0); + } + } + } + + function getNextSqrtPriceFromInputInvariants(uint160 sqrtP, uint128 liquidity, uint256 amountIn, bool zeroForOne) + external + pure + { + uint160 sqrtQ = SqrtPriceMath.getNextSqrtPriceFromInput(sqrtP, liquidity, amountIn, zeroForOne); + + if (zeroForOne) { + assert(sqrtQ <= sqrtP); + assert(amountIn >= SqrtPriceMath.getAmount0Delta(sqrtQ, sqrtP, liquidity, true)); + } else { + assert(sqrtQ >= sqrtP); + assert(amountIn >= SqrtPriceMath.getAmount1Delta(sqrtP, sqrtQ, liquidity, true)); + } + } + + function getNextSqrtPriceFromOutputInvariants(uint160 sqrtP, uint128 liquidity, uint256 amountOut, bool zeroForOne) + external + pure + { + uint160 sqrtQ = SqrtPriceMath.getNextSqrtPriceFromOutput(sqrtP, liquidity, amountOut, zeroForOne); + + if (zeroForOne) { + assert(sqrtQ <= sqrtP); + assert(amountOut <= SqrtPriceMath.getAmount1Delta(sqrtQ, sqrtP, liquidity, false)); + } else { + assert(sqrtQ > 0); // this has to be true, otherwise we need another require + assert(sqrtQ >= sqrtP); + assert(amountOut <= SqrtPriceMath.getAmount0Delta(sqrtP, sqrtQ, liquidity, false)); + } + } + + function getNextSqrtPriceFromAmount0RoundingUpInvariants( + uint160 sqrtPX96, + uint128 liquidity, + uint256 amount, + bool add + ) external pure { + require(sqrtPX96 > 0); + require(liquidity > 0); + uint160 sqrtQX96 = SqrtPriceMath.getNextSqrtPriceFromAmount0RoundingUp(sqrtPX96, liquidity, amount, add); + + if (add) { + assert(sqrtQX96 <= sqrtPX96); + } else { + assert(sqrtQX96 >= sqrtPX96); + } + + if (amount == 0) { + assert(sqrtPX96 == sqrtQX96); + } + } + + function getNextSqrtPriceFromAmount1RoundingDownInvariants( + uint160 sqrtPX96, + uint128 liquidity, + uint256 amount, + bool add + ) external pure { + require(sqrtPX96 > 0); + require(liquidity > 0); + uint160 sqrtQX96 = SqrtPriceMath.getNextSqrtPriceFromAmount1RoundingDown(sqrtPX96, liquidity, amount, add); + + if (add) { + assert(sqrtQX96 >= sqrtPX96); + } else { + assert(sqrtQX96 <= sqrtPX96); + } + + if (amount == 0) { + assert(sqrtPX96 == sqrtQX96); + } + } + + function getAmount0DeltaInvariants(uint160 sqrtP, uint160 sqrtQ, uint128 liquidity) external pure { + require(sqrtP > 0 && sqrtQ > 0); + + uint256 amount0Down = SqrtPriceMath.getAmount0Delta(sqrtQ, sqrtP, liquidity, false); + assert(amount0Down == SqrtPriceMath.getAmount0Delta(sqrtP, sqrtQ, liquidity, false)); + + uint256 amount0Up = SqrtPriceMath.getAmount0Delta(sqrtQ, sqrtP, liquidity, true); + assert(amount0Up == SqrtPriceMath.getAmount0Delta(sqrtP, sqrtQ, liquidity, true)); + + assert(amount0Down <= amount0Up); + // diff is 0 or 1 + assert(amount0Up - amount0Down < 2); + } + + // ensure that chained division is always equal to the full-precision case for + // liquidity * (sqrt(P) - sqrt(Q)) / (sqrt(P) * sqrt(Q)) + function getAmount0DeltaEquivalency(uint160 sqrtP, uint160 sqrtQ, uint128 liquidity, bool roundUp) external pure { + require(sqrtP >= sqrtQ); + require(sqrtP > 0 && sqrtQ > 0); + require((sqrtP * sqrtQ) / sqrtP == sqrtQ); + + uint256 numerator1 = uint256(liquidity) << FixedPoint96.RESOLUTION; + uint256 numerator2 = sqrtP - sqrtQ; + uint256 denominator = uint256(sqrtP) * sqrtQ; + + uint256 safeResult = roundUp + ? FullMath.mulDivRoundingUp(numerator1, numerator2, denominator) + : FullMath.mulDiv(numerator1, numerator2, denominator); + uint256 fullResult = SqrtPriceMath.getAmount0Delta(sqrtQ, sqrtP, liquidity, roundUp); + + assert(safeResult == fullResult); + } + + function getAmount1DeltaInvariants(uint160 sqrtP, uint160 sqrtQ, uint128 liquidity) external pure { + require(sqrtP > 0 && sqrtQ > 0); + + uint256 amount1Down = SqrtPriceMath.getAmount1Delta(sqrtP, sqrtQ, liquidity, false); + assert(amount1Down == SqrtPriceMath.getAmount1Delta(sqrtQ, sqrtP, liquidity, false)); + + uint256 amount1Up = SqrtPriceMath.getAmount1Delta(sqrtP, sqrtQ, liquidity, true); + assert(amount1Up == SqrtPriceMath.getAmount1Delta(sqrtQ, sqrtP, liquidity, true)); + + assert(amount1Down <= amount1Up); + // diff is 0 or 1 + assert(amount1Up - amount1Down < 2); + } + + function getAmount0DeltaSignedInvariants(uint160 sqrtP, uint160 sqrtQ, int128 liquidity) external pure { + require(sqrtP > 0 && sqrtQ > 0); + + int256 amount0 = SqrtPriceMath.getAmount0Delta(sqrtQ, sqrtP, liquidity); + if (liquidity < 0) assert(amount0 <= 0); + if (liquidity > 0) { + if (sqrtP == sqrtQ) assert(amount0 == 0); + else assert(amount0 > 0); + } + if (liquidity == 0) assert(amount0 == 0); + } + + function getAmount1DeltaSignedInvariants(uint160 sqrtP, uint160 sqrtQ, int128 liquidity) external pure { + require(sqrtP > 0 && sqrtQ > 0); + + int256 amount1 = SqrtPriceMath.getAmount1Delta(sqrtP, sqrtQ, liquidity); + if (liquidity < 0) assert(amount1 <= 0); + if (liquidity > 0) { + if (sqrtP == sqrtQ) assert(amount1 == 0); + else assert(amount1 > 0); + } + if (liquidity == 0) assert(amount1 == 0); + } + + function getOutOfRangeMintInvariants(uint160 sqrtA, uint160 sqrtB, int128 liquidity) external pure { + require(sqrtA > 0 && sqrtB > 0); + require(liquidity > 0); + + int256 amount0 = SqrtPriceMath.getAmount0Delta(sqrtA, sqrtB, liquidity); + int256 amount1 = SqrtPriceMath.getAmount1Delta(sqrtA, sqrtB, liquidity); + + if (sqrtA == sqrtB) { + assert(amount0 == 0); + assert(amount1 == 0); + } else { + assert(amount0 > 0); + assert(amount1 > 0); + } + } + + function getInRangeMintInvariants(uint160 sqrtLower, uint160 sqrtCurrent, uint160 sqrtUpper, int128 liquidity) + external + pure + { + require(sqrtLower > 0); + require(sqrtLower < sqrtUpper); + require(sqrtLower <= sqrtCurrent && sqrtCurrent <= sqrtUpper); + require(liquidity > 0); + + int256 amount0 = SqrtPriceMath.getAmount0Delta(sqrtCurrent, sqrtUpper, liquidity); + int256 amount1 = SqrtPriceMath.getAmount1Delta(sqrtLower, sqrtCurrent, liquidity); + + assert(amount0 > 0 || amount1 > 0); + } +} diff --git a/CVLByExample/UniswapHooks/lib/uniswap-hooks/lib/v4-core/src/test/SwapRouterNoChecks.sol b/CVLByExample/UniswapHooks/lib/uniswap-hooks/lib/v4-core/src/test/SwapRouterNoChecks.sol new file mode 100644 index 00000000..c1caedc8 --- /dev/null +++ b/CVLByExample/UniswapHooks/lib/uniswap-hooks/lib/v4-core/src/test/SwapRouterNoChecks.sol @@ -0,0 +1,49 @@ +// SPDX-License-Identifier: UNLICENSED +pragma solidity ^0.8.24; + +import {Currency} from "../types/Currency.sol"; +import {IPoolManager} from "../interfaces/IPoolManager.sol"; +import {BalanceDelta} from "../types/BalanceDelta.sol"; +import {PoolKey} from "../types/PoolKey.sol"; +import {SwapParams} from "../types/PoolOperation.sol"; +import {IHooks} from "../interfaces/IHooks.sol"; +import {Hooks} from "../libraries/Hooks.sol"; +import {PoolTestBase} from "./PoolTestBase.sol"; +import {CurrencySettler} from "../../test/utils/CurrencySettler.sol"; + +contract SwapRouterNoChecks is PoolTestBase { + using CurrencySettler for Currency; + using Hooks for IHooks; + + constructor(IPoolManager _manager) PoolTestBase(_manager) {} + + error NoSwapOccurred(); + + struct CallbackData { + address sender; + PoolKey key; + SwapParams params; + } + + function swap(PoolKey memory key, SwapParams memory params) external payable { + manager.unlock(abi.encode(CallbackData(msg.sender, key, params))); + } + + function unlockCallback(bytes calldata rawData) external returns (bytes memory) { + require(msg.sender == address(manager)); + + CallbackData memory data = abi.decode(rawData, (CallbackData)); + + BalanceDelta delta = manager.swap(data.key, data.params, new bytes(0)); + + if (data.params.zeroForOne) { + data.key.currency0.settle(manager, data.sender, uint256(int256(-delta.amount0())), false); + data.key.currency1.take(manager, data.sender, uint256(int256(delta.amount1())), false); + } else { + data.key.currency1.settle(manager, data.sender, uint256(int256(-delta.amount1())), false); + data.key.currency0.take(manager, data.sender, uint256(int256(delta.amount0())), false); + } + + return ""; + } +} diff --git a/CVLByExample/UniswapHooks/lib/uniswap-hooks/lib/v4-core/src/test/TestERC20.sol b/CVLByExample/UniswapHooks/lib/uniswap-hooks/lib/v4-core/src/test/TestERC20.sol new file mode 100644 index 00000000..7ceb3276 --- /dev/null +++ b/CVLByExample/UniswapHooks/lib/uniswap-hooks/lib/v4-core/src/test/TestERC20.sol @@ -0,0 +1,55 @@ +// SPDX-License-Identifier: UNLICENSED +pragma solidity ^0.8.20; + +import {IERC20Minimal} from "../interfaces/external/IERC20Minimal.sol"; + +contract TestERC20 is IERC20Minimal { + mapping(address => uint256) public override balanceOf; + mapping(address => mapping(address => uint256)) public override allowance; + + constructor(uint256 amountToMint) { + mint(msg.sender, amountToMint); + } + + function mint(address to, uint256 amount) public { + uint256 balanceNext = balanceOf[to] + amount; + require(balanceNext >= amount, "overflow balance"); + balanceOf[to] = balanceNext; + } + + function transfer(address recipient, uint256 amount) external override returns (bool) { + uint256 balanceBefore = balanceOf[msg.sender]; + require(balanceBefore >= amount, "insufficient balance"); + balanceOf[msg.sender] = balanceBefore - amount; + + uint256 balanceRecipient = balanceOf[recipient]; + require(balanceRecipient + amount >= balanceRecipient, "recipient balance overflow"); + balanceOf[recipient] = balanceRecipient + amount; + + emit Transfer(msg.sender, recipient, amount); + return true; + } + + function approve(address spender, uint256 amount) external override returns (bool) { + allowance[msg.sender][spender] = amount; + emit Approval(msg.sender, spender, amount); + return true; + } + + function transferFrom(address sender, address recipient, uint256 amount) external override returns (bool) { + uint256 allowanceBefore = allowance[sender][msg.sender]; + require(allowanceBefore >= amount, "allowance insufficient"); + + allowance[sender][msg.sender] = allowanceBefore - amount; + + uint256 balanceRecipient = balanceOf[recipient]; + require(balanceRecipient + amount >= balanceRecipient, "overflow balance recipient"); + balanceOf[recipient] = balanceRecipient + amount; + uint256 balanceSender = balanceOf[sender]; + require(balanceSender >= amount, "underflow balance sender"); + balanceOf[sender] = balanceSender - amount; + + emit Transfer(sender, recipient, amount); + return true; + } +} diff --git a/CVLByExample/UniswapHooks/lib/uniswap-hooks/lib/v4-core/src/test/TestInvalidERC20.sol b/CVLByExample/UniswapHooks/lib/uniswap-hooks/lib/v4-core/src/test/TestInvalidERC20.sol new file mode 100644 index 00000000..b0e549a9 --- /dev/null +++ b/CVLByExample/UniswapHooks/lib/uniswap-hooks/lib/v4-core/src/test/TestInvalidERC20.sol @@ -0,0 +1,56 @@ +// SPDX-License-Identifier: UNLICENSED +pragma solidity ^0.8.20; + +import {IERC20Minimal} from "../interfaces/external/IERC20Minimal.sol"; + +// Regular ERC20 but it doesn't return true on transfer. +contract TestInvalidERC20 is IERC20Minimal { + mapping(address => uint256) public override balanceOf; + mapping(address => mapping(address => uint256)) public override allowance; + + constructor(uint256 amountToMint) { + mint(msg.sender, amountToMint); + } + + function mint(address to, uint256 amount) public { + uint256 balanceNext = balanceOf[to] + amount; + require(balanceNext >= amount, "overflow balance"); + balanceOf[to] = balanceNext; + } + + function transfer(address recipient, uint256 amount) external override returns (bool) { + uint256 balanceBefore = balanceOf[msg.sender]; + require(balanceBefore >= amount, "insufficient balance"); + balanceOf[msg.sender] = balanceBefore - amount; + + uint256 balanceRecipient = balanceOf[recipient]; + require(balanceRecipient + amount >= balanceRecipient, "recipient balance overflow"); + balanceOf[recipient] = balanceRecipient + amount; + + emit Transfer(msg.sender, recipient, amount); + return false; // returns false even though it succeeded + } + + function approve(address spender, uint256 amount) external override returns (bool) { + allowance[msg.sender][spender] = amount; + emit Approval(msg.sender, spender, amount); + return false; + } + + function transferFrom(address sender, address recipient, uint256 amount) external override returns (bool) { + uint256 allowanceBefore = allowance[sender][msg.sender]; + require(allowanceBefore >= amount, "allowance insufficient"); + + allowance[sender][msg.sender] = allowanceBefore - amount; + + uint256 balanceRecipient = balanceOf[recipient]; + require(balanceRecipient + amount >= balanceRecipient, "overflow balance recipient"); + balanceOf[recipient] = balanceRecipient + amount; + uint256 balanceSender = balanceOf[sender]; + require(balanceSender >= amount, "underflow balance sender"); + balanceOf[sender] = balanceSender - amount; + + emit Transfer(sender, recipient, amount); + return false; // returns false even though it succeeded + } +} diff --git a/CVLByExample/UniswapHooks/lib/uniswap-hooks/lib/v4-core/src/test/TickMathEchidnaTest.sol b/CVLByExample/UniswapHooks/lib/uniswap-hooks/lib/v4-core/src/test/TickMathEchidnaTest.sol new file mode 100644 index 00000000..d224cf2a --- /dev/null +++ b/CVLByExample/UniswapHooks/lib/uniswap-hooks/lib/v4-core/src/test/TickMathEchidnaTest.sol @@ -0,0 +1,22 @@ +// SPDX-License-Identifier: UNLICENSED +pragma solidity ^0.8.20; + +import {TickMath} from "../libraries/TickMath.sol"; + +contract TickMathEchidnaTest { + // uniqueness and increasing order + function checkGetSqrtPriceAtTickInvariants(int24 tick) external pure { + uint160 price = TickMath.getSqrtPriceAtTick(tick); + assert(TickMath.getSqrtPriceAtTick(tick - 1) < price && price < TickMath.getSqrtPriceAtTick(tick + 1)); + assert(price >= TickMath.MIN_SQRT_PRICE); + assert(price <= TickMath.MAX_SQRT_PRICE); + } + + // the price is always between the returned tick and the returned tick+1 + function checkGetTickAtSqrtPriceInvariants(uint160 price) external pure { + int24 tick = TickMath.getTickAtSqrtPrice(price); + assert(price >= TickMath.getSqrtPriceAtTick(tick) && price < TickMath.getSqrtPriceAtTick(tick + 1)); + assert(tick >= TickMath.MIN_TICK); + assert(tick < TickMath.MAX_TICK); + } +} diff --git a/CVLByExample/UniswapHooks/lib/uniswap-hooks/lib/v4-core/src/test/TickMathTest.sol b/CVLByExample/UniswapHooks/lib/uniswap-hooks/lib/v4-core/src/test/TickMathTest.sol new file mode 100644 index 00000000..ea0a42af --- /dev/null +++ b/CVLByExample/UniswapHooks/lib/uniswap-hooks/lib/v4-core/src/test/TickMathTest.sol @@ -0,0 +1,42 @@ +// SPDX-License-Identifier: UNLICENSED +pragma solidity ^0.8.20; + +import {TickMath} from "../libraries/TickMath.sol"; + +contract TickMathTest { + function getSqrtPriceAtTick(int24 tick) external pure returns (uint160) { + return TickMath.getSqrtPriceAtTick(tick); + } + + function getGasCostOfGetSqrtPriceAtTick(int24 tick) external view returns (uint256) { + uint256 gasBefore = gasleft(); + TickMath.getSqrtPriceAtTick(tick); + return gasBefore - gasleft(); + } + + function getTickAtSqrtPrice(uint160 sqrtPriceX96) external pure returns (int24) { + return TickMath.getTickAtSqrtPrice(sqrtPriceX96); + } + + function getGasCostOfGetTickAtSqrtPrice(uint160 sqrtPriceX96) external view returns (uint256) { + uint256 gasBefore = gasleft(); + TickMath.getTickAtSqrtPrice(sqrtPriceX96); + return gasBefore - gasleft(); + } + + function MIN_SQRT_PRICE() external pure returns (uint160) { + return TickMath.MIN_SQRT_PRICE; + } + + function MAX_SQRT_PRICE() external pure returns (uint160) { + return TickMath.MAX_SQRT_PRICE; + } + + function MIN_TICK() external pure returns (int24) { + return TickMath.MIN_TICK; + } + + function MAX_TICK() external pure returns (int24) { + return TickMath.MAX_TICK; + } +} diff --git a/CVLByExample/UniswapHooks/lib/uniswap-hooks/lib/v4-core/src/test/TickOverflowSafetyEchidnaTest.sol b/CVLByExample/UniswapHooks/lib/uniswap-hooks/lib/v4-core/src/test/TickOverflowSafetyEchidnaTest.sol new file mode 100644 index 00000000..f7fdc95f --- /dev/null +++ b/CVLByExample/UniswapHooks/lib/uniswap-hooks/lib/v4-core/src/test/TickOverflowSafetyEchidnaTest.sol @@ -0,0 +1,89 @@ +// SPDX-License-Identifier: UNLICENSED +pragma solidity ^0.8.20; + +import {Pool} from "../libraries/Pool.sol"; + +contract TickOverflowSafetyEchidnaTest { + using Pool for Pool.State; + + int24 private constant MIN_TICK = -16; + int24 private constant MAX_TICK = 16; + + Pool.State private pool; + int24 private tick = 0; + + // half the cap of fee growth has happened, this can overflow + uint256 feeGrowthGlobal0X128 = type(uint256).max / 2; + uint256 feeGrowthGlobal1X128 = type(uint256).max / 2; + + // used to track how much total liquidity has been added. should never be negative + int256 totalLiquidity = 0; + // how much total growth has happened, this cannot overflow + uint256 private totalGrowth0 = 0; + uint256 private totalGrowth1 = 0; + + function increaseFeeGrowthGlobal0X128(uint256 amount) external { + require(totalGrowth0 + amount > totalGrowth0); // overflow check + feeGrowthGlobal0X128 += amount; // overflow desired + totalGrowth0 += amount; + } + + function increaseFeeGrowthGlobal1X128(uint256 amount) external { + require(totalGrowth1 + amount > totalGrowth1); // overflow check + feeGrowthGlobal1X128 += amount; // overflow desired + totalGrowth1 += amount; + } + + function setPosition(int24 tickLower, int24 tickUpper, int128 liquidityDelta) external { + require(tickLower > MIN_TICK); + require(tickUpper < MAX_TICK); + require(tickLower < tickUpper); + (bool flippedLower,) = pool.updateTick(tickLower, liquidityDelta, false); + (bool flippedUpper,) = pool.updateTick(tickUpper, liquidityDelta, true); + + if (flippedLower) { + if (liquidityDelta < 0) { + assert(pool.ticks[tickLower].liquidityGross == 0); + pool.clearTick(tickLower); + } else { + assert(pool.ticks[tickLower].liquidityGross > 0); + } + } + + if (flippedUpper) { + if (liquidityDelta < 0) { + assert(pool.ticks[tickUpper].liquidityGross == 0); + pool.clearTick(tickUpper); + } else { + assert(pool.ticks[tickUpper].liquidityGross > 0); + } + } + + totalLiquidity += liquidityDelta; + // requires should have prevented this + assert(totalLiquidity >= 0); + + if (totalLiquidity == 0) { + totalGrowth0 = 0; + totalGrowth1 = 0; + } + } + + function moveToTick(int24 target) external { + require(target > MIN_TICK); + require(target < MAX_TICK); + while (tick != target) { + if (tick < target) { + if (pool.ticks[tick + 1].liquidityGross > 0) { + pool.crossTick(tick + 1, feeGrowthGlobal0X128, feeGrowthGlobal1X128); + } + tick++; + } else { + if (pool.ticks[tick].liquidityGross > 0) { + pool.crossTick(tick, feeGrowthGlobal0X128, feeGrowthGlobal1X128); + } + tick--; + } + } + } +} diff --git a/CVLByExample/UniswapHooks/lib/uniswap-hooks/lib/v4-core/src/types/BalanceDelta.sol b/CVLByExample/UniswapHooks/lib/uniswap-hooks/lib/v4-core/src/types/BalanceDelta.sol new file mode 100644 index 00000000..4dca00e0 --- /dev/null +++ b/CVLByExample/UniswapHooks/lib/uniswap-hooks/lib/v4-core/src/types/BalanceDelta.sol @@ -0,0 +1,72 @@ +// SPDX-License-Identifier: MIT +pragma solidity ^0.8.0; + +import {SafeCast} from "../libraries/SafeCast.sol"; + +/// @dev Two `int128` values packed into a single `int256` where the upper 128 bits represent the amount0 +/// and the lower 128 bits represent the amount1. +type BalanceDelta is int256; + +using {add as +, sub as -, eq as ==, neq as !=} for BalanceDelta global; +using BalanceDeltaLibrary for BalanceDelta global; +using SafeCast for int256; + +function toBalanceDelta(int128 _amount0, int128 _amount1) pure returns (BalanceDelta balanceDelta) { + assembly ("memory-safe") { + balanceDelta := or(shl(128, _amount0), and(sub(shl(128, 1), 1), _amount1)) + } +} + +function add(BalanceDelta a, BalanceDelta b) pure returns (BalanceDelta) { + int256 res0; + int256 res1; + assembly ("memory-safe") { + let a0 := sar(128, a) + let a1 := signextend(15, a) + let b0 := sar(128, b) + let b1 := signextend(15, b) + res0 := add(a0, b0) + res1 := add(a1, b1) + } + return toBalanceDelta(res0.toInt128(), res1.toInt128()); +} + +function sub(BalanceDelta a, BalanceDelta b) pure returns (BalanceDelta) { + int256 res0; + int256 res1; + assembly ("memory-safe") { + let a0 := sar(128, a) + let a1 := signextend(15, a) + let b0 := sar(128, b) + let b1 := signextend(15, b) + res0 := sub(a0, b0) + res1 := sub(a1, b1) + } + return toBalanceDelta(res0.toInt128(), res1.toInt128()); +} + +function eq(BalanceDelta a, BalanceDelta b) pure returns (bool) { + return BalanceDelta.unwrap(a) == BalanceDelta.unwrap(b); +} + +function neq(BalanceDelta a, BalanceDelta b) pure returns (bool) { + return BalanceDelta.unwrap(a) != BalanceDelta.unwrap(b); +} + +/// @notice Library for getting the amount0 and amount1 deltas from the BalanceDelta type +library BalanceDeltaLibrary { + /// @notice A BalanceDelta of 0 + BalanceDelta public constant ZERO_DELTA = BalanceDelta.wrap(0); + + function amount0(BalanceDelta balanceDelta) internal pure returns (int128 _amount0) { + assembly ("memory-safe") { + _amount0 := sar(128, balanceDelta) + } + } + + function amount1(BalanceDelta balanceDelta) internal pure returns (int128 _amount1) { + assembly ("memory-safe") { + _amount1 := signextend(15, balanceDelta) + } + } +} diff --git a/CVLByExample/UniswapHooks/lib/uniswap-hooks/lib/v4-core/src/types/BeforeSwapDelta.sol b/CVLByExample/UniswapHooks/lib/uniswap-hooks/lib/v4-core/src/types/BeforeSwapDelta.sol new file mode 100644 index 00000000..30fd9cef --- /dev/null +++ b/CVLByExample/UniswapHooks/lib/uniswap-hooks/lib/v4-core/src/types/BeforeSwapDelta.sol @@ -0,0 +1,38 @@ +// SPDX-License-Identifier: MIT +pragma solidity ^0.8.0; + +// Return type of the beforeSwap hook. +// Upper 128 bits is the delta in specified tokens. Lower 128 bits is delta in unspecified tokens (to match the afterSwap hook) +type BeforeSwapDelta is int256; + +// Creates a BeforeSwapDelta from specified and unspecified +function toBeforeSwapDelta(int128 deltaSpecified, int128 deltaUnspecified) + pure + returns (BeforeSwapDelta beforeSwapDelta) +{ + assembly ("memory-safe") { + beforeSwapDelta := or(shl(128, deltaSpecified), and(sub(shl(128, 1), 1), deltaUnspecified)) + } +} + +/// @notice Library for getting the specified and unspecified deltas from the BeforeSwapDelta type +library BeforeSwapDeltaLibrary { + /// @notice A BeforeSwapDelta of 0 + BeforeSwapDelta public constant ZERO_DELTA = BeforeSwapDelta.wrap(0); + + /// extracts int128 from the upper 128 bits of the BeforeSwapDelta + /// returned by beforeSwap + function getSpecifiedDelta(BeforeSwapDelta delta) internal pure returns (int128 deltaSpecified) { + assembly ("memory-safe") { + deltaSpecified := sar(128, delta) + } + } + + /// extracts int128 from the lower 128 bits of the BeforeSwapDelta + /// returned by beforeSwap and afterSwap + function getUnspecifiedDelta(BeforeSwapDelta delta) internal pure returns (int128 deltaUnspecified) { + assembly ("memory-safe") { + deltaUnspecified := signextend(15, delta) + } + } +} diff --git a/CVLByExample/UniswapHooks/lib/uniswap-hooks/lib/v4-core/src/types/Currency.sol b/CVLByExample/UniswapHooks/lib/uniswap-hooks/lib/v4-core/src/types/Currency.sol new file mode 100644 index 00000000..77165340 --- /dev/null +++ b/CVLByExample/UniswapHooks/lib/uniswap-hooks/lib/v4-core/src/types/Currency.sol @@ -0,0 +1,119 @@ +// SPDX-License-Identifier: MIT +pragma solidity ^0.8.0; + +import {IERC20Minimal} from "../interfaces/external/IERC20Minimal.sol"; +import {CustomRevert} from "../libraries/CustomRevert.sol"; + +type Currency is address; + +using {greaterThan as >, lessThan as <, greaterThanOrEqualTo as >=, equals as ==} for Currency global; +using CurrencyLibrary for Currency global; + +function equals(Currency currency, Currency other) pure returns (bool) { + return Currency.unwrap(currency) == Currency.unwrap(other); +} + +function greaterThan(Currency currency, Currency other) pure returns (bool) { + return Currency.unwrap(currency) > Currency.unwrap(other); +} + +function lessThan(Currency currency, Currency other) pure returns (bool) { + return Currency.unwrap(currency) < Currency.unwrap(other); +} + +function greaterThanOrEqualTo(Currency currency, Currency other) pure returns (bool) { + return Currency.unwrap(currency) >= Currency.unwrap(other); +} + +/// @title CurrencyLibrary +/// @dev This library allows for transferring and holding native tokens and ERC20 tokens +library CurrencyLibrary { + /// @notice Additional context for ERC-7751 wrapped error when a native transfer fails + error NativeTransferFailed(); + + /// @notice Additional context for ERC-7751 wrapped error when an ERC20 transfer fails + error ERC20TransferFailed(); + + /// @notice A constant to represent the native currency + Currency public constant ADDRESS_ZERO = Currency.wrap(address(0)); + + function transfer(Currency currency, address to, uint256 amount) internal { + // altered from https://github.com/transmissions11/solmate/blob/44a9963d4c78111f77caa0e65d677b8b46d6f2e6/src/utils/SafeTransferLib.sol + // modified custom error selectors + + bool success; + if (currency.isAddressZero()) { + assembly ("memory-safe") { + // Transfer the ETH and revert if it fails. + success := call(gas(), to, amount, 0, 0, 0, 0) + } + // revert with NativeTransferFailed, containing the bubbled up error as an argument + if (!success) { + CustomRevert.bubbleUpAndRevertWith(to, bytes4(0), NativeTransferFailed.selector); + } + } else { + assembly ("memory-safe") { + // Get a pointer to some free memory. + let fmp := mload(0x40) + + // Write the abi-encoded calldata into memory, beginning with the function selector. + mstore(fmp, 0xa9059cbb00000000000000000000000000000000000000000000000000000000) + mstore(add(fmp, 4), and(to, 0xffffffffffffffffffffffffffffffffffffffff)) // Append and mask the "to" argument. + mstore(add(fmp, 36), amount) // Append the "amount" argument. Masking not required as it's a full 32 byte type. + + success := + and( + // Set success to whether the call reverted, if not we check it either + // returned exactly 1 (can't just be non-zero data), or had no return data. + or(and(eq(mload(0), 1), gt(returndatasize(), 31)), iszero(returndatasize())), + // We use 68 because the length of our calldata totals up like so: 4 + 32 * 2. + // We use 0 and 32 to copy up to 32 bytes of return data into the scratch space. + // Counterintuitively, this call must be positioned second to the or() call in the + // surrounding and() call or else returndatasize() will be zero during the computation. + call(gas(), currency, 0, fmp, 68, 0, 32) + ) + + // Now clean the memory we used + mstore(fmp, 0) // 4 byte `selector` and 28 bytes of `to` were stored here + mstore(add(fmp, 0x20), 0) // 4 bytes of `to` and 28 bytes of `amount` were stored here + mstore(add(fmp, 0x40), 0) // 4 bytes of `amount` were stored here + } + // revert with ERC20TransferFailed, containing the bubbled up error as an argument + if (!success) { + CustomRevert.bubbleUpAndRevertWith( + Currency.unwrap(currency), IERC20Minimal.transfer.selector, ERC20TransferFailed.selector + ); + } + } + } + + function balanceOfSelf(Currency currency) internal view returns (uint256) { + if (currency.isAddressZero()) { + return address(this).balance; + } else { + return IERC20Minimal(Currency.unwrap(currency)).balanceOf(address(this)); + } + } + + function balanceOf(Currency currency, address owner) internal view returns (uint256) { + if (currency.isAddressZero()) { + return owner.balance; + } else { + return IERC20Minimal(Currency.unwrap(currency)).balanceOf(owner); + } + } + + function isAddressZero(Currency currency) internal pure returns (bool) { + return Currency.unwrap(currency) == Currency.unwrap(ADDRESS_ZERO); + } + + function toId(Currency currency) internal pure returns (uint256) { + return uint160(Currency.unwrap(currency)); + } + + // If the upper 12 bytes are non-zero, they will be zero-ed out + // Therefore, fromId() and toId() are not inverses of each other + function fromId(uint256 id) internal pure returns (Currency) { + return Currency.wrap(address(uint160(id))); + } +} diff --git a/CVLByExample/UniswapHooks/lib/uniswap-hooks/lib/v4-core/src/types/PoolId.sol b/CVLByExample/UniswapHooks/lib/uniswap-hooks/lib/v4-core/src/types/PoolId.sol new file mode 100644 index 00000000..22a0b4f9 --- /dev/null +++ b/CVLByExample/UniswapHooks/lib/uniswap-hooks/lib/v4-core/src/types/PoolId.sol @@ -0,0 +1,17 @@ +// SPDX-License-Identifier: MIT +pragma solidity ^0.8.0; + +import {PoolKey} from "./PoolKey.sol"; + +type PoolId is bytes32; + +/// @notice Library for computing the ID of a pool +library PoolIdLibrary { + /// @notice Returns value equal to keccak256(abi.encode(poolKey)) + function toId(PoolKey memory poolKey) internal pure returns (PoolId poolId) { + assembly ("memory-safe") { + // 0xa0 represents the total size of the poolKey struct (5 slots of 32 bytes) + poolId := keccak256(poolKey, 0xa0) + } + } +} diff --git a/CVLByExample/UniswapHooks/lib/uniswap-hooks/lib/v4-core/src/types/PoolKey.sol b/CVLByExample/UniswapHooks/lib/uniswap-hooks/lib/v4-core/src/types/PoolKey.sol new file mode 100644 index 00000000..0fa66f93 --- /dev/null +++ b/CVLByExample/UniswapHooks/lib/uniswap-hooks/lib/v4-core/src/types/PoolKey.sol @@ -0,0 +1,22 @@ +// SPDX-License-Identifier: MIT +pragma solidity ^0.8.0; + +import {Currency} from "./Currency.sol"; +import {IHooks} from "../interfaces/IHooks.sol"; +import {PoolIdLibrary} from "./PoolId.sol"; + +using PoolIdLibrary for PoolKey global; + +/// @notice Returns the key for identifying a pool +struct PoolKey { + /// @notice The lower currency of the pool, sorted numerically + Currency currency0; + /// @notice The higher currency of the pool, sorted numerically + Currency currency1; + /// @notice The pool LP fee, capped at 1_000_000. If the highest bit is 1, the pool has a dynamic fee and must be exactly equal to 0x800000 + uint24 fee; + /// @notice Ticks that involve positions must be a multiple of tick spacing + int24 tickSpacing; + /// @notice The hooks of the pool + IHooks hooks; +} diff --git a/CVLByExample/UniswapHooks/lib/uniswap-hooks/lib/v4-core/src/types/PoolOperation.sol b/CVLByExample/UniswapHooks/lib/uniswap-hooks/lib/v4-core/src/types/PoolOperation.sol new file mode 100644 index 00000000..4982097d --- /dev/null +++ b/CVLByExample/UniswapHooks/lib/uniswap-hooks/lib/v4-core/src/types/PoolOperation.sol @@ -0,0 +1,26 @@ +// SPDX-License-Identifier: MIT +pragma solidity ^0.8.24; + +import {PoolKey} from "../types/PoolKey.sol"; +import {BalanceDelta} from "../types/BalanceDelta.sol"; + +/// @notice Parameter struct for `ModifyLiquidity` pool operations +struct ModifyLiquidityParams { + // the lower and upper tick of the position + int24 tickLower; + int24 tickUpper; + // how to modify the liquidity + int256 liquidityDelta; + // a value to set if you want unique liquidity positions at the same range + bytes32 salt; +} + +/// @notice Parameter struct for `Swap` pool operations +struct SwapParams { + /// Whether to swap token0 for token1 or vice versa + bool zeroForOne; + /// The desired input amount if negative (exactIn), or the desired output amount if positive (exactOut) + int256 amountSpecified; + /// The sqrt price at which, if reached, the swap will stop executing + uint160 sqrtPriceLimitX96; +} diff --git a/CVLByExample/UniswapHooks/lib/uniswap-hooks/lib/v4-core/src/types/Slot0.sol b/CVLByExample/UniswapHooks/lib/uniswap-hooks/lib/v4-core/src/types/Slot0.sol new file mode 100644 index 00000000..bd440d58 --- /dev/null +++ b/CVLByExample/UniswapHooks/lib/uniswap-hooks/lib/v4-core/src/types/Slot0.sol @@ -0,0 +1,94 @@ +// SPDX-License-Identifier: MIT +pragma solidity ^0.8.0; + +/** + * @dev Slot0 is a packed version of solidity structure. + * Using the packaged version saves gas by not storing the structure fields in memory slots. + * + * Layout: + * 24 bits empty | 24 bits lpFee | 12 bits protocolFee 1->0 | 12 bits protocolFee 0->1 | 24 bits tick | 160 bits sqrtPriceX96 + * + * Fields in the direction from the least significant bit: + * + * The current price + * uint160 sqrtPriceX96; + * + * The current tick + * int24 tick; + * + * Protocol fee, expressed in hundredths of a bip, upper 12 bits are for 1->0, and the lower 12 are for 0->1 + * the maximum is 1000 - meaning the maximum protocol fee is 0.1% + * the protocolFee is taken from the input first, then the lpFee is taken from the remaining input + * uint24 protocolFee; + * + * The current LP fee of the pool. If the pool is dynamic, this does not include the dynamic fee flag. + * uint24 lpFee; + */ +type Slot0 is bytes32; + +using Slot0Library for Slot0 global; + +/// @notice Library for getting and setting values in the Slot0 type +library Slot0Library { + uint160 internal constant MASK_160_BITS = 0x00FFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFF; + uint24 internal constant MASK_24_BITS = 0xFFFFFF; + + uint8 internal constant TICK_OFFSET = 160; + uint8 internal constant PROTOCOL_FEE_OFFSET = 184; + uint8 internal constant LP_FEE_OFFSET = 208; + + // #### GETTERS #### + function sqrtPriceX96(Slot0 _packed) internal pure returns (uint160 _sqrtPriceX96) { + assembly ("memory-safe") { + _sqrtPriceX96 := and(MASK_160_BITS, _packed) + } + } + + function tick(Slot0 _packed) internal pure returns (int24 _tick) { + assembly ("memory-safe") { + _tick := signextend(2, shr(TICK_OFFSET, _packed)) + } + } + + function protocolFee(Slot0 _packed) internal pure returns (uint24 _protocolFee) { + assembly ("memory-safe") { + _protocolFee := and(MASK_24_BITS, shr(PROTOCOL_FEE_OFFSET, _packed)) + } + } + + function lpFee(Slot0 _packed) internal pure returns (uint24 _lpFee) { + assembly ("memory-safe") { + _lpFee := and(MASK_24_BITS, shr(LP_FEE_OFFSET, _packed)) + } + } + + // #### SETTERS #### + function setSqrtPriceX96(Slot0 _packed, uint160 _sqrtPriceX96) internal pure returns (Slot0 _result) { + assembly ("memory-safe") { + _result := or(and(not(MASK_160_BITS), _packed), and(MASK_160_BITS, _sqrtPriceX96)) + } + } + + function setTick(Slot0 _packed, int24 _tick) internal pure returns (Slot0 _result) { + assembly ("memory-safe") { + _result := or(and(not(shl(TICK_OFFSET, MASK_24_BITS)), _packed), shl(TICK_OFFSET, and(MASK_24_BITS, _tick))) + } + } + + function setProtocolFee(Slot0 _packed, uint24 _protocolFee) internal pure returns (Slot0 _result) { + assembly ("memory-safe") { + _result := + or( + and(not(shl(PROTOCOL_FEE_OFFSET, MASK_24_BITS)), _packed), + shl(PROTOCOL_FEE_OFFSET, and(MASK_24_BITS, _protocolFee)) + ) + } + } + + function setLpFee(Slot0 _packed, uint24 _lpFee) internal pure returns (Slot0 _result) { + assembly ("memory-safe") { + _result := + or(and(not(shl(LP_FEE_OFFSET, MASK_24_BITS)), _packed), shl(LP_FEE_OFFSET, and(MASK_24_BITS, _lpFee))) + } + } +} diff --git a/CVLByExample/UniswapHooks/lib/uniswap-hooks/lib/v4-periphery/src/PositionDescriptor.sol b/CVLByExample/UniswapHooks/lib/uniswap-hooks/lib/v4-periphery/src/PositionDescriptor.sol new file mode 100644 index 00000000..3aa744ee --- /dev/null +++ b/CVLByExample/UniswapHooks/lib/uniswap-hooks/lib/v4-periphery/src/PositionDescriptor.sol @@ -0,0 +1,127 @@ +// SPDX-License-Identifier: MIT +pragma solidity 0.8.26; + +import {IPoolManager} from "@uniswap/v4-core/src/interfaces/IPoolManager.sol"; +import {Currency} from "@uniswap/v4-core/src/types/Currency.sol"; +import {StateLibrary} from "@uniswap/v4-core/src/libraries/StateLibrary.sol"; +import {PoolKey} from "@uniswap/v4-core/src/types/PoolKey.sol"; +import {PoolId} from "@uniswap/v4-core/src/types/PoolId.sol"; +import {IPositionManager} from "./interfaces/IPositionManager.sol"; +import {IPositionDescriptor} from "./interfaces/IPositionDescriptor.sol"; +import {PositionInfo} from "./libraries/PositionInfoLibrary.sol"; +import {Descriptor} from "./libraries/Descriptor.sol"; +import {CurrencyRatioSortOrder} from "./libraries/CurrencyRatioSortOrder.sol"; +import {SafeCurrencyMetadata} from "./libraries/SafeCurrencyMetadata.sol"; + +/// @title Describes NFT token positions +/// @notice Produces a string containing the data URI for a JSON metadata string +contract PositionDescriptor is IPositionDescriptor { + using StateLibrary for IPoolManager; + + // mainnet addresses + address private constant DAI = 0x6B175474E89094C44Da98b954EedeAC495271d0F; + address private constant USDC = 0xA0b86991c6218b36c1d19D4a2e9Eb0cE3606eB48; + address private constant USDT = 0xdAC17F958D2ee523a2206206994597C13D831ec7; + address private constant TBTC = 0x8dAEBADE922dF735c38C80C7eBD708Af50815fAa; + address private constant WBTC = 0x2260FAC5E5542a773Aa44fBCfeDf7C193bc2C599; + + address public immutable wrappedNative; + bytes32 private immutable nativeCurrencyLabelBytes; + + IPoolManager public immutable poolManager; + + constructor(IPoolManager _poolManager, address _wrappedNative, bytes32 _nativeCurrencyLabelBytes) { + poolManager = _poolManager; + wrappedNative = _wrappedNative; + nativeCurrencyLabelBytes = _nativeCurrencyLabelBytes; + } + + /// @notice Returns the native currency label as a string + function nativeCurrencyLabel() public view returns (string memory) { + uint256 len = 0; + while (len < 32 && nativeCurrencyLabelBytes[len] != 0) { + len++; + } + bytes memory b = new bytes(len); + for (uint256 i = 0; i < len; i++) { + b[i] = nativeCurrencyLabelBytes[i]; + } + return string(b); + } + + /// @inheritdoc IPositionDescriptor + function tokenURI(IPositionManager positionManager, uint256 tokenId) + external + view + override + returns (string memory) + { + (PoolKey memory poolKey, PositionInfo positionInfo) = positionManager.getPoolAndPositionInfo(tokenId); + if (positionInfo.poolId() == 0) { + revert InvalidTokenId(tokenId); + } + (, int24 tick,,) = poolManager.getSlot0(poolKey.toId()); + + address currency0 = Currency.unwrap(poolKey.currency0); + address currency1 = Currency.unwrap(poolKey.currency1); + + // If possible, flip currencies to get the larger currency as the base currency, so that the price (quote/base) is more readable + // flip if currency0 priority is greater than currency1 priority + bool _flipRatio = flipRatio(currency0, currency1); + + // If not flipped, quote currency is currency1, base currency is currency0 + // If flipped, quote currency is currency0, base currency is currency1 + address quoteCurrency = !_flipRatio ? currency1 : currency0; + address baseCurrency = !_flipRatio ? currency0 : currency1; + + return Descriptor.constructTokenURI( + Descriptor.ConstructTokenURIParams({ + tokenId: tokenId, + quoteCurrency: quoteCurrency, + baseCurrency: baseCurrency, + quoteCurrencySymbol: SafeCurrencyMetadata.currencySymbol(quoteCurrency, nativeCurrencyLabel()), + baseCurrencySymbol: SafeCurrencyMetadata.currencySymbol(baseCurrency, nativeCurrencyLabel()), + quoteCurrencyDecimals: SafeCurrencyMetadata.currencyDecimals(quoteCurrency), + baseCurrencyDecimals: SafeCurrencyMetadata.currencyDecimals(baseCurrency), + flipRatio: _flipRatio, + tickLower: positionInfo.tickLower(), + tickUpper: positionInfo.tickUpper(), + tickCurrent: tick, + tickSpacing: poolKey.tickSpacing, + fee: poolKey.fee, + poolManager: address(poolManager), + hooks: address(poolKey.hooks) + }) + ); + } + + /// @inheritdoc IPositionDescriptor + function flipRatio(address currency0, address currency1) public view returns (bool) { + return currencyRatioPriority(currency0) > currencyRatioPriority(currency1); + } + + /// @inheritdoc IPositionDescriptor + function currencyRatioPriority(address currency) public view returns (int256) { + // Currencies in order of priority on mainnet: USDC, USDT, DAI, (ETH, WETH), TBTC, WBTC + // wrapped native is different address on different chains. passed in constructor + + // native currency + if (currency == address(0) || currency == wrappedNative) { + return CurrencyRatioSortOrder.DENOMINATOR; + } + if (block.chainid == 1) { + if (currency == USDC) { + return CurrencyRatioSortOrder.NUMERATOR_MOST; + } else if (currency == USDT) { + return CurrencyRatioSortOrder.NUMERATOR_MORE; + } else if (currency == DAI) { + return CurrencyRatioSortOrder.NUMERATOR; + } else if (currency == TBTC) { + return CurrencyRatioSortOrder.DENOMINATOR_MORE; + } else if (currency == WBTC) { + return CurrencyRatioSortOrder.DENOMINATOR_MOST; + } + } + return 0; + } +} diff --git a/CVLByExample/UniswapHooks/lib/uniswap-hooks/lib/v4-periphery/src/PositionManager.sol b/CVLByExample/UniswapHooks/lib/uniswap-hooks/lib/v4-periphery/src/PositionManager.sol new file mode 100644 index 00000000..71feac7b --- /dev/null +++ b/CVLByExample/UniswapHooks/lib/uniswap-hooks/lib/v4-periphery/src/PositionManager.sol @@ -0,0 +1,561 @@ +// SPDX-License-Identifier: MIT +pragma solidity 0.8.26; + +import {IPoolManager} from "@uniswap/v4-core/src/interfaces/IPoolManager.sol"; +import {PoolKey} from "@uniswap/v4-core/src/types/PoolKey.sol"; +import {Currency, CurrencyLibrary} from "@uniswap/v4-core/src/types/Currency.sol"; +import {BalanceDelta} from "@uniswap/v4-core/src/types/BalanceDelta.sol"; +import {SafeCast} from "@uniswap/v4-core/src/libraries/SafeCast.sol"; +import {Position} from "@uniswap/v4-core/src/libraries/Position.sol"; +import {StateLibrary} from "@uniswap/v4-core/src/libraries/StateLibrary.sol"; +import {TransientStateLibrary} from "@uniswap/v4-core/src/libraries/TransientStateLibrary.sol"; +import {IAllowanceTransfer} from "permit2/src/interfaces/IAllowanceTransfer.sol"; +import {TickMath} from "@uniswap/v4-core/src/libraries/TickMath.sol"; +import {ModifyLiquidityParams, SwapParams} from "@uniswap/v4-core/src/types/PoolOperation.sol"; + +import {IPositionDescriptor} from "./interfaces/IPositionDescriptor.sol"; +import {ERC721Permit_v4} from "./base/ERC721Permit_v4.sol"; +import {ReentrancyLock} from "./base/ReentrancyLock.sol"; +import {IPositionManager} from "./interfaces/IPositionManager.sol"; +import {Multicall_v4} from "./base/Multicall_v4.sol"; +import {PoolInitializer_v4} from "./base/PoolInitializer_v4.sol"; +import {DeltaResolver} from "./base/DeltaResolver.sol"; +import {BaseActionsRouter} from "./base/BaseActionsRouter.sol"; +import {Actions} from "./libraries/Actions.sol"; +import {Notifier} from "./base/Notifier.sol"; +import {CalldataDecoder} from "./libraries/CalldataDecoder.sol"; +import {Permit2Forwarder} from "./base/Permit2Forwarder.sol"; +import {SlippageCheck} from "./libraries/SlippageCheck.sol"; +import {PositionInfo, PositionInfoLibrary} from "./libraries/PositionInfoLibrary.sol"; +import {LiquidityAmounts} from "./libraries/LiquidityAmounts.sol"; +import {NativeWrapper} from "./base/NativeWrapper.sol"; +import {IWETH9} from "./interfaces/external/IWETH9.sol"; + +// 444444444 +// 444444444444 444444 +// 444 44 4444 +// 44 4 44 444 +// 44 44 44 44 +// 44 44 44 44 +// 44 444444 44 44 +// 444 4444 4444 44 +// 44 4444 444444444444444444 +// 44 44 4 44444 444444 +// 444444444444 44 4 444 44 +// 44 44444444 4 444 44 +// 444 44 44 444 +// 44 4 4444444444444444444444444 4444444444 4444 +// 44 44444444444444444444444444 444 44444 +// 444 44 44444444444444444444444444 +// 4444 444444444444444444444444 +// 4444 444444 444444444444444 +// 44444 444444 44444444444444444444444 +// 444444444444444 4 44444 44444444444444444444 +// 444 444444444444444444444444444 +// 444 44444 44444444444 44444444 +// 444 4 44444444444444 444444444 +// 4444 444 44 4444444444444 44444444 +// 44 44444 44444444 44444444444444444444 44444 +// 444 444444 4444 4444 444444444444444444 44 4444 +// 4444 44 44444 44444444444 444444444444444444444 44444444 +// 44444 4444 4444444444 444444444444444444444444 44444 +// 44444 44444 444 444444 4444444444444444444444444 44444 +// 4444 44 44 4 44444444444444444444444444 444 44444 +// 44444444 444 44 4 4 444444 4 44444444444444444444444444444 4444444 +// 444444 44 44444444444 44444444444444 444444444444444 444444 +// 444444 44 4444 44444 44 44444444444444444444444 4444444 44444 +// 44 444444 44 444444444 444 4444444444444444444444444444444444 4444444 +// 44 4444444444444 44 44 44 4444444444444444444444444444444 444444 +// 44 44444444444444444444444444 4 44 4444444444444444444444444444444 4 444444 +// 4 4444 4 4 4444444444444444444444444 44 4444444 +// 4444 4444444444444444444444444 4 4444 44444444 +// 4444 444444444444444444444444 44444 44444 4444444444 +// 44444 44 444444444444444444444444444444444444444444444444444444 +// 44444444444 4444444444444444444444444444444444444444444444444444444 +// 4444444444444 44444444444444444444444444444444444444444444444444444444 +// 444444444444444 444444444444444444444444444444444444444444444444444444444 +// 44444444444444444 4444444444444444444444444444444444444444444444444444444444 +// 44444444444444444 44444444444444444444444444444444444444444444444444444444 +// 44444444444444444444 444444444444444444444444444444444444444444444444444444444 +// 444444444444444444444 444444444444444444444444444444444444444444444444444444444 +// 444444444444444444444 4444444444444444444444444444444444444444444444444444444 +// 44444444444444444444444444444444444444444444444444444444444444444444444444444 +// 444444444444444444444444444444444444444444444444444444444444444444444444444 +// 44444444444444444444444444444444444444444444444444444444444444444444444444 +// 44444444444444444444444444444444444444444444444444 444444444444444444 +// 444444444444444444444444444444444444444444444444 44444444444444444444 +// 444 444 444 44 444444444444444444444 4444 444444444444444444444 +// 444 444 44 44 44444444 4444444444444 44444444444444444444444 +// 444 444 4444 4444 4444444444444444 44444444444444444444444444 +// 4444444444444444444444444444444444444444 44444444444444444444444444444 +// 444 4444444444444444444444444 44444444444444444444444444444444 +// 4444444 444444444444 4444444444444444444444444444444444 +// 4444444444 44444444444444444444444444444444444 +// 444444444444444444444444444444444444444444444444444444 +// 44444444444444444444444444444444444444444 +// 4444444444444444444 + +/// @notice The PositionManager (PosM) contract is responsible for creating liquidity positions on v4. +/// PosM mints and manages ERC721 tokens associated with each position. +contract PositionManager is + IPositionManager, + ERC721Permit_v4, + PoolInitializer_v4, + Multicall_v4, + DeltaResolver, + ReentrancyLock, + BaseActionsRouter, + Notifier, + Permit2Forwarder, + NativeWrapper +{ + using StateLibrary for IPoolManager; + using TransientStateLibrary for IPoolManager; + using SafeCast for uint256; + using SafeCast for int256; + using CalldataDecoder for bytes; + using SlippageCheck for BalanceDelta; + + /// @inheritdoc IPositionManager + /// @dev The ID of the next token that will be minted. Skips 0 + uint256 public nextTokenId = 1; + + IPositionDescriptor public immutable tokenDescriptor; + + mapping(uint256 tokenId => PositionInfo info) public positionInfo; + mapping(bytes25 poolId => PoolKey poolKey) public poolKeys; + + constructor( + IPoolManager _poolManager, + IAllowanceTransfer _permit2, + uint256 _unsubscribeGasLimit, + IPositionDescriptor _tokenDescriptor, + IWETH9 _weth9 + ) + BaseActionsRouter(_poolManager) + Permit2Forwarder(_permit2) + ERC721Permit_v4("Uniswap v4 Positions NFT", "UNI-V4-POSM") + Notifier(_unsubscribeGasLimit) + NativeWrapper(_weth9) + { + tokenDescriptor = _tokenDescriptor; + } + + /// @notice Reverts if the deadline has passed + /// @param deadline The timestamp at which the call is no longer valid, passed in by the caller + modifier checkDeadline(uint256 deadline) { + if (block.timestamp > deadline) revert DeadlinePassed(deadline); + _; + } + + /// @notice Reverts if the caller is not the owner or approved for the ERC721 token + /// @param caller The address of the caller + /// @param tokenId the unique identifier of the ERC721 token + /// @dev either msg.sender or msgSender() is passed in as the caller + /// msgSender() should ONLY be used if this is called from within the unlockCallback, unless the codepath has reentrancy protection + modifier onlyIfApproved(address caller, uint256 tokenId) override { + if (!_isApprovedOrOwner(caller, tokenId)) revert NotApproved(caller); + _; + } + + /// @notice Enforces that the PoolManager is locked. + modifier onlyIfPoolManagerLocked() override { + if (poolManager.isUnlocked()) revert PoolManagerMustBeLocked(); + _; + } + + function tokenURI(uint256 tokenId) public view override returns (string memory) { + return IPositionDescriptor(tokenDescriptor).tokenURI(this, tokenId); + } + + /// @inheritdoc IPositionManager + function modifyLiquidities(bytes calldata unlockData, uint256 deadline) + external + payable + isNotLocked + checkDeadline(deadline) + { + _executeActions(unlockData); + } + + /// @inheritdoc IPositionManager + function modifyLiquiditiesWithoutUnlock(bytes calldata actions, bytes[] calldata params) + external + payable + isNotLocked + { + _executeActionsWithoutUnlock(actions, params); + } + + /// @inheritdoc BaseActionsRouter + function msgSender() public view override returns (address) { + return _getLocker(); + } + + function _handleAction(uint256 action, bytes calldata params) internal virtual override { + if (action < Actions.SETTLE) { + if (action == Actions.INCREASE_LIQUIDITY) { + (uint256 tokenId, uint256 liquidity, uint128 amount0Max, uint128 amount1Max, bytes calldata hookData) = + params.decodeModifyLiquidityParams(); + _increase(tokenId, liquidity, amount0Max, amount1Max, hookData); + return; + } else if (action == Actions.INCREASE_LIQUIDITY_FROM_DELTAS) { + (uint256 tokenId, uint128 amount0Max, uint128 amount1Max, bytes calldata hookData) = + params.decodeIncreaseLiquidityFromDeltasParams(); + _increaseFromDeltas(tokenId, amount0Max, amount1Max, hookData); + return; + } else if (action == Actions.DECREASE_LIQUIDITY) { + (uint256 tokenId, uint256 liquidity, uint128 amount0Min, uint128 amount1Min, bytes calldata hookData) = + params.decodeModifyLiquidityParams(); + _decrease(tokenId, liquidity, amount0Min, amount1Min, hookData); + return; + } else if (action == Actions.MINT_POSITION) { + ( + PoolKey calldata poolKey, + int24 tickLower, + int24 tickUpper, + uint256 liquidity, + uint128 amount0Max, + uint128 amount1Max, + address owner, + bytes calldata hookData + ) = params.decodeMintParams(); + _mint(poolKey, tickLower, tickUpper, liquidity, amount0Max, amount1Max, _mapRecipient(owner), hookData); + return; + } else if (action == Actions.MINT_POSITION_FROM_DELTAS) { + ( + PoolKey calldata poolKey, + int24 tickLower, + int24 tickUpper, + uint128 amount0Max, + uint128 amount1Max, + address owner, + bytes calldata hookData + ) = params.decodeMintFromDeltasParams(); + _mintFromDeltas(poolKey, tickLower, tickUpper, amount0Max, amount1Max, _mapRecipient(owner), hookData); + return; + } else if (action == Actions.BURN_POSITION) { + // Will automatically decrease liquidity to 0 if the position is not already empty. + (uint256 tokenId, uint128 amount0Min, uint128 amount1Min, bytes calldata hookData) = + params.decodeBurnParams(); + _burn(tokenId, amount0Min, amount1Min, hookData); + return; + } + } else { + if (action == Actions.SETTLE_PAIR) { + (Currency currency0, Currency currency1) = params.decodeCurrencyPair(); + _settlePair(currency0, currency1); + return; + } else if (action == Actions.TAKE_PAIR) { + (Currency currency0, Currency currency1, address recipient) = params.decodeCurrencyPairAndAddress(); + _takePair(currency0, currency1, _mapRecipient(recipient)); + return; + } else if (action == Actions.SETTLE) { + (Currency currency, uint256 amount, bool payerIsUser) = params.decodeCurrencyUint256AndBool(); + _settle(currency, _mapPayer(payerIsUser), _mapSettleAmount(amount, currency)); + return; + } else if (action == Actions.TAKE) { + (Currency currency, address recipient, uint256 amount) = params.decodeCurrencyAddressAndUint256(); + _take(currency, _mapRecipient(recipient), _mapTakeAmount(amount, currency)); + return; + } else if (action == Actions.CLOSE_CURRENCY) { + Currency currency = params.decodeCurrency(); + _close(currency); + return; + } else if (action == Actions.CLEAR_OR_TAKE) { + (Currency currency, uint256 amountMax) = params.decodeCurrencyAndUint256(); + _clearOrTake(currency, amountMax); + return; + } else if (action == Actions.SWEEP) { + (Currency currency, address to) = params.decodeCurrencyAndAddress(); + _sweep(currency, _mapRecipient(to)); + return; + } else if (action == Actions.WRAP) { + uint256 amount = params.decodeUint256(); + _wrap(_mapWrapUnwrapAmount(CurrencyLibrary.ADDRESS_ZERO, amount, Currency.wrap(address(WETH9)))); + return; + } else if (action == Actions.UNWRAP) { + uint256 amount = params.decodeUint256(); + _unwrap(_mapWrapUnwrapAmount(Currency.wrap(address(WETH9)), amount, CurrencyLibrary.ADDRESS_ZERO)); + return; + } + } + revert UnsupportedAction(action); + } + + /// @dev Calling increase with 0 liquidity will credit the caller with any underlying fees of the position + function _increase( + uint256 tokenId, + uint256 liquidity, + uint128 amount0Max, + uint128 amount1Max, + bytes calldata hookData + ) internal onlyIfApproved(msgSender(), tokenId) { + (PoolKey memory poolKey, PositionInfo info) = getPoolAndPositionInfo(tokenId); + + // Note: The tokenId is used as the salt for this position, so every minted position has unique storage in the pool manager. + (BalanceDelta liquidityDelta, BalanceDelta feesAccrued) = + _modifyLiquidity(info, poolKey, liquidity.toInt256(), bytes32(tokenId), hookData); + // Slippage checks should be done on the principal liquidityDelta which is the liquidityDelta - feesAccrued + (liquidityDelta - feesAccrued).validateMaxIn(amount0Max, amount1Max); + } + + /// @dev The liquidity delta is derived from open deltas in the pool manager. + function _increaseFromDeltas(uint256 tokenId, uint128 amount0Max, uint128 amount1Max, bytes calldata hookData) + internal + onlyIfApproved(msgSender(), tokenId) + { + (PoolKey memory poolKey, PositionInfo info) = getPoolAndPositionInfo(tokenId); + + uint256 liquidity; + { + (uint160 sqrtPriceX96,,,) = poolManager.getSlot0(poolKey.toId()); + + // Use the credit on the pool manager as the amounts for the mint. + liquidity = LiquidityAmounts.getLiquidityForAmounts( + sqrtPriceX96, + TickMath.getSqrtPriceAtTick(info.tickLower()), + TickMath.getSqrtPriceAtTick(info.tickUpper()), + _getFullCredit(poolKey.currency0), + _getFullCredit(poolKey.currency1) + ); + } + + // Note: The tokenId is used as the salt for this position, so every minted position has unique storage in the pool manager. + (BalanceDelta liquidityDelta, BalanceDelta feesAccrued) = + _modifyLiquidity(info, poolKey, liquidity.toInt256(), bytes32(tokenId), hookData); + // Slippage checks should be done on the principal liquidityDelta which is the liquidityDelta - feesAccrued + (liquidityDelta - feesAccrued).validateMaxIn(amount0Max, amount1Max); + } + + /// @dev Calling decrease with 0 liquidity will credit the caller with any underlying fees of the position + function _decrease( + uint256 tokenId, + uint256 liquidity, + uint128 amount0Min, + uint128 amount1Min, + bytes calldata hookData + ) internal onlyIfApproved(msgSender(), tokenId) { + (PoolKey memory poolKey, PositionInfo info) = getPoolAndPositionInfo(tokenId); + + // Note: the tokenId is used as the salt. + (BalanceDelta liquidityDelta, BalanceDelta feesAccrued) = + _modifyLiquidity(info, poolKey, -(liquidity.toInt256()), bytes32(tokenId), hookData); + // Slippage checks should be done on the principal liquidityDelta which is the liquidityDelta - feesAccrued + (liquidityDelta - feesAccrued).validateMinOut(amount0Min, amount1Min); + } + + function _mint( + PoolKey calldata poolKey, + int24 tickLower, + int24 tickUpper, + uint256 liquidity, + uint128 amount0Max, + uint128 amount1Max, + address owner, + bytes calldata hookData + ) internal { + // mint receipt token + uint256 tokenId; + // tokenId is assigned to current nextTokenId before incrementing it + unchecked { + tokenId = nextTokenId++; + } + _mint(owner, tokenId); + + // Initialize the position info + PositionInfo info = PositionInfoLibrary.initialize(poolKey, tickLower, tickUpper); + positionInfo[tokenId] = info; + + // Store the poolKey if it is not already stored. + // On UniswapV4, the minimum tick spacing is 1, which means that if the tick spacing is 0, the pool key has not been set. + bytes25 poolId = info.poolId(); + if (poolKeys[poolId].tickSpacing == 0) { + poolKeys[poolId] = poolKey; + } + + // fee delta can be ignored as this is a new position + (BalanceDelta liquidityDelta,) = + _modifyLiquidity(info, poolKey, liquidity.toInt256(), bytes32(tokenId), hookData); + liquidityDelta.validateMaxIn(amount0Max, amount1Max); + } + + function _mintFromDeltas( + PoolKey calldata poolKey, + int24 tickLower, + int24 tickUpper, + uint128 amount0Max, + uint128 amount1Max, + address owner, + bytes calldata hookData + ) internal { + (uint160 sqrtPriceX96,,,) = poolManager.getSlot0(poolKey.toId()); + + // Use the credit on the pool manager as the amounts for the mint. + uint256 liquidity = LiquidityAmounts.getLiquidityForAmounts( + sqrtPriceX96, + TickMath.getSqrtPriceAtTick(tickLower), + TickMath.getSqrtPriceAtTick(tickUpper), + _getFullCredit(poolKey.currency0), + _getFullCredit(poolKey.currency1) + ); + + _mint(poolKey, tickLower, tickUpper, liquidity, amount0Max, amount1Max, owner, hookData); + } + + /// @dev this is overloaded with ERC721Permit_v4._burn + function _burn(uint256 tokenId, uint128 amount0Min, uint128 amount1Min, bytes calldata hookData) + internal + onlyIfApproved(msgSender(), tokenId) + { + (PoolKey memory poolKey, PositionInfo info) = getPoolAndPositionInfo(tokenId); + + uint256 liquidity = uint256(_getLiquidity(tokenId, poolKey, info.tickLower(), info.tickUpper())); + + address owner = ownerOf(tokenId); + + // Clear the position info. + positionInfo[tokenId] = PositionInfoLibrary.EMPTY_POSITION_INFO; + // Burn the token. + _burn(tokenId); + + // Can only call modify if there is non zero liquidity. + BalanceDelta feesAccrued; + if (liquidity > 0) { + BalanceDelta liquidityDelta; + // do not use _modifyLiquidity as we do not need to notify on modification for burns. + ModifyLiquidityParams memory params = ModifyLiquidityParams({ + tickLower: info.tickLower(), + tickUpper: info.tickUpper(), + liquidityDelta: -(liquidity.toInt256()), + salt: bytes32(tokenId) + }); + (liquidityDelta, feesAccrued) = poolManager.modifyLiquidity(poolKey, params, hookData); + // Slippage checks should be done on the principal liquidityDelta which is the liquidityDelta - feesAccrued + (liquidityDelta - feesAccrued).validateMinOut(amount0Min, amount1Min); + } + + // deletes then notifies the subscriber + if (info.hasSubscriber()) _removeSubscriberAndNotifyBurn(tokenId, owner, info, liquidity, feesAccrued); + } + + function _settlePair(Currency currency0, Currency currency1) internal { + // the locker is the payer when settling + address caller = msgSender(); + _settle(currency0, caller, _getFullDebt(currency0)); + _settle(currency1, caller, _getFullDebt(currency1)); + } + + function _takePair(Currency currency0, Currency currency1, address recipient) internal { + _take(currency0, recipient, _getFullCredit(currency0)); + _take(currency1, recipient, _getFullCredit(currency1)); + } + + function _close(Currency currency) internal { + // this address has applied all deltas on behalf of the user/owner + // it is safe to close this entire delta because of slippage checks throughout the batched calls. + int256 currencyDelta = poolManager.currencyDelta(address(this), currency); + + // the locker is the payer or receiver + address caller = msgSender(); + if (currencyDelta < 0) { + // Casting is safe due to limits on the total supply of a pool + _settle(currency, caller, uint256(-currencyDelta)); + } else { + _take(currency, caller, uint256(currencyDelta)); + } + } + + /// @dev integrators may elect to forfeit positive deltas with clear + /// if the forfeit amount exceeds the user-specified max, the amount is taken instead + /// if there is no credit, no call is made. + function _clearOrTake(Currency currency, uint256 amountMax) internal { + uint256 delta = _getFullCredit(currency); + if (delta == 0) return; + + // forfeit the delta if its less than or equal to the user-specified limit + if (delta <= amountMax) { + poolManager.clear(currency, delta); + } else { + _take(currency, msgSender(), delta); + } + } + + /// @notice Sweeps the entire contract balance of specified currency to the recipient + function _sweep(Currency currency, address to) internal { + uint256 balance = currency.balanceOfSelf(); + if (balance > 0) currency.transfer(to, balance); + } + + /// @dev if there is a subscriber attached to the position, this function will notify the subscriber + function _modifyLiquidity( + PositionInfo info, + PoolKey memory poolKey, + int256 liquidityChange, + bytes32 salt, + bytes calldata hookData + ) internal returns (BalanceDelta liquidityDelta, BalanceDelta feesAccrued) { + (liquidityDelta, feesAccrued) = poolManager.modifyLiquidity( + poolKey, + ModifyLiquidityParams({ + tickLower: info.tickLower(), + tickUpper: info.tickUpper(), + liquidityDelta: liquidityChange, + salt: salt + }), + hookData + ); + + if (info.hasSubscriber()) { + _notifyModifyLiquidity(uint256(salt), liquidityChange, feesAccrued); + } + } + + // implementation of abstract function DeltaResolver._pay + function _pay(Currency currency, address payer, uint256 amount) internal override { + if (payer == address(this)) { + currency.transfer(address(poolManager), amount); + } else { + // Casting from uint256 to uint160 is safe due to limits on the total supply of a pool + permit2.transferFrom(payer, address(poolManager), uint160(amount), Currency.unwrap(currency)); + } + } + + /// @notice an internal helper used by Notifier + function _setSubscribed(uint256 tokenId) internal override { + positionInfo[tokenId] = positionInfo[tokenId].setSubscribe(); + } + + /// @notice an internal helper used by Notifier + function _setUnsubscribed(uint256 tokenId) internal override { + positionInfo[tokenId] = positionInfo[tokenId].setUnsubscribe(); + } + + /// @dev overrides solmate transferFrom in case a notification to subscribers is needed + /// @dev will revert if pool manager is locked + function transferFrom(address from, address to, uint256 id) public virtual override onlyIfPoolManagerLocked { + super.transferFrom(from, to, id); + if (positionInfo[id].hasSubscriber()) _unsubscribe(id); + } + + /// @inheritdoc IPositionManager + function getPoolAndPositionInfo(uint256 tokenId) public view returns (PoolKey memory poolKey, PositionInfo info) { + info = positionInfo[tokenId]; + poolKey = poolKeys[info.poolId()]; + } + + /// @inheritdoc IPositionManager + function getPositionLiquidity(uint256 tokenId) external view returns (uint128 liquidity) { + (PoolKey memory poolKey, PositionInfo info) = getPoolAndPositionInfo(tokenId); + liquidity = _getLiquidity(tokenId, poolKey, info.tickLower(), info.tickUpper()); + } + + function _getLiquidity(uint256 tokenId, PoolKey memory poolKey, int24 tickLower, int24 tickUpper) + internal + view + returns (uint128 liquidity) + { + bytes32 positionId = Position.calculatePositionKey(address(this), tickLower, tickUpper, bytes32(tokenId)); + liquidity = poolManager.getPositionLiquidity(poolKey.toId(), positionId); + } +} diff --git a/CVLByExample/UniswapHooks/lib/uniswap-hooks/lib/v4-periphery/src/UniswapV4DeployerCompetition.sol b/CVLByExample/UniswapHooks/lib/uniswap-hooks/lib/v4-periphery/src/UniswapV4DeployerCompetition.sol new file mode 100644 index 00000000..deec4177 --- /dev/null +++ b/CVLByExample/UniswapHooks/lib/uniswap-hooks/lib/v4-periphery/src/UniswapV4DeployerCompetition.sol @@ -0,0 +1,85 @@ +// SPDX-License-Identifier: MIT +pragma solidity 0.8.26; + +import {Create2} from "@openzeppelin/contracts/utils/Create2.sol"; +import {VanityAddressLib} from "./libraries/VanityAddressLib.sol"; +import {IUniswapV4DeployerCompetition} from "./interfaces/IUniswapV4DeployerCompetition.sol"; + +/// @title UniswapV4DeployerCompetition +/// @notice A contract to crowdsource a salt for the best Uniswap V4 address +contract UniswapV4DeployerCompetition is IUniswapV4DeployerCompetition { + using VanityAddressLib for address; + + /// @dev The salt for the best address found so far + bytes32 public bestAddressSalt; + /// @dev The submitter of the best address found so far + address public bestAddressSubmitter; + + /// @dev The deadline for the competition + uint256 public immutable competitionDeadline; + /// @dev The init code hash of the V4 contract + bytes32 public immutable initCodeHash; + + /// @dev The deployer who can initiate the deployment of the v4 PoolManager, until the exclusive deploy deadline. + /// @dev After this deadline anyone can deploy. + address public immutable deployer; + /// @dev The deadline for exclusive deployment by deployer after deadline + uint256 public immutable exclusiveDeployDeadline; + + constructor( + bytes32 _initCodeHash, + uint256 _competitionDeadline, + address _exclusiveDeployer, + uint256 _exclusiveDeployLength + ) { + initCodeHash = _initCodeHash; + competitionDeadline = _competitionDeadline; + exclusiveDeployDeadline = _competitionDeadline + _exclusiveDeployLength; + deployer = _exclusiveDeployer; + } + + /// @inheritdoc IUniswapV4DeployerCompetition + function updateBestAddress(bytes32 salt) external { + if (block.timestamp > competitionDeadline) { + revert CompetitionOver(block.timestamp, competitionDeadline); + } + + address saltSubAddress = address(bytes20(salt)); + if (saltSubAddress != msg.sender && saltSubAddress != address(0)) revert InvalidSender(salt, msg.sender); + + address newAddress = Create2.computeAddress(salt, initCodeHash); + address _bestAddress = bestAddress(); + if (!newAddress.betterThan(_bestAddress)) { + revert WorseAddress(newAddress, _bestAddress, newAddress.score(), _bestAddress.score()); + } + + bestAddressSalt = salt; + bestAddressSubmitter = msg.sender; + + emit NewAddressFound(newAddress, msg.sender, newAddress.score()); + } + + /// @inheritdoc IUniswapV4DeployerCompetition + function deploy(bytes memory bytecode) external { + if (keccak256(bytecode) != initCodeHash) { + revert InvalidBytecode(); + } + + if (block.timestamp <= competitionDeadline) { + revert CompetitionNotOver(block.timestamp, competitionDeadline); + } + + if (msg.sender != deployer && block.timestamp <= exclusiveDeployDeadline) { + // anyone can deploy after the deadline + revert NotAllowedToDeploy(msg.sender, deployer); + } + + // the owner of the contract must be encoded in the bytecode + Create2.deploy(0, bestAddressSalt, bytecode); + } + + /// @dev returns the best address found so far + function bestAddress() public view returns (address) { + return Create2.computeAddress(bestAddressSalt, initCodeHash); + } +} diff --git a/CVLByExample/UniswapHooks/lib/uniswap-hooks/lib/v4-periphery/src/V4Router.sol b/CVLByExample/UniswapHooks/lib/uniswap-hooks/lib/v4-periphery/src/V4Router.sol new file mode 100644 index 00000000..8e25953d --- /dev/null +++ b/CVLByExample/UniswapHooks/lib/uniswap-hooks/lib/v4-periphery/src/V4Router.sol @@ -0,0 +1,173 @@ +// SPDX-License-Identifier: MIT +pragma solidity 0.8.26; + +import {IPoolManager} from "@uniswap/v4-core/src/interfaces/IPoolManager.sol"; +import {BalanceDelta} from "@uniswap/v4-core/src/types/BalanceDelta.sol"; +import {PoolKey} from "@uniswap/v4-core/src/types/PoolKey.sol"; +import {Currency} from "@uniswap/v4-core/src/types/Currency.sol"; +import {TickMath} from "@uniswap/v4-core/src/libraries/TickMath.sol"; +import {SafeCast} from "@uniswap/v4-core/src/libraries/SafeCast.sol"; + +import {PathKey} from "./libraries/PathKey.sol"; +import {CalldataDecoder} from "./libraries/CalldataDecoder.sol"; +import {IV4Router} from "./interfaces/IV4Router.sol"; +import {BaseActionsRouter} from "./base/BaseActionsRouter.sol"; +import {DeltaResolver} from "./base/DeltaResolver.sol"; +import {Actions} from "./libraries/Actions.sol"; +import {ActionConstants} from "./libraries/ActionConstants.sol"; +import {BipsLibrary} from "./libraries/BipsLibrary.sol"; +import {SwapParams} from "@uniswap/v4-core/src/types/PoolOperation.sol"; + +/// @title UniswapV4Router +/// @notice Abstract contract that contains all internal logic needed for routing through Uniswap v4 pools +/// @dev the entry point to executing actions in this contract is calling `BaseActionsRouter._executeActions` +/// An inheriting contract should call _executeActions at the point that they wish actions to be executed +abstract contract V4Router is IV4Router, BaseActionsRouter, DeltaResolver { + using SafeCast for *; + using CalldataDecoder for bytes; + using BipsLibrary for uint256; + + constructor(IPoolManager _poolManager) BaseActionsRouter(_poolManager) {} + + function _handleAction(uint256 action, bytes calldata params) internal override { + // swap actions and payment actions in different blocks for gas efficiency + if (action < Actions.SETTLE) { + if (action == Actions.SWAP_EXACT_IN) { + IV4Router.ExactInputParams calldata swapParams = params.decodeSwapExactInParams(); + _swapExactInput(swapParams); + return; + } else if (action == Actions.SWAP_EXACT_IN_SINGLE) { + IV4Router.ExactInputSingleParams calldata swapParams = params.decodeSwapExactInSingleParams(); + _swapExactInputSingle(swapParams); + return; + } else if (action == Actions.SWAP_EXACT_OUT) { + IV4Router.ExactOutputParams calldata swapParams = params.decodeSwapExactOutParams(); + _swapExactOutput(swapParams); + return; + } else if (action == Actions.SWAP_EXACT_OUT_SINGLE) { + IV4Router.ExactOutputSingleParams calldata swapParams = params.decodeSwapExactOutSingleParams(); + _swapExactOutputSingle(swapParams); + return; + } + } else { + if (action == Actions.SETTLE_ALL) { + (Currency currency, uint256 maxAmount) = params.decodeCurrencyAndUint256(); + uint256 amount = _getFullDebt(currency); + if (amount > maxAmount) revert V4TooMuchRequested(maxAmount, amount); + _settle(currency, msgSender(), amount); + return; + } else if (action == Actions.TAKE_ALL) { + (Currency currency, uint256 minAmount) = params.decodeCurrencyAndUint256(); + uint256 amount = _getFullCredit(currency); + if (amount < minAmount) revert V4TooLittleReceived(minAmount, amount); + _take(currency, msgSender(), amount); + return; + } else if (action == Actions.SETTLE) { + (Currency currency, uint256 amount, bool payerIsUser) = params.decodeCurrencyUint256AndBool(); + _settle(currency, _mapPayer(payerIsUser), _mapSettleAmount(amount, currency)); + return; + } else if (action == Actions.TAKE) { + (Currency currency, address recipient, uint256 amount) = params.decodeCurrencyAddressAndUint256(); + _take(currency, _mapRecipient(recipient), _mapTakeAmount(amount, currency)); + return; + } else if (action == Actions.TAKE_PORTION) { + (Currency currency, address recipient, uint256 bips) = params.decodeCurrencyAddressAndUint256(); + _take(currency, _mapRecipient(recipient), _getFullCredit(currency).calculatePortion(bips)); + return; + } + } + revert UnsupportedAction(action); + } + + function _swapExactInputSingle(IV4Router.ExactInputSingleParams calldata params) private { + uint128 amountIn = params.amountIn; + if (amountIn == ActionConstants.OPEN_DELTA) { + amountIn = + _getFullCredit(params.zeroForOne ? params.poolKey.currency0 : params.poolKey.currency1).toUint128(); + } + uint128 amountOut = + _swap(params.poolKey, params.zeroForOne, -int256(uint256(amountIn)), params.hookData).toUint128(); + if (amountOut < params.amountOutMinimum) revert V4TooLittleReceived(params.amountOutMinimum, amountOut); + } + + function _swapExactInput(IV4Router.ExactInputParams calldata params) private { + unchecked { + // Caching for gas savings + uint256 pathLength = params.path.length; + uint128 amountOut; + Currency currencyIn = params.currencyIn; + uint128 amountIn = params.amountIn; + if (amountIn == ActionConstants.OPEN_DELTA) amountIn = _getFullCredit(currencyIn).toUint128(); + PathKey calldata pathKey; + + for (uint256 i = 0; i < pathLength; i++) { + pathKey = params.path[i]; + (PoolKey memory poolKey, bool zeroForOne) = pathKey.getPoolAndSwapDirection(currencyIn); + // The output delta will always be positive, except for when interacting with certain hook pools + amountOut = _swap(poolKey, zeroForOne, -int256(uint256(amountIn)), pathKey.hookData).toUint128(); + + amountIn = amountOut; + currencyIn = pathKey.intermediateCurrency; + } + + if (amountOut < params.amountOutMinimum) revert V4TooLittleReceived(params.amountOutMinimum, amountOut); + } + } + + function _swapExactOutputSingle(IV4Router.ExactOutputSingleParams calldata params) private { + uint128 amountOut = params.amountOut; + if (amountOut == ActionConstants.OPEN_DELTA) { + amountOut = + _getFullDebt(params.zeroForOne ? params.poolKey.currency1 : params.poolKey.currency0).toUint128(); + } + uint128 amountIn = ( + uint256(-int256(_swap(params.poolKey, params.zeroForOne, int256(uint256(amountOut)), params.hookData))) + ).toUint128(); + if (amountIn > params.amountInMaximum) revert V4TooMuchRequested(params.amountInMaximum, amountIn); + } + + function _swapExactOutput(IV4Router.ExactOutputParams calldata params) private { + unchecked { + // Caching for gas savings + uint256 pathLength = params.path.length; + uint128 amountIn; + uint128 amountOut = params.amountOut; + Currency currencyOut = params.currencyOut; + PathKey calldata pathKey; + + if (amountOut == ActionConstants.OPEN_DELTA) { + amountOut = _getFullDebt(currencyOut).toUint128(); + } + + for (uint256 i = pathLength; i > 0; i--) { + pathKey = params.path[i - 1]; + (PoolKey memory poolKey, bool oneForZero) = pathKey.getPoolAndSwapDirection(currencyOut); + // The output delta will always be negative, except for when interacting with certain hook pools + amountIn = (uint256(-int256(_swap(poolKey, !oneForZero, int256(uint256(amountOut)), pathKey.hookData)))) + .toUint128(); + + amountOut = amountIn; + currencyOut = pathKey.intermediateCurrency; + } + if (amountIn > params.amountInMaximum) revert V4TooMuchRequested(params.amountInMaximum, amountIn); + } + } + + function _swap(PoolKey memory poolKey, bool zeroForOne, int256 amountSpecified, bytes calldata hookData) + private + returns (int128 reciprocalAmount) + { + // for protection of exactOut swaps, sqrtPriceLimit is not exposed as a feature in this contract + unchecked { + BalanceDelta delta = poolManager.swap( + poolKey, + SwapParams( + zeroForOne, amountSpecified, zeroForOne ? TickMath.MIN_SQRT_PRICE + 1 : TickMath.MAX_SQRT_PRICE - 1 + ), + hookData + ); + + reciprocalAmount = (zeroForOne == amountSpecified < 0) ? delta.amount1() : delta.amount0(); + } + } +} diff --git a/CVLByExample/UniswapHooks/lib/uniswap-hooks/lib/v4-periphery/src/base/BaseActionsRouter.sol b/CVLByExample/UniswapHooks/lib/uniswap-hooks/lib/v4-periphery/src/base/BaseActionsRouter.sol new file mode 100644 index 00000000..7adcd208 --- /dev/null +++ b/CVLByExample/UniswapHooks/lib/uniswap-hooks/lib/v4-periphery/src/base/BaseActionsRouter.sol @@ -0,0 +1,75 @@ +// SPDX-License-Identifier: MIT +pragma solidity ^0.8.0; + +import {IPoolManager} from "@uniswap/v4-core/src/interfaces/IPoolManager.sol"; +import {SafeCallback} from "./SafeCallback.sol"; +import {CalldataDecoder} from "../libraries/CalldataDecoder.sol"; +import {ActionConstants} from "../libraries/ActionConstants.sol"; +import {IMsgSender} from "../interfaces/IMsgSender.sol"; + +/// @notice Abstract contract for performing a combination of actions on Uniswap v4. +/// @dev Suggested uint256 action values are defined in Actions.sol, however any definition can be used +abstract contract BaseActionsRouter is IMsgSender, SafeCallback { + using CalldataDecoder for bytes; + + /// @notice emitted when different numbers of parameters and actions are provided + error InputLengthMismatch(); + + /// @notice emitted when an inheriting contract does not support an action + error UnsupportedAction(uint256 action); + + constructor(IPoolManager _poolManager) SafeCallback(_poolManager) {} + + /// @notice internal function that triggers the execution of a set of actions on v4 + /// @dev inheriting contracts should call this function to trigger execution + function _executeActions(bytes calldata unlockData) internal { + poolManager.unlock(unlockData); + } + + /// @notice function that is called by the PoolManager through the SafeCallback.unlockCallback + /// @param data Abi encoding of (bytes actions, bytes[] params) + /// where params[i] is the encoded parameters for actions[i] + function _unlockCallback(bytes calldata data) internal override returns (bytes memory) { + // abi.decode(data, (bytes, bytes[])); + (bytes calldata actions, bytes[] calldata params) = data.decodeActionsRouterParams(); + _executeActionsWithoutUnlock(actions, params); + return ""; + } + + function _executeActionsWithoutUnlock(bytes calldata actions, bytes[] calldata params) internal { + uint256 numActions = actions.length; + if (numActions != params.length) revert InputLengthMismatch(); + + for (uint256 actionIndex = 0; actionIndex < numActions; actionIndex++) { + uint256 action = uint8(actions[actionIndex]); + + _handleAction(action, params[actionIndex]); + } + } + + /// @notice function to handle the parsing and execution of an action and its parameters + function _handleAction(uint256 action, bytes calldata params) internal virtual; + + /// @notice function that returns address considered executor of the actions + /// @dev The other context functions, _msgData and _msgValue, are not supported by this contract + /// In many contracts this will be the address that calls the initial entry point that calls `_executeActions` + /// `msg.sender` shouldn't be used, as this will be the v4 pool manager contract that calls `unlockCallback` + /// If using ReentrancyLock.sol, this function can return _getLocker() + function msgSender() public view virtual returns (address); + + /// @notice Calculates the address for a action + function _mapRecipient(address recipient) internal view returns (address) { + if (recipient == ActionConstants.MSG_SENDER) { + return msgSender(); + } else if (recipient == ActionConstants.ADDRESS_THIS) { + return address(this); + } else { + return recipient; + } + } + + /// @notice Calculates the payer for an action + function _mapPayer(bool payerIsUser) internal view returns (address) { + return payerIsUser ? msgSender() : address(this); + } +} diff --git a/CVLByExample/UniswapHooks/lib/uniswap-hooks/lib/v4-periphery/src/base/BaseV4Quoter.sol b/CVLByExample/UniswapHooks/lib/uniswap-hooks/lib/v4-periphery/src/base/BaseV4Quoter.sol new file mode 100644 index 00000000..24bf9f1d --- /dev/null +++ b/CVLByExample/UniswapHooks/lib/uniswap-hooks/lib/v4-periphery/src/base/BaseV4Quoter.sol @@ -0,0 +1,59 @@ +// SPDX-License-Identifier: MIT +pragma solidity ^0.8.0; + +import {IPoolManager} from "@uniswap/v4-core/src/interfaces/IPoolManager.sol"; +import {BalanceDelta} from "@uniswap/v4-core/src/types/BalanceDelta.sol"; +import {PoolKey} from "@uniswap/v4-core/src/types/PoolKey.sol"; +import {QuoterRevert} from "../libraries/QuoterRevert.sol"; +import {SafeCallback} from "../base/SafeCallback.sol"; +import {PoolId} from "@uniswap/v4-core/src/types/PoolId.sol"; +import {TickMath} from "@uniswap/v4-core/src/libraries/TickMath.sol"; +import {SwapParams} from "@uniswap/v4-core/src/types/PoolOperation.sol"; + +abstract contract BaseV4Quoter is SafeCallback { + using QuoterRevert for *; + + error NotEnoughLiquidity(PoolId poolId); + error NotSelf(); + error UnexpectedCallSuccess(); + + constructor(IPoolManager _poolManager) SafeCallback(_poolManager) {} + + /// @dev Only this address may call this function. Used to mimic internal functions, using an + /// external call to catch and parse revert reasons + modifier selfOnly() { + if (msg.sender != address(this)) revert NotSelf(); + _; + } + + function _unlockCallback(bytes calldata data) internal override returns (bytes memory) { + (bool success, bytes memory returnData) = address(this).call(data); + // Every quote path gathers a quote, and then reverts either with QuoteSwap(quoteAmount) or alternative error + if (success) revert UnexpectedCallSuccess(); + // Bubble the revert string, whether a valid quote or an alternative error + returnData.bubbleReason(); + } + + /// @dev Execute a swap and return the balance delta + /// @notice if amountSpecified < 0, the swap is exactInput, otherwise exactOutput + function _swap(PoolKey memory poolKey, bool zeroForOne, int256 amountSpecified, bytes calldata hookData) + internal + returns (BalanceDelta swapDelta) + { + swapDelta = poolManager.swap( + poolKey, + SwapParams({ + zeroForOne: zeroForOne, + amountSpecified: amountSpecified, + sqrtPriceLimitX96: zeroForOne ? TickMath.MIN_SQRT_PRICE + 1 : TickMath.MAX_SQRT_PRICE - 1 + }), + hookData + ); + + // Check that the pool was not illiquid. + int128 amountSpecifiedActual = (zeroForOne == (amountSpecified < 0)) ? swapDelta.amount0() : swapDelta.amount1(); + if (amountSpecifiedActual != amountSpecified) { + revert NotEnoughLiquidity(poolKey.toId()); + } + } +} diff --git a/CVLByExample/UniswapHooks/lib/uniswap-hooks/lib/v4-periphery/src/base/DeltaResolver.sol b/CVLByExample/UniswapHooks/lib/uniswap-hooks/lib/v4-periphery/src/base/DeltaResolver.sol new file mode 100644 index 00000000..bd0d39bf --- /dev/null +++ b/CVLByExample/UniswapHooks/lib/uniswap-hooks/lib/v4-periphery/src/base/DeltaResolver.sol @@ -0,0 +1,123 @@ +// SPDX-License-Identifier: MIT +pragma solidity ^0.8.24; + +import {Currency} from "@uniswap/v4-core/src/types/Currency.sol"; +import {TransientStateLibrary} from "@uniswap/v4-core/src/libraries/TransientStateLibrary.sol"; +import {IPoolManager} from "@uniswap/v4-core/src/interfaces/IPoolManager.sol"; +import {ImmutableState} from "./ImmutableState.sol"; +import {ActionConstants} from "../libraries/ActionConstants.sol"; + +/// @notice Abstract contract used to sync, send, and settle funds to the pool manager +/// @dev Note that sync() is called before any erc-20 transfer in `settle`. +abstract contract DeltaResolver is ImmutableState { + using TransientStateLibrary for IPoolManager; + + /// @notice Emitted trying to settle a positive delta. + error DeltaNotPositive(Currency currency); + /// @notice Emitted trying to take a negative delta. + error DeltaNotNegative(Currency currency); + /// @notice Emitted when the contract does not have enough balance to wrap or unwrap. + error InsufficientBalance(); + + /// @notice Take an amount of currency out of the PoolManager + /// @param currency Currency to take + /// @param recipient Address to receive the currency + /// @param amount Amount to take + /// @dev Returns early if the amount is 0 + function _take(Currency currency, address recipient, uint256 amount) internal { + if (amount == 0) return; + poolManager.take(currency, recipient, amount); + } + + /// @notice Pay and settle a currency to the PoolManager + /// @dev The implementing contract must ensure that the `payer` is a secure address + /// @param currency Currency to settle + /// @param payer Address of the payer + /// @param amount Amount to send + /// @dev Returns early if the amount is 0 + function _settle(Currency currency, address payer, uint256 amount) internal { + if (amount == 0) return; + + poolManager.sync(currency); + if (currency.isAddressZero()) { + poolManager.settle{value: amount}(); + } else { + _pay(currency, payer, amount); + poolManager.settle(); + } + } + + /// @notice Abstract function for contracts to implement paying tokens to the poolManager + /// @dev The recipient of the payment should be the poolManager + /// @param token The token to settle. This is known not to be the native currency + /// @param payer The address who should pay tokens + /// @param amount The number of tokens to send + function _pay(Currency token, address payer, uint256 amount) internal virtual; + + /// @notice Obtain the full amount owed by this contract (negative delta) + /// @param currency Currency to get the delta for + /// @return amount The amount owed by this contract as a uint256 + function _getFullDebt(Currency currency) internal view returns (uint256 amount) { + int256 _amount = poolManager.currencyDelta(address(this), currency); + // If the amount is positive, it should be taken not settled. + if (_amount > 0) revert DeltaNotNegative(currency); + // Casting is safe due to limits on the total supply of a pool + amount = uint256(-_amount); + } + + /// @notice Obtain the full credit owed to this contract (positive delta) + /// @param currency Currency to get the delta for + /// @return amount The amount owed to this contract as a uint256 + function _getFullCredit(Currency currency) internal view returns (uint256 amount) { + int256 _amount = poolManager.currencyDelta(address(this), currency); + // If the amount is negative, it should be settled not taken. + if (_amount < 0) revert DeltaNotPositive(currency); + amount = uint256(_amount); + } + + /// @notice Calculates the amount for a settle action + function _mapSettleAmount(uint256 amount, Currency currency) internal view returns (uint256) { + if (amount == ActionConstants.CONTRACT_BALANCE) { + return currency.balanceOfSelf(); + } else if (amount == ActionConstants.OPEN_DELTA) { + return _getFullDebt(currency); + } else { + return amount; + } + } + + /// @notice Calculates the amount for a take action + function _mapTakeAmount(uint256 amount, Currency currency) internal view returns (uint256) { + if (amount == ActionConstants.OPEN_DELTA) { + return _getFullCredit(currency); + } else { + return amount; + } + } + + /// @notice Calculates the sanitized amount before wrapping/unwrapping. + /// @param inputCurrency The currency, either native or wrapped native, that this contract holds + /// @param amount The amount to wrap or unwrap. Can be CONTRACT_BALANCE, OPEN_DELTA or a specific amount + /// @param outputCurrency The currency after the wrap/unwrap that the user may owe a balance in on the poolManager + function _mapWrapUnwrapAmount(Currency inputCurrency, uint256 amount, Currency outputCurrency) + internal + view + returns (uint256) + { + // if wrapping, the balance in this contract is in ETH + // if unwrapping, the balance in this contract is in WETH + uint256 balance = inputCurrency.balanceOf(address(this)); + if (amount == ActionConstants.CONTRACT_BALANCE) { + // return early to avoid unnecessary balance check + return balance; + } + if (amount == ActionConstants.OPEN_DELTA) { + // if wrapping, the open currency on the PoolManager is WETH. + // if unwrapping, the open currency on the PoolManager is ETH. + // note that we use the DEBT amount. Positive deltas can be taken and then wrapped. + amount = _getFullDebt(outputCurrency); + } + if (amount > balance) revert InsufficientBalance(); + return amount; + } +} diff --git a/CVLByExample/UniswapHooks/lib/uniswap-hooks/lib/v4-periphery/src/base/EIP712_v4.sol b/CVLByExample/UniswapHooks/lib/uniswap-hooks/lib/v4-periphery/src/base/EIP712_v4.sol new file mode 100644 index 00000000..e66261aa --- /dev/null +++ b/CVLByExample/UniswapHooks/lib/uniswap-hooks/lib/v4-periphery/src/base/EIP712_v4.sol @@ -0,0 +1,56 @@ +// SPDX-License-Identifier: MIT +pragma solidity ^0.8.0; + +import {IEIP712_v4} from "../interfaces/IEIP712_v4.sol"; + +/// @notice Generic EIP712 implementation +/// @dev Maintains cross-chain replay protection in the event of a fork +/// @dev Should not be delegatecall'd because DOMAIN_SEPARATOR returns the cached hash and does not recompute with the delegatecallers address +/// @dev Reference: https://github.com/Uniswap/permit2/blob/3f17e8db813189a03950dc7fc8382524a095c053/src/EIP712.sol +/// @dev Reference: https://github.com/OpenZeppelin/openzeppelin-contracts/blob/7bd2b2aaf68c21277097166a9a51eb72ae239b34/contracts/utils/cryptography/EIP712.sol +contract EIP712_v4 is IEIP712_v4 { + // Cache the domain separator as an immutable value, but also store the chain id that it + // corresponds to, in order to invalidate the cached domain separator if the chain id changes. + bytes32 private immutable _CACHED_DOMAIN_SEPARATOR; + uint256 private immutable _CACHED_CHAIN_ID; + bytes32 private immutable _HASHED_NAME; + + bytes32 private constant _TYPE_HASH = + keccak256("EIP712Domain(string name,uint256 chainId,address verifyingContract)"); + + constructor(string memory name) { + _HASHED_NAME = keccak256(bytes(name)); + + _CACHED_CHAIN_ID = block.chainid; + _CACHED_DOMAIN_SEPARATOR = _buildDomainSeparator(); + } + + /// @inheritdoc IEIP712_v4 + function DOMAIN_SEPARATOR() public view returns (bytes32) { + // uses cached version if chainid is unchanged from construction + return block.chainid == _CACHED_CHAIN_ID ? _CACHED_DOMAIN_SEPARATOR : _buildDomainSeparator(); + } + + /// @notice Builds a domain separator using the current chainId and contract address. + function _buildDomainSeparator() private view returns (bytes32) { + return keccak256(abi.encode(_TYPE_HASH, _HASHED_NAME, block.chainid, address(this))); + } + + /// @notice Creates an EIP-712 typed data hash + function _hashTypedData(bytes32 dataHash) internal view returns (bytes32 digest) { + // equal to keccak256(abi.encodePacked("\x19\x01", DOMAIN_SEPARATOR(), dataHash)); + bytes32 domainSeparator = DOMAIN_SEPARATOR(); + assembly ("memory-safe") { + let fmp := mload(0x40) + mstore(fmp, hex"1901") + mstore(add(fmp, 0x02), domainSeparator) + mstore(add(fmp, 0x22), dataHash) + digest := keccak256(fmp, 0x42) + + // now clean the memory we used + mstore(fmp, 0) // fmp held "\x19\x01", domainSeparator + mstore(add(fmp, 0x20), 0) // fmp+0x20 held domainSeparator, dataHash + mstore(add(fmp, 0x40), 0) // fmp+0x40 held dataHash + } + } +} diff --git a/CVLByExample/UniswapHooks/lib/uniswap-hooks/lib/v4-periphery/src/base/ERC721Permit_v4.sol b/CVLByExample/UniswapHooks/lib/uniswap-hooks/lib/v4-periphery/src/base/ERC721Permit_v4.sol new file mode 100644 index 00000000..e6ded04d --- /dev/null +++ b/CVLByExample/UniswapHooks/lib/uniswap-hooks/lib/v4-periphery/src/base/ERC721Permit_v4.sol @@ -0,0 +1,98 @@ +// SPDX-License-Identifier: MIT +pragma solidity ^0.8.0; + +import {ERC721} from "solmate/src/tokens/ERC721.sol"; +import {EIP712_v4} from "./EIP712_v4.sol"; +import {ERC721PermitHash} from "../libraries/ERC721PermitHash.sol"; +import {SignatureVerification} from "permit2/src/libraries/SignatureVerification.sol"; + +import {IERC721Permit_v4} from "../interfaces/IERC721Permit_v4.sol"; +import {UnorderedNonce} from "./UnorderedNonce.sol"; + +/// @title ERC721 with permit +/// @notice Nonfungible tokens that support an approve via signature, i.e. permit +abstract contract ERC721Permit_v4 is ERC721, IERC721Permit_v4, EIP712_v4, UnorderedNonce { + using SignatureVerification for bytes; + + /// @notice Computes the nameHash and versionHash + constructor(string memory name_, string memory symbol_) ERC721(name_, symbol_) EIP712_v4(name_) {} + + /// @notice Checks if the block's timestamp is before a signature's deadline + modifier checkSignatureDeadline(uint256 deadline) { + if (block.timestamp > deadline) revert SignatureDeadlineExpired(); + _; + } + + /// @inheritdoc IERC721Permit_v4 + function permit(address spender, uint256 tokenId, uint256 deadline, uint256 nonce, bytes calldata signature) + external + payable + checkSignatureDeadline(deadline) + { + // the .verify function checks the owner is non-0 + address owner = _ownerOf[tokenId]; + + bytes32 digest = ERC721PermitHash.hashPermit(spender, tokenId, nonce, deadline); + signature.verify(_hashTypedData(digest), owner); + + _useUnorderedNonce(owner, nonce); + _approve(owner, spender, tokenId); + } + + /// @inheritdoc IERC721Permit_v4 + function permitForAll( + address owner, + address operator, + bool approved, + uint256 deadline, + uint256 nonce, + bytes calldata signature + ) external payable checkSignatureDeadline(deadline) { + bytes32 digest = ERC721PermitHash.hashPermitForAll(operator, approved, nonce, deadline); + signature.verify(_hashTypedData(digest), owner); + + _useUnorderedNonce(owner, nonce); + _approveForAll(owner, operator, approved); + } + + /// @notice Enable or disable approval for a third party ("operator") to manage + /// all of `msg.sender`'s assets + /// @dev Emits the ApprovalForAll event. The contract MUST allow + /// multiple operators per owner. + /// @dev Override Solmate's ERC721 setApprovalForAll so setApprovalForAll() and permit() share the _approveForAll method + /// @param operator Address to add to the set of authorized operators + /// @param approved True if the operator is approved, false to revoke approval + function setApprovalForAll(address operator, bool approved) public override { + _approveForAll(msg.sender, operator, approved); + } + + function _approveForAll(address owner, address operator, bool approved) internal { + isApprovedForAll[owner][operator] = approved; + emit ApprovalForAll(owner, operator, approved); + } + + /// @notice Change or reaffirm the approved address for an NFT + /// @dev override Solmate's ERC721 approve so approve() and permit() share the _approve method + /// Passing a spender address of zero can be used to remove any outstanding approvals + /// Throws error unless `msg.sender` is the current NFT owner, + /// or an authorized operator of the current owner. + /// @param spender The new approved NFT controller + /// @param id The tokenId of the NFT to approve + function approve(address spender, uint256 id) public override { + address owner = _ownerOf[id]; + + if (msg.sender != owner && !isApprovedForAll[owner][msg.sender]) revert Unauthorized(); + + _approve(owner, spender, id); + } + + function _approve(address owner, address spender, uint256 id) internal { + getApproved[id] = spender; + emit Approval(owner, spender, id); + } + + function _isApprovedOrOwner(address spender, uint256 tokenId) internal view returns (bool) { + return spender == ownerOf(tokenId) || getApproved[tokenId] == spender + || isApprovedForAll[ownerOf(tokenId)][spender]; + } +} diff --git a/CVLByExample/UniswapHooks/lib/uniswap-hooks/lib/v4-periphery/src/base/ImmutableState.sol b/CVLByExample/UniswapHooks/lib/uniswap-hooks/lib/v4-periphery/src/base/ImmutableState.sol new file mode 100644 index 00000000..4b35794f --- /dev/null +++ b/CVLByExample/UniswapHooks/lib/uniswap-hooks/lib/v4-periphery/src/base/ImmutableState.sol @@ -0,0 +1,25 @@ +// SPDX-License-Identifier: MIT +pragma solidity ^0.8.0; + +import {IPoolManager} from "@uniswap/v4-core/src/interfaces/IPoolManager.sol"; +import {IImmutableState} from "../interfaces/IImmutableState.sol"; + +/// @title Immutable State +/// @notice A collection of immutable state variables, commonly used across multiple contracts +contract ImmutableState is IImmutableState { + /// @inheritdoc IImmutableState + IPoolManager public immutable poolManager; + + /// @notice Thrown when the caller is not PoolManager + error NotPoolManager(); + + /// @notice Only allow calls from the PoolManager contract + modifier onlyPoolManager() { + if (msg.sender != address(poolManager)) revert NotPoolManager(); + _; + } + + constructor(IPoolManager _poolManager) { + poolManager = _poolManager; + } +} diff --git a/CVLByExample/UniswapHooks/lib/uniswap-hooks/lib/v4-periphery/src/base/Multicall_v4.sol b/CVLByExample/UniswapHooks/lib/uniswap-hooks/lib/v4-periphery/src/base/Multicall_v4.sol new file mode 100644 index 00000000..e4fb7dac --- /dev/null +++ b/CVLByExample/UniswapHooks/lib/uniswap-hooks/lib/v4-periphery/src/base/Multicall_v4.sol @@ -0,0 +1,25 @@ +// SPDX-License-Identifier: MIT +pragma solidity ^0.8.0; + +import {IMulticall_v4} from "../interfaces/IMulticall_v4.sol"; + +/// @title Multicall_v4 +/// @notice Enables calling multiple methods in a single call to the contract +abstract contract Multicall_v4 is IMulticall_v4 { + /// @inheritdoc IMulticall_v4 + function multicall(bytes[] calldata data) external payable returns (bytes[] memory results) { + results = new bytes[](data.length); + for (uint256 i = 0; i < data.length; i++) { + (bool success, bytes memory result) = address(this).delegatecall(data[i]); + + if (!success) { + // bubble up the revert reason + assembly { + revert(add(result, 0x20), mload(result)) + } + } + + results[i] = result; + } + } +} diff --git a/CVLByExample/UniswapHooks/lib/uniswap-hooks/lib/v4-periphery/src/base/NativeWrapper.sol b/CVLByExample/UniswapHooks/lib/uniswap-hooks/lib/v4-periphery/src/base/NativeWrapper.sol new file mode 100644 index 00000000..eb4eba07 --- /dev/null +++ b/CVLByExample/UniswapHooks/lib/uniswap-hooks/lib/v4-periphery/src/base/NativeWrapper.sol @@ -0,0 +1,34 @@ +// SPDX-License-Identifier: MIT +pragma solidity ^0.8.0; + +import {IWETH9} from "../interfaces/external/IWETH9.sol"; +import {ActionConstants} from "../libraries/ActionConstants.sol"; +import {ImmutableState} from "./ImmutableState.sol"; + +/// @title Native Wrapper +/// @notice Used for wrapping and unwrapping native +abstract contract NativeWrapper is ImmutableState { + /// @notice The address for WETH9 + IWETH9 public immutable WETH9; + + /// @notice Thrown when an unexpected address sends ETH to this contract + error InvalidEthSender(); + + constructor(IWETH9 _weth9) { + WETH9 = _weth9; + } + + /// @dev The amount should already be <= the current balance in this contract. + function _wrap(uint256 amount) internal { + if (amount > 0) WETH9.deposit{value: amount}(); + } + + /// @dev The amount should already be <= the current balance in this contract. + function _unwrap(uint256 amount) internal { + if (amount > 0) WETH9.withdraw(amount); + } + + receive() external payable { + if (msg.sender != address(WETH9) && msg.sender != address(poolManager)) revert InvalidEthSender(); + } +} diff --git a/CVLByExample/UniswapHooks/lib/uniswap-hooks/lib/v4-periphery/src/base/Notifier.sol b/CVLByExample/UniswapHooks/lib/uniswap-hooks/lib/v4-periphery/src/base/Notifier.sol new file mode 100644 index 00000000..7e755c66 --- /dev/null +++ b/CVLByExample/UniswapHooks/lib/uniswap-hooks/lib/v4-periphery/src/base/Notifier.sol @@ -0,0 +1,132 @@ +// SPDX-License-Identifier: MIT +pragma solidity ^0.8.0; + +import {ISubscriber} from "../interfaces/ISubscriber.sol"; +import {INotifier} from "../interfaces/INotifier.sol"; +import {CustomRevert} from "@uniswap/v4-core/src/libraries/CustomRevert.sol"; +import {BalanceDelta} from "@uniswap/v4-core/src/types/BalanceDelta.sol"; +import {PositionInfo} from "../libraries/PositionInfoLibrary.sol"; + +/// @notice Notifier is used to opt in to sending updates to external contracts about position modifications or transfers +abstract contract Notifier is INotifier { + using CustomRevert for *; + + ISubscriber private constant NO_SUBSCRIBER = ISubscriber(address(0)); + + /// @inheritdoc INotifier + uint256 public immutable unsubscribeGasLimit; + + /// @inheritdoc INotifier + mapping(uint256 tokenId => ISubscriber subscriber) public subscriber; + + constructor(uint256 _unsubscribeGasLimit) { + unsubscribeGasLimit = _unsubscribeGasLimit; + } + + /// @notice Only allow callers that are approved as spenders or operators of the tokenId + /// @dev to be implemented by the parent contract (PositionManager) + /// @param caller the address of the caller + /// @param tokenId the tokenId of the position + modifier onlyIfApproved(address caller, uint256 tokenId) virtual; + + /// @notice Enforces that the PoolManager is locked. + modifier onlyIfPoolManagerLocked() virtual; + + function _setUnsubscribed(uint256 tokenId) internal virtual; + + function _setSubscribed(uint256 tokenId) internal virtual; + + /// @inheritdoc INotifier + function subscribe(uint256 tokenId, address newSubscriber, bytes calldata data) + external + payable + onlyIfPoolManagerLocked + onlyIfApproved(msg.sender, tokenId) + { + ISubscriber _subscriber = subscriber[tokenId]; + + if (_subscriber != NO_SUBSCRIBER) revert AlreadySubscribed(tokenId, address(_subscriber)); + _setSubscribed(tokenId); + + subscriber[tokenId] = ISubscriber(newSubscriber); + + bool success = _call(newSubscriber, abi.encodeCall(ISubscriber.notifySubscribe, (tokenId, data))); + + if (!success) { + newSubscriber.bubbleUpAndRevertWith(ISubscriber.notifySubscribe.selector, SubscriptionReverted.selector); + } + + emit Subscription(tokenId, newSubscriber); + } + + /// @inheritdoc INotifier + function unsubscribe(uint256 tokenId) + external + payable + onlyIfPoolManagerLocked + onlyIfApproved(msg.sender, tokenId) + { + _unsubscribe(tokenId); + } + + function _unsubscribe(uint256 tokenId) internal { + ISubscriber _subscriber = subscriber[tokenId]; + + if (_subscriber == NO_SUBSCRIBER) revert NotSubscribed(); + _setUnsubscribed(tokenId); + + delete subscriber[tokenId]; + + if (address(_subscriber).code.length > 0) { + // require that the remaining gas is sufficient to notify the subscriber + // otherwise, users can select a gas limit where .notifyUnsubscribe hits OutOfGas yet the + // transaction/unsubscription can still succeed + if (gasleft() < unsubscribeGasLimit) GasLimitTooLow.selector.revertWith(); + try _subscriber.notifyUnsubscribe{gas: unsubscribeGasLimit}(tokenId) {} catch {} + } + + emit Unsubscription(tokenId, address(_subscriber)); + } + + /// @dev note this function also deletes the subscriber address from the mapping + function _removeSubscriberAndNotifyBurn( + uint256 tokenId, + address owner, + PositionInfo info, + uint256 liquidity, + BalanceDelta feesAccrued + ) internal { + address _subscriber = address(subscriber[tokenId]); + + // remove the subscriber + delete subscriber[tokenId]; + + bool success = + _call(_subscriber, abi.encodeCall(ISubscriber.notifyBurn, (tokenId, owner, info, liquidity, feesAccrued))); + + if (!success) { + _subscriber.bubbleUpAndRevertWith(ISubscriber.notifyBurn.selector, BurnNotificationReverted.selector); + } + } + + function _notifyModifyLiquidity(uint256 tokenId, int256 liquidityChange, BalanceDelta feesAccrued) internal { + address _subscriber = address(subscriber[tokenId]); + + bool success = _call( + _subscriber, abi.encodeCall(ISubscriber.notifyModifyLiquidity, (tokenId, liquidityChange, feesAccrued)) + ); + + if (!success) { + _subscriber.bubbleUpAndRevertWith( + ISubscriber.notifyModifyLiquidity.selector, ModifyLiquidityNotificationReverted.selector + ); + } + } + + function _call(address target, bytes memory encodedCall) internal returns (bool success) { + if (target.code.length == 0) NoCodeSubscriber.selector.revertWith(); + assembly ("memory-safe") { + success := call(gas(), target, 0, add(encodedCall, 0x20), mload(encodedCall), 0, 0) + } + } +} diff --git a/CVLByExample/UniswapHooks/lib/uniswap-hooks/lib/v4-periphery/src/base/Permit2Forwarder.sol b/CVLByExample/UniswapHooks/lib/uniswap-hooks/lib/v4-periphery/src/base/Permit2Forwarder.sol new file mode 100644 index 00000000..43cdc1ce --- /dev/null +++ b/CVLByExample/UniswapHooks/lib/uniswap-hooks/lib/v4-periphery/src/base/Permit2Forwarder.sol @@ -0,0 +1,41 @@ +// SPDX-License-Identifier: MIT +pragma solidity ^0.8.0; + +import {IPermit2Forwarder, IAllowanceTransfer} from "../interfaces/IPermit2Forwarder.sol"; + +/// @notice Permit2Forwarder allows permitting this contract as a spender on permit2 +/// @dev This contract does not enforce the spender to be this contract, but that is the intended use case +contract Permit2Forwarder is IPermit2Forwarder { + /// @notice the Permit2 contract to forward approvals + IAllowanceTransfer public immutable permit2; + + constructor(IAllowanceTransfer _permit2) { + permit2 = _permit2; + } + + /// @inheritdoc IPermit2Forwarder + function permit(address owner, IAllowanceTransfer.PermitSingle calldata permitSingle, bytes calldata signature) + external + payable + returns (bytes memory err) + { + // use try/catch in case an actor front-runs the permit, which would DOS multicalls + try permit2.permit(owner, permitSingle, signature) {} + catch (bytes memory reason) { + err = reason; + } + } + + /// @inheritdoc IPermit2Forwarder + function permitBatch(address owner, IAllowanceTransfer.PermitBatch calldata _permitBatch, bytes calldata signature) + external + payable + returns (bytes memory err) + { + // use try/catch in case an actor front-runs the permit, which would DOS multicalls + try permit2.permit(owner, _permitBatch, signature) {} + catch (bytes memory reason) { + err = reason; + } + } +} diff --git a/CVLByExample/UniswapHooks/lib/uniswap-hooks/lib/v4-periphery/src/base/PoolInitializer_v4.sol b/CVLByExample/UniswapHooks/lib/uniswap-hooks/lib/v4-periphery/src/base/PoolInitializer_v4.sol new file mode 100644 index 00000000..a95cdd88 --- /dev/null +++ b/CVLByExample/UniswapHooks/lib/uniswap-hooks/lib/v4-periphery/src/base/PoolInitializer_v4.sol @@ -0,0 +1,20 @@ +// SPDX-License-Identifier: MIT +pragma solidity ^0.8.0; + +import {ImmutableState} from "./ImmutableState.sol"; +import {PoolKey} from "@uniswap/v4-core/src/types/PoolKey.sol"; +import {IPoolInitializer_v4} from "../interfaces/IPoolInitializer_v4.sol"; + +/// @title Pool Initializer +/// @notice Initializes a Uniswap v4 Pool +/// @dev Enables create pool + mint liquidity in a single transaction with multicall +abstract contract PoolInitializer_v4 is ImmutableState, IPoolInitializer_v4 { + /// @inheritdoc IPoolInitializer_v4 + function initializePool(PoolKey calldata key, uint160 sqrtPriceX96) external payable returns (int24) { + try poolManager.initialize(key, sqrtPriceX96) returns (int24 tick) { + return tick; + } catch { + return type(int24).max; + } + } +} diff --git a/CVLByExample/UniswapHooks/lib/uniswap-hooks/lib/v4-periphery/src/base/ReentrancyLock.sol b/CVLByExample/UniswapHooks/lib/uniswap-hooks/lib/v4-periphery/src/base/ReentrancyLock.sol new file mode 100644 index 00000000..c1abf028 --- /dev/null +++ b/CVLByExample/UniswapHooks/lib/uniswap-hooks/lib/v4-periphery/src/base/ReentrancyLock.sol @@ -0,0 +1,20 @@ +// SPDX-License-Identifier: MIT +pragma solidity ^0.8.24; + +import {Locker} from "../libraries/Locker.sol"; + +/// @notice A transient reentrancy lock, that stores the caller's address as the lock +contract ReentrancyLock { + error ContractLocked(); + + modifier isNotLocked() { + if (Locker.get() != address(0)) revert ContractLocked(); + Locker.set(msg.sender); + _; + Locker.set(address(0)); + } + + function _getLocker() internal view returns (address) { + return Locker.get(); + } +} diff --git a/CVLByExample/UniswapHooks/lib/uniswap-hooks/lib/v4-periphery/src/base/SafeCallback.sol b/CVLByExample/UniswapHooks/lib/uniswap-hooks/lib/v4-periphery/src/base/SafeCallback.sol new file mode 100644 index 00000000..b4f99309 --- /dev/null +++ b/CVLByExample/UniswapHooks/lib/uniswap-hooks/lib/v4-periphery/src/base/SafeCallback.sol @@ -0,0 +1,21 @@ +// SPDX-License-Identifier: MIT +pragma solidity ^0.8.0; + +import {IUnlockCallback} from "@uniswap/v4-core/src/interfaces/callback/IUnlockCallback.sol"; +import {IPoolManager} from "@uniswap/v4-core/src/interfaces/IPoolManager.sol"; +import {ImmutableState} from "./ImmutableState.sol"; + +/// @title Safe Callback +/// @notice A contract that only allows the Uniswap v4 PoolManager to call the unlockCallback +abstract contract SafeCallback is ImmutableState, IUnlockCallback { + constructor(IPoolManager _poolManager) ImmutableState(_poolManager) {} + + /// @inheritdoc IUnlockCallback + /// @dev We force the onlyPoolManager modifier by exposing a virtual function after the onlyPoolManager check. + function unlockCallback(bytes calldata data) external onlyPoolManager returns (bytes memory) { + return _unlockCallback(data); + } + + /// @dev to be implemented by the child contract, to safely guarantee the logic is only executed by the PoolManager + function _unlockCallback(bytes calldata data) internal virtual returns (bytes memory); +} diff --git a/CVLByExample/UniswapHooks/lib/uniswap-hooks/lib/v4-periphery/src/base/UnorderedNonce.sol b/CVLByExample/UniswapHooks/lib/uniswap-hooks/lib/v4-periphery/src/base/UnorderedNonce.sol new file mode 100644 index 00000000..53b93b96 --- /dev/null +++ b/CVLByExample/UniswapHooks/lib/uniswap-hooks/lib/v4-periphery/src/base/UnorderedNonce.sol @@ -0,0 +1,28 @@ +// SPDX-License-Identifier: MIT +pragma solidity ^0.8.0; + +import {IUnorderedNonce} from "../interfaces/IUnorderedNonce.sol"; + +/// @title Unordered Nonce +/// @notice Contract state and methods for using unordered nonces in signatures +contract UnorderedNonce is IUnorderedNonce { + /// @inheritdoc IUnorderedNonce + mapping(address owner => mapping(uint256 word => uint256 bitmap)) public nonces; + + /// @notice Consume a nonce, reverting if it has already been used + /// @param owner address, the owner/signer of the nonce + /// @param nonce uint256, the nonce to consume. The top 248 bits are the word, the bottom 8 bits indicate the bit position + function _useUnorderedNonce(address owner, uint256 nonce) internal { + uint256 wordPos = nonce >> 8; + uint256 bitPos = uint8(nonce); + + uint256 bit = 1 << bitPos; + uint256 flipped = nonces[owner][wordPos] ^= bit; + if (flipped & bit == 0) revert NonceAlreadyUsed(); + } + + /// @inheritdoc IUnorderedNonce + function revokeNonce(uint256 nonce) external payable { + _useUnorderedNonce(msg.sender, nonce); + } +} diff --git a/CVLByExample/UniswapHooks/lib/uniswap-hooks/lib/v4-periphery/src/base/hooks/BaseTokenWrapperHook.sol b/CVLByExample/UniswapHooks/lib/uniswap-hooks/lib/v4-periphery/src/base/hooks/BaseTokenWrapperHook.sol new file mode 100644 index 00000000..152031a4 --- /dev/null +++ b/CVLByExample/UniswapHooks/lib/uniswap-hooks/lib/v4-periphery/src/base/hooks/BaseTokenWrapperHook.sol @@ -0,0 +1,217 @@ +// SPDX-License-Identifier: MIT +pragma solidity ^0.8.0; + +import { + toBeforeSwapDelta, BeforeSwapDelta, BeforeSwapDeltaLibrary +} from "@uniswap/v4-core/src/types/BeforeSwapDelta.sol"; +import {Currency, CurrencyLibrary} from "@uniswap/v4-core/src/types/Currency.sol"; +import {PoolKey} from "@uniswap/v4-core/src/types/PoolKey.sol"; +import {SafeCast} from "@uniswap/v4-core/src/libraries/SafeCast.sol"; +import {IHooks} from "@uniswap/v4-core/src/interfaces/IHooks.sol"; +import {Hooks} from "@uniswap/v4-core/src/libraries/Hooks.sol"; +import {IPoolManager} from "@uniswap/v4-core/src/interfaces/IPoolManager.sol"; +import {BaseHook} from "../../utils/BaseHook.sol"; +import {DeltaResolver} from "../DeltaResolver.sol"; +import {ModifyLiquidityParams, SwapParams} from "@uniswap/v4-core/src/types/PoolOperation.sol"; + +/// @title Base Token Wrapper Hook +/// @notice Abstract base contract for implementing token wrapper hooks in Uniswap V4 +/// @dev This contract provides the base functionality for wrapping/unwrapping tokens through V4 pools +/// @dev All liquidity operations are blocked as liquidity is managed through the underlying token wrapper +/// @dev Implementing contracts must provide deposit() and withdraw() functions +abstract contract BaseTokenWrapperHook is BaseHook, DeltaResolver { + using CurrencyLibrary for Currency; + using SafeCast for int256; + using SafeCast for uint256; + + /// @notice Thrown when attempting to add or remove liquidity + /// @dev Liquidity operations are blocked since all liquidity is managed by the token wrapper + error LiquidityNotAllowed(); + + /// @notice Thrown when initializing a pool with invalid tokens + /// @dev Pool must contain exactly one wrapper token and its underlying token + error InvalidPoolToken(); + + /// @notice Thrown when initializing a pool with non-zero fee + /// @dev Fee must be 0 as wrapper pools don't charge fees + error InvalidPoolFee(); + + /// @notice Thrown when exact input swaps are not supported + error ExactInputNotSupported(); + + /// @notice Thrown when exact output swaps are not supported + error ExactOutputNotSupported(); + + /// @notice The wrapped token currency (e.g., WETH) + Currency public immutable wrapperCurrency; + + /// @notice The underlying token currency (e.g., ETH) + Currency public immutable underlyingCurrency; + + /// @notice Indicates whether wrapping occurs when swapping from token0 to token1 + /// @dev This is determined by the relative ordering of the wrapper and underlying tokens + /// @dev If true: token0 is underlying (e.g. ETH) and token1 is wrapper (e.g. WETH) + /// @dev If false: token0 is wrapper (e.g. WETH) and token1 is underlying (e.g. ETH) + /// @dev This is set in the constructor based on the token addresses to ensure consistent behavior + bool public immutable wrapZeroForOne; + + /// @notice Creates a new token wrapper hook + /// @param _manager The Uniswap V4 pool manager + /// @param _wrapper The wrapped token currency (e.g., WETH) + /// @param _underlying The underlying token currency (e.g., ETH) + constructor(IPoolManager _manager, Currency _wrapper, Currency _underlying) BaseHook(_manager) { + wrapperCurrency = _wrapper; + underlyingCurrency = _underlying; + wrapZeroForOne = _underlying < _wrapper; + } + + /// @inheritdoc BaseHook + function getHookPermissions() public pure override returns (Hooks.Permissions memory) { + return Hooks.Permissions({ + beforeInitialize: true, + beforeAddLiquidity: true, + beforeSwap: true, + beforeSwapReturnDelta: true, + afterSwap: false, + afterInitialize: false, + beforeRemoveLiquidity: false, + afterAddLiquidity: false, + afterRemoveLiquidity: false, + beforeDonate: false, + afterDonate: false, + afterSwapReturnDelta: false, + afterAddLiquidityReturnDelta: false, + afterRemoveLiquidityReturnDelta: false + }); + } + + /// @notice Validates pool initialization parameters + /// @dev Ensures pool contains wrapper and underlying tokens with zero fee + /// @param poolKey The pool configuration including tokens and fee + /// @return The function selector if validation passes + function _beforeInitialize(address, PoolKey calldata poolKey, uint160) internal view override returns (bytes4) { + // ensure pool tokens are the wrapper currency and underlying currency + bool isValidPair = wrapZeroForOne + ? (poolKey.currency0 == underlyingCurrency && poolKey.currency1 == wrapperCurrency) + : (poolKey.currency0 == wrapperCurrency && poolKey.currency1 == underlyingCurrency); + + if (!isValidPair) revert InvalidPoolToken(); + if (poolKey.fee != 0) revert InvalidPoolFee(); + + return IHooks.beforeInitialize.selector; + } + + /// @notice Prevents liquidity operations on wrapper pools + /// @dev Always reverts as liquidity is managed through the token wrapper + function _beforeAddLiquidity(address, PoolKey calldata, ModifyLiquidityParams calldata, bytes calldata) + internal + pure + override + returns (bytes4) + { + revert LiquidityNotAllowed(); + } + + /// @notice Handles token wrapping and unwrapping during swaps + /// @dev Processes both exact input (amountSpecified < 0) and exact output (amountSpecified > 0) swaps + /// @param params The swap parameters including direction and amount + /// @return selector The function selector + /// @return swapDelta The input/output token amounts for pool accounting + /// @return lpFeeOverride The fee override (always 0 for wrapper pools) + function _beforeSwap(address, PoolKey calldata, SwapParams calldata params, bytes calldata) + internal + override + returns (bytes4, BeforeSwapDelta swapDelta, uint24) + { + bool isExactInput = params.amountSpecified < 0; + if (isExactInput && !_supportsExactInput()) revert ExactInputNotSupported(); + if (!isExactInput && !_supportsExactOutput()) revert ExactOutputNotSupported(); + + if (wrapZeroForOne == params.zeroForOne) { + // we are wrapping + uint256 inputAmount = + isExactInput ? uint256(-params.amountSpecified) : _getWrapInputRequired(uint256(params.amountSpecified)); + (uint256 actualUnderlyingAmount, uint256 wrappedAmount) = _deposit(inputAmount); + int128 amountUnspecified = + isExactInput ? -wrappedAmount.toInt256().toInt128() : actualUnderlyingAmount.toInt256().toInt128(); + swapDelta = toBeforeSwapDelta(-params.amountSpecified.toInt128(), amountUnspecified); + } else { + // we are unwrapping + uint256 inputAmount = isExactInput + ? uint256(-params.amountSpecified) + : _getUnwrapInputRequired(uint256(params.amountSpecified)); + (uint256 actualWrappedAmount, uint256 unwrappedAmount) = _withdraw(inputAmount); + int128 amountUnspecified = + isExactInput ? -unwrappedAmount.toInt256().toInt128() : actualWrappedAmount.toInt256().toInt128(); + swapDelta = toBeforeSwapDelta(-params.amountSpecified.toInt128(), amountUnspecified); + } + + return (IHooks.beforeSwap.selector, swapDelta, 0); + } + + /// @notice Transfers tokens to the pool manager + /// @param token The token to transfer + /// @param amount The amount to transfer + /// @inheritdoc DeltaResolver + function _pay(Currency token, address, uint256 amount) internal override { + token.transfer(address(poolManager), amount); + } + + /// @notice Deposits underlying tokens to receive wrapper tokens + /// @param underlyingAmount The amount of underlying tokens to deposit + /// @return actualUnderlyingAmount the actual number of underlying tokens used, i.e. to account for rebasing rounding errors + /// @return wrappedAmount The amount of wrapper tokens received + /// @dev Implementing contracts should handle: + // - taking tokens from PoolManager + // - performing the wrapping operation + // - settling tokens on PoolManager + function _deposit(uint256 underlyingAmount) + internal + virtual + returns (uint256 actualUnderlyingAmount, uint256 wrappedAmount); + + /// @notice Withdraws wrapper tokens to receive underlying tokens + /// @param wrappedAmount The amount of wrapper tokens to withdraw + /// @return actualWrappedAmount the actual number of wrapped tokens used, i.e. to account for rebasing rounding errors + /// @return underlyingAmount The amount of underlying tokens received + /// @dev Implementing contracts should handle: + // - taking tokens from PoolManager + // - performing the unwrapping operation + // - settling tokens on PoolManager + function _withdraw(uint256 wrappedAmount) + internal + virtual + returns (uint256 actualWrappedAmount, uint256 underlyingAmount); + + /// @notice Calculates underlying tokens needed to receive desired wrapper tokens + /// @param wrappedAmount The desired amount of wrapper tokens + /// @return The required amount of underlying tokens + /// @dev Default implementation assumes 1:1 ratio + /// @dev Override for wrappers with different exchange rates + function _getWrapInputRequired(uint256 wrappedAmount) internal view virtual returns (uint256) { + return wrappedAmount; + } + + /// @notice Calculates wrapper tokens needed to receive desired underlying tokens + /// @param underlyingAmount The desired amount of underlying tokens + /// @return The required amount of wrapper tokens + /// @dev Default implementation assumes 1:1 ratio + /// @dev Override for wrappers with different exchange rates + function _getUnwrapInputRequired(uint256 underlyingAmount) internal view virtual returns (uint256) { + return underlyingAmount; + } + + /// @notice Indicates whether the hook supports exact output swaps + /// @dev Default implementation returns true + /// @dev Override for wrappers that cannot support exact output swaps + function _supportsExactOutput() internal view virtual returns (bool) { + return true; + } + + /// @notice Indicates whether the hook supports exact input swaps + /// @dev Default implementation returns true + /// @dev Override for wrappers that cannot support exact input swaps + function _supportsExactInput() internal view virtual returns (bool) { + return true; + } +} diff --git a/CVLByExample/UniswapHooks/lib/uniswap-hooks/lib/v4-periphery/src/hooks/WETHHook.sol b/CVLByExample/UniswapHooks/lib/uniswap-hooks/lib/v4-periphery/src/hooks/WETHHook.sol new file mode 100644 index 00000000..d2aeef31 --- /dev/null +++ b/CVLByExample/UniswapHooks/lib/uniswap-hooks/lib/v4-periphery/src/hooks/WETHHook.sol @@ -0,0 +1,54 @@ +// SPDX-License-Identifier: MIT +pragma solidity 0.8.26; + +import {WETH} from "solmate/src/tokens/WETH.sol"; +import {BaseTokenWrapperHook} from "../base/hooks/BaseTokenWrapperHook.sol"; +import {IPoolManager} from "@uniswap/v4-core/src/interfaces/IPoolManager.sol"; +import {Currency, CurrencyLibrary} from "@uniswap/v4-core/src/types/Currency.sol"; + +/// @title Wrapped Ether Hook +/// @notice Hook for wrapping/unwrapping ETH in Uniswap V4 pools +/// @dev Implements 1:1 wrapping/unwrapping of ETH to WETH +contract WETHHook is BaseTokenWrapperHook { + /// @notice The WETH9 contract + WETH public immutable weth; + + /// @notice Creates a new WETH wrapper hook + /// @param _manager The Uniswap V4 pool manager + /// @param _weth The WETH9 contract address + constructor(IPoolManager _manager, address payable _weth) + BaseTokenWrapperHook( + _manager, + Currency.wrap(_weth), // wrapper token is WETH + CurrencyLibrary.ADDRESS_ZERO // underlying token is ETH (address(0)) + ) + { + weth = WETH(payable(_weth)); + } + + /// @inheritdoc BaseTokenWrapperHook + /// @dev Note the WETH deposit relies on the WETH wrapper having a receive function that mints WETH to msg.sender + function _deposit(uint256 underlyingAmount) internal override returns (uint256, uint256) { + // Sync WETH on PoolManager + poolManager.sync(wrapperCurrency); + // take ETH from PoolManager and deposit directly into the WETH contract + // this will mint WETH to msg.sender (PoolManager in this case) + _take(underlyingCurrency, address(weth), underlyingAmount); + // Settle on PoolManager which will take into account the new weth + poolManager.settle(); + return (underlyingAmount, underlyingAmount); // 1:1 ratio + } + + /// @inheritdoc BaseTokenWrapperHook + function _withdraw(uint256 wrapperAmount) internal override returns (uint256, uint256) { + // take WETH into this hook contract + _take(wrapperCurrency, address(this), wrapperAmount); + // Withdraw WETH - this returns ETH back to this hook contract + weth.withdraw(wrapperAmount); + _settle(underlyingCurrency, address(this), wrapperAmount); + return (wrapperAmount, wrapperAmount); // 1:1 ratio + } + + /// @notice Required to receive ETH + receive() external payable {} +} diff --git a/CVLByExample/UniswapHooks/lib/uniswap-hooks/lib/v4-periphery/src/hooks/WstETHHook.sol b/CVLByExample/UniswapHooks/lib/uniswap-hooks/lib/v4-periphery/src/hooks/WstETHHook.sol new file mode 100644 index 00000000..1a8a067a --- /dev/null +++ b/CVLByExample/UniswapHooks/lib/uniswap-hooks/lib/v4-periphery/src/hooks/WstETHHook.sol @@ -0,0 +1,87 @@ +// SPDX-License-Identifier: MIT +pragma solidity 0.8.26; + +import {ERC20} from "solmate/src/tokens/ERC20.sol"; +import {IPoolManager} from "@uniswap/v4-core/src/interfaces/IPoolManager.sol"; +import {FixedPointMathLib} from "solmate/src/utils/FixedPointMathLib.sol"; +import {Currency} from "@uniswap/v4-core/src/types/Currency.sol"; +import {BaseTokenWrapperHook} from "../base/hooks/BaseTokenWrapperHook.sol"; +import {IWstETH, IStETH} from "../interfaces/external/IWstETH.sol"; +import {SafeTransferLib} from "solmate/src/utils/SafeTransferLib.sol"; + +/// @title Wrapped Staked ETH (wstETH) Hook +/// @notice Hook for wrapping/unwrapping stETH/wstETH in Uniswap V4 pools +/// @dev Implements dynamic exchange rate wrapping/unwrapping between stETH and wstETH +/// @dev wstETH represents stETH with accrued staking rewards, maintaining a dynamic exchange rate +contract WstETHHook is BaseTokenWrapperHook { + using FixedPointMathLib for uint256; + using SafeTransferLib for ERC20; + + /// @notice The wstETH contract used for wrapping/unwrapping operations + IWstETH public immutable wstETH; + + /// @notice Creates a new wstETH wrapper hook + /// @param _manager The Uniswap V4 pool manager + /// @param _wsteth The wstETH contract address + /// @dev Initializes with wstETH as wrapper token and stETH as underlying token + constructor(IPoolManager _manager, IWstETH _wsteth) + BaseTokenWrapperHook( + _manager, + Currency.wrap(address(_wsteth)), // wrapper token is wstETH + Currency.wrap(_wsteth.stETH()) // underlying token is stETH + ) + { + wstETH = _wsteth; + ERC20(Currency.unwrap(underlyingCurrency)).safeApprove(address(wstETH), type(uint256).max); + } + + /// @inheritdoc BaseTokenWrapperHook + function _deposit(uint256 underlyingAmount) + internal + override + returns (uint256 actualUnderlyingAmount, uint256 wrappedAmount) + { + _take(underlyingCurrency, address(this), underlyingAmount); + // For wrapping, the key is ensuring we wrap exactly what we got + actualUnderlyingAmount = IStETH(Currency.unwrap(underlyingCurrency)).balanceOf(address(this)); + + // Wrap exactly what we have (which might be 1-2 wei less than requested) + wrappedAmount = wstETH.wrap(actualUnderlyingAmount); + _settle(wrapperCurrency, address(this), wrappedAmount); + } + + /// @inheritdoc BaseTokenWrapperHook + function _withdraw(uint256 wrapperAmount) + internal + override + returns (uint256 actualWrappedAmount, uint256 unwrappedAmount) + { + _take(wrapperCurrency, address(this), wrapperAmount); + actualWrappedAmount = wrapperAmount; + unwrappedAmount = wstETH.unwrap(actualWrappedAmount); + _settle(underlyingCurrency, address(this), unwrappedAmount); + } + + /// @inheritdoc BaseTokenWrapperHook + /// @notice Calculates how much stETH is needed to receive a specific amount of wstETH + /// @param wrappedAmount Desired amount of wstETH + /// @return Amount of stETH required + /// @dev Uses current stETH/wstETH exchange rate for calculation + function _getWrapInputRequired(uint256 wrappedAmount) internal view override returns (uint256) { + return wrappedAmount.divWadUp(wstETH.tokensPerStEth()); + } + + /// @inheritdoc BaseTokenWrapperHook + /// @notice Calculates how much wstETH is needed to receive a specific amount of stETH + /// @param underlyingAmount Desired amount of stETH + /// @return Amount of wstETH required + /// @dev Uses current stETH/wstETH exchange rate for calculation + function _getUnwrapInputRequired(uint256 underlyingAmount) internal view override returns (uint256) { + return wstETH.getWstETHByStETH(underlyingAmount); + } + + /// @inheritdoc BaseTokenWrapperHook + function _supportsExactOutput() internal pure override returns (bool) { + return false; + } +} diff --git a/CVLByExample/UniswapHooks/lib/uniswap-hooks/lib/v4-periphery/src/interfaces/IEIP712_v4.sol b/CVLByExample/UniswapHooks/lib/uniswap-hooks/lib/v4-periphery/src/interfaces/IEIP712_v4.sol new file mode 100644 index 00000000..8e78c586 --- /dev/null +++ b/CVLByExample/UniswapHooks/lib/uniswap-hooks/lib/v4-periphery/src/interfaces/IEIP712_v4.sol @@ -0,0 +1,10 @@ +// SPDX-License-Identifier: MIT +pragma solidity ^0.8.0; + +/// @title IEIP712_v4 +/// @notice Interface for the EIP712 contract +interface IEIP712_v4 { + /// @notice Returns the domain separator for the current chain. + /// @return bytes32 The domain separator + function DOMAIN_SEPARATOR() external view returns (bytes32); +} diff --git a/CVLByExample/UniswapHooks/lib/uniswap-hooks/lib/v4-periphery/src/interfaces/IERC721Permit_v4.sol b/CVLByExample/UniswapHooks/lib/uniswap-hooks/lib/v4-periphery/src/interfaces/IERC721Permit_v4.sol new file mode 100644 index 00000000..b45a7d81 --- /dev/null +++ b/CVLByExample/UniswapHooks/lib/uniswap-hooks/lib/v4-periphery/src/interfaces/IERC721Permit_v4.sol @@ -0,0 +1,38 @@ +// SPDX-License-Identifier: MIT +pragma solidity ^0.8.0; + +/// @title IERC721Permit_v4 +/// @notice Interface for the ERC721Permit_v4 contract +interface IERC721Permit_v4 { + error SignatureDeadlineExpired(); + error NoSelfPermit(); + error Unauthorized(); + + /// @notice Approve of a specific token ID for spending by spender via signature + /// @param spender The account that is being approved + /// @param tokenId The ID of the token that is being approved for spending + /// @param deadline The deadline timestamp by which the call must be mined for the approve to work + /// @param nonce a unique value, for an owner, to prevent replay attacks; an unordered nonce where the top 248 bits correspond to a word and the bottom 8 bits calculate the bit position of the word + /// @param signature Concatenated data from a valid secp256k1 signature from the holder, i.e. abi.encodePacked(r, s, v) + /// @dev payable so it can be multicalled with NATIVE related actions + function permit(address spender, uint256 tokenId, uint256 deadline, uint256 nonce, bytes calldata signature) + external + payable; + + /// @notice Set an operator with full permission to an owner's tokens via signature + /// @param owner The address that is setting the operator + /// @param operator The address that will be set as an operator for the owner + /// @param approved The permission to set on the operator + /// @param deadline The deadline timestamp by which the call must be mined for the approve to work + /// @param nonce a unique value, for an owner, to prevent replay attacks; an unordered nonce where the top 248 bits correspond to a word and the bottom 8 bits calculate the bit position of the word + /// @param signature Concatenated data from a valid secp256k1 signature from the holder, i.e. abi.encodePacked(r, s, v) + /// @dev payable so it can be multicalled with NATIVE related actions + function permitForAll( + address owner, + address operator, + bool approved, + uint256 deadline, + uint256 nonce, + bytes calldata signature + ) external payable; +} diff --git a/CVLByExample/UniswapHooks/lib/uniswap-hooks/lib/v4-periphery/src/interfaces/IImmutableState.sol b/CVLByExample/UniswapHooks/lib/uniswap-hooks/lib/v4-periphery/src/interfaces/IImmutableState.sol new file mode 100644 index 00000000..4d265164 --- /dev/null +++ b/CVLByExample/UniswapHooks/lib/uniswap-hooks/lib/v4-periphery/src/interfaces/IImmutableState.sol @@ -0,0 +1,11 @@ +// SPDX-License-Identifier: MIT +pragma solidity ^0.8.0; + +import {IPoolManager} from "@uniswap/v4-core/src/interfaces/IPoolManager.sol"; + +/// @title IImmutableState +/// @notice Interface for the ImmutableState contract +interface IImmutableState { + /// @notice The Uniswap v4 PoolManager contract + function poolManager() external view returns (IPoolManager); +} diff --git a/CVLByExample/UniswapHooks/lib/uniswap-hooks/lib/v4-periphery/src/interfaces/IMsgSender.sol b/CVLByExample/UniswapHooks/lib/uniswap-hooks/lib/v4-periphery/src/interfaces/IMsgSender.sol new file mode 100644 index 00000000..c0d44fdc --- /dev/null +++ b/CVLByExample/UniswapHooks/lib/uniswap-hooks/lib/v4-periphery/src/interfaces/IMsgSender.sol @@ -0,0 +1,13 @@ +// SPDX-License-Identifier: MIT +pragma solidity ^0.8.0; + +/// @title IMsgSender +/// @notice Interface for contracts that expose the original caller +interface IMsgSender { + /// @notice Returns the address of the original caller (msg.sender) + /// @dev Uniswap v4 periphery contracts implement a callback pattern which lose + /// the original msg.sender caller context. This view function provides a way for + /// integrating contracts (e.g. hooks) to access the original caller address. + /// @return The address of the original caller + function msgSender() external view returns (address); +} diff --git a/CVLByExample/UniswapHooks/lib/uniswap-hooks/lib/v4-periphery/src/interfaces/IMulticall_v4.sol b/CVLByExample/UniswapHooks/lib/uniswap-hooks/lib/v4-periphery/src/interfaces/IMulticall_v4.sol new file mode 100644 index 00000000..3caddb72 --- /dev/null +++ b/CVLByExample/UniswapHooks/lib/uniswap-hooks/lib/v4-periphery/src/interfaces/IMulticall_v4.sol @@ -0,0 +1,13 @@ +// SPDX-License-Identifier: MIT +pragma solidity ^0.8.0; + +/// @title IMulticall_v4 +/// @notice Interface for the Multicall_v4 contract +interface IMulticall_v4 { + /// @notice Call multiple functions in the current contract and return the data from all of them if they all succeed + /// @dev The `msg.value` is passed onto all subcalls, even if a previous subcall has consumed the ether. + /// Subcalls can instead use `address(this).value` to see the available ETH, and consume it using {value: x}. + /// @param data The encoded function data for each of the calls to make to this contract + /// @return results The results from each of the calls passed in via data + function multicall(bytes[] calldata data) external payable returns (bytes[] memory results); +} diff --git a/CVLByExample/UniswapHooks/lib/uniswap-hooks/lib/v4-periphery/src/interfaces/INotifier.sol b/CVLByExample/UniswapHooks/lib/uniswap-hooks/lib/v4-periphery/src/interfaces/INotifier.sol new file mode 100644 index 00000000..e7b61487 --- /dev/null +++ b/CVLByExample/UniswapHooks/lib/uniswap-hooks/lib/v4-periphery/src/interfaces/INotifier.sol @@ -0,0 +1,54 @@ +// SPDX-License-Identifier: MIT +pragma solidity ^0.8.0; + +import {ISubscriber} from "./ISubscriber.sol"; + +/// @title INotifier +/// @notice Interface for the Notifier contract +interface INotifier { + /// @notice Thrown when unsubscribing without a subscriber + error NotSubscribed(); + /// @notice Thrown when a subscriber does not have code + error NoCodeSubscriber(); + /// @notice Thrown when a user specifies a gas limit too low to avoid valid unsubscribe notifications + error GasLimitTooLow(); + /// @notice Wraps the revert message of the subscriber contract on a reverting subscription + error SubscriptionReverted(address subscriber, bytes reason); + /// @notice Wraps the revert message of the subscriber contract on a reverting modify liquidity notification + error ModifyLiquidityNotificationReverted(address subscriber, bytes reason); + /// @notice Wraps the revert message of the subscriber contract on a reverting burn notification + error BurnNotificationReverted(address subscriber, bytes reason); + /// @notice Thrown when a tokenId already has a subscriber + error AlreadySubscribed(uint256 tokenId, address subscriber); + + /// @notice Emitted on a successful call to subscribe + event Subscription(uint256 indexed tokenId, address indexed subscriber); + /// @notice Emitted on a successful call to unsubscribe + event Unsubscription(uint256 indexed tokenId, address indexed subscriber); + + /// @notice Returns the subscriber for a respective position + /// @param tokenId the ERC721 tokenId + /// @return subscriber the subscriber contract + function subscriber(uint256 tokenId) external view returns (ISubscriber subscriber); + + /// @notice Enables the subscriber to receive notifications for a respective position + /// @param tokenId the ERC721 tokenId + /// @param newSubscriber the address of the subscriber contract + /// @param data caller-provided data that's forwarded to the subscriber contract + /// @dev Calling subscribe when a position is already subscribed will revert + /// @dev payable so it can be multicalled with NATIVE related actions + /// @dev will revert if pool manager is locked + function subscribe(uint256 tokenId, address newSubscriber, bytes calldata data) external payable; + + /// @notice Removes the subscriber from receiving notifications for a respective position + /// @param tokenId the ERC721 tokenId + /// @dev Callers must specify a high gas limit (remaining gas should be higher than unsubscriberGasLimit) such that the subscriber can be notified + /// @dev payable so it can be multicalled with NATIVE related actions + /// @dev Must always allow a user to unsubscribe. In the case of a malicious subscriber, a user can always unsubscribe safely, ensuring liquidity is always modifiable. + /// @dev will revert if pool manager is locked + function unsubscribe(uint256 tokenId) external payable; + + /// @notice Returns and determines the maximum allowable gas-used for notifying unsubscribe + /// @return uint256 the maximum gas limit when notifying a subscriber's `notifyUnsubscribe` function + function unsubscribeGasLimit() external view returns (uint256); +} diff --git a/CVLByExample/UniswapHooks/lib/uniswap-hooks/lib/v4-periphery/src/interfaces/IPermit2Forwarder.sol b/CVLByExample/UniswapHooks/lib/uniswap-hooks/lib/v4-periphery/src/interfaces/IPermit2Forwarder.sol new file mode 100644 index 00000000..98403ac8 --- /dev/null +++ b/CVLByExample/UniswapHooks/lib/uniswap-hooks/lib/v4-periphery/src/interfaces/IPermit2Forwarder.sol @@ -0,0 +1,30 @@ +// SPDX-License-Identifier: MIT +pragma solidity ^0.8.0; + +import {IAllowanceTransfer} from "permit2/src/interfaces/IAllowanceTransfer.sol"; + +/// @title IPermit2Forwarder +/// @notice Interface for the Permit2Forwarder contract +interface IPermit2Forwarder { + /// @notice allows forwarding a single permit to permit2 + /// @dev this function is payable to allow multicall with NATIVE based actions + /// @param owner the owner of the tokens + /// @param permitSingle the permit data + /// @param signature the signature of the permit; abi.encodePacked(r, s, v) + /// @return err the error returned by a reverting permit call, empty if successful + function permit(address owner, IAllowanceTransfer.PermitSingle calldata permitSingle, bytes calldata signature) + external + payable + returns (bytes memory err); + + /// @notice allows forwarding batch permits to permit2 + /// @dev this function is payable to allow multicall with NATIVE based actions + /// @param owner the owner of the tokens + /// @param _permitBatch a batch of approvals + /// @param signature the signature of the permit; abi.encodePacked(r, s, v) + /// @return err the error returned by a reverting permit call, empty if successful + function permitBatch(address owner, IAllowanceTransfer.PermitBatch calldata _permitBatch, bytes calldata signature) + external + payable + returns (bytes memory err); +} diff --git a/CVLByExample/UniswapHooks/lib/uniswap-hooks/lib/v4-periphery/src/interfaces/IPoolInitializer_v4.sol b/CVLByExample/UniswapHooks/lib/uniswap-hooks/lib/v4-periphery/src/interfaces/IPoolInitializer_v4.sol new file mode 100644 index 00000000..ad0e14b1 --- /dev/null +++ b/CVLByExample/UniswapHooks/lib/uniswap-hooks/lib/v4-periphery/src/interfaces/IPoolInitializer_v4.sol @@ -0,0 +1,15 @@ +// SPDX-License-Identifier: MIT +pragma solidity ^0.8.0; + +import {PoolKey} from "@uniswap/v4-core/src/types/PoolKey.sol"; + +/// @title IPoolInitializer_v4 +/// @notice Interface for the PoolInitializer_v4 contract +interface IPoolInitializer_v4 { + /// @notice Initialize a Uniswap v4 Pool + /// @dev If the pool is already initialized, this function will not revert and just return type(int24).max + /// @param key The PoolKey of the pool to initialize + /// @param sqrtPriceX96 The initial starting price of the pool, expressed as a sqrtPriceX96 + /// @return The current tick of the pool, or type(int24).max if the pool creation failed, or the pool already existed + function initializePool(PoolKey calldata key, uint160 sqrtPriceX96) external payable returns (int24); +} diff --git a/CVLByExample/UniswapHooks/lib/uniswap-hooks/lib/v4-periphery/src/interfaces/IPositionDescriptor.sol b/CVLByExample/UniswapHooks/lib/uniswap-hooks/lib/v4-periphery/src/interfaces/IPositionDescriptor.sol new file mode 100644 index 00000000..92085217 --- /dev/null +++ b/CVLByExample/UniswapHooks/lib/uniswap-hooks/lib/v4-periphery/src/interfaces/IPositionDescriptor.sol @@ -0,0 +1,40 @@ +// SPDX-License-Identifier: MIT +pragma solidity ^0.8.24; + +import "./IPositionManager.sol"; +import {IPoolManager} from "@uniswap/v4-core/src/interfaces/IPoolManager.sol"; + +/// @title IPositionDescriptor +/// @notice Interface for the PositionDescriptor contract +interface IPositionDescriptor { + error InvalidTokenId(uint256 tokenId); + + /// @notice Produces the URI describing a particular token ID + /// @dev Note this URI may be a data: URI with the JSON contents directly inlined + /// @param positionManager The position manager for which to describe the token + /// @param tokenId The ID of the token for which to produce a description, which may not be valid + /// @return The URI of the ERC721-compliant metadata + function tokenURI(IPositionManager positionManager, uint256 tokenId) external view returns (string memory); + + /// @notice Returns true if currency0 has higher priority than currency1 + /// @param currency0 The first currency address + /// @param currency1 The second currency address + /// @return True if currency0 has higher priority than currency1 + function flipRatio(address currency0, address currency1) external view returns (bool); + + /// @notice Returns the priority of a currency. + /// For certain currencies on mainnet, the smaller the currency, the higher the priority + /// And those with the higher priority values (more positive values) will be in the numerator of the price ratio + /// @param currency The currency address + /// @return The priority of the currency + function currencyRatioPriority(address currency) external view returns (int256); + + /// @return The wrapped native token for this descriptor + function wrappedNative() external view returns (address); + + /// @return The native currency label for this descriptor + function nativeCurrencyLabel() external view returns (string memory); + + /// @return The pool manager for this descriptor + function poolManager() external view returns (IPoolManager); +} diff --git a/CVLByExample/UniswapHooks/lib/uniswap-hooks/lib/v4-periphery/src/interfaces/IPositionManager.sol b/CVLByExample/UniswapHooks/lib/uniswap-hooks/lib/v4-periphery/src/interfaces/IPositionManager.sol new file mode 100644 index 00000000..03cb25a4 --- /dev/null +++ b/CVLByExample/UniswapHooks/lib/uniswap-hooks/lib/v4-periphery/src/interfaces/IPositionManager.sol @@ -0,0 +1,68 @@ +// SPDX-License-Identifier: MIT +pragma solidity ^0.8.0; + +import {PoolKey} from "@uniswap/v4-core/src/types/PoolKey.sol"; +import {PositionInfo} from "../libraries/PositionInfoLibrary.sol"; + +import {INotifier} from "./INotifier.sol"; +import {IImmutableState} from "./IImmutableState.sol"; +import {IERC721Permit_v4} from "./IERC721Permit_v4.sol"; +import {IEIP712_v4} from "./IEIP712_v4.sol"; +import {IMulticall_v4} from "./IMulticall_v4.sol"; +import {IPoolInitializer_v4} from "./IPoolInitializer_v4.sol"; +import {IUnorderedNonce} from "./IUnorderedNonce.sol"; +import {IPermit2Forwarder} from "./IPermit2Forwarder.sol"; + +/// @title IPositionManager +/// @notice Interface for the PositionManager contract +interface IPositionManager is + INotifier, + IImmutableState, + IERC721Permit_v4, + IEIP712_v4, + IMulticall_v4, + IPoolInitializer_v4, + IUnorderedNonce, + IPermit2Forwarder +{ + /// @notice Thrown when the caller is not approved to modify a position + error NotApproved(address caller); + /// @notice Thrown when the block.timestamp exceeds the user-provided deadline + error DeadlinePassed(uint256 deadline); + /// @notice Thrown when calling transfer, subscribe, or unsubscribe when the PoolManager is unlocked. + /// @dev This is to prevent hooks from being able to trigger notifications at the same time the position is being modified. + error PoolManagerMustBeLocked(); + + /// @notice Unlocks Uniswap v4 PoolManager and batches actions for modifying liquidity + /// @dev This is the standard entrypoint for the PositionManager + /// @param unlockData is an encoding of actions, and parameters for those actions + /// @param deadline is the deadline for the batched actions to be executed + function modifyLiquidities(bytes calldata unlockData, uint256 deadline) external payable; + + /// @notice Batches actions for modifying liquidity without unlocking v4 PoolManager + /// @dev This must be called by a contract that has already unlocked the v4 PoolManager + /// @param actions the actions to perform + /// @param params the parameters to provide for the actions + function modifyLiquiditiesWithoutUnlock(bytes calldata actions, bytes[] calldata params) external payable; + + /// @notice Used to get the ID that will be used for the next minted liquidity position + /// @return uint256 The next token ID + function nextTokenId() external view returns (uint256); + + /// @notice Returns the liquidity of a position + /// @param tokenId the ERC721 tokenId + /// @return liquidity the position's liquidity, as a liquidityAmount + /// @dev this value can be processed as an amount0 and amount1 by using the LiquidityAmounts library + function getPositionLiquidity(uint256 tokenId) external view returns (uint128 liquidity); + + /// @notice Returns the pool key and position info of a position + /// @param tokenId the ERC721 tokenId + /// @return poolKey the pool key of the position + /// @return PositionInfo a uint256 packed value holding information about the position including the range (tickLower, tickUpper) + function getPoolAndPositionInfo(uint256 tokenId) external view returns (PoolKey memory, PositionInfo); + + /// @notice Returns the position info of a position + /// @param tokenId the ERC721 tokenId + /// @return a uint256 packed value holding information about the position including the range (tickLower, tickUpper) + function positionInfo(uint256 tokenId) external view returns (PositionInfo); +} diff --git a/CVLByExample/UniswapHooks/lib/uniswap-hooks/lib/v4-periphery/src/interfaces/IStateView.sol b/CVLByExample/UniswapHooks/lib/uniswap-hooks/lib/v4-periphery/src/interfaces/IStateView.sol new file mode 100644 index 00000000..8455fabc --- /dev/null +++ b/CVLByExample/UniswapHooks/lib/uniswap-hooks/lib/v4-periphery/src/interfaces/IStateView.sol @@ -0,0 +1,133 @@ +// SPDX-License-Identifier: MIT +pragma solidity ^0.8.0; + +import {StateLibrary} from "@uniswap/v4-core/src/libraries/StateLibrary.sol"; +import {PoolId} from "@uniswap/v4-core/src/types/PoolId.sol"; +import {IPoolManager} from "@uniswap/v4-core/src/interfaces/IPoolManager.sol"; +import {Position} from "@uniswap/v4-core/src/libraries/Position.sol"; +import {IImmutableState} from "../interfaces/IImmutableState.sol"; + +/// @title IStateView +/// @notice Interface for the StateView contract +interface IStateView is IImmutableState { + /// @notice Get Slot0 of the pool: sqrtPriceX96, tick, protocolFee, lpFee + /// @dev Corresponds to pools[poolId].slot0 + /// @param poolId The ID of the pool. + /// @return sqrtPriceX96 The square root of the price of the pool, in Q96 precision. + /// @return tick The current tick of the pool. + /// @return protocolFee The protocol fee of the pool. + /// @return lpFee The swap fee of the pool. + function getSlot0(PoolId poolId) + external + view + returns (uint160 sqrtPriceX96, int24 tick, uint24 protocolFee, uint24 lpFee); + + /// @notice Retrieves the tick information of a pool at a specific tick. + /// @dev Corresponds to pools[poolId].ticks[tick] + /// @param poolId The ID of the pool. + /// @param tick The tick to retrieve information for. + /// @return liquidityGross The total position liquidity that references this tick + /// @return liquidityNet The amount of net liquidity added (subtracted) when tick is crossed from left to right (right to left) + /// @return feeGrowthOutside0X128 fee growth per unit of liquidity on the _other_ side of this tick (relative to the current tick) + /// @return feeGrowthOutside1X128 fee growth per unit of liquidity on the _other_ side of this tick (relative to the current tick) + function getTickInfo(PoolId poolId, int24 tick) + external + view + returns ( + uint128 liquidityGross, + int128 liquidityNet, + uint256 feeGrowthOutside0X128, + uint256 feeGrowthOutside1X128 + ); + + /// @notice Retrieves the liquidity information of a pool at a specific tick. + /// @dev Corresponds to pools[poolId].ticks[tick].liquidityGross and pools[poolId].ticks[tick].liquidityNet. A more gas efficient version of getTickInfo + /// @param poolId The ID of the pool. + /// @param tick The tick to retrieve liquidity for. + /// @return liquidityGross The total position liquidity that references this tick + /// @return liquidityNet The amount of net liquidity added (subtracted) when tick is crossed from left to right (right to left) + function getTickLiquidity(PoolId poolId, int24 tick) + external + view + returns (uint128 liquidityGross, int128 liquidityNet); + + /// @notice Retrieves the fee growth outside a tick range of a pool + /// @dev Corresponds to pools[poolId].ticks[tick].feeGrowthOutside0X128 and pools[poolId].ticks[tick].feeGrowthOutside1X128. A more gas efficient version of getTickInfo + /// @param poolId The ID of the pool. + /// @param tick The tick to retrieve fee growth for. + /// @return feeGrowthOutside0X128 fee growth per unit of liquidity on the _other_ side of this tick (relative to the current tick) + /// @return feeGrowthOutside1X128 fee growth per unit of liquidity on the _other_ side of this tick (relative to the current tick) + function getTickFeeGrowthOutside(PoolId poolId, int24 tick) + external + view + returns (uint256 feeGrowthOutside0X128, uint256 feeGrowthOutside1X128); + + /// @notice Retrieves the global fee growth of a pool. + /// @dev Corresponds to pools[poolId].feeGrowthGlobal0X128 and pools[poolId].feeGrowthGlobal1X128 + /// @param poolId The ID of the pool. + /// @return feeGrowthGlobal0 The global fee growth for token0. + /// @return feeGrowthGlobal1 The global fee growth for token1. + function getFeeGrowthGlobals(PoolId poolId) + external + view + returns (uint256 feeGrowthGlobal0, uint256 feeGrowthGlobal1); + + /// @notice Retrieves the total liquidity of a pool. + /// @dev Corresponds to pools[poolId].liquidity + /// @param poolId The ID of the pool. + /// @return liquidity The liquidity of the pool. + function getLiquidity(PoolId poolId) external view returns (uint128 liquidity); + + /// @notice Retrieves the tick bitmap of a pool at a specific tick. + /// @dev Corresponds to pools[poolId].tickBitmap[tick] + /// @param poolId The ID of the pool. + /// @param tick The tick to retrieve the bitmap for. + /// @return tickBitmap The bitmap of the tick. + function getTickBitmap(PoolId poolId, int16 tick) external view returns (uint256 tickBitmap); + + /// @notice Retrieves the position info without needing to calculate the `positionId`. + /// @dev Corresponds to pools[poolId].positions[positionId] + /// @param poolId The ID of the pool. + /// @param owner The owner of the liquidity position. + /// @param tickLower The lower tick of the liquidity range. + /// @param tickUpper The upper tick of the liquidity range. + /// @param salt The bytes32 randomness to further distinguish position state. + /// @return liquidity The liquidity of the position. + /// @return feeGrowthInside0LastX128 The fee growth inside the position for token0. + /// @return feeGrowthInside1LastX128 The fee growth inside the position for token1. + function getPositionInfo(PoolId poolId, address owner, int24 tickLower, int24 tickUpper, bytes32 salt) + external + view + returns (uint128 liquidity, uint256 feeGrowthInside0LastX128, uint256 feeGrowthInside1LastX128); + + /// @notice Retrieves the position information of a pool at a specific position ID. + /// @dev Corresponds to pools[poolId].positions[positionId] + /// @param poolId The ID of the pool. + /// @param positionId The ID of the position. + /// @return liquidity The liquidity of the position. + /// @return feeGrowthInside0LastX128 The fee growth inside the position for token0. + /// @return feeGrowthInside1LastX128 The fee growth inside the position for token1. + function getPositionInfo(PoolId poolId, bytes32 positionId) + external + view + returns (uint128 liquidity, uint256 feeGrowthInside0LastX128, uint256 feeGrowthInside1LastX128); + + /// @notice Retrieves the liquidity of a position. + /// @dev Corresponds to pools[poolId].positions[positionId].liquidity. More gas efficient for just retrieving liquidity as compared to getPositionInfo + /// @param poolId The ID of the pool. + /// @param positionId The ID of the position. + /// @return liquidity The liquidity of the position. + function getPositionLiquidity(PoolId poolId, bytes32 positionId) external view returns (uint128 liquidity); + + /// @notice Calculate the fee growth inside a tick range of a pool + /// @dev pools[poolId].feeGrowthInside0LastX128 in Position.Info is cached and can become stale. This function will calculate the up to date feeGrowthInside + /// @param poolId The ID of the pool. + /// @param tickLower The lower tick of the range. + /// @param tickUpper The upper tick of the range. + /// @return feeGrowthInside0X128 The fee growth inside the tick range for token0. + /// @return feeGrowthInside1X128 The fee growth inside the tick range for token1. + function getFeeGrowthInside(PoolId poolId, int24 tickLower, int24 tickUpper) + external + view + returns (uint256 feeGrowthInside0X128, uint256 feeGrowthInside1X128); +} diff --git a/CVLByExample/UniswapHooks/lib/uniswap-hooks/lib/v4-periphery/src/interfaces/ISubscriber.sol b/CVLByExample/UniswapHooks/lib/uniswap-hooks/lib/v4-periphery/src/interfaces/ISubscriber.sol new file mode 100644 index 00000000..d98099e4 --- /dev/null +++ b/CVLByExample/UniswapHooks/lib/uniswap-hooks/lib/v4-periphery/src/interfaces/ISubscriber.sol @@ -0,0 +1,38 @@ +// SPDX-License-Identifier: MIT +pragma solidity ^0.8.0; + +import {BalanceDelta} from "@uniswap/v4-core/src/types/BalanceDelta.sol"; +import {PositionInfo} from "../libraries/PositionInfoLibrary.sol"; + +/// @title ISubscriber +/// @notice Interface that a Subscriber contract should implement to receive updates from the v4 position manager +interface ISubscriber { + /// @notice Called when a position subscribes to this subscriber contract + /// @param tokenId the token ID of the position + /// @param data additional data passed in by the caller + function notifySubscribe(uint256 tokenId, bytes memory data) external; + + /// @notice Called when a position unsubscribes from the subscriber + /// @dev This call's gas is capped at `unsubscribeGasLimit` (set at deployment) + /// @dev Because of EIP-150, solidity may only allocate 63/64 of gasleft() + /// @param tokenId the token ID of the position + function notifyUnsubscribe(uint256 tokenId) external; + + /// @notice Called when a position is burned + /// @param tokenId the token ID of the position + /// @param owner the current owner of the tokenId + /// @param info information about the position + /// @param liquidity the amount of liquidity decreased in the position, may be 0 + /// @param feesAccrued the fees accrued by the position if liquidity was decreased + function notifyBurn(uint256 tokenId, address owner, PositionInfo info, uint256 liquidity, BalanceDelta feesAccrued) + external; + + /// @notice Called when a position modifies its liquidity or collects fees + /// @param tokenId the token ID of the position + /// @param liquidityChange the change in liquidity on the underlying position + /// @param feesAccrued the fees to be collected from the position as a result of the modifyLiquidity call + /// @dev Note that feesAccrued can be artificially inflated by a malicious user + /// Pools with a single liquidity position can inflate feeGrowthGlobal (and consequently feesAccrued) by donating to themselves; + /// atomically donating and collecting fees within the same unlockCallback may further inflate feeGrowthGlobal/feesAccrued + function notifyModifyLiquidity(uint256 tokenId, int256 liquidityChange, BalanceDelta feesAccrued) external; +} diff --git a/CVLByExample/UniswapHooks/lib/uniswap-hooks/lib/v4-periphery/src/interfaces/IUniswapV4DeployerCompetition.sol b/CVLByExample/UniswapHooks/lib/uniswap-hooks/lib/v4-periphery/src/interfaces/IUniswapV4DeployerCompetition.sol new file mode 100644 index 00000000..7e3489b5 --- /dev/null +++ b/CVLByExample/UniswapHooks/lib/uniswap-hooks/lib/v4-periphery/src/interfaces/IUniswapV4DeployerCompetition.sol @@ -0,0 +1,25 @@ +// SPDX-License-Identifier: MIT +pragma solidity 0.8.26; + +/// @title IUniswapV4DeployerCompetition +/// @notice Interface for the UniswapV4DeployerCompetition contract +interface IUniswapV4DeployerCompetition { + event NewAddressFound(address indexed bestAddress, address indexed submitter, uint256 score); + + error InvalidBytecode(); + error CompetitionNotOver(uint256 currentTime, uint256 deadline); + error CompetitionOver(uint256 currentTime, uint256 deadline); + error NotAllowedToDeploy(address sender, address deployer); + error WorseAddress(address newAddress, address bestAddress, uint256 newScore, uint256 bestScore); + error InvalidSender(bytes32 salt, address sender); + + /// @notice Updates the best address if the new address has a better vanity score + /// @param salt The salt to use to compute the new address with CREATE2 + /// @dev The first 20 bytes of the salt must be either address(0) or msg.sender + function updateBestAddress(bytes32 salt) external; + + /// @notice deploys the Uniswap v4 PoolManager contract + /// @param bytecode The bytecode of the Uniswap v4 PoolManager contract + /// @dev The bytecode must match the initCodeHash + function deploy(bytes memory bytecode) external; +} diff --git a/CVLByExample/UniswapHooks/lib/uniswap-hooks/lib/v4-periphery/src/interfaces/IUnorderedNonce.sol b/CVLByExample/UniswapHooks/lib/uniswap-hooks/lib/v4-periphery/src/interfaces/IUnorderedNonce.sol new file mode 100644 index 00000000..022afb34 --- /dev/null +++ b/CVLByExample/UniswapHooks/lib/uniswap-hooks/lib/v4-periphery/src/interfaces/IUnorderedNonce.sol @@ -0,0 +1,17 @@ +// SPDX-License-Identifier: MIT +pragma solidity ^0.8.0; + +/// @title IUnorderedNonce +/// @notice Interface for the UnorderedNonce contract +interface IUnorderedNonce { + error NonceAlreadyUsed(); + + /// @notice mapping of nonces consumed by each address, where a nonce is a single bit on the 256-bit bitmap + /// @dev word is at most type(uint248).max + function nonces(address owner, uint256 word) external view returns (uint256); + + /// @notice Revoke a nonce by spending it, preventing it from being used again + /// @dev Used in cases where a valid nonce has not been broadcasted onchain, and the owner wants to revoke the validity of the nonce + /// @dev payable so it can be multicalled with native-token related actions + function revokeNonce(uint256 nonce) external payable; +} diff --git a/CVLByExample/UniswapHooks/lib/uniswap-hooks/lib/v4-periphery/src/interfaces/IV4Quoter.sol b/CVLByExample/UniswapHooks/lib/uniswap-hooks/lib/v4-periphery/src/interfaces/IV4Quoter.sol new file mode 100644 index 00000000..760d941a --- /dev/null +++ b/CVLByExample/UniswapHooks/lib/uniswap-hooks/lib/v4-periphery/src/interfaces/IV4Quoter.sol @@ -0,0 +1,71 @@ +// SPDX-License-Identifier: MIT +pragma solidity ^0.8.0; + +import {PoolKey} from "@uniswap/v4-core/src/types/PoolKey.sol"; +import {Currency} from "@uniswap/v4-core/src/types/Currency.sol"; +import {PathKey} from "../libraries/PathKey.sol"; +import {IImmutableState} from "./IImmutableState.sol"; +import {IMsgSender} from "./IMsgSender.sol"; + +/// @title IV4Quoter +/// @notice Interface for the V4Quoter contract +interface IV4Quoter is IImmutableState, IMsgSender { + struct QuoteExactSingleParams { + PoolKey poolKey; + bool zeroForOne; + uint128 exactAmount; + bytes hookData; + } + + struct QuoteExactParams { + Currency exactCurrency; + PathKey[] path; + uint128 exactAmount; + } + + /// @notice Returns the delta amounts for a given exact input swap of a single pool + /// @param params The params for the quote, encoded as `QuoteExactSingleParams` + /// poolKey The key for identifying a V4 pool + /// zeroForOne If the swap is from currency0 to currency1 + /// exactAmount The desired input amount + /// hookData arbitrary hookData to pass into the associated hooks + /// @return amountOut The output quote for the exactIn swap + /// @return gasEstimate Estimated gas units used for the swap + function quoteExactInputSingle(QuoteExactSingleParams memory params) + external + returns (uint256 amountOut, uint256 gasEstimate); + + /// @notice Returns the delta amounts along the swap path for a given exact input swap + /// @param params the params for the quote, encoded as 'QuoteExactParams' + /// currencyIn The input currency of the swap + /// path The path of the swap encoded as PathKeys that contains currency, fee, tickSpacing, and hook info + /// exactAmount The desired input amount + /// @return amountOut The output quote for the exactIn swap + /// @return gasEstimate Estimated gas units used for the swap + function quoteExactInput(QuoteExactParams memory params) + external + returns (uint256 amountOut, uint256 gasEstimate); + + /// @notice Returns the delta amounts for a given exact output swap of a single pool + /// @param params The params for the quote, encoded as `QuoteExactSingleParams` + /// poolKey The key for identifying a V4 pool + /// zeroForOne If the swap is from currency0 to currency1 + /// exactAmount The desired output amount + /// hookData arbitrary hookData to pass into the associated hooks + /// @return amountIn The input quote for the exactOut swap + /// @return gasEstimate Estimated gas units used for the swap + function quoteExactOutputSingle(QuoteExactSingleParams memory params) + external + returns (uint256 amountIn, uint256 gasEstimate); + + /// @notice Returns the delta amounts along the swap path for a given exact output swap + /// @param params the params for the quote, encoded as 'QuoteExactParams' + /// currencyOut The output currency of the swap + /// path The path of the swap encoded as PathKeys that contains currency, fee, tickSpacing, and hook info + /// exactAmount The desired output amount + /// @return amountIn The input quote for the exactOut swap + /// @return gasEstimate Estimated gas units used for the swap + function quoteExactOutput(QuoteExactParams memory params) + external + returns (uint256 amountIn, uint256 gasEstimate); +} diff --git a/CVLByExample/UniswapHooks/lib/uniswap-hooks/lib/v4-periphery/src/interfaces/IV4Router.sol b/CVLByExample/UniswapHooks/lib/uniswap-hooks/lib/v4-periphery/src/interfaces/IV4Router.sol new file mode 100644 index 00000000..6e3f5311 --- /dev/null +++ b/CVLByExample/UniswapHooks/lib/uniswap-hooks/lib/v4-periphery/src/interfaces/IV4Router.sol @@ -0,0 +1,50 @@ +// SPDX-License-Identifier: MIT +pragma solidity ^0.8.0; + +import {PoolKey} from "@uniswap/v4-core/src/types/PoolKey.sol"; +import {Currency} from "@uniswap/v4-core/src/types/Currency.sol"; +import {PathKey} from "../libraries/PathKey.sol"; +import {IImmutableState} from "./IImmutableState.sol"; + +/// @title IV4Router +/// @notice Interface for the V4Router contract +interface IV4Router is IImmutableState { + /// @notice Emitted when an exactInput swap does not receive its minAmountOut + error V4TooLittleReceived(uint256 minAmountOutReceived, uint256 amountReceived); + /// @notice Emitted when an exactOutput is asked for more than its maxAmountIn + error V4TooMuchRequested(uint256 maxAmountInRequested, uint256 amountRequested); + + /// @notice Parameters for a single-hop exact-input swap + struct ExactInputSingleParams { + PoolKey poolKey; + bool zeroForOne; + uint128 amountIn; + uint128 amountOutMinimum; + bytes hookData; + } + + /// @notice Parameters for a multi-hop exact-input swap + struct ExactInputParams { + Currency currencyIn; + PathKey[] path; + uint128 amountIn; + uint128 amountOutMinimum; + } + + /// @notice Parameters for a single-hop exact-output swap + struct ExactOutputSingleParams { + PoolKey poolKey; + bool zeroForOne; + uint128 amountOut; + uint128 amountInMaximum; + bytes hookData; + } + + /// @notice Parameters for a multi-hop exact-output swap + struct ExactOutputParams { + Currency currencyOut; + PathKey[] path; + uint128 amountOut; + uint128 amountInMaximum; + } +} diff --git a/CVLByExample/UniswapHooks/lib/uniswap-hooks/lib/v4-periphery/src/interfaces/external/IWETH9.sol b/CVLByExample/UniswapHooks/lib/uniswap-hooks/lib/v4-periphery/src/interfaces/external/IWETH9.sol new file mode 100644 index 00000000..f06b197b --- /dev/null +++ b/CVLByExample/UniswapHooks/lib/uniswap-hooks/lib/v4-periphery/src/interfaces/external/IWETH9.sol @@ -0,0 +1,13 @@ +// SPDX-License-Identifier: MIT +pragma solidity ^0.8.0; + +import {IERC20} from "@openzeppelin/contracts/token/ERC20/IERC20.sol"; + +/// @title IWETH9 +interface IWETH9 is IERC20 { + /// @notice Deposit ether to get wrapped ether + function deposit() external payable; + + /// @notice Withdraw wrapped ether to get ether + function withdraw(uint256) external; +} diff --git a/CVLByExample/UniswapHooks/lib/uniswap-hooks/lib/v4-periphery/src/interfaces/external/IWstETH.sol b/CVLByExample/UniswapHooks/lib/uniswap-hooks/lib/v4-periphery/src/interfaces/external/IWstETH.sol new file mode 100644 index 00000000..a3b48747 --- /dev/null +++ b/CVLByExample/UniswapHooks/lib/uniswap-hooks/lib/v4-periphery/src/interfaces/external/IWstETH.sol @@ -0,0 +1,25 @@ +// SPDX-FileCopyrightText: 2021 Lido +// https://github.com/lidofinance/core/blob/master/contracts/0.6.12/WstETH.sol + +// SPDX-License-Identifier: GPL-3.0 + +/* See contracts/COMPILERS.md */ +pragma solidity ^0.8.0; + +interface IWstETH { + function wrap(uint256 _stETHAmount) external returns (uint256); + function unwrap(uint256 _wstETHAmount) external returns (uint256); + function getStETHByWstETH(uint256 _wstETHAmount) external view returns (uint256); + function getWstETHByStETH(uint256 _stETHAmount) external view returns (uint256); + function tokensPerStEth() external view returns (uint256); + function stEthPerToken() external view returns (uint256); + function stETH() external view returns (address); +} + +interface IStETH { + function getSharesByPooledEth(uint256 stEthAmount) external view returns (uint256); + function getPooledEthByShares(uint256 _sharesAmount) external view returns (uint256); + function sharesOf(address _account) external view returns (uint256); + function transferShares(address recipient, uint256 shares) external; + function balanceOf(address _account) external view returns (uint256); +} diff --git a/CVLByExample/UniswapHooks/lib/uniswap-hooks/lib/v4-periphery/src/lens/StateView.sol b/CVLByExample/UniswapHooks/lib/uniswap-hooks/lib/v4-periphery/src/lens/StateView.sol new file mode 100644 index 00000000..deeab4b6 --- /dev/null +++ b/CVLByExample/UniswapHooks/lib/uniswap-hooks/lib/v4-periphery/src/lens/StateView.sol @@ -0,0 +1,109 @@ +// SPDX-License-Identifier: MIT +pragma solidity ^0.8.0; + +import {StateLibrary} from "@uniswap/v4-core/src/libraries/StateLibrary.sol"; +import {PoolId} from "@uniswap/v4-core/src/types/PoolId.sol"; +import {IPoolManager} from "@uniswap/v4-core/src/interfaces/IPoolManager.sol"; +import {Position} from "@uniswap/v4-core/src/libraries/Position.sol"; +import {ImmutableState} from "../base/ImmutableState.sol"; +import {IStateView} from "../interfaces/IStateView.sol"; + +/// @notice A view only contract wrapping the StateLibrary.sol library for reading storage in v4-core. +/// @dev The contract is intended for offchain clients. Use StateLibrary.sol directly if reading state onchain. +contract StateView is ImmutableState, IStateView { + using StateLibrary for IPoolManager; + + constructor(IPoolManager _poolManager) ImmutableState(_poolManager) {} + + /// @inheritdoc IStateView + function getSlot0(PoolId poolId) + external + view + returns (uint160 sqrtPriceX96, int24 tick, uint24 protocolFee, uint24 lpFee) + { + return poolManager.getSlot0(poolId); + } + + /// @inheritdoc IStateView + function getTickInfo(PoolId poolId, int24 tick) + external + view + returns ( + uint128 liquidityGross, + int128 liquidityNet, + uint256 feeGrowthOutside0X128, + uint256 feeGrowthOutside1X128 + ) + { + return poolManager.getTickInfo(poolId, tick); + } + + /// @inheritdoc IStateView + function getTickLiquidity(PoolId poolId, int24 tick) + external + view + returns (uint128 liquidityGross, int128 liquidityNet) + { + return poolManager.getTickLiquidity(poolId, tick); + } + + /// @inheritdoc IStateView + function getTickFeeGrowthOutside(PoolId poolId, int24 tick) + external + view + returns (uint256 feeGrowthOutside0X128, uint256 feeGrowthOutside1X128) + { + return poolManager.getTickFeeGrowthOutside(poolId, tick); + } + + /// @inheritdoc IStateView + function getFeeGrowthGlobals(PoolId poolId) + external + view + returns (uint256 feeGrowthGlobal0, uint256 feeGrowthGlobal1) + { + return poolManager.getFeeGrowthGlobals(poolId); + } + + /// @inheritdoc IStateView + function getLiquidity(PoolId poolId) external view returns (uint128 liquidity) { + return poolManager.getLiquidity(poolId); + } + + /// @inheritdoc IStateView + function getTickBitmap(PoolId poolId, int16 tick) external view returns (uint256 tickBitmap) { + return poolManager.getTickBitmap(poolId, tick); + } + + /// @inheritdoc IStateView + function getPositionInfo(PoolId poolId, address owner, int24 tickLower, int24 tickUpper, bytes32 salt) + external + view + returns (uint128 liquidity, uint256 feeGrowthInside0LastX128, uint256 feeGrowthInside1LastX128) + { + return poolManager.getPositionInfo(poolId, owner, tickLower, tickUpper, salt); + } + + /// @inheritdoc IStateView + function getPositionInfo(PoolId poolId, bytes32 positionId) + external + view + returns (uint128 liquidity, uint256 feeGrowthInside0LastX128, uint256 feeGrowthInside1LastX128) + { + return poolManager.getPositionInfo(poolId, positionId); + } + + /// @inheritdoc IStateView + function getPositionLiquidity(PoolId poolId, bytes32 positionId) external view returns (uint128 liquidity) { + return poolManager.getPositionLiquidity(poolId, positionId); + } + + /// @inheritdoc IStateView + function getFeeGrowthInside(PoolId poolId, int24 tickLower, int24 tickUpper) + external + view + returns (uint256 feeGrowthInside0X128, uint256 feeGrowthInside1X128) + { + return poolManager.getFeeGrowthInside(poolId, tickLower, tickUpper); + } +} diff --git a/CVLByExample/UniswapHooks/lib/uniswap-hooks/lib/v4-periphery/src/lens/V4Quoter.sol b/CVLByExample/UniswapHooks/lib/uniswap-hooks/lib/v4-periphery/src/lens/V4Quoter.sol new file mode 100644 index 00000000..fa115ce0 --- /dev/null +++ b/CVLByExample/UniswapHooks/lib/uniswap-hooks/lib/v4-periphery/src/lens/V4Quoter.sol @@ -0,0 +1,159 @@ +// SPDX-License-Identifier: MIT +pragma solidity ^0.8.0; + +import {IPoolManager} from "@uniswap/v4-core/src/interfaces/IPoolManager.sol"; +import {BalanceDelta} from "@uniswap/v4-core/src/types/BalanceDelta.sol"; +import {Currency} from "@uniswap/v4-core/src/types/Currency.sol"; +import {PoolKey} from "@uniswap/v4-core/src/types/PoolKey.sol"; +import {StateLibrary} from "@uniswap/v4-core/src/libraries/StateLibrary.sol"; +import {IV4Quoter} from "../interfaces/IV4Quoter.sol"; +import {PathKey} from "../libraries/PathKey.sol"; +import {QuoterRevert} from "../libraries/QuoterRevert.sol"; +import {BaseV4Quoter} from "../base/BaseV4Quoter.sol"; +import {Locker} from "../libraries/Locker.sol"; +import {IMsgSender} from "../interfaces/IMsgSender.sol"; + +/// @title V4Quoter +/// @notice Supports quoting the delta amounts for exact input or exact output swaps. +/// @dev These functions are not marked view because they rely on calling non-view functions and reverting +/// to compute the result. They are also not gas efficient and should not be called on-chain. +contract V4Quoter is IV4Quoter, BaseV4Quoter { + using QuoterRevert for *; + + constructor(IPoolManager _poolManager) BaseV4Quoter(_poolManager) {} + + modifier setMsgSender() { + Locker.set(msg.sender); + _; // execute the function + Locker.set(address(0)); // reset the locker + } + + /// @inheritdoc IV4Quoter + function quoteExactInputSingle(QuoteExactSingleParams memory params) + external + setMsgSender + returns (uint256 amountOut, uint256 gasEstimate) + { + uint256 gasBefore = gasleft(); + try poolManager.unlock(abi.encodeCall(this._quoteExactInputSingle, (params))) {} + catch (bytes memory reason) { + gasEstimate = gasBefore - gasleft(); + // Extract the quote from QuoteSwap error, or throw if the quote failed + amountOut = reason.parseQuoteAmount(); + } + } + + /// @inheritdoc IV4Quoter + function quoteExactInput(QuoteExactParams memory params) + external + setMsgSender + returns (uint256 amountOut, uint256 gasEstimate) + { + uint256 gasBefore = gasleft(); + try poolManager.unlock(abi.encodeCall(this._quoteExactInput, (params))) {} + catch (bytes memory reason) { + gasEstimate = gasBefore - gasleft(); + // Extract the quote from QuoteSwap error, or throw if the quote failed + amountOut = reason.parseQuoteAmount(); + } + } + + /// @inheritdoc IV4Quoter + function quoteExactOutputSingle(QuoteExactSingleParams memory params) + external + setMsgSender + returns (uint256 amountIn, uint256 gasEstimate) + { + uint256 gasBefore = gasleft(); + try poolManager.unlock(abi.encodeCall(this._quoteExactOutputSingle, (params))) {} + catch (bytes memory reason) { + gasEstimate = gasBefore - gasleft(); + // Extract the quote from QuoteSwap error, or throw if the quote failed + amountIn = reason.parseQuoteAmount(); + } + } + + /// @inheritdoc IV4Quoter + function quoteExactOutput(QuoteExactParams memory params) + external + setMsgSender + returns (uint256 amountIn, uint256 gasEstimate) + { + uint256 gasBefore = gasleft(); + try poolManager.unlock(abi.encodeCall(this._quoteExactOutput, (params))) {} + catch (bytes memory reason) { + gasEstimate = gasBefore - gasleft(); + // Extract the quote from QuoteSwap error, or throw if the quote failed + amountIn = reason.parseQuoteAmount(); + } + } + + /// @dev external function called within the _unlockCallback, to simulate an exact input swap, then revert with the result + function _quoteExactInput(QuoteExactParams calldata params) external selfOnly returns (bytes memory) { + uint256 pathLength = params.path.length; + BalanceDelta swapDelta; + uint128 amountIn = params.exactAmount; + Currency inputCurrency = params.exactCurrency; + PathKey calldata pathKey; + + for (uint256 i = 0; i < pathLength; i++) { + pathKey = params.path[i]; + (PoolKey memory poolKey, bool zeroForOne) = pathKey.getPoolAndSwapDirection(inputCurrency); + + swapDelta = _swap(poolKey, zeroForOne, -int256(int128(amountIn)), pathKey.hookData); + + amountIn = zeroForOne ? uint128(swapDelta.amount1()) : uint128(swapDelta.amount0()); + inputCurrency = pathKey.intermediateCurrency; + } + // amountIn after the loop actually holds the amountOut of the trade + amountIn.revertQuote(); + } + + /// @dev external function called within the _unlockCallback, to simulate a single-hop exact input swap, then revert with the result + function _quoteExactInputSingle(QuoteExactSingleParams calldata params) external selfOnly returns (bytes memory) { + BalanceDelta swapDelta = + _swap(params.poolKey, params.zeroForOne, -int256(int128(params.exactAmount)), params.hookData); + + // the output delta of a swap is positive + uint256 amountOut = params.zeroForOne ? uint128(swapDelta.amount1()) : uint128(swapDelta.amount0()); + amountOut.revertQuote(); + } + + /// @dev external function called within the _unlockCallback, to simulate an exact output swap, then revert with the result + function _quoteExactOutput(QuoteExactParams calldata params) external selfOnly returns (bytes memory) { + uint256 pathLength = params.path.length; + BalanceDelta swapDelta; + uint128 amountOut = params.exactAmount; + Currency outputCurrency = params.exactCurrency; + PathKey calldata pathKey; + + for (uint256 i = pathLength; i > 0; i--) { + pathKey = params.path[i - 1]; + (PoolKey memory poolKey, bool oneForZero) = pathKey.getPoolAndSwapDirection(outputCurrency); + + swapDelta = _swap(poolKey, !oneForZero, int256(uint256(amountOut)), pathKey.hookData); + + amountOut = oneForZero ? uint128(-swapDelta.amount1()) : uint128(-swapDelta.amount0()); + + outputCurrency = pathKey.intermediateCurrency; + } + // amountOut after the loop exits actually holds the amountIn of the trade + amountOut.revertQuote(); + } + + /// @dev external function called within the _unlockCallback, to simulate a single-hop exact output swap, then revert with the result + function _quoteExactOutputSingle(QuoteExactSingleParams calldata params) external selfOnly returns (bytes memory) { + BalanceDelta swapDelta = + _swap(params.poolKey, params.zeroForOne, int256(uint256(params.exactAmount)), params.hookData); + + // the input delta of a swap is negative so we must flip it + uint256 amountIn = params.zeroForOne ? uint128(-swapDelta.amount0()) : uint128(-swapDelta.amount1()); + amountIn.revertQuote(); + } + + /// @inheritdoc IMsgSender + function msgSender() external view returns (address) { + // despite using the Locker library, V4Quoter does not have a reentrancy lock + return Locker.get(); + } +} diff --git a/CVLByExample/UniswapHooks/lib/uniswap-hooks/lib/v4-periphery/src/libraries/ActionConstants.sol b/CVLByExample/UniswapHooks/lib/uniswap-hooks/lib/v4-periphery/src/libraries/ActionConstants.sol new file mode 100644 index 00000000..4c84e11a --- /dev/null +++ b/CVLByExample/UniswapHooks/lib/uniswap-hooks/lib/v4-periphery/src/libraries/ActionConstants.sol @@ -0,0 +1,20 @@ +// SPDX-License-Identifier: MIT +pragma solidity ^0.8.0; + +/// @title Action Constants +/// @notice Common constants used in actions +/// @dev Constants are gas efficient alternatives to their literal values +library ActionConstants { + /// @notice used to signal that an action should use the input value of the open delta on the pool manager + /// or of the balance that the contract holds + uint128 internal constant OPEN_DELTA = 0; + /// @notice used to signal that an action should use the contract's entire balance of a currency + /// This value is equivalent to 1<<255, i.e. a singular 1 in the most significant bit. + uint256 internal constant CONTRACT_BALANCE = 0x8000000000000000000000000000000000000000000000000000000000000000; + + /// @notice used to signal that the recipient of an action should be the msgSender + address internal constant MSG_SENDER = address(1); + + /// @notice used to signal that the recipient of an action should be the address(this) + address internal constant ADDRESS_THIS = address(2); +} diff --git a/CVLByExample/UniswapHooks/lib/uniswap-hooks/lib/v4-periphery/src/libraries/Actions.sol b/CVLByExample/UniswapHooks/lib/uniswap-hooks/lib/v4-periphery/src/libraries/Actions.sol new file mode 100644 index 00000000..a33c4e87 --- /dev/null +++ b/CVLByExample/UniswapHooks/lib/uniswap-hooks/lib/v4-periphery/src/libraries/Actions.sol @@ -0,0 +1,49 @@ +// SPDX-License-Identifier: MIT +pragma solidity ^0.8.0; + +/// @notice Library to define different pool actions. +/// @dev These are suggested common commands, however additional commands should be defined as required +/// Some of these actions are not supported in the Router contracts or Position Manager contracts, but are left as they may be helpful commands for other peripheral contracts. +library Actions { + // pool actions + // liquidity actions + uint256 internal constant INCREASE_LIQUIDITY = 0x00; + uint256 internal constant DECREASE_LIQUIDITY = 0x01; + uint256 internal constant MINT_POSITION = 0x02; + uint256 internal constant BURN_POSITION = 0x03; + uint256 internal constant INCREASE_LIQUIDITY_FROM_DELTAS = 0x04; + uint256 internal constant MINT_POSITION_FROM_DELTAS = 0x05; + + // swapping + uint256 internal constant SWAP_EXACT_IN_SINGLE = 0x06; + uint256 internal constant SWAP_EXACT_IN = 0x07; + uint256 internal constant SWAP_EXACT_OUT_SINGLE = 0x08; + uint256 internal constant SWAP_EXACT_OUT = 0x09; + + // donate + // note this is not supported in the position manager or router + uint256 internal constant DONATE = 0x0a; + + // closing deltas on the pool manager + // settling + uint256 internal constant SETTLE = 0x0b; + uint256 internal constant SETTLE_ALL = 0x0c; + uint256 internal constant SETTLE_PAIR = 0x0d; + // taking + uint256 internal constant TAKE = 0x0e; + uint256 internal constant TAKE_ALL = 0x0f; + uint256 internal constant TAKE_PORTION = 0x10; + uint256 internal constant TAKE_PAIR = 0x11; + + uint256 internal constant CLOSE_CURRENCY = 0x12; + uint256 internal constant CLEAR_OR_TAKE = 0x13; + uint256 internal constant SWEEP = 0x14; + + uint256 internal constant WRAP = 0x15; + uint256 internal constant UNWRAP = 0x16; + + // minting/burning 6909s to close deltas + // note this is not supported in the position manager or router + uint256 internal constant MINT_6909 = 0x17; + uint256 internal constant BURN_6909 = 0x18; +} diff --git a/CVLByExample/UniswapHooks/lib/uniswap-hooks/lib/v4-periphery/src/libraries/AddressStringUtil.sol b/CVLByExample/UniswapHooks/lib/uniswap-hooks/lib/v4-periphery/src/libraries/AddressStringUtil.sol new file mode 100644 index 00000000..3add136f --- /dev/null +++ b/CVLByExample/UniswapHooks/lib/uniswap-hooks/lib/v4-periphery/src/libraries/AddressStringUtil.sol @@ -0,0 +1,46 @@ +// SPDX-License-Identifier: MIT +pragma solidity ^0.8.0; + +/// @title AddressStringUtil +/// @notice provides utility functions for converting addresses to strings +/// @dev Reference: https://github.com/Uniswap/solidity-lib/blob/master/contracts/libraries/AddressStringUtil.sol +library AddressStringUtil { + error InvalidAddressLength(uint256 len); + + /// @notice Converts an address to the uppercase hex string, extracting only len bytes (up to 20, multiple of 2) + /// @param addr the address to convert + /// @param len the number of bytes to extract + /// @return the hex string + function toAsciiString(address addr, uint256 len) internal pure returns (string memory) { + if (!(len % 2 == 0 && len > 0 && len <= 40)) { + revert InvalidAddressLength(len); + } + + bytes memory s = new bytes(len); + uint256 addrNum = uint256(uint160(addr)); + for (uint256 i = 0; i < len / 2; i++) { + // shift right and truncate all but the least significant byte to extract the byte at position 19-i + uint8 b = uint8(addrNum >> (8 * (19 - i))); + // first hex character is the most significant 4 bits + uint8 hi = b >> 4; + // second hex character is the least significant 4 bits + uint8 lo = b - (hi << 4); + s[2 * i] = char(hi); + s[2 * i + 1] = char(lo); + } + return string(s); + } + + /// @notice Converts a value into is corresponding ASCII character for the hex representation + // hi and lo are only 4 bits and between 0 and 16 + // uses upper case for the characters + /// @param b the value to convert + /// @return c the ASCII character + function char(uint8 b) private pure returns (bytes1 c) { + if (b < 10) { + return bytes1(b + 0x30); + } else { + return bytes1(b + 0x37); + } + } +} diff --git a/CVLByExample/UniswapHooks/lib/uniswap-hooks/lib/v4-periphery/src/libraries/BipsLibrary.sol b/CVLByExample/UniswapHooks/lib/uniswap-hooks/lib/v4-periphery/src/libraries/BipsLibrary.sol new file mode 100644 index 00000000..d85486a8 --- /dev/null +++ b/CVLByExample/UniswapHooks/lib/uniswap-hooks/lib/v4-periphery/src/libraries/BipsLibrary.sol @@ -0,0 +1,17 @@ +// SPDX-License-Identifier: MIT +pragma solidity ^0.8.0; + +/// @title For calculating a percentage of an amount, using bips +library BipsLibrary { + uint256 internal constant BPS_DENOMINATOR = 10_000; + + /// @notice emitted when an invalid percentage is provided + error InvalidBips(); + + /// @param amount The total amount to calculate a percentage of + /// @param bips The percentage to calculate, in bips + function calculatePortion(uint256 amount, uint256 bips) internal pure returns (uint256) { + if (bips > BPS_DENOMINATOR) revert InvalidBips(); + return (amount * bips) / BPS_DENOMINATOR; + } +} diff --git a/CVLByExample/UniswapHooks/lib/uniswap-hooks/lib/v4-periphery/src/libraries/CalldataDecoder.sol b/CVLByExample/UniswapHooks/lib/uniswap-hooks/lib/v4-periphery/src/libraries/CalldataDecoder.sol new file mode 100644 index 00000000..496852bd --- /dev/null +++ b/CVLByExample/UniswapHooks/lib/uniswap-hooks/lib/v4-periphery/src/libraries/CalldataDecoder.sol @@ -0,0 +1,391 @@ +// SPDX-License-Identifier: MIT +pragma solidity ^0.8.0; + +import {Currency} from "@uniswap/v4-core/src/types/Currency.sol"; +import {IV4Router} from "../interfaces/IV4Router.sol"; +import {PoolKey} from "@uniswap/v4-core/src/types/PoolKey.sol"; + +/// @title Library for abi decoding in calldata +library CalldataDecoder { + using CalldataDecoder for bytes; + + error SliceOutOfBounds(); + + /// @notice mask used for offsets and lengths to ensure no overflow + /// @dev no sane abi encoding will pass in an offset or length greater than type(uint32).max + /// (note that this does deviate from standard solidity behavior and offsets/lengths will + /// be interpreted as mod type(uint32).max which will only impact malicious/buggy callers) + uint256 constant OFFSET_OR_LENGTH_MASK = 0xffffffff; + uint256 constant OFFSET_OR_LENGTH_MASK_AND_WORD_ALIGN = 0xffffffe0; + + /// @notice equivalent to SliceOutOfBounds.selector, stored in least-significant bits + uint256 constant SLICE_ERROR_SELECTOR = 0x3b99b53d; + + /// @dev equivalent to: abi.decode(params, (bytes, bytes[])) in calldata (requires strict abi encoding) + function decodeActionsRouterParams(bytes calldata _bytes) + internal + pure + returns (bytes calldata actions, bytes[] calldata params) + { + assembly ("memory-safe") { + // Strict encoding requires that the data begin with: + // 0x00: 0x40 (offset to `actions.length`) + // 0x20: 0x60 + actions.length (offset to `params.length`) + // 0x40: `actions.length` + // 0x60: beginning of actions + + // Verify actions offset matches strict encoding + let invalidData := xor(calldataload(_bytes.offset), 0x40) + actions.offset := add(_bytes.offset, 0x60) + actions.length := and(calldataload(add(_bytes.offset, 0x40)), OFFSET_OR_LENGTH_MASK) + + // Round actions length up to be word-aligned, and add 0x60 (for the first 3 words of encoding) + let paramsLengthOffset := add(and(add(actions.length, 0x1f), OFFSET_OR_LENGTH_MASK_AND_WORD_ALIGN), 0x60) + // Verify params offset matches strict encoding + invalidData := or(invalidData, xor(calldataload(add(_bytes.offset, 0x20)), paramsLengthOffset)) + let paramsLengthPointer := add(_bytes.offset, paramsLengthOffset) + params.length := and(calldataload(paramsLengthPointer), OFFSET_OR_LENGTH_MASK) + params.offset := add(paramsLengthPointer, 0x20) + + // Expected offset for `params[0]` is params.length * 32 + // As the first `params.length` slots are pointers to each of the array element lengths + let tailOffset := shl(5, params.length) + let expectedOffset := tailOffset + + for { let offset := 0 } lt(offset, tailOffset) { offset := add(offset, 32) } { + let itemLengthOffset := calldataload(add(params.offset, offset)) + // Verify that the offset matches the expected offset from strict encoding + invalidData := or(invalidData, xor(itemLengthOffset, expectedOffset)) + let itemLengthPointer := add(params.offset, itemLengthOffset) + let length := + add(and(add(calldataload(itemLengthPointer), 0x1f), OFFSET_OR_LENGTH_MASK_AND_WORD_ALIGN), 0x20) + expectedOffset := add(expectedOffset, length) + } + + // if the data encoding was invalid, or the provided bytes string isnt as long as the encoding says, revert + if or(invalidData, lt(add(_bytes.length, _bytes.offset), add(params.offset, expectedOffset))) { + mstore(0, SLICE_ERROR_SELECTOR) + revert(0x1c, 4) + } + } + } + + /// @dev equivalent to: abi.decode(params, (uint256, uint256, uint128, uint128, bytes)) in calldata + function decodeModifyLiquidityParams(bytes calldata params) + internal + pure + returns (uint256 tokenId, uint256 liquidity, uint128 amount0, uint128 amount1, bytes calldata hookData) + { + // no length check performed, as there is a length check in `toBytes` + assembly ("memory-safe") { + tokenId := calldataload(params.offset) + liquidity := calldataload(add(params.offset, 0x20)) + amount0 := calldataload(add(params.offset, 0x40)) + amount1 := calldataload(add(params.offset, 0x60)) + } + + hookData = params.toBytes(4); + } + + /// @dev equivalent to: abi.decode(params, (uint256, uint128, uint128, bytes)) in calldata + function decodeIncreaseLiquidityFromDeltasParams(bytes calldata params) + internal + pure + returns (uint256 tokenId, uint128 amount0Max, uint128 amount1Max, bytes calldata hookData) + { + // no length check performed, as there is a length check in `toBytes` + assembly ("memory-safe") { + tokenId := calldataload(params.offset) + amount0Max := calldataload(add(params.offset, 0x20)) + amount1Max := calldataload(add(params.offset, 0x40)) + } + + hookData = params.toBytes(3); + } + + /// @dev equivalent to: abi.decode(params, (PoolKey, int24, int24, uint256, uint128, uint128, address, bytes)) in calldata + function decodeMintParams(bytes calldata params) + internal + pure + returns ( + PoolKey calldata poolKey, + int24 tickLower, + int24 tickUpper, + uint256 liquidity, + uint128 amount0Max, + uint128 amount1Max, + address owner, + bytes calldata hookData + ) + { + // no length check performed, as there is a length check in `toBytes` + assembly ("memory-safe") { + poolKey := params.offset + tickLower := calldataload(add(params.offset, 0xa0)) + tickUpper := calldataload(add(params.offset, 0xc0)) + liquidity := calldataload(add(params.offset, 0xe0)) + amount0Max := calldataload(add(params.offset, 0x100)) + amount1Max := calldataload(add(params.offset, 0x120)) + owner := calldataload(add(params.offset, 0x140)) + } + hookData = params.toBytes(11); + } + + /// @dev equivalent to: abi.decode(params, (PoolKey, int24, int24, uint128, uint128, address, bytes)) in calldata + function decodeMintFromDeltasParams(bytes calldata params) + internal + pure + returns ( + PoolKey calldata poolKey, + int24 tickLower, + int24 tickUpper, + uint128 amount0Max, + uint128 amount1Max, + address owner, + bytes calldata hookData + ) + { + // no length check performed, as there is a length check in `toBytes` + assembly ("memory-safe") { + poolKey := params.offset + tickLower := calldataload(add(params.offset, 0xa0)) + tickUpper := calldataload(add(params.offset, 0xc0)) + amount0Max := calldataload(add(params.offset, 0xe0)) + amount1Max := calldataload(add(params.offset, 0x100)) + owner := calldataload(add(params.offset, 0x120)) + } + + hookData = params.toBytes(10); + } + + /// @dev equivalent to: abi.decode(params, (uint256, uint128, uint128, bytes)) in calldata + function decodeBurnParams(bytes calldata params) + internal + pure + returns (uint256 tokenId, uint128 amount0Min, uint128 amount1Min, bytes calldata hookData) + { + // no length check performed, as there is a length check in `toBytes` + assembly ("memory-safe") { + tokenId := calldataload(params.offset) + amount0Min := calldataload(add(params.offset, 0x20)) + amount1Min := calldataload(add(params.offset, 0x40)) + } + + hookData = params.toBytes(3); + } + + /// @dev equivalent to: abi.decode(params, (IV4Router.ExactInputParams)) + function decodeSwapExactInParams(bytes calldata params) + internal + pure + returns (IV4Router.ExactInputParams calldata swapParams) + { + // ExactInputParams is a variable length struct so we just have to look up its location + assembly ("memory-safe") { + // only safety checks for the minimum length, where path is empty + // 0xa0 = 5 * 0x20 -> 3 elements, path offset, and path length 0 + if lt(params.length, 0xa0) { + mstore(0, SLICE_ERROR_SELECTOR) + revert(0x1c, 4) + } + swapParams := add(params.offset, calldataload(params.offset)) + } + } + + /// @dev equivalent to: abi.decode(params, (IV4Router.ExactInputSingleParams)) + function decodeSwapExactInSingleParams(bytes calldata params) + internal + pure + returns (IV4Router.ExactInputSingleParams calldata swapParams) + { + // ExactInputSingleParams is a variable length struct so we just have to look up its location + assembly ("memory-safe") { + // only safety checks for the minimum length, where hookData is empty + // 0x140 = 10 * 0x20 -> 8 elements, bytes offset, and bytes length 0 + if lt(params.length, 0x140) { + mstore(0, SLICE_ERROR_SELECTOR) + revert(0x1c, 4) + } + swapParams := add(params.offset, calldataload(params.offset)) + } + } + + /// @dev equivalent to: abi.decode(params, (IV4Router.ExactOutputParams)) + function decodeSwapExactOutParams(bytes calldata params) + internal + pure + returns (IV4Router.ExactOutputParams calldata swapParams) + { + // ExactOutputParams is a variable length struct so we just have to look up its location + assembly ("memory-safe") { + // only safety checks for the minimum length, where path is empty + // 0xa0 = 5 * 0x20 -> 3 elements, path offset, and path length 0 + if lt(params.length, 0xa0) { + mstore(0, SLICE_ERROR_SELECTOR) + revert(0x1c, 4) + } + swapParams := add(params.offset, calldataload(params.offset)) + } + } + + /// @dev equivalent to: abi.decode(params, (IV4Router.ExactOutputSingleParams)) + function decodeSwapExactOutSingleParams(bytes calldata params) + internal + pure + returns (IV4Router.ExactOutputSingleParams calldata swapParams) + { + // ExactOutputSingleParams is a variable length struct so we just have to look up its location + assembly ("memory-safe") { + // only safety checks for the minimum length, where hookData is empty + // 0x140 = 10 * 0x20 -> 8 elements, bytes offset, and bytes length 0 + if lt(params.length, 0x140) { + mstore(0, SLICE_ERROR_SELECTOR) + revert(0x1c, 4) + } + swapParams := add(params.offset, calldataload(params.offset)) + } + } + + /// @dev equivalent to: abi.decode(params, (Currency)) in calldata + function decodeCurrency(bytes calldata params) internal pure returns (Currency currency) { + assembly ("memory-safe") { + if lt(params.length, 0x20) { + mstore(0, SLICE_ERROR_SELECTOR) + revert(0x1c, 4) + } + currency := calldataload(params.offset) + } + } + + /// @dev equivalent to: abi.decode(params, (Currency, Currency)) in calldata + function decodeCurrencyPair(bytes calldata params) internal pure returns (Currency currency0, Currency currency1) { + assembly ("memory-safe") { + if lt(params.length, 0x40) { + mstore(0, SLICE_ERROR_SELECTOR) + revert(0x1c, 4) + } + currency0 := calldataload(params.offset) + currency1 := calldataload(add(params.offset, 0x20)) + } + } + + /// @dev equivalent to: abi.decode(params, (Currency, Currency, address)) in calldata + function decodeCurrencyPairAndAddress(bytes calldata params) + internal + pure + returns (Currency currency0, Currency currency1, address _address) + { + assembly ("memory-safe") { + if lt(params.length, 0x60) { + mstore(0, SLICE_ERROR_SELECTOR) + revert(0x1c, 4) + } + currency0 := calldataload(params.offset) + currency1 := calldataload(add(params.offset, 0x20)) + _address := calldataload(add(params.offset, 0x40)) + } + } + + /// @dev equivalent to: abi.decode(params, (Currency, address)) in calldata + function decodeCurrencyAndAddress(bytes calldata params) + internal + pure + returns (Currency currency, address _address) + { + assembly ("memory-safe") { + if lt(params.length, 0x40) { + mstore(0, SLICE_ERROR_SELECTOR) + revert(0x1c, 4) + } + currency := calldataload(params.offset) + _address := calldataload(add(params.offset, 0x20)) + } + } + + /// @dev equivalent to: abi.decode(params, (Currency, address, uint256)) in calldata + function decodeCurrencyAddressAndUint256(bytes calldata params) + internal + pure + returns (Currency currency, address _address, uint256 amount) + { + assembly ("memory-safe") { + if lt(params.length, 0x60) { + mstore(0, SLICE_ERROR_SELECTOR) + revert(0x1c, 4) + } + currency := calldataload(params.offset) + _address := calldataload(add(params.offset, 0x20)) + amount := calldataload(add(params.offset, 0x40)) + } + } + + /// @dev equivalent to: abi.decode(params, (Currency, uint256)) in calldata + function decodeCurrencyAndUint256(bytes calldata params) + internal + pure + returns (Currency currency, uint256 amount) + { + assembly ("memory-safe") { + if lt(params.length, 0x40) { + mstore(0, SLICE_ERROR_SELECTOR) + revert(0x1c, 4) + } + currency := calldataload(params.offset) + amount := calldataload(add(params.offset, 0x20)) + } + } + + /// @dev equivalent to: abi.decode(params, (uint256)) in calldata + function decodeUint256(bytes calldata params) internal pure returns (uint256 amount) { + assembly ("memory-safe") { + if lt(params.length, 0x20) { + mstore(0, SLICE_ERROR_SELECTOR) + revert(0x1c, 4) + } + amount := calldataload(params.offset) + } + } + + /// @dev equivalent to: abi.decode(params, (Currency, uint256, bool)) in calldata + function decodeCurrencyUint256AndBool(bytes calldata params) + internal + pure + returns (Currency currency, uint256 amount, bool boolean) + { + assembly ("memory-safe") { + if lt(params.length, 0x60) { + mstore(0, SLICE_ERROR_SELECTOR) + revert(0x1c, 4) + } + currency := calldataload(params.offset) + amount := calldataload(add(params.offset, 0x20)) + boolean := calldataload(add(params.offset, 0x40)) + } + } + + /// @notice Decode the `_arg`-th element in `_bytes` as `bytes` + /// @param _bytes The input bytes string to extract a bytes string from + /// @param _arg The index of the argument to extract + function toBytes(bytes calldata _bytes, uint256 _arg) internal pure returns (bytes calldata res) { + uint256 length; + assembly ("memory-safe") { + // The offset of the `_arg`-th element is `32 * arg`, which stores the offset of the length pointer. + // shl(5, x) is equivalent to mul(32, x) + let lengthPtr := + add(_bytes.offset, and(calldataload(add(_bytes.offset, shl(5, _arg))), OFFSET_OR_LENGTH_MASK)) + // the number of bytes in the bytes string + length := and(calldataload(lengthPtr), OFFSET_OR_LENGTH_MASK) + // the offset where the bytes string begins + let offset := add(lengthPtr, 0x20) + // assign the return parameters + res.length := length + res.offset := offset + + // if the provided bytes string isnt as long as the encoding says, revert + if lt(add(_bytes.length, _bytes.offset), add(length, offset)) { + mstore(0, SLICE_ERROR_SELECTOR) + revert(0x1c, 4) + } + } + } +} diff --git a/CVLByExample/UniswapHooks/lib/uniswap-hooks/lib/v4-periphery/src/libraries/CurrencyRatioSortOrder.sol b/CVLByExample/UniswapHooks/lib/uniswap-hooks/lib/v4-periphery/src/libraries/CurrencyRatioSortOrder.sol new file mode 100644 index 00000000..c02d1084 --- /dev/null +++ b/CVLByExample/UniswapHooks/lib/uniswap-hooks/lib/v4-periphery/src/libraries/CurrencyRatioSortOrder.sol @@ -0,0 +1,16 @@ +// SPDX-License-Identifier: MIT +pragma solidity ^0.8.0; + +/// @title CurrencyRatioSortOrder +/// @notice Provides constants for sorting currencies when displaying price ratios +/// Currencies given larger values will be in the numerator of the price ratio +/// @dev Reference: https://github.com/Uniswap/v3-periphery/blob/main/contracts/libraries/TokenRatioSortOrder.sol +library CurrencyRatioSortOrder { + int256 constant NUMERATOR_MOST = 300; + int256 constant NUMERATOR_MORE = 200; + int256 constant NUMERATOR = 100; + + int256 constant DENOMINATOR_MOST = -300; + int256 constant DENOMINATOR_MORE = -200; + int256 constant DENOMINATOR = -100; +} diff --git a/CVLByExample/UniswapHooks/lib/uniswap-hooks/lib/v4-periphery/src/libraries/Descriptor.sol b/CVLByExample/UniswapHooks/lib/uniswap-hooks/lib/v4-periphery/src/libraries/Descriptor.sol new file mode 100644 index 00000000..327937c8 --- /dev/null +++ b/CVLByExample/UniswapHooks/lib/uniswap-hooks/lib/v4-periphery/src/libraries/Descriptor.sol @@ -0,0 +1,528 @@ +// SPDX-License-Identifier: MIT +pragma solidity ^0.8.0; + +import {TickMath} from "@uniswap/v4-core/src/libraries/TickMath.sol"; +import {FullMath} from "@uniswap/v4-core/src/libraries/FullMath.sol"; +import {LPFeeLibrary} from "@uniswap/v4-core/src/libraries/LPFeeLibrary.sol"; +import {Strings} from "openzeppelin-contracts/contracts/utils/Strings.sol"; +import {Base64} from "openzeppelin-contracts/contracts/utils/Base64.sol"; +import {SVG} from "./SVG.sol"; +import {HexStrings} from "./HexStrings.sol"; + +/// @title Descriptor +/// @notice Describes NFT token positions +/// @dev Reference: https://github.com/Uniswap/v3-periphery/blob/main/contracts/libraries/NFTDescriptor.sol +library Descriptor { + using TickMath for int24; + using Strings for uint256; + using HexStrings for uint256; + using LPFeeLibrary for uint24; + + uint256 constant sqrt10X128 = 1076067327063303206878105757264492625226; + + struct ConstructTokenURIParams { + uint256 tokenId; + address quoteCurrency; + address baseCurrency; + string quoteCurrencySymbol; + string baseCurrencySymbol; + uint8 quoteCurrencyDecimals; + uint8 baseCurrencyDecimals; + bool flipRatio; + int24 tickLower; + int24 tickUpper; + int24 tickCurrent; + int24 tickSpacing; + uint24 fee; + address poolManager; + address hooks; + } + + /// @notice Constructs the token URI for a Uniswap v4 NFT + /// @param params Parameters needed to construct the token URI + /// @return The token URI as a string + function constructTokenURI(ConstructTokenURIParams memory params) internal pure returns (string memory) { + string memory name = generateName(params, feeToPercentString(params.fee)); + string memory descriptionPartOne = generateDescriptionPartOne( + escapeSpecialCharacters(params.quoteCurrencySymbol), + escapeSpecialCharacters(params.baseCurrencySymbol), + addressToString(params.poolManager) + ); + string memory descriptionPartTwo = generateDescriptionPartTwo( + params.tokenId.toString(), + escapeSpecialCharacters(params.baseCurrencySymbol), + params.quoteCurrency == address(0) ? "Native" : addressToString(params.quoteCurrency), + params.baseCurrency == address(0) ? "Native" : addressToString(params.baseCurrency), + params.hooks == address(0) ? "No Hook" : addressToString(params.hooks), + feeToPercentString(params.fee) + ); + string memory image = Base64.encode(bytes(generateSVGImage(params))); + + return string( + abi.encodePacked( + "data:application/json;base64,", + Base64.encode( + bytes( + abi.encodePacked( + '{"name":"', + name, + '", "description":"', + descriptionPartOne, + descriptionPartTwo, + '", "image": "', + "data:image/svg+xml;base64,", + image, + '"}' + ) + ) + ) + ) + ); + } + + /// @notice Escapes special characters in a string if they are present + function escapeSpecialCharacters(string memory symbol) internal pure returns (string memory) { + bytes memory symbolBytes = bytes(symbol); + uint8 specialCharCount = 0; + // count the amount of double quotes, form feeds, new lines, carriage returns, or tabs in the symbol + for (uint8 i = 0; i < symbolBytes.length; i++) { + if (isSpecialCharacter(symbolBytes[i])) { + specialCharCount++; + } + } + if (specialCharCount > 0) { + // create a new bytes array with enough space to hold the original bytes plus space for the backslashes to escape the special characters + bytes memory escapedBytes = new bytes(symbolBytes.length + specialCharCount); + uint256 index; + for (uint8 i = 0; i < symbolBytes.length; i++) { + // add a '\' before any double quotes, form feeds, new lines, carriage returns, or tabs + if (isSpecialCharacter(symbolBytes[i])) { + escapedBytes[index++] = "\\"; + } + // copy each byte from original string to the new array + escapedBytes[index++] = symbolBytes[i]; + } + return string(escapedBytes); + } + return symbol; + } + + /// @notice Generates the first part of the description for a Uniswap v4 NFT + /// @param quoteCurrencySymbol The symbol of the quote currency + /// @param baseCurrencySymbol The symbol of the base currency + /// @param poolManager The address of the pool manager + /// @return The first part of the description + function generateDescriptionPartOne( + string memory quoteCurrencySymbol, + string memory baseCurrencySymbol, + string memory poolManager + ) private pure returns (string memory) { + // displays quote currency first, then base currency + return string( + abi.encodePacked( + "This NFT represents a liquidity position in a Uniswap v4 ", + quoteCurrencySymbol, + "-", + baseCurrencySymbol, + " pool. ", + "The owner of this NFT can modify or redeem the position.\\n", + "\\nPool Manager Address: ", + poolManager, + "\\n", + quoteCurrencySymbol + ) + ); + } + + /// @notice Generates the second part of the description for a Uniswap v4 NFTs + /// @param tokenId The token ID + /// @param baseCurrencySymbol The symbol of the base currency + /// @param quoteCurrency The address of the quote currency + /// @param baseCurrency The address of the base currency + /// @param hooks The address of the hooks contract + /// @param feeTier The fee tier of the pool + /// @return The second part of the description + function generateDescriptionPartTwo( + string memory tokenId, + string memory baseCurrencySymbol, + string memory quoteCurrency, + string memory baseCurrency, + string memory hooks, + string memory feeTier + ) private pure returns (string memory) { + return string( + abi.encodePacked( + " Address: ", + quoteCurrency, + "\\n", + baseCurrencySymbol, + " Address: ", + baseCurrency, + "\\nHook Address: ", + hooks, + "\\nFee Tier: ", + feeTier, + "\\nToken ID: ", + tokenId, + "\\n\\n", + unicode"⚠️ DISCLAIMER: Due diligence is imperative when assessing this NFT. Make sure currency addresses match the expected currencies, as currency symbols may be imitated." + ) + ); + } + + /// @notice Generates the name for a Uniswap v4 NFT + /// @param params Parameters needed to generate the name + /// @param feeTier The fee tier of the pool + /// @return The name of the NFT + function generateName(ConstructTokenURIParams memory params, string memory feeTier) + private + pure + returns (string memory) + { + // image shows in terms of price, ie quoteCurrency/baseCurrency + return string( + abi.encodePacked( + "Uniswap - ", + feeTier, + " - ", + escapeSpecialCharacters(params.quoteCurrencySymbol), + "/", + escapeSpecialCharacters(params.baseCurrencySymbol), + " - ", + tickToDecimalString( + !params.flipRatio ? params.tickLower : params.tickUpper, + params.tickSpacing, + params.baseCurrencyDecimals, + params.quoteCurrencyDecimals, + params.flipRatio + ), + "<>", + tickToDecimalString( + !params.flipRatio ? params.tickUpper : params.tickLower, + params.tickSpacing, + params.baseCurrencyDecimals, + params.quoteCurrencyDecimals, + params.flipRatio + ) + ) + ); + } + + struct DecimalStringParams { + // significant figures of decimal + uint256 sigfigs; + // length of decimal string + uint8 bufferLength; + // ending index for significant figures (funtion works backwards when copying sigfigs) + uint8 sigfigIndex; + // index of decimal place (0 if no decimal) + uint8 decimalIndex; + // start index for trailing/leading 0's for very small/large numbers + uint8 zerosStartIndex; + // end index for trailing/leading 0's for very small/large numbers + uint8 zerosEndIndex; + // true if decimal number is less than one + bool isLessThanOne; + // true if string should include "%" + bool isPercent; + } + + function generateDecimalString(DecimalStringParams memory params) private pure returns (string memory) { + bytes memory buffer = new bytes(params.bufferLength); + if (params.isPercent) { + buffer[buffer.length - 1] = "%"; + } + if (params.isLessThanOne) { + buffer[0] = "0"; + buffer[1] = "."; + } + + // add leading/trailing 0's + for (uint256 zerosCursor = params.zerosStartIndex; zerosCursor < params.zerosEndIndex + 1; zerosCursor++) { + // converts the ASCII code for 0 (which is 48) into a bytes1 to store in the buffer + buffer[zerosCursor] = bytes1(uint8(48)); + } + // add sigfigs + while (params.sigfigs > 0) { + if (params.decimalIndex > 0 && params.sigfigIndex == params.decimalIndex) { + buffer[params.sigfigIndex--] = "."; + } + buffer[params.sigfigIndex] = bytes1(uint8(48 + (params.sigfigs % 10))); + // can overflow when sigfigIndex = 0 + unchecked { + params.sigfigIndex--; + } + params.sigfigs /= 10; + } + return string(buffer); + } + + /// @notice Gets the price (quote/base) at a specific tick in decimal form + /// MIN or MAX are returned if tick is at the bottom or top of the price curve + /// @param tick The tick (either tickLower or tickUpper) + /// @param tickSpacing The tick spacing of the pool + /// @param baseCurrencyDecimals The decimals of the base currency + /// @param quoteCurrencyDecimals The decimals of the quote currency + /// @param flipRatio True if the ratio was flipped + /// @return The ratio value as a string + function tickToDecimalString( + int24 tick, + int24 tickSpacing, + uint8 baseCurrencyDecimals, + uint8 quoteCurrencyDecimals, + bool flipRatio + ) internal pure returns (string memory) { + if (tick == (TickMath.MIN_TICK / tickSpacing) * tickSpacing) { + return !flipRatio ? "MIN" : "MAX"; + } else if (tick == (TickMath.MAX_TICK / tickSpacing) * tickSpacing) { + return !flipRatio ? "MAX" : "MIN"; + } else { + uint160 sqrtRatioX96 = TickMath.getSqrtPriceAtTick(tick); + if (flipRatio) { + sqrtRatioX96 = uint160(uint256(1 << 192) / sqrtRatioX96); + } + return fixedPointToDecimalString(sqrtRatioX96, baseCurrencyDecimals, quoteCurrencyDecimals); + } + } + + function sigfigsRounded(uint256 value, uint8 digits) private pure returns (uint256, bool) { + bool extraDigit; + if (digits > 5) { + value = value / (10 ** (digits - 5)); + } + bool roundUp = value % 10 > 4; + value = value / 10; + if (roundUp) { + value = value + 1; + } + // 99999 -> 100000 gives an extra sigfig + if (value == 100000) { + value /= 10; + extraDigit = true; + } + return (value, extraDigit); + } + + /// @notice Adjusts the sqrt price for different currencies with different decimals + /// @param sqrtRatioX96 The sqrt price at a specific tick + /// @param baseCurrencyDecimals The decimals of the base currency + /// @param quoteCurrencyDecimals The decimals of the quote currency + /// @return adjustedSqrtRatioX96 The adjusted sqrt price + function adjustForDecimalPrecision(uint160 sqrtRatioX96, uint8 baseCurrencyDecimals, uint8 quoteCurrencyDecimals) + private + pure + returns (uint256 adjustedSqrtRatioX96) + { + uint256 difference = abs(int256(uint256(baseCurrencyDecimals)) - (int256(uint256(quoteCurrencyDecimals)))); + if (difference > 0 && difference <= 18) { + if (baseCurrencyDecimals > quoteCurrencyDecimals) { + adjustedSqrtRatioX96 = sqrtRatioX96 * (10 ** (difference / 2)); + if (difference % 2 == 1) { + adjustedSqrtRatioX96 = FullMath.mulDiv(adjustedSqrtRatioX96, sqrt10X128, 1 << 128); + } + } else { + adjustedSqrtRatioX96 = sqrtRatioX96 / (10 ** (difference / 2)); + if (difference % 2 == 1) { + adjustedSqrtRatioX96 = FullMath.mulDiv(adjustedSqrtRatioX96, 1 << 128, sqrt10X128); + } + } + } else { + adjustedSqrtRatioX96 = uint256(sqrtRatioX96); + } + } + + /// @notice Absolute value of a signed integer + /// @param x The signed integer + /// @return The absolute value of x + function abs(int256 x) private pure returns (uint256) { + return uint256(x >= 0 ? x : -x); + } + + function fixedPointToDecimalString(uint160 sqrtRatioX96, uint8 baseCurrencyDecimals, uint8 quoteCurrencyDecimals) + internal + pure + returns (string memory) + { + uint256 adjustedSqrtRatioX96 = + adjustForDecimalPrecision(sqrtRatioX96, baseCurrencyDecimals, quoteCurrencyDecimals); + uint256 value = FullMath.mulDiv(adjustedSqrtRatioX96, adjustedSqrtRatioX96, 1 << 64); + + bool priceBelow1 = adjustedSqrtRatioX96 < 2 ** 96; + if (priceBelow1) { + // 10 ** 43 is precision needed to retreive 5 sigfigs of smallest possible price + 1 for rounding + value = FullMath.mulDiv(value, 10 ** 44, 1 << 128); + } else { + // leave precision for 4 decimal places + 1 place for rounding + value = FullMath.mulDiv(value, 10 ** 5, 1 << 128); + } + + // get digit count + uint256 temp = value; + uint8 digits; + while (temp != 0) { + digits++; + temp /= 10; + } + // don't count extra digit kept for rounding + digits = digits - 1; + + // address rounding + (uint256 sigfigs, bool extraDigit) = sigfigsRounded(value, digits); + if (extraDigit) { + digits++; + } + + DecimalStringParams memory params; + if (priceBelow1) { + // 7 bytes ( "0." and 5 sigfigs) + leading 0's bytes + params.bufferLength = uint8(uint8(7) + (uint8(43) - digits)); + params.zerosStartIndex = 2; + params.zerosEndIndex = uint8(uint256(43) - digits + 1); + params.sigfigIndex = uint8(params.bufferLength - 1); + } else if (digits >= 9) { + // no decimal in price string + params.bufferLength = uint8(digits - 4); + params.zerosStartIndex = 5; + params.zerosEndIndex = uint8(params.bufferLength - 1); + params.sigfigIndex = 4; + } else { + // 5 sigfigs surround decimal + params.bufferLength = 6; + params.sigfigIndex = 5; + params.decimalIndex = uint8(digits - 5 + 1); + } + params.sigfigs = sigfigs; + params.isLessThanOne = priceBelow1; + params.isPercent = false; + + return generateDecimalString(params); + } + + /// @notice Converts fee amount in pips to decimal string with percent sign + /// @param fee fee amount + /// @return fee as a decimal string with percent sign + function feeToPercentString(uint24 fee) internal pure returns (string memory) { + if (fee.isDynamicFee()) { + return "Dynamic"; + } + if (fee == 0) { + return "0%"; + } + uint24 temp = fee; + uint256 digits; + uint8 numSigfigs; + // iterates over each digit of fee by dividing temp by 10 in each iteration until temp becomes 0 + // calculates number of digits and number of significant figures (non-zero digits) + while (temp != 0) { + if (numSigfigs > 0) { + // count all digits preceding least significant figure + numSigfigs++; + } else if (temp % 10 != 0) { + numSigfigs++; + } + digits++; + temp /= 10; + } + + DecimalStringParams memory params; + uint256 nZeros; + if (digits >= 5) { + // represents fee greater than or equal to 1% + // if decimal > 1 (5th digit is the ones place) + uint256 decimalPlace = digits - numSigfigs >= 4 ? 0 : 1; + nZeros = digits - 5 < numSigfigs - 1 ? 0 : digits - 5 - (numSigfigs - 1); + params.zerosStartIndex = numSigfigs; + params.zerosEndIndex = uint8(params.zerosStartIndex + nZeros - 1); + params.sigfigIndex = uint8(params.zerosStartIndex - 1 + decimalPlace); + params.bufferLength = uint8(nZeros + numSigfigs + 1 + decimalPlace); + } else { + // represents fee less than 1% + // else if decimal < 1 + nZeros = 5 - digits; // number of zeros, inlcuding the zero before decimal + params.zerosStartIndex = 2; // leading zeros will start after the decimal point + params.zerosEndIndex = uint8(nZeros + params.zerosStartIndex - 1); // end index for leading zeros + params.bufferLength = uint8(nZeros + numSigfigs + 2); // total length of string buffer, including "0." and "%" + params.sigfigIndex = uint8(params.bufferLength - 2); // index of starting signficant figure + params.isLessThanOne = true; + } + params.sigfigs = uint256(fee) / (10 ** (digits - numSigfigs)); // the signficant figures of the fee + params.isPercent = true; + params.decimalIndex = digits > 4 ? uint8(digits - 4) : 0; // based on total number of digits in the fee + + return generateDecimalString(params); + } + + function addressToString(address addr) internal pure returns (string memory) { + return (uint256(uint160(addr))).toHexString(20); + } + + /// @notice Generates the SVG image for a Uniswap v4 NFT + /// @param params Parameters needed to generate the SVG image + /// @return svg The SVG image as a string + function generateSVGImage(ConstructTokenURIParams memory params) internal pure returns (string memory svg) { + SVG.SVGParams memory svgParams = SVG.SVGParams({ + quoteCurrency: addressToString(params.quoteCurrency), + baseCurrency: addressToString(params.baseCurrency), + hooks: params.hooks, + quoteCurrencySymbol: params.quoteCurrencySymbol, + baseCurrencySymbol: params.baseCurrencySymbol, + feeTier: feeToPercentString(params.fee), + tickLower: params.tickLower, + tickUpper: params.tickUpper, + tickSpacing: params.tickSpacing, + overRange: overRange(params.tickLower, params.tickUpper, params.tickCurrent), + tokenId: params.tokenId, + color0: currencyToColorHex(uint256(uint160(params.quoteCurrency)), 136), + color1: currencyToColorHex(uint256(uint160(params.baseCurrency)), 136), + color2: currencyToColorHex(uint256(uint160(params.quoteCurrency)), 0), + color3: currencyToColorHex(uint256(uint160(params.baseCurrency)), 0), + x1: scale(getCircleCoord(uint256(uint160(params.quoteCurrency)), 16, params.tokenId), 0, 255, 16, 274), + y1: scale(getCircleCoord(uint256(uint160(params.baseCurrency)), 16, params.tokenId), 0, 255, 100, 484), + x2: scale(getCircleCoord(uint256(uint160(params.quoteCurrency)), 32, params.tokenId), 0, 255, 16, 274), + y2: scale(getCircleCoord(uint256(uint160(params.baseCurrency)), 32, params.tokenId), 0, 255, 100, 484), + x3: scale(getCircleCoord(uint256(uint160(params.quoteCurrency)), 48, params.tokenId), 0, 255, 16, 274), + y3: scale(getCircleCoord(uint256(uint160(params.baseCurrency)), 48, params.tokenId), 0, 255, 100, 484) + }); + + return SVG.generateSVG(svgParams); + } + + /// @notice Checks if the current price is within your position range, above, or below + /// @param tickLower The lower tick + /// @param tickUpper The upper tick + /// @param tickCurrent The current tick + /// @return 0 if the current price is within the position range, -1 if below, 1 if above + function overRange(int24 tickLower, int24 tickUpper, int24 tickCurrent) private pure returns (int8) { + if (tickCurrent < tickLower) { + return -1; + } else if (tickCurrent > tickUpper) { + return 1; + } else { + return 0; + } + } + + function isSpecialCharacter(bytes1 b) private pure returns (bool) { + return b == '"' || b == "\u000c" || b == "\n" || b == "\r" || b == "\t"; + } + + function scale(uint256 n, uint256 inMn, uint256 inMx, uint256 outMn, uint256 outMx) + private + pure + returns (string memory) + { + return ((n - inMn) * (outMx - outMn) / (inMx - inMn) + outMn).toString(); + } + + function currencyToColorHex(uint256 currency, uint256 offset) internal pure returns (string memory str) { + return string((currency >> offset).toHexStringNoPrefix(3)); + } + + function getCircleCoord(uint256 currency, uint256 offset, uint256 tokenId) internal pure returns (uint256) { + return (sliceCurrencyHex(currency, offset) * tokenId) % 255; + } + + function sliceCurrencyHex(uint256 currency, uint256 offset) internal pure returns (uint256) { + return uint256(uint8(currency >> offset)); + } +} diff --git a/CVLByExample/UniswapHooks/lib/uniswap-hooks/lib/v4-periphery/src/libraries/ERC721PermitHash.sol b/CVLByExample/UniswapHooks/lib/uniswap-hooks/lib/v4-periphery/src/libraries/ERC721PermitHash.sol new file mode 100644 index 00000000..3ead157a --- /dev/null +++ b/CVLByExample/UniswapHooks/lib/uniswap-hooks/lib/v4-periphery/src/libraries/ERC721PermitHash.sol @@ -0,0 +1,70 @@ +// SPDX-License-Identifier: MIT +pragma solidity ^0.8.0; + +library ERC721PermitHash { + /// @dev Value is equal to keccak256("Permit(address spender,uint256 tokenId,uint256 nonce,uint256 deadline)"); + bytes32 constant PERMIT_TYPEHASH = 0x49ecf333e5b8c95c40fdafc95c1ad136e8914a8fb55e9dc8bb01eaa83a2df9ad; + + /// @dev Value is equal to keccak256("PermitForAll(address operator,bool approved,uint256 nonce,uint256 deadline)"); + bytes32 constant PERMIT_FOR_ALL_TYPEHASH = 0x6673cb397ee2a50b6b8401653d3638b4ac8b3db9c28aa6870ffceb7574ec2f76; + + /// @notice Hashes the data that will be signed for IERC721Permit_v4.permit() + /// @param spender The address which may spend the tokenId + /// @param tokenId The tokenId of the owner, which may be spent by spender + /// @param nonce A unique non-ordered value for each signature to prevent replay attacks + /// @param deadline The time at which the signature expires + /// @return digest The hash of the data to be signed; the equivalent to keccak256(abi.encode(PERMIT_TYPEHASH, spender, tokenId, nonce, deadline)); + function hashPermit(address spender, uint256 tokenId, uint256 nonce, uint256 deadline) + internal + pure + returns (bytes32 digest) + { + // equivalent to: keccak256(abi.encode(PERMIT_TYPEHASH, spender, tokenId, nonce, deadline)); + assembly ("memory-safe") { + let fmp := mload(0x40) + mstore(fmp, PERMIT_TYPEHASH) + mstore(add(fmp, 0x20), and(spender, 0xffffffffffffffffffffffffffffffffffffffff)) + mstore(add(fmp, 0x40), tokenId) + mstore(add(fmp, 0x60), nonce) + mstore(add(fmp, 0x80), deadline) + digest := keccak256(fmp, 0xa0) + + // now clean the memory we used + mstore(fmp, 0) // fmp held PERMIT_TYPEHASH + mstore(add(fmp, 0x20), 0) // fmp+0x20 held spender + mstore(add(fmp, 0x40), 0) // fmp+0x40 held tokenId + mstore(add(fmp, 0x60), 0) // fmp+0x60 held nonce + mstore(add(fmp, 0x80), 0) // fmp+0x80 held deadline + } + } + + /// @notice Hashes the data that will be signed for IERC721Permit_v4.permit() + /// @param operator The address which may spend any of the owner's tokenIds + /// @param approved true if the operator is to have full permission over the owner's tokenIds; false otherwise + /// @param nonce A unique non-ordered value for each signature to prevent replay attacks + /// @param deadline The time at which the signature expires + /// @return digest The hash of the data to be signed; the equivalent to keccak256(abi.encode(PERMIT_FOR_ALL_TYPEHASH, operator, approved, nonce, deadline)); + function hashPermitForAll(address operator, bool approved, uint256 nonce, uint256 deadline) + internal + pure + returns (bytes32 digest) + { + // equivalent to: keccak256(abi.encode(PERMIT_FOR_ALL_TYPEHASH, operator, approved, nonce, deadline)); + assembly ("memory-safe") { + let fmp := mload(0x40) + mstore(fmp, PERMIT_FOR_ALL_TYPEHASH) + mstore(add(fmp, 0x20), and(operator, 0xffffffffffffffffffffffffffffffffffffffff)) + mstore(add(fmp, 0x40), and(approved, 0x1)) + mstore(add(fmp, 0x60), nonce) + mstore(add(fmp, 0x80), deadline) + digest := keccak256(fmp, 0xa0) + + // now clean the memory we used + mstore(fmp, 0) // fmp held PERMIT_FOR_ALL_TYPEHASH + mstore(add(fmp, 0x20), 0) // fmp+0x20 held operator + mstore(add(fmp, 0x40), 0) // fmp+0x40 held approved + mstore(add(fmp, 0x60), 0) // fmp+0x60 held nonce + mstore(add(fmp, 0x80), 0) // fmp+0x80 held deadline + } + } +} diff --git a/CVLByExample/UniswapHooks/lib/uniswap-hooks/lib/v4-periphery/src/libraries/HexStrings.sol b/CVLByExample/UniswapHooks/lib/uniswap-hooks/lib/v4-periphery/src/libraries/HexStrings.sol new file mode 100644 index 00000000..714a9c8e --- /dev/null +++ b/CVLByExample/UniswapHooks/lib/uniswap-hooks/lib/v4-periphery/src/libraries/HexStrings.sol @@ -0,0 +1,22 @@ +// SPDX-License-Identifier: MIT +pragma solidity ^0.8.0; + +/// @title HexStrings +/// @notice Provides function for converting numbers to hexadecimal strings +/// @dev Reference: https://github.com/Uniswap/v3-periphery/blob/main/contracts/libraries/HexStrings.sol +library HexStrings { + bytes16 internal constant ALPHABET = "0123456789abcdef"; + + /// @notice Convert a number to a hex string without the '0x' prefix with a fixed length + /// @param value The number to convert + /// @param length The length of the output string, starting from the last character of the string + /// @return The hex string + function toHexStringNoPrefix(uint256 value, uint256 length) internal pure returns (string memory) { + bytes memory buffer = new bytes(2 * length); + for (uint256 i = buffer.length; i > 0; i--) { + buffer[i - 1] = ALPHABET[value & 0xf]; + value >>= 4; + } + return string(buffer); + } +} diff --git a/CVLByExample/UniswapHooks/lib/uniswap-hooks/lib/v4-periphery/src/libraries/LiquidityAmounts.sol b/CVLByExample/UniswapHooks/lib/uniswap-hooks/lib/v4-periphery/src/libraries/LiquidityAmounts.sol new file mode 100644 index 00000000..d6d2dd96 --- /dev/null +++ b/CVLByExample/UniswapHooks/lib/uniswap-hooks/lib/v4-periphery/src/libraries/LiquidityAmounts.sol @@ -0,0 +1,75 @@ +// SPDX-License-Identifier: MIT +pragma solidity ^0.8.0; + +import {FullMath} from "@uniswap/v4-core/src/libraries/FullMath.sol"; +import {FixedPoint96} from "@uniswap/v4-core/src/libraries/FixedPoint96.sol"; +import {SafeCast} from "@uniswap/v4-core/src/libraries/SafeCast.sol"; + +/// @notice Provides functions for computing liquidity amounts from token amounts and prices +library LiquidityAmounts { + using SafeCast for uint256; + + /// @notice Computes the amount of liquidity received for a given amount of token0 and price range + /// @dev Calculates amount0 * (sqrt(upper) * sqrt(lower)) / (sqrt(upper) - sqrt(lower)) + /// @param sqrtPriceAX96 A sqrt price representing the first tick boundary + /// @param sqrtPriceBX96 A sqrt price representing the second tick boundary + /// @param amount0 The amount0 being sent in + /// @return liquidity The amount of returned liquidity + function getLiquidityForAmount0(uint160 sqrtPriceAX96, uint160 sqrtPriceBX96, uint256 amount0) + internal + pure + returns (uint128 liquidity) + { + unchecked { + if (sqrtPriceAX96 > sqrtPriceBX96) (sqrtPriceAX96, sqrtPriceBX96) = (sqrtPriceBX96, sqrtPriceAX96); + uint256 intermediate = FullMath.mulDiv(sqrtPriceAX96, sqrtPriceBX96, FixedPoint96.Q96); + return FullMath.mulDiv(amount0, intermediate, sqrtPriceBX96 - sqrtPriceAX96).toUint128(); + } + } + + /// @notice Computes the amount of liquidity received for a given amount of token1 and price range + /// @dev Calculates amount1 / (sqrt(upper) - sqrt(lower)). + /// @param sqrtPriceAX96 A sqrt price representing the first tick boundary + /// @param sqrtPriceBX96 A sqrt price representing the second tick boundary + /// @param amount1 The amount1 being sent in + /// @return liquidity The amount of returned liquidity + function getLiquidityForAmount1(uint160 sqrtPriceAX96, uint160 sqrtPriceBX96, uint256 amount1) + internal + pure + returns (uint128 liquidity) + { + unchecked { + if (sqrtPriceAX96 > sqrtPriceBX96) (sqrtPriceAX96, sqrtPriceBX96) = (sqrtPriceBX96, sqrtPriceAX96); + return FullMath.mulDiv(amount1, FixedPoint96.Q96, sqrtPriceBX96 - sqrtPriceAX96).toUint128(); + } + } + + /// @notice Computes the maximum amount of liquidity received for a given amount of token0, token1, the current + /// pool prices and the prices at the tick boundaries + /// @param sqrtPriceX96 A sqrt price representing the current pool prices + /// @param sqrtPriceAX96 A sqrt price representing the first tick boundary + /// @param sqrtPriceBX96 A sqrt price representing the second tick boundary + /// @param amount0 The amount of token0 being sent in + /// @param amount1 The amount of token1 being sent in + /// @return liquidity The maximum amount of liquidity received + function getLiquidityForAmounts( + uint160 sqrtPriceX96, + uint160 sqrtPriceAX96, + uint160 sqrtPriceBX96, + uint256 amount0, + uint256 amount1 + ) internal pure returns (uint128 liquidity) { + if (sqrtPriceAX96 > sqrtPriceBX96) (sqrtPriceAX96, sqrtPriceBX96) = (sqrtPriceBX96, sqrtPriceAX96); + + if (sqrtPriceX96 <= sqrtPriceAX96) { + liquidity = getLiquidityForAmount0(sqrtPriceAX96, sqrtPriceBX96, amount0); + } else if (sqrtPriceX96 < sqrtPriceBX96) { + uint128 liquidity0 = getLiquidityForAmount0(sqrtPriceX96, sqrtPriceBX96, amount0); + uint128 liquidity1 = getLiquidityForAmount1(sqrtPriceAX96, sqrtPriceX96, amount1); + + liquidity = liquidity0 < liquidity1 ? liquidity0 : liquidity1; + } else { + liquidity = getLiquidityForAmount1(sqrtPriceAX96, sqrtPriceBX96, amount1); + } + } +} diff --git a/CVLByExample/UniswapHooks/lib/uniswap-hooks/lib/v4-periphery/src/libraries/Locker.sol b/CVLByExample/UniswapHooks/lib/uniswap-hooks/lib/v4-periphery/src/libraries/Locker.sol new file mode 100644 index 00000000..246b10a9 --- /dev/null +++ b/CVLByExample/UniswapHooks/lib/uniswap-hooks/lib/v4-periphery/src/libraries/Locker.sol @@ -0,0 +1,21 @@ +// SPDX-License-Identifier: MIT +pragma solidity ^0.8.24; + +/// @notice This is a temporary library that allows us to use transient storage (tstore/tload) +/// TODO: This library can be deleted when we have the transient keyword support in solidity. +library Locker { + // The slot holding the locker state, transiently. bytes32(uint256(keccak256("LockedBy")) - 1) + bytes32 constant LOCKED_BY_SLOT = 0x0aedd6bde10e3aa2adec092b02a3e3e805795516cda41f27aa145b8f300af87a; + + function set(address locker) internal { + assembly { + tstore(LOCKED_BY_SLOT, locker) + } + } + + function get() internal view returns (address locker) { + assembly { + locker := tload(LOCKED_BY_SLOT) + } + } +} diff --git a/CVLByExample/UniswapHooks/lib/uniswap-hooks/lib/v4-periphery/src/libraries/PathKey.sol b/CVLByExample/UniswapHooks/lib/uniswap-hooks/lib/v4-periphery/src/libraries/PathKey.sol new file mode 100644 index 00000000..b6902db3 --- /dev/null +++ b/CVLByExample/UniswapHooks/lib/uniswap-hooks/lib/v4-periphery/src/libraries/PathKey.sol @@ -0,0 +1,38 @@ +//SPDX-License-Identifier: MIT +pragma solidity ^0.8.0; + +import {Currency} from "@uniswap/v4-core/src/types/Currency.sol"; +import {IHooks} from "@uniswap/v4-core/src/interfaces/IHooks.sol"; +import {PoolKey} from "@uniswap/v4-core/src/types/PoolKey.sol"; + +struct PathKey { + Currency intermediateCurrency; + uint24 fee; + int24 tickSpacing; + IHooks hooks; + bytes hookData; +} + +using PathKeyLibrary for PathKey global; + +/// @title PathKey Library +/// @notice Functions for working with PathKeys +library PathKeyLibrary { + /// @notice Get the pool and swap direction for a given PathKey + /// @param params the given PathKey + /// @param currencyIn the input currency + /// @return poolKey the pool key of the swap + /// @return zeroForOne the direction of the swap, true if currency0 is being swapped for currency1 + function getPoolAndSwapDirection(PathKey calldata params, Currency currencyIn) + internal + pure + returns (PoolKey memory poolKey, bool zeroForOne) + { + Currency currencyOut = params.intermediateCurrency; + (Currency currency0, Currency currency1) = + currencyIn < currencyOut ? (currencyIn, currencyOut) : (currencyOut, currencyIn); + + zeroForOne = currencyIn == currency0; + poolKey = PoolKey(currency0, currency1, params.fee, params.tickSpacing, params.hooks); + } +} diff --git a/CVLByExample/UniswapHooks/lib/uniswap-hooks/lib/v4-periphery/src/libraries/PositionConfig.sol b/CVLByExample/UniswapHooks/lib/uniswap-hooks/lib/v4-periphery/src/libraries/PositionConfig.sol new file mode 100644 index 00000000..2fd6cd0d --- /dev/null +++ b/CVLByExample/UniswapHooks/lib/uniswap-hooks/lib/v4-periphery/src/libraries/PositionConfig.sol @@ -0,0 +1,35 @@ +// SPDX-License-Identifier: MIT +pragma solidity ^0.8.0; + +import {PoolKey} from "@uniswap/v4-core/src/types/PoolKey.sol"; + +// A PositionConfig is the input for creating and modifying a Position in core, whose truncated hash is set per tokenId +struct PositionConfig { + PoolKey poolKey; + int24 tickLower; + int24 tickUpper; +} + +/// @notice Library to calculate the PositionConfigId from the PositionConfig struct +library PositionConfigLibrary { + function toId(PositionConfig calldata config) internal pure returns (bytes32 id) { + // id = keccak256(abi.encodePacked(currency0, currency1, fee, tickSpacing, hooks, tickLower, tickUpper))) >> 1 + assembly ("memory-safe") { + let fmp := mload(0x40) + mstore(add(fmp, 0x34), calldataload(add(config, 0xc0))) // tickUpper: [0x51, 0x54) + mstore(add(fmp, 0x31), calldataload(add(config, 0xa0))) // tickLower: [0x4E, 0x51) + mstore(add(fmp, 0x2E), calldataload(add(config, 0x80))) // hooks: [0x3A, 0x4E) + mstore(add(fmp, 0x1A), calldataload(add(config, 0x60))) // tickSpacing: [0x37, 0x3A) + mstore(add(fmp, 0x17), calldataload(add(config, 0x40))) // fee: [0x34, 0x37) + mstore(add(fmp, 0x14), calldataload(add(config, 0x20))) // currency1: [0x20, 0x34) + mstore(fmp, calldataload(config)) // currency0: [0x0c, 0x20) + + id := shr(1, keccak256(add(fmp, 0x0c), 0x48)) // len is 72 bytes, truncate lower bit of the hash + + // now clean the memory we used + mstore(add(fmp, 0x40), 0) // fmp+0x40 held hooks (14 bytes), tickLower, tickUpper + mstore(add(fmp, 0x20), 0) // fmp+0x20 held currency1, fee, tickSpacing, hooks (6 bytes) + mstore(fmp, 0) // fmp held currency0 + } + } +} diff --git a/CVLByExample/UniswapHooks/lib/uniswap-hooks/lib/v4-periphery/src/libraries/PositionConfigId.sol b/CVLByExample/UniswapHooks/lib/uniswap-hooks/lib/v4-periphery/src/libraries/PositionConfigId.sol new file mode 100644 index 00000000..e6bdf84d --- /dev/null +++ b/CVLByExample/UniswapHooks/lib/uniswap-hooks/lib/v4-periphery/src/libraries/PositionConfigId.sol @@ -0,0 +1,39 @@ +// SPDX-License-Identifier: MIT +pragma solidity ^0.8.0; + +/// @notice A configId is set per tokenId +/// The lower 255 bits are used to store the truncated hash of the corresponding PositionConfig +/// The upper bit is used to signal if the tokenId has a subscriber +struct PositionConfigId { + bytes32 id; +} + +library PositionConfigIdLibrary { + bytes32 constant MASK_UPPER_BIT = 0x7FFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFF; + bytes32 constant DIRTY_UPPER_BIT = 0x8000000000000000000000000000000000000000000000000000000000000000; + + /// @notice returns the truncated hash of the PositionConfig for a given tokenId + function getConfigId(PositionConfigId storage _configId) internal view returns (bytes32 configId) { + configId = _configId.id & MASK_UPPER_BIT; + } + + /// @dev We only set the config on mint, guaranteeing that the most significant bit is unset, so we can just assign the entire 32 bytes to the id. + function setConfigId(PositionConfigId storage _configId, bytes32 configId) internal { + _configId.id = configId; + } + + function setSubscribe(PositionConfigId storage configId) internal { + configId.id |= DIRTY_UPPER_BIT; + } + + function setUnsubscribe(PositionConfigId storage configId) internal { + configId.id &= MASK_UPPER_BIT; + } + + function hasSubscriber(PositionConfigId storage configId) internal view returns (bool subscribed) { + bytes32 _id = configId.id; + assembly ("memory-safe") { + subscribed := shr(255, _id) + } + } +} diff --git a/CVLByExample/UniswapHooks/lib/uniswap-hooks/lib/v4-periphery/src/libraries/PositionInfoLibrary.sol b/CVLByExample/UniswapHooks/lib/uniswap-hooks/lib/v4-periphery/src/libraries/PositionInfoLibrary.sol new file mode 100644 index 00000000..85650585 --- /dev/null +++ b/CVLByExample/UniswapHooks/lib/uniswap-hooks/lib/v4-periphery/src/libraries/PositionInfoLibrary.sol @@ -0,0 +1,105 @@ +// SPDX-License-Identifier: MIT +pragma solidity ^0.8.24; + +import {PoolKey} from "@uniswap/v4-core/src/types/PoolKey.sol"; +import {PoolId} from "@uniswap/v4-core/src/types/PoolId.sol"; + +/** + * @dev PositionInfo is a packed version of solidity structure. + * Using the packaged version saves gas and memory by not storing the structure fields in memory slots. + * + * Layout: + * 200 bits poolId | 24 bits tickUpper | 24 bits tickLower | 8 bits hasSubscriber + * + * Fields in the direction from the least significant bit: + * + * A flag to know if the tokenId is subscribed to an address + * uint8 hasSubscriber; + * + * The tickUpper of the position + * int24 tickUpper; + * + * The tickLower of the position + * int24 tickLower; + * + * The truncated poolId. Truncates a bytes32 value so the most signifcant (highest) 200 bits are used. + * bytes25 poolId; + * + * Note: If more bits are needed, hasSubscriber can be a single bit. + * + */ +type PositionInfo is uint256; + +using PositionInfoLibrary for PositionInfo global; + +library PositionInfoLibrary { + PositionInfo internal constant EMPTY_POSITION_INFO = PositionInfo.wrap(0); + + uint256 internal constant MASK_UPPER_200_BITS = 0xFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFF00000000000000; + uint256 internal constant MASK_8_BITS = 0xFF; + uint24 internal constant MASK_24_BITS = 0xFFFFFF; + uint256 internal constant SET_UNSUBSCRIBE = 0xFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFF00; + uint256 internal constant SET_SUBSCRIBE = 0x01; + uint8 internal constant TICK_LOWER_OFFSET = 8; + uint8 internal constant TICK_UPPER_OFFSET = 32; + + /// @dev This poolId is NOT compatible with the poolId used in UniswapV4 core. It is truncated to 25 bytes, and just used to lookup PoolKey in the poolKeys mapping. + function poolId(PositionInfo info) internal pure returns (bytes25 _poolId) { + assembly ("memory-safe") { + _poolId := and(MASK_UPPER_200_BITS, info) + } + } + + function tickLower(PositionInfo info) internal pure returns (int24 _tickLower) { + assembly ("memory-safe") { + _tickLower := signextend(2, shr(TICK_LOWER_OFFSET, info)) + } + } + + function tickUpper(PositionInfo info) internal pure returns (int24 _tickUpper) { + assembly ("memory-safe") { + _tickUpper := signextend(2, shr(TICK_UPPER_OFFSET, info)) + } + } + + function hasSubscriber(PositionInfo info) internal pure returns (bool _hasSubscriber) { + assembly ("memory-safe") { + _hasSubscriber := and(MASK_8_BITS, info) + } + } + + /// @dev this does not actually set any storage + function setSubscribe(PositionInfo info) internal pure returns (PositionInfo _info) { + assembly ("memory-safe") { + _info := or(info, SET_SUBSCRIBE) + } + } + + /// @dev this does not actually set any storage + function setUnsubscribe(PositionInfo info) internal pure returns (PositionInfo _info) { + assembly ("memory-safe") { + _info := and(info, SET_UNSUBSCRIBE) + } + } + + /// @notice Creates the default PositionInfo struct + /// @dev Called when minting a new position + /// @param _poolKey the pool key of the position + /// @param _tickLower the lower tick of the position + /// @param _tickUpper the upper tick of the position + /// @return info packed position info, with the truncated poolId and the hasSubscriber flag set to false + function initialize(PoolKey memory _poolKey, int24 _tickLower, int24 _tickUpper) + internal + pure + returns (PositionInfo info) + { + bytes25 _poolId = bytes25(PoolId.unwrap(_poolKey.toId())); + assembly { + info := + or( + or(and(MASK_UPPER_200_BITS, _poolId), shl(TICK_UPPER_OFFSET, and(MASK_24_BITS, _tickUpper))), + shl(TICK_LOWER_OFFSET, and(MASK_24_BITS, _tickLower)) + ) + } + } +} diff --git a/CVLByExample/UniswapHooks/lib/uniswap-hooks/lib/v4-periphery/src/libraries/QuoterRevert.sol b/CVLByExample/UniswapHooks/lib/uniswap-hooks/lib/v4-periphery/src/libraries/QuoterRevert.sol new file mode 100644 index 00000000..d53ec844 --- /dev/null +++ b/CVLByExample/UniswapHooks/lib/uniswap-hooks/lib/v4-periphery/src/libraries/QuoterRevert.sol @@ -0,0 +1,49 @@ +// SPDX-License-Identifier: MIT +pragma solidity ^0.8.0; + +import {ParseBytes} from "@uniswap/v4-core/src/libraries/ParseBytes.sol"; + +library QuoterRevert { + using QuoterRevert for bytes; + using ParseBytes for bytes; + + /// @notice error thrown when invalid revert bytes are thrown by the quote + error UnexpectedRevertBytes(bytes revertData); + + /// @notice error thrown containing the quote as the data, to be caught and parsed later + error QuoteSwap(uint256 amount); + + /// @notice reverts, where the revert data is the provided bytes + /// @dev called when quoting, to record the quote amount in an error + /// @dev QuoteSwap is used to differentiate this error from other errors thrown when simulating the swap + function revertQuote(uint256 quoteAmount) internal pure { + revert QuoteSwap(quoteAmount); + } + + /// @notice reverts using the revertData as the reason + /// @dev to bubble up both the valid QuoteSwap(amount) error, or an alternative error thrown during simulation + function bubbleReason(bytes memory revertData) internal pure { + // mload(revertData): the length of the revert data + // add(revertData, 0x20): a pointer to the start of the revert data + assembly ("memory-safe") { + revert(add(revertData, 0x20), mload(revertData)) + } + } + + /// @notice validates whether a revert reason is a valid swap quote or not + /// if valid, it decodes the quote to return. Otherwise it reverts. + function parseQuoteAmount(bytes memory reason) internal pure returns (uint256 quoteAmount) { + // If the error doesnt start with QuoteSwap, we know this isnt a valid quote to parse + // Instead it is another revert that was triggered somewhere in the simulation + if (reason.parseSelector() != QuoteSwap.selector) { + revert UnexpectedRevertBytes(reason); + } + + // reason -> reason+0x1f is the length of the reason string + // reason+0x20 -> reason+0x23 is the selector of QuoteSwap + // reason+0x24 -> reason+0x43 is the quoteAmount + assembly ("memory-safe") { + quoteAmount := mload(add(reason, 0x24)) + } + } +} diff --git a/CVLByExample/UniswapHooks/lib/uniswap-hooks/lib/v4-periphery/src/libraries/SVG.sol b/CVLByExample/UniswapHooks/lib/uniswap-hooks/lib/v4-periphery/src/libraries/SVG.sol new file mode 100644 index 00000000..03c4236c --- /dev/null +++ b/CVLByExample/UniswapHooks/lib/uniswap-hooks/lib/v4-periphery/src/libraries/SVG.sol @@ -0,0 +1,471 @@ +// SPDX-License-Identifier: MIT +pragma solidity ^0.8.0; + +import {IHooks} from "@uniswap/v4-core/src/interfaces/IHooks.sol"; +import {BitMath} from "@uniswap/v4-core/src/libraries/BitMath.sol"; +import {Strings} from "openzeppelin-contracts/contracts/utils/Strings.sol"; +import {Base64} from "openzeppelin-contracts/contracts/utils/Base64.sol"; + +/// @title SVG +/// @notice Provides a function for generating an SVG associated with a Uniswap NFT +/// @dev Reference: https://github.com/Uniswap/v3-periphery/blob/main/contracts/libraries/NFTSVG.sol +library SVG { + using Strings for uint256; + + // SVG path commands for the curve that represent the steepness of the position + // defined using the Cubic Bezier Curve syntax + // curve1 is the smallest (linear) curve, curve8 is the largest curve + string constant curve1 = "M1 1C41 41 105 105 145 145"; + string constant curve2 = "M1 1C33 49 97 113 145 145"; + string constant curve3 = "M1 1C33 57 89 113 145 145"; + string constant curve4 = "M1 1C25 65 81 121 145 145"; + string constant curve5 = "M1 1C17 73 73 129 145 145"; + string constant curve6 = "M1 1C9 81 65 137 145 145"; + string constant curve7 = "M1 1C1 89 57.5 145 145 145"; + string constant curve8 = "M1 1C1 97 49 145 145 145"; + + struct SVGParams { + string quoteCurrency; + string baseCurrency; + address hooks; + string quoteCurrencySymbol; + string baseCurrencySymbol; + string feeTier; + int24 tickLower; + int24 tickUpper; + int24 tickSpacing; + int8 overRange; + uint256 tokenId; + string color0; + string color1; + string color2; + string color3; + string x1; + string y1; + string x2; + string y2; + string x3; + string y3; + } + + /// @notice Generate the SVG associated with a Uniswap v4 NFT + /// @param params The SVGParams struct containing the parameters for the SVG + /// @return svg The SVG string associated with the NFT + function generateSVG(SVGParams memory params) internal pure returns (string memory svg) { + return string( + abi.encodePacked( + generateSVGDefs(params), + generateSVGBorderText( + params.quoteCurrency, params.baseCurrency, params.quoteCurrencySymbol, params.baseCurrencySymbol + ), + generateSVGCardMantle(params.quoteCurrencySymbol, params.baseCurrencySymbol, params.feeTier), + generageSvgCurve(params.tickLower, params.tickUpper, params.tickSpacing, params.overRange), + generateSVGPositionDataAndLocationCurve( + params.tokenId.toString(), params.hooks, params.tickLower, params.tickUpper + ), + generateSVGRareSparkle(params.tokenId, params.hooks), + "" + ) + ); + } + + /// @notice Generate the SVG defs that create the color scheme for the SVG + /// @param params The SVGParams struct containing the parameters to generate the SVG defs + /// @return svg The SVG defs string + function generateSVGDefs(SVGParams memory params) private pure returns (string memory svg) { + svg = string( + abi.encodePacked( + '", + "", + '" + ) + ) + ), + '"/>" + ) + ) + ), + '"/>" + ) + ) + ), + '" />', + '" + ) + ) + ), + '" /> ', + '', + '', + '', + '', + '', + '', + '', + '', + '', + '', + '', + '', + '', + '', + ' ', + '', + '', + '' + ) + ); + } + + /// @notice Generate the SVG for the moving border text displaying the quote and base currency addresses with their symbols + /// @param quoteCurrency The quote currency + /// @param baseCurrency The base currency + /// @param quoteCurrencySymbol The quote currency symbol + /// @param baseCurrencySymbol The base currency symbol + /// @return svg The SVG for the border NFT's border text + function generateSVGBorderText( + string memory quoteCurrency, + string memory baseCurrency, + string memory quoteCurrencySymbol, + string memory baseCurrencySymbol + ) private pure returns (string memory svg) { + svg = string( + abi.encodePacked( + '', + '', + baseCurrency, + unicode" • ", + baseCurrencySymbol, + ' ', + ' ', + baseCurrency, + unicode" • ", + baseCurrencySymbol, + ' ', + '', + quoteCurrency, + unicode" • ", + quoteCurrencySymbol, + ' ', + quoteCurrency, + unicode" • ", + quoteCurrencySymbol, + ' ' + ) + ); + } + + /// @notice Generate the SVG for the card mantle displaying the quote and base currency symbols and fee tier + /// @param quoteCurrencySymbol The quote currency symbol + /// @param baseCurrencySymbol The base currency symbol + /// @param feeTier The fee tier + /// @return svg The SVG for the card mantle + function generateSVGCardMantle( + string memory quoteCurrencySymbol, + string memory baseCurrencySymbol, + string memory feeTier + ) private pure returns (string memory svg) { + svg = string( + abi.encodePacked( + ' ', + quoteCurrencySymbol, + "/", + baseCurrencySymbol, + '', + feeTier, + "", + '' + ) + ); + } + + /// @notice Generate the SVG for the curve that represents the position. Fade up (top is faded) if current price is above your position range, fade down (bottom is faded) if current price is below your position range + /// Circles are generated at the ends of the curve if the position is in range, or at one end of the curve it is on if not in range + /// @param tickLower The lower tick + /// @param tickUpper The upper tick + /// @param tickSpacing The tick spacing + /// @param overRange Whether the current tick is in range, over range, or under range + /// @return svg The SVG for the curve + function generageSvgCurve(int24 tickLower, int24 tickUpper, int24 tickSpacing, int8 overRange) + private + pure + returns (string memory svg) + { + string memory fade = overRange == 1 ? "#fade-up" : overRange == -1 ? "#fade-down" : "#none"; + string memory curve = getCurve(tickLower, tickUpper, tickSpacing); + svg = string( + abi.encodePacked( + '' + '' '', + '', + '', + '', + generateSVGCurveCircle(overRange) + ) + ); + } + + /// @notice Get the curve based on the tick range + /// The smaller the tick range, the smaller/more linear the curve + /// @param tickLower The lower tick + /// @param tickUpper The upper tick + /// @param tickSpacing The tick spacing + /// @return curve The curve path + function getCurve(int24 tickLower, int24 tickUpper, int24 tickSpacing) + internal + pure + returns (string memory curve) + { + int24 tickRange = (tickUpper - tickLower) / tickSpacing; + if (tickRange <= 4) { + curve = curve1; + } else if (tickRange <= 8) { + curve = curve2; + } else if (tickRange <= 16) { + curve = curve3; + } else if (tickRange <= 32) { + curve = curve4; + } else if (tickRange <= 64) { + curve = curve5; + } else if (tickRange <= 128) { + curve = curve6; + } else if (tickRange <= 256) { + curve = curve7; + } else { + curve = curve8; + } + } + + /// @notice Generate the SVG for the circles on the curve + /// @param overRange 0 if the current tick is in range, 1 if the current tick is over range, -1 if the current tick is under range + /// @return svg The SVG for the circles + function generateSVGCurveCircle(int8 overRange) internal pure returns (string memory svg) { + string memory curvex1 = "73"; + string memory curvey1 = "190"; + string memory curvex2 = "217"; + string memory curvey2 = "334"; + /// If the position is over or under range, generate one circle at the end of the curve on the side of the range it is on with a larger circle around it + if (overRange == 1 || overRange == -1) { + svg = string( + abi.encodePacked( + '' + ) + ); + } else { + /// If the position is in range, generate two circles at the ends of the curve + svg = string( + abi.encodePacked( + '', + '' + ) + ); + } + } + + /// @notice Generate the SVG for the position data (token ID, hooks address, min tick, max tick) and the location curve (where your position falls on the curve) + /// @param tokenId The token ID + /// @param hook The hooks address + /// @param tickLower The lower tick + /// @param tickUpper The upper tick + /// @return svg The SVG for the position data and location curve + function generateSVGPositionDataAndLocationCurve( + string memory tokenId, + address hook, + int24 tickLower, + int24 tickUpper + ) private pure returns (string memory svg) { + string memory hookStr = (uint256(uint160(hook))).toHexString(20); + string memory tickLowerStr = tickToString(tickLower); + string memory tickUpperStr = tickToString(tickUpper); + uint256 str1length = bytes(tokenId).length + 4; + string memory hookSlice = hook == address(0) + ? "No Hook" + : string(abi.encodePacked(substring(hookStr, 0, 5), "...", substring(hookStr, 39, 42))); + uint256 str2length = bytes(hookSlice).length + 5; + uint256 str3length = bytes(tickLowerStr).length + 10; + uint256 str4length = bytes(tickUpperStr).length + 10; + (string memory xCoord, string memory yCoord) = rangeLocation(tickLower, tickUpper); + svg = string( + abi.encodePacked( + ' ', + '', + 'ID: ', + tokenId, + "", + ' ', + '', + 'Hook: ', + hookSlice, + "", + ' ', + '', + 'Min Tick: ', + tickLowerStr, + "", + ' ', + '', + 'Max Tick: ', + tickUpperStr, + "" '', + '', + '', + '' + ) + ); + } + + function substring(string memory str, uint256 startIndex, uint256 endIndex) internal pure returns (string memory) { + bytes memory strBytes = bytes(str); + bytes memory result = new bytes(endIndex - startIndex); + for (uint256 i = startIndex; i < endIndex; i++) { + result[i - startIndex] = strBytes[i]; + } + return string(result); + } + + function tickToString(int24 tick) private pure returns (string memory) { + string memory sign = ""; + if (tick < 0) { + tick = tick * -1; + sign = "-"; + } + return string(abi.encodePacked(sign, uint256(uint24(tick)).toString())); + } + + /// @notice Get the location of where your position falls on the curve + /// @param tickLower The lower tick + /// @param tickUpper The upper tick + /// @return The x and y coordinates of the location of the liquidity + function rangeLocation(int24 tickLower, int24 tickUpper) internal pure returns (string memory, string memory) { + int24 midPoint = (tickLower + tickUpper) / 2; + if (midPoint < -125_000) { + return ("8", "7"); + } else if (midPoint < -75_000) { + return ("8", "10.5"); + } else if (midPoint < -25_000) { + return ("8", "14.25"); + } else if (midPoint < -5_000) { + return ("10", "18"); + } else if (midPoint < 0) { + return ("11", "21"); + } else if (midPoint < 5_000) { + return ("13", "23"); + } else if (midPoint < 25_000) { + return ("15", "25"); + } else if (midPoint < 75_000) { + return ("18", "26"); + } else if (midPoint < 125_000) { + return ("21", "27"); + } else { + return ("24", "27"); + } + } + + /// @notice Generates the SVG for a rare sparkle if the NFT is rare. Else, returns an empty string + /// @param tokenId The token ID + /// @param hooks The hooks address + /// @return svg The SVG for the rare sparkle + function generateSVGRareSparkle(uint256 tokenId, address hooks) private pure returns (string memory svg) { + if (isRare(tokenId, hooks)) { + svg = string( + abi.encodePacked( + '', + '', + '' + ) + ); + } else { + svg = ""; + } + } + + /// @notice Determines if an NFT is rare based on the token ID and hooks address + /// @param tokenId The token ID + /// @param hooks The hooks address + /// @return Whether the NFT is rare or not + function isRare(uint256 tokenId, address hooks) internal pure returns (bool) { + bytes32 h = keccak256(abi.encodePacked(tokenId, hooks)); + return uint256(h) < type(uint256).max / (1 + BitMath.mostSignificantBit(tokenId) * 2); + } +} diff --git a/CVLByExample/UniswapHooks/lib/uniswap-hooks/lib/v4-periphery/src/libraries/SafeCurrencyMetadata.sol b/CVLByExample/UniswapHooks/lib/uniswap-hooks/lib/v4-periphery/src/libraries/SafeCurrencyMetadata.sol new file mode 100644 index 00000000..e1a4c059 --- /dev/null +++ b/CVLByExample/UniswapHooks/lib/uniswap-hooks/lib/v4-periphery/src/libraries/SafeCurrencyMetadata.sol @@ -0,0 +1,108 @@ +// SPDX-License-Identifier: MIT +pragma solidity ^0.8.0; + +import {IERC20Metadata} from "openzeppelin-contracts/contracts/token/ERC20/extensions/IERC20Metadata.sol"; +import {AddressStringUtil} from "./AddressStringUtil.sol"; + +/// @title SafeCurrencyMetadata +/// @notice can produce symbols and decimals from inconsistent or absent ERC20 implementations +/// @dev Reference: https://github.com/Uniswap/solidity-lib/blob/master/contracts/libraries/SafeERC20Namer.sol +library SafeCurrencyMetadata { + uint8 constant MAX_SYMBOL_LENGTH = 12; + + /// @notice attempts to extract the currency symbol. if it does not implement symbol, returns a symbol derived from the address + /// @param currency The currency address + /// @param nativeLabel The native label + /// @return the currency symbol + function currencySymbol(address currency, string memory nativeLabel) internal view returns (string memory) { + if (currency == address(0)) { + return nativeLabel; + } + string memory symbol = callAndParseStringReturn(currency, IERC20Metadata.symbol.selector); + if (bytes(symbol).length == 0) { + // fallback to 6 uppercase hex of address + return addressToSymbol(currency); + } + if (bytes(symbol).length > MAX_SYMBOL_LENGTH) { + return truncateSymbol(symbol); + } + return symbol; + } + + /// @notice attempts to extract the token decimals, returns 0 if not implemented or not a uint8 + /// @param currency The currency address + /// @return the currency decimals + function currencyDecimals(address currency) internal view returns (uint8) { + if (currency == address(0)) { + return 18; + } + (bool success, bytes memory data) = currency.staticcall(abi.encodeCall(IERC20Metadata.decimals, ())); + if (!success) { + return 0; + } + if (data.length == 32) { + uint256 decimals = abi.decode(data, (uint256)); + if (decimals <= type(uint8).max) { + return uint8(decimals); + } + } + return 0; + } + + function bytes32ToString(bytes32 x) private pure returns (string memory) { + bytes memory bytesString = new bytes(32); + uint256 charCount = 0; + for (uint256 j = 0; j < 32; j++) { + bytes1 char = x[j]; + if (char != 0) { + bytesString[charCount] = char; + charCount++; + } + } + bytes memory bytesStringTrimmed = new bytes(charCount); + for (uint256 j = 0; j < charCount; j++) { + bytesStringTrimmed[j] = bytesString[j]; + } + return string(bytesStringTrimmed); + } + + /// @notice produces a symbol from the address - the first 6 hex of the address string in upper case + /// @param currencyAddress the address of the currency + /// @return the symbol + function addressToSymbol(address currencyAddress) private pure returns (string memory) { + return AddressStringUtil.toAsciiString(currencyAddress, 6); + } + + /// @notice calls an external view contract method that returns a symbol, and parses the output into a string + /// @param currencyAddress the address of the currency + /// @param selector the selector of the symbol method + /// @return the symbol + function callAndParseStringReturn(address currencyAddress, bytes4 selector) private view returns (string memory) { + (bool success, bytes memory data) = currencyAddress.staticcall(abi.encodeWithSelector(selector)); + // if not implemented, return empty string + if (!success) { + return ""; + } + // bytes32 data always has length 32 + if (data.length == 32) { + bytes32 decoded = abi.decode(data, (bytes32)); + return bytes32ToString(decoded); + } else if (data.length > 64) { + return abi.decode(data, (string)); + } + return ""; + } + + /// @notice truncates the symbol to the MAX_SYMBOL_LENGTH + /// @dev assumes the string is already longer than MAX_SYMBOL_LENGTH (or the same) + /// @param str the symbol + /// @return the truncated symbol + function truncateSymbol(string memory str) internal pure returns (string memory) { + bytes memory strBytes = bytes(str); + bytes memory truncatedBytes = new bytes(MAX_SYMBOL_LENGTH); + for (uint256 i = 0; i < MAX_SYMBOL_LENGTH; i++) { + truncatedBytes[i] = strBytes[i]; + } + return string(truncatedBytes); + } +} diff --git a/CVLByExample/UniswapHooks/lib/uniswap-hooks/lib/v4-periphery/src/libraries/SlippageCheck.sol b/CVLByExample/UniswapHooks/lib/uniswap-hooks/lib/v4-periphery/src/libraries/SlippageCheck.sol new file mode 100644 index 00000000..e4c7e960 --- /dev/null +++ b/CVLByExample/UniswapHooks/lib/uniswap-hooks/lib/v4-periphery/src/libraries/SlippageCheck.sol @@ -0,0 +1,53 @@ +// SPDX-License-Identifier: MIT +pragma solidity ^0.8.0; + +import {BalanceDelta} from "@uniswap/v4-core/src/types/BalanceDelta.sol"; +import {SafeCast} from "@uniswap/v4-core/src/libraries/SafeCast.sol"; + +/// @title Slippage Check Library +/// @notice a library for checking if a delta exceeds a maximum ceiling or fails to meet a minimum floor +library SlippageCheck { + using SafeCast for int128; + + error MaximumAmountExceeded(uint128 maximumAmount, uint128 amountRequested); + error MinimumAmountInsufficient(uint128 minimumAmount, uint128 amountReceived); + + /// @notice Revert if one or both deltas does not meet a minimum output + /// @param delta The principal amount of tokens to be removed, does not include any fees accrued + /// @param amount0Min The minimum amount of token0 to receive + /// @param amount1Min The minimum amount of token1 to receive + /// @dev This should be called when removing liquidity (burn or decrease) + function validateMinOut(BalanceDelta delta, uint128 amount0Min, uint128 amount1Min) internal pure { + // Called on burn or decrease, where we expect the returned delta to be positive. + // However, on pools where hooks can return deltas on modify liquidity, it is possible for a returned delta to be negative. + // Because we use SafeCast, this will revert in those cases when the delta is negative. + // This means this contract will NOT support pools where the hook returns a negative delta on burn/decrease. + if (delta.amount0().toUint128() < amount0Min) { + revert MinimumAmountInsufficient(amount0Min, delta.amount0().toUint128()); + } + if (delta.amount1().toUint128() < amount1Min) { + revert MinimumAmountInsufficient(amount1Min, delta.amount1().toUint128()); + } + } + + /// @notice Revert if one or both deltas exceeds a maximum input + /// @param delta The principal amount of tokens to be added, does not include any fees accrued (which is possible on increase) + /// @param amount0Max The maximum amount of token0 to spend + /// @param amount1Max The maximum amount of token1 to spend + /// @dev This should be called when adding liquidity (mint or increase) + function validateMaxIn(BalanceDelta delta, uint128 amount0Max, uint128 amount1Max) internal pure { + // Called on mint or increase, where we expect the returned delta to be negative. + // However, on pools where hooks can return deltas on modify liquidity, it is possible for a returned delta to be positive (even after discounting fees accrued). + // Thus, we only cast the delta if it is guaranteed to be negative. + // And we do NOT revert in the positive delta case. Since a positive delta means the hook is crediting tokens to the user for minting/increasing liquidity, we do not check slippage. + // This means this contract will NOT support _positive_ slippage checks (minAmountOut checks) on pools where the hook returns a positive delta on mint/increase. + int256 amount0 = delta.amount0(); + int256 amount1 = delta.amount1(); + if (amount0 < 0 && amount0Max < uint128(uint256(-amount0))) { + revert MaximumAmountExceeded(amount0Max, uint128(uint256(-amount0))); + } + if (amount1 < 0 && amount1Max < uint128(uint256(-amount1))) { + revert MaximumAmountExceeded(amount1Max, uint128(uint256(-amount1))); + } + } +} diff --git a/CVLByExample/UniswapHooks/lib/uniswap-hooks/lib/v4-periphery/src/libraries/VanityAddressLib.sol b/CVLByExample/UniswapHooks/lib/uniswap-hooks/lib/v4-periphery/src/libraries/VanityAddressLib.sol new file mode 100644 index 00000000..0139aa54 --- /dev/null +++ b/CVLByExample/UniswapHooks/lib/uniswap-hooks/lib/v4-periphery/src/libraries/VanityAddressLib.sol @@ -0,0 +1,97 @@ +// SPDX-License-Identifier: MIT +pragma solidity ^0.8.0; + +/// @title VanityAddressLib +/// @notice A library to score addresses based on their vanity +library VanityAddressLib { + /// @notice Compares two addresses and returns true if the first address has a better vanity score + /// @param first The first address to compare + /// @param second The second address to compare + /// @return better True if the first address has a better vanity score + function betterThan(address first, address second) internal pure returns (bool better) { + return score(first) > score(second); + } + + /// @notice Scores an address based on its vanity + /// @dev Scoring rules: + /// Requirement: The first nonzero nibble must be 4 + /// 10 points for every leading 0 nibble + /// 40 points if the first 4 is followed by 3 more 4s + /// 20 points if the first nibble after the 4 4s is NOT a 4 + /// 20 points if the last 4 nibbles are 4s + /// 1 point for every 4 + /// @param addr The address to score + /// @return calculatedScore The vanity score of the address + function score(address addr) internal pure returns (uint256 calculatedScore) { + // convert the address to bytes for easier parsing + bytes20 addrBytes = bytes20(addr); + + unchecked { + // 10 points per leading zero nibble + uint256 leadingZeroCount = getLeadingNibbleCount(addrBytes, 0, 0); + calculatedScore += (leadingZeroCount * 10); + + // special handling for 4s immediately after leading 0s + uint256 leadingFourCount = getLeadingNibbleCount(addrBytes, leadingZeroCount, 4); + // If the first nonzero nibble is not 4, return 0 + if (leadingFourCount == 0) { + return 0; + } else if (leadingFourCount == 4) { + // 60 points if exactly 4 4s + calculatedScore += 60; + } else if (leadingFourCount > 4) { + // 40 points if more than 4 4s + calculatedScore += 40; + } + + // handling for remaining nibbles + for (uint256 i = 0; i < addrBytes.length * 2; i++) { + uint8 currentNibble = getNibble(addrBytes, i); + + // 1 extra point for any 4 nibbles + if (currentNibble == 4) { + calculatedScore += 1; + } + } + + // If the last 4 nibbles are 4s, add 20 points + if (addrBytes[18] == 0x44 && addrBytes[19] == 0x44) { + calculatedScore += 20; + } + } + } + + /// @notice Returns the number of leading nibbles in an address that match a given value + /// @param addrBytes The address to count the leading zero nibbles in + function getLeadingNibbleCount(bytes20 addrBytes, uint256 startIndex, uint8 comparison) + internal + pure + returns (uint256 count) + { + if (startIndex >= addrBytes.length * 2) { + return count; + } + + for (uint256 i = startIndex; i < addrBytes.length * 2; i++) { + uint8 currentNibble = getNibble(addrBytes, i); + if (currentNibble != comparison) { + return count; + } + count += 1; + } + } + + /// @notice Returns the nibble at a given index in an address + /// @param input The address to get the nibble from + /// @param nibbleIndex The index of the nibble to get + function getNibble(bytes20 input, uint256 nibbleIndex) internal pure returns (uint8 currentNibble) { + uint8 currByte = uint8(input[nibbleIndex / 2]); + if (nibbleIndex % 2 == 0) { + // Get the higher nibble of the byte + currentNibble = currByte >> 4; + } else { + // Get the lower nibble of the byte + currentNibble = currByte & 0x0F; + } + } +} diff --git a/CVLByExample/UniswapHooks/lib/uniswap-hooks/lib/v4-periphery/src/utils/BaseHook.sol b/CVLByExample/UniswapHooks/lib/uniswap-hooks/lib/v4-periphery/src/utils/BaseHook.sol new file mode 100644 index 00000000..8e2b6030 --- /dev/null +++ b/CVLByExample/UniswapHooks/lib/uniswap-hooks/lib/v4-periphery/src/utils/BaseHook.sol @@ -0,0 +1,216 @@ +// SPDX-License-Identifier: MIT +pragma solidity ^0.8.0; + +import {Hooks} from "@uniswap/v4-core/src/libraries/Hooks.sol"; +import {IPoolManager} from "@uniswap/v4-core/src/interfaces/IPoolManager.sol"; +import {IHooks} from "@uniswap/v4-core/src/interfaces/IHooks.sol"; +import {BalanceDelta} from "@uniswap/v4-core/src/types/BalanceDelta.sol"; +import {PoolKey} from "@uniswap/v4-core/src/types/PoolKey.sol"; +import {BeforeSwapDelta} from "@uniswap/v4-core/src/types/BeforeSwapDelta.sol"; +import {ImmutableState} from "../base/ImmutableState.sol"; +import {ModifyLiquidityParams, SwapParams} from "@uniswap/v4-core/src/types/PoolOperation.sol"; + +/// @title Base Hook +/// @notice abstract contract for hook implementations +abstract contract BaseHook is IHooks, ImmutableState { + error HookNotImplemented(); + + constructor(IPoolManager _manager) ImmutableState(_manager) { + validateHookAddress(this); + } + + /// @notice Returns a struct of permissions to signal which hook functions are to be implemented + /// @dev Used at deployment to validate the address correctly represents the expected permissions + /// @return Permissions struct + function getHookPermissions() public pure virtual returns (Hooks.Permissions memory); + + /// @notice Validates the deployed hook address agrees with the expected permissions of the hook + /// @dev this function is virtual so that we can override it during testing, + /// which allows us to deploy an implementation to any address + /// and then etch the bytecode into the correct address + function validateHookAddress(BaseHook _this) internal pure virtual { + Hooks.validateHookPermissions(_this, getHookPermissions()); + } + + /// @inheritdoc IHooks + function beforeInitialize(address sender, PoolKey calldata key, uint160 sqrtPriceX96) + external + onlyPoolManager + returns (bytes4) + { + return _beforeInitialize(sender, key, sqrtPriceX96); + } + + function _beforeInitialize(address, PoolKey calldata, uint160) internal virtual returns (bytes4) { + revert HookNotImplemented(); + } + + /// @inheritdoc IHooks + function afterInitialize(address sender, PoolKey calldata key, uint160 sqrtPriceX96, int24 tick) + external + onlyPoolManager + returns (bytes4) + { + return _afterInitialize(sender, key, sqrtPriceX96, tick); + } + + function _afterInitialize(address, PoolKey calldata, uint160, int24) internal virtual returns (bytes4) { + revert HookNotImplemented(); + } + + /// @inheritdoc IHooks + function beforeAddLiquidity( + address sender, + PoolKey calldata key, + ModifyLiquidityParams calldata params, + bytes calldata hookData + ) external onlyPoolManager returns (bytes4) { + return _beforeAddLiquidity(sender, key, params, hookData); + } + + function _beforeAddLiquidity(address, PoolKey calldata, ModifyLiquidityParams calldata, bytes calldata) + internal + virtual + returns (bytes4) + { + revert HookNotImplemented(); + } + + /// @inheritdoc IHooks + function beforeRemoveLiquidity( + address sender, + PoolKey calldata key, + ModifyLiquidityParams calldata params, + bytes calldata hookData + ) external onlyPoolManager returns (bytes4) { + return _beforeRemoveLiquidity(sender, key, params, hookData); + } + + function _beforeRemoveLiquidity(address, PoolKey calldata, ModifyLiquidityParams calldata, bytes calldata) + internal + virtual + returns (bytes4) + { + revert HookNotImplemented(); + } + + /// @inheritdoc IHooks + function afterAddLiquidity( + address sender, + PoolKey calldata key, + ModifyLiquidityParams calldata params, + BalanceDelta delta, + BalanceDelta feesAccrued, + bytes calldata hookData + ) external onlyPoolManager returns (bytes4, BalanceDelta) { + return _afterAddLiquidity(sender, key, params, delta, feesAccrued, hookData); + } + + function _afterAddLiquidity( + address, + PoolKey calldata, + ModifyLiquidityParams calldata, + BalanceDelta, + BalanceDelta, + bytes calldata + ) internal virtual returns (bytes4, BalanceDelta) { + revert HookNotImplemented(); + } + + /// @inheritdoc IHooks + function afterRemoveLiquidity( + address sender, + PoolKey calldata key, + ModifyLiquidityParams calldata params, + BalanceDelta delta, + BalanceDelta feesAccrued, + bytes calldata hookData + ) external onlyPoolManager returns (bytes4, BalanceDelta) { + return _afterRemoveLiquidity(sender, key, params, delta, feesAccrued, hookData); + } + + function _afterRemoveLiquidity( + address, + PoolKey calldata, + ModifyLiquidityParams calldata, + BalanceDelta, + BalanceDelta, + bytes calldata + ) internal virtual returns (bytes4, BalanceDelta) { + revert HookNotImplemented(); + } + + /// @inheritdoc IHooks + function beforeSwap(address sender, PoolKey calldata key, SwapParams calldata params, bytes calldata hookData) + external + onlyPoolManager + returns (bytes4, BeforeSwapDelta, uint24) + { + return _beforeSwap(sender, key, params, hookData); + } + + function _beforeSwap(address, PoolKey calldata, SwapParams calldata, bytes calldata) + internal + virtual + returns (bytes4, BeforeSwapDelta, uint24) + { + revert HookNotImplemented(); + } + + /// @inheritdoc IHooks + function afterSwap( + address sender, + PoolKey calldata key, + SwapParams calldata params, + BalanceDelta delta, + bytes calldata hookData + ) external onlyPoolManager returns (bytes4, int128) { + return _afterSwap(sender, key, params, delta, hookData); + } + + function _afterSwap(address, PoolKey calldata, SwapParams calldata, BalanceDelta, bytes calldata) + internal + virtual + returns (bytes4, int128) + { + revert HookNotImplemented(); + } + + /// @inheritdoc IHooks + function beforeDonate( + address sender, + PoolKey calldata key, + uint256 amount0, + uint256 amount1, + bytes calldata hookData + ) external onlyPoolManager returns (bytes4) { + return _beforeDonate(sender, key, amount0, amount1, hookData); + } + + function _beforeDonate(address, PoolKey calldata, uint256, uint256, bytes calldata) + internal + virtual + returns (bytes4) + { + revert HookNotImplemented(); + } + + /// @inheritdoc IHooks + function afterDonate( + address sender, + PoolKey calldata key, + uint256 amount0, + uint256 amount1, + bytes calldata hookData + ) external onlyPoolManager returns (bytes4) { + return _afterDonate(sender, key, amount0, amount1, hookData); + } + + function _afterDonate(address, PoolKey calldata, uint256, uint256, bytes calldata) + internal + virtual + returns (bytes4) + { + revert HookNotImplemented(); + } +} diff --git a/CVLByExample/UniswapHooks/lib/uniswap-hooks/lib/v4-periphery/src/utils/HookMiner.sol b/CVLByExample/UniswapHooks/lib/uniswap-hooks/lib/v4-periphery/src/utils/HookMiner.sol new file mode 100644 index 00000000..3c1f487e --- /dev/null +++ b/CVLByExample/UniswapHooks/lib/uniswap-hooks/lib/v4-periphery/src/utils/HookMiner.sol @@ -0,0 +1,57 @@ +// SPDX-License-Identifier: MIT +pragma solidity ^0.8.21; + +import {Hooks} from "@uniswap/v4-core/src/libraries/Hooks.sol"; + +/// @title HookMiner +/// @notice a minimal library for mining hook addresses +library HookMiner { + // mask to slice out the bottom 14 bit of the address + uint160 constant FLAG_MASK = Hooks.ALL_HOOK_MASK; // 0000 ... 0000 0011 1111 1111 1111 + + // Maximum number of iterations to find a salt, avoid infinite loops or MemoryOOG + // (arbitrarily set) + uint256 constant MAX_LOOP = 160_444; + + /// @notice Find a salt that produces a hook address with the desired `flags` + /// @param deployer The address that will deploy the hook. In `forge test`, this will be the test contract `address(this)` or the pranking address + /// In `forge script`, this should be `0x4e59b44847b379578588920cA78FbF26c0B4956C` (CREATE2 Deployer Proxy) + /// @param flags The desired flags for the hook address. Example `uint160(Hooks.BEFORE_SWAP_FLAG | Hooks.AFTER_SWAP_FLAG | ...)` + /// @param creationCode The creation code of a hook contract. Example: `type(Counter).creationCode` + /// @param constructorArgs The encoded constructor arguments of a hook contract. Example: `abi.encode(address(manager))` + /// @return (hookAddress, salt) The hook deploys to `hookAddress` when using `salt` with the syntax: `new Hook{salt: salt}()` + function find(address deployer, uint160 flags, bytes memory creationCode, bytes memory constructorArgs) + internal + view + returns (address, bytes32) + { + flags = flags & FLAG_MASK; // mask for only the bottom 14 bits + bytes memory creationCodeWithArgs = abi.encodePacked(creationCode, constructorArgs); + + address hookAddress; + for (uint256 salt; salt < MAX_LOOP; salt++) { + hookAddress = computeAddress(deployer, salt, creationCodeWithArgs); + + // if the hook's bottom 14 bits match the desired flags AND the address does not have bytecode, we found a match + if (uint160(hookAddress) & FLAG_MASK == flags && hookAddress.code.length == 0) { + return (hookAddress, bytes32(salt)); + } + } + revert("HookMiner: could not find salt"); + } + + /// @notice Precompute a contract address deployed via CREATE2 + /// @param deployer The address that will deploy the hook. In `forge test`, this will be the test contract `address(this)` or the pranking address + /// In `forge script`, this should be `0x4e59b44847b379578588920cA78FbF26c0B4956C` (CREATE2 Deployer Proxy) + /// @param salt The salt used to deploy the hook + /// @param creationCodeWithArgs The creation code of a hook contract, with encoded constructor arguments appended. Example: `abi.encodePacked(type(Counter).creationCode, abi.encode(constructorArg1, constructorArg2))` + function computeAddress(address deployer, uint256 salt, bytes memory creationCodeWithArgs) + internal + pure + returns (address hookAddress) + { + return address( + uint160(uint256(keccak256(abi.encodePacked(bytes1(0xFF), deployer, salt, keccak256(creationCodeWithArgs))))) + ); + } +} diff --git a/CVLByExample/UniswapHooks/lib/uniswap-hooks/src/base/BaseAsyncSwap.sol b/CVLByExample/UniswapHooks/lib/uniswap-hooks/src/base/BaseAsyncSwap.sol new file mode 100644 index 00000000..895a2c94 --- /dev/null +++ b/CVLByExample/UniswapHooks/lib/uniswap-hooks/src/base/BaseAsyncSwap.sol @@ -0,0 +1,132 @@ +// SPDX-License-Identifier: MIT +// OpenZeppelin Uniswap Hooks (last updated v0.1.0) (src/base/BaseAsyncSwap.sol) + +pragma solidity ^0.8.24; + +import {BaseHook} from "src/base/BaseHook.sol"; +import {Hooks} from "v4-core/src/libraries/Hooks.sol"; +import {PoolKey} from "v4-core/src/types/PoolKey.sol"; +import {IPoolManager} from "v4-core/src/interfaces/IPoolManager.sol"; +import {BeforeSwapDelta, BeforeSwapDeltaLibrary, toBeforeSwapDelta} from "v4-core/src/types/BeforeSwapDelta.sol"; +import {Currency} from "v4-core/src/types/Currency.sol"; +import {SafeCast} from "v4-core/src/libraries/SafeCast.sol"; +import {CurrencySettler} from "src/utils/CurrencySettler.sol"; +import {PoolId} from "v4-core/src/types/PoolId.sol"; +import {IHookEvents} from "src/interfaces/IHookEvents.sol"; +import {SwapParams} from "v4-core/src/types/PoolOperation.sol"; + +/** + * @dev Base implementation for async swaps, which skip the v3-like swap implementation of the `PoolManager` + * by taking the full swap input amount and returning a delta that nets out the specified amount to 0. + * + * This base hook allows developers to implement arbitrary logic to handle swaps, including use-cases like + * asynchronous swaps and custom swap-ordering. However, given this flexibility, developers should ensure + * that any logic implemented interacts safely with the `PoolManager` and works correctly. + * + * In order to handle async swaps, the hook mints ERC-6909 claim tokens for the specified currency and amount. + * Inheriting contracts are free to handle these claim tokens as necessary, which can be redeemed for the + * underlying currency by using the `settle` function from the `CurrencySettler` library. + * + * IMPORTANT: If the hook is used for multiple pools, the ERC-6909 tokens must be separated and managed + * independently for each pool in order to prevent draining of ERC-6909 tokens from one pool to another. + * + * NOTE: The hook only supports async exact-input swaps. Exact-output swaps will be processed normally + * by the `PoolManager`. + * + * WARNING: This is experimental software and is provided on an "as is" and "as available" basis. We do + * not give any warranties and will not be liable for any losses incurred through any use of this code + * base. + * + * _Available since v0.1.0_ + */ +abstract contract BaseAsyncSwap is BaseHook, IHookEvents { + using SafeCast for uint256; + using CurrencySettler for Currency; + + /** + * @dev Set the `PoolManager` address. + */ + constructor(IPoolManager _poolManager) BaseHook(_poolManager) {} + + /** + * @dev Skip the v3-like swap implementation of the `PoolManager` by returning a delta that nets out the + * specified amount to 0 to enable asynchronous swaps. + */ + function _beforeSwap(address sender, PoolKey calldata key, SwapParams calldata params, bytes calldata) + internal + virtual + override + returns (bytes4, BeforeSwapDelta, uint24) + { + // Async swaps are only possible on exact-input swaps, so exact-output swaps are executed by the `PoolManager` as normal + if (params.amountSpecified < 0) { + // Determine which currency is specified + Currency specified = params.zeroForOne ? key.currency0 : key.currency1; + + // Get the positive specified amount + uint256 specifiedAmount = uint256(-params.amountSpecified); + + // Mint ERC-6909 claim token for the specified currency and amount + specified.take(poolManager, address(this), specifiedAmount, true); + + // Calculate the fee amount for the swap, paid to LPs + uint256 feeAmount = _calculateSwapFee(key, specifiedAmount); + + // Emit the swap event with the specified amount signifying the amount taken by the hook + if (specified == key.currency0) { + emit HookSwap( + PoolId.unwrap(key.toId()), sender, specifiedAmount.toInt128(), 0, feeAmount.toUint128(), 0 + ); + } else { + emit HookSwap( + PoolId.unwrap(key.toId()), sender, 0, specifiedAmount.toInt128(), 0, feeAmount.toUint128() + ); + } + + // Return delta that nets out specified amount to 0. + return (this.beforeSwap.selector, toBeforeSwapDelta(specifiedAmount.toInt128(), 0), 0); + } else { + return (this.beforeSwap.selector, BeforeSwapDeltaLibrary.ZERO_DELTA, 0); + } + } + + /** + * @dev Calculate the fee amount for the swap. + * + * @param key The pool key. + * @param specifiedAmount The specified amount of the swap. + * + * @return feeAmount The fee amount for the swap. + */ + function _calculateSwapFee(PoolKey calldata key, uint256 specifiedAmount) + internal + virtual + returns (uint256 feeAmount) + { + return 0; + } + + /** + * @dev Set the hook permissions, specifically `beforeSwap` and `beforeSwapReturnDelta`. + * + * @return permissions The hook permissions. + */ + function getHookPermissions() public pure virtual override returns (Hooks.Permissions memory permissions) { + return Hooks.Permissions({ + beforeInitialize: false, + afterInitialize: false, + beforeAddLiquidity: false, + beforeRemoveLiquidity: false, + afterAddLiquidity: false, + afterRemoveLiquidity: false, + beforeSwap: true, + afterSwap: false, + beforeDonate: false, + afterDonate: false, + beforeSwapReturnDelta: true, + afterSwapReturnDelta: false, + afterAddLiquidityReturnDelta: false, + afterRemoveLiquidityReturnDelta: false + }); + } +} diff --git a/CVLByExample/UniswapHooks/lib/uniswap-hooks/src/base/BaseCustomAccounting.sol b/CVLByExample/UniswapHooks/lib/uniswap-hooks/src/base/BaseCustomAccounting.sol new file mode 100644 index 00000000..047c324b --- /dev/null +++ b/CVLByExample/UniswapHooks/lib/uniswap-hooks/src/base/BaseCustomAccounting.sol @@ -0,0 +1,433 @@ +// SPDX-License-Identifier: MIT +// OpenZeppelin Uniswap Hooks (last updated v0.1.0) (src/base/BaseCustomAccounting.sol) + +pragma solidity ^0.8.24; + +import {BaseHook} from "src/base/BaseHook.sol"; +import {CurrencySettler} from "src/utils/CurrencySettler.sol"; +import {Hooks} from "v4-core/src/libraries/Hooks.sol"; +import {PoolKey} from "v4-core/src/types/PoolKey.sol"; +import {IPoolManager} from "v4-core/src/interfaces/IPoolManager.sol"; +import {IUnlockCallback} from "v4-core/src/interfaces/callback/IUnlockCallback.sol"; +import {Currency, CurrencyLibrary} from "v4-core/src/types/Currency.sol"; +import {BalanceDelta} from "v4-core/src/types/BalanceDelta.sol"; +import {StateLibrary} from "v4-core/src/libraries/StateLibrary.sol"; +import {PoolId} from "v4-core/src/types/PoolId.sol"; +import {IHookEvents} from "src/interfaces/IHookEvents.sol"; +import {SwapParams, ModifyLiquidityParams} from "v4-core/src/types/PoolOperation.sol"; + +/** + * @dev Base implementation for custom accounting and hook-owned liquidity. + * + * To enable hook-owned liquidity, tokens must be deposited via the hook to allow control and flexibility + * over the liquidity. The implementation inheriting this hook must implement the respective functions + * to calculate the liquidity modification parameters and the amount of liquidity shares to mint or burn. + * + * Additionally, the implementer must consider that the hook is the sole owner of the liquidity and + * manage fees over liquidity shares accordingly. + * + * NOTE: This base hook is designed to work with a single pool key. If you want to use the same custom + * accounting hook for multiple pools, you must have multiple storage instances of this contract and + * initialize them via the `PoolManager` with their respective pool keys. + * + * WARNING: This is experimental software and is provided on an "as is" and "as available" basis. We do + * not give any warranties and will not be liable for any losses incurred through any use of this code + * base. + * + * _Available since v0.1.0_ + */ +abstract contract BaseCustomAccounting is BaseHook, IHookEvents, IUnlockCallback { + using CurrencySettler for Currency; + using CurrencyLibrary for Currency; + using StateLibrary for IPoolManager; + + /** + * @dev A liquidity modification order was attempted to be executed after the deadline. + */ + error ExpiredPastDeadline(); + + /** + * @dev Pool was not initialized. + */ + error PoolNotInitialized(); + + /** + * @dev Principal delta of liquidity modification resulted in too much slippage. + */ + error TooMuchSlippage(); + + /** + * @dev Liquidity was attempted to be added or removed via the `PoolManager` instead of the hook. + */ + error LiquidityOnlyViaHook(); + + /** + * @dev Native currency was not sent with the correct amount. + */ + error InvalidNativeValue(); + + /** + * @dev Hook was already initialized. + */ + error AlreadyInitialized(); + + struct AddLiquidityParams { + uint256 amount0Desired; + uint256 amount1Desired; + uint256 amount0Min; + uint256 amount1Min; + uint256 deadline; + int24 tickLower; + int24 tickUpper; + bytes32 userInputSalt; + } + + struct RemoveLiquidityParams { + uint256 liquidity; + uint256 amount0Min; + uint256 amount1Min; + uint256 deadline; + int24 tickLower; + int24 tickUpper; + bytes32 userInputSalt; + } + + struct CallbackData { + address sender; + ModifyLiquidityParams params; + } + + /** + * @notice The hook's pool key. + */ + PoolKey public poolKey; + + /** + * @dev Ensure the deadline of a liquidity modification request is not expired. + * + * @param deadline Deadline of the request, passed in by the caller. + */ + modifier ensure(uint256 deadline) { + if (deadline < block.timestamp) revert ExpiredPastDeadline(); + _; + } + + /** + * @dev Set the pool `PoolManager` address. + */ + constructor(IPoolManager _poolManager) BaseHook(_poolManager) {} + + /** + * @notice Adds liquidity to the hook's pool. + * + * @dev To cover all possible scenarios, `msg.sender` should have already given the hook an allowance + * of at least amount0Desired/amount1Desired on token0/token1. Always adds assets at the ideal ratio, + * according to the price when the transaction is executed. + * + * NOTE: The `amount0Min` and `amount1Min` parameters are relative to the principal delta, which excludes + * fees accrued from the liquidity modification delta. + * + * @param params The parameters for the liquidity addition. + * @return delta The principal delta of the liquidity addition. + */ + function addLiquidity(AddLiquidityParams calldata params) + external + payable + virtual + ensure(params.deadline) + returns (BalanceDelta delta) + { + (uint160 sqrtPriceX96,,,) = poolManager.getSlot0(poolKey.toId()); + + if (sqrtPriceX96 == 0) revert PoolNotInitialized(); + + // Revert if msg.value is non-zero but currency0 is not native + bool isNative = poolKey.currency0.isAddressZero(); + if (!isNative && msg.value > 0) revert InvalidNativeValue(); + + // Get the liquidity modification parameters and the amount of liquidity shares to mint + (bytes memory modifyParams, uint256 shares) = _getAddLiquidity(sqrtPriceX96, params); + + // Apply the liquidity modification + (BalanceDelta callerDelta, BalanceDelta feesAccrued) = _modifyLiquidity(modifyParams); + + // Mint the liquidity shares to sender + _mint(params, callerDelta, feesAccrued, shares); + + // Get the principal delta by subtracting the fee delta from the caller delta (-= is not supported) + delta = callerDelta - feesAccrued; + + // Check for slippage on principal delta + uint128 amount0 = uint128(-delta.amount0()); + if (amount0 < params.amount0Min || uint128(-delta.amount1()) < params.amount1Min) { + revert TooMuchSlippage(); + } + + // If the currency0 is native, refund any remaining msg.value that wasn't used based on the principal delta + if (isNative) { + // Check that delta amount was covered by msg.value given that settle would be valid if hook can pay for difference + // It also allows users to provide more native value than the desired amount + if (msg.value < amount0) revert InvalidNativeValue(); + + // Previous check prevents underflow revert + poolKey.currency0.transfer(msg.sender, msg.value - amount0); + } + } + + /** + * @notice Removes liquidity from the hook's pool. + * + * NOTE: The `amount0Min` and `amount1Min` parameters are relative to the principal delta, which + * excludes fees accrued from the liquidity modification delta. + * + * @param params The parameters for the liquidity removal. + * @return delta The principal delta of the liquidity removal. + */ + function removeLiquidity(RemoveLiquidityParams calldata params) + external + virtual + ensure(params.deadline) + returns (BalanceDelta delta) + { + (uint160 sqrtPriceX96,,,) = poolManager.getSlot0(poolKey.toId()); + + if (sqrtPriceX96 == 0) revert PoolNotInitialized(); + + // Get the liquidity modification parameters and the amount of liquidity shares to burn + (bytes memory modifyParams, uint256 shares) = _getRemoveLiquidity(params); + + // Apply the liquidity modification + (BalanceDelta callerDelta, BalanceDelta feesAccrued) = _modifyLiquidity(modifyParams); + + // Burn the liquidity shares from the sender + _burn(params, callerDelta, feesAccrued, shares); + + // Get the principal delta by subtracting the fee delta from the caller delta (-= is not supported) + delta = callerDelta - feesAccrued; + + // Check for slippage + if (uint128(delta.amount0()) < params.amount0Min || uint128(delta.amount1()) < params.amount1Min) { + revert TooMuchSlippage(); + } + } + + /** + * @dev Calls the `PoolManager` to unlock and call back the hook's `unlockCallback` function. + * + * @param params The encoded parameters for the liquidity modification based on the `ModifyLiquidityParams` struct. + * @return callerDelta The balance delta from the liquidity modification. This is the total of both principal and fee deltas. + * @return feesAccrued The balance delta of the fees generated in the liquidity range. + */ + // slither-disable-next-line dead-code + function _modifyLiquidity(bytes memory params) + internal + virtual + returns (BalanceDelta callerDelta, BalanceDelta feesAccrued) + { + (callerDelta, feesAccrued) = abi.decode( + poolManager.unlock(abi.encode(CallbackData(msg.sender, abi.decode(params, (ModifyLiquidityParams))))), + (BalanceDelta, BalanceDelta) + ); + } + + /** + * @dev Callback from the `PoolManager` when liquidity is modified, either adding or removing. + * + * @param rawData The encoded `CallbackData` struct. + * @return returnData The encoded caller and fees accrued deltas. + */ + function unlockCallback(bytes calldata rawData) + external + virtual + override + onlyPoolManager + returns (bytes memory returnData) + { + CallbackData memory data = abi.decode(rawData, (CallbackData)); + PoolKey memory key = poolKey; + + // Set the salt value of the liquidity position, which is the keccak256 hash of the sender and salt from the callback data + // This ensures that each liquidity position is unique and cannot be accessed by other users + data.params.salt = keccak256(abi.encode(data.sender, data.params.salt)); + + // Get liquidity modification deltas + (BalanceDelta callerDelta, BalanceDelta feesAccrued) = poolManager.modifyLiquidity(key, data.params, ""); + + // Calculate the principal delta + BalanceDelta principalDelta = callerDelta - feesAccrued; + + // Handle each currency amount based on its sign after applying the liquidity modification + if (principalDelta.amount0() < 0) { + // If amount0 is negative, send tokens from the sender to the pool + key.currency0.settle(poolManager, data.sender, uint256(int256(-principalDelta.amount0())), false); + } else { + // If amount0 is positive, send tokens from the pool to the sender + key.currency0.take(poolManager, data.sender, uint256(int256(principalDelta.amount0())), false); + } + + if (principalDelta.amount1() < 0) { + // If amount1 is negative, send tokens from the sender to the pool + key.currency1.settle(poolManager, data.sender, uint256(int256(-principalDelta.amount1())), false); + } else { + // If amount1 is positive, send tokens from the pool to the sender + key.currency1.take(poolManager, data.sender, uint256(int256(principalDelta.amount1())), false); + } + + // Handle any accrued fees (by default, transfer all fees to the sender) + _handleAccruedFees(data, callerDelta, feesAccrued); + + emit HookModifyLiquidity( + PoolId.unwrap(poolKey.toId()), data.sender, principalDelta.amount0(), principalDelta.amount1() + ); + + // Return both deltas so that slippage checks can be done on the principal delta + return abi.encode(callerDelta, feesAccrued); + } + + /** + * @dev Handle any fees accrued in a liquidity position. By default, this function transfers the tokens to the + * owner of the liquidity position. However, this function can be overriden to take fees accrued in the position, + * or any other desired logic. + * + * @param data The encoded `CallbackData` struct, including the sender and the parameters for the liquidity modification. + * @param callerDelta The balance delta from the liquidity modification. + * @param feesAccrued The balance delta of the fees generated in the liquidity range. + */ + function _handleAccruedFees(CallbackData memory data, BalanceDelta callerDelta, BalanceDelta feesAccrued) + internal + virtual + { + // Send any accrued fees to the sender + poolKey.currency0.take(poolManager, data.sender, uint256(int256(feesAccrued.amount0())), false); + poolKey.currency1.take(poolManager, data.sender, uint256(int256(feesAccrued.amount1())), false); + } + + /** + * @dev Initialize the hook's pool key. The stored key should act immutably so that + * it can safely be used across the hook's functions. + */ + function _beforeInitialize(address, PoolKey calldata key, uint160) internal override returns (bytes4) { + // Check if the pool key is already initialized + if (address(poolKey.hooks) != address(0)) revert AlreadyInitialized(); + + // Store the pool key to be used in other functions + poolKey = key; + return this.beforeInitialize.selector; + } + + /** + * @dev Revert when liquidity is attempted to be added via the `PoolManager`. + */ + function _beforeAddLiquidity(address, PoolKey calldata, ModifyLiquidityParams calldata, bytes calldata) + internal + virtual + override + returns (bytes4) + { + revert LiquidityOnlyViaHook(); + } + + /** + * @dev Revert when liquidity is attempted to be removed via the `PoolManager`. + */ + function _beforeRemoveLiquidity(address, PoolKey calldata, ModifyLiquidityParams calldata, bytes calldata) + internal + virtual + override + returns (bytes4) + { + revert LiquidityOnlyViaHook(); + } + + /** + * @dev Get the liquidity modification to apply for a given liquidity addition, + * and the amount of liquidity shares would be minted to the sender. + * + * @param sqrtPriceX96 The current square root price of the pool. + * @param params The parameters for the liquidity addition. + * @return modify The encoded parameters for the liquidity addition, which must follow the + * same encoding structure as in `_getRemoveLiquidity` and `_modifyLiquidity`. + * @return shares The liquidity shares to mint. + * + * IMPORTANT: The salt returned in `modify` indicates which position of the sender the liquidity + * modification is applied given that the `unlockCallback` function uses the keccak256 hash of + * the sender and the salt returned here to determine the liquidity position. By default, we + * recommend using the `userInputSalt` parameter from the `AddLiquidityParams` struct as the salt + * here. + */ + function _getAddLiquidity(uint160 sqrtPriceX96, AddLiquidityParams memory params) + internal + virtual + returns (bytes memory modify, uint256 shares); + + /** + * @dev Get the liquidity modification to apply for a given liquidity removal, + * and the amount of liquidity shares would be burned from the sender. + * + * @param params The parameters for the liquidity removal. + * @return modify The encoded parameters for the liquidity removal, which must follow the + * same encoding structure as in `_getAddLiquidity` and `_modifyLiquidity`. + * @return shares The liquidity shares to burn. + * + * IMPORTANT: The salt returned in `modify` indicates which position of the sender the liquidity + * modification is applied given that the `unlockCallback` function uses the keccak256 hash of + * the sender and the salt returned here to determine the liquidity position. By default, we + * recommend using the `userInputSalt` parameter from the `AddLiquidityParams` struct as the salt + * here. + */ + function _getRemoveLiquidity(RemoveLiquidityParams memory params) + internal + virtual + returns (bytes memory modify, uint256 shares); + + /** + * @dev Mint liquidity shares to the sender. + * + * @param params The parameters for the liquidity addition. + * @param callerDelta The balance delta from the liquidity addition. This is the total of both principal and fee delta. + * @param feesAccrued The balance delta of the fees generated in the liquidity range. + * @param shares The liquidity shares to mint. + */ + function _mint(AddLiquidityParams memory params, BalanceDelta callerDelta, BalanceDelta feesAccrued, uint256 shares) + internal + virtual; + + /** + * @dev Burn liquidity shares from the sender. + * + * @param params The parameters for the liquidity removal. + * @param callerDelta The balance delta from the liquidity removal. This is the total of both principal and fee delta. + * @param feesAccrued The balance delta of the fees generated in the liquidity range. + * @param shares The liquidity shares to burn. + */ + function _burn( + RemoveLiquidityParams memory params, + BalanceDelta callerDelta, + BalanceDelta feesAccrued, + uint256 shares + ) internal virtual; + + /** + * @dev Set the hook permissions, specifically `beforeInitialize`, `beforeAddLiquidity` and `beforeRemoveLiquidity`. + * + * @return permissions The hook permissions. + */ + function getHookPermissions() public pure virtual override returns (Hooks.Permissions memory permissions) { + return Hooks.Permissions({ + beforeInitialize: true, + afterInitialize: false, + beforeAddLiquidity: true, + beforeRemoveLiquidity: true, + afterAddLiquidity: false, + afterRemoveLiquidity: false, + beforeSwap: false, + afterSwap: false, + beforeDonate: false, + afterDonate: false, + beforeSwapReturnDelta: false, + afterSwapReturnDelta: false, + afterAddLiquidityReturnDelta: false, + afterRemoveLiquidityReturnDelta: false + }); + } +} diff --git a/CVLByExample/UniswapHooks/lib/uniswap-hooks/src/base/BaseCustomCurve.sol b/CVLByExample/UniswapHooks/lib/uniswap-hooks/src/base/BaseCustomCurve.sol new file mode 100644 index 00000000..57780723 --- /dev/null +++ b/CVLByExample/UniswapHooks/lib/uniswap-hooks/src/base/BaseCustomCurve.sol @@ -0,0 +1,312 @@ +// SPDX-License-Identifier: MIT +// OpenZeppelin Uniswap Hooks (last updated v0.1.0) (src/base/BaseCustomCurve.sol) + +pragma solidity ^0.8.24; + +import {BaseCustomAccounting} from "src/base/BaseCustomAccounting.sol"; +import {Hooks} from "v4-core/src/libraries/Hooks.sol"; +import {PoolKey} from "v4-core/src/types/PoolKey.sol"; +import {IPoolManager} from "v4-core/src/interfaces/IPoolManager.sol"; +import {Currency} from "v4-core/src/types/Currency.sol"; +import {SafeCast} from "v4-core/src/libraries/SafeCast.sol"; +import {CurrencySettler} from "src/utils/CurrencySettler.sol"; +import {BeforeSwapDeltaLibrary, BeforeSwapDelta, toBeforeSwapDelta} from "v4-core/src/types/BeforeSwapDelta.sol"; +import {BalanceDelta, toBalanceDelta, BalanceDeltaLibrary} from "v4-core/src/types/BalanceDelta.sol"; +import {PoolId} from "v4-core/src/types/PoolId.sol"; +import {SwapParams} from "v4-core/src/types/PoolOperation.sol"; + +/** + * @dev Base implementation for custom curves, inheriting from {BaseCustomAccounting}. + * + * This hook allows to implement a custom curve (or any logic) for swaps, which overrides the default v3-like + * concentrated liquidity implementation of the `PoolManager`. During a swap, the hook calls the + * {_getUnspecifiedAmount} function to get the amount of tokens to be sent to the receiver. The return delta + * created from this calculation is then consumed and applied by the `PoolManager`. + * + * NOTE: This hook by default does not include fee or salt mechanisms, which can be implemented by inheriting + * contracts if needed. + * + * WARNING: This is experimental software and is provided on an "as is" and "as available" basis. We do + * not give any warranties and will not be liable for any losses incurred through any use of this code + * base. + * + * _Available since v0.1.0_ + */ +abstract contract BaseCustomCurve is BaseCustomAccounting { + using CurrencySettler for Currency; + using SafeCast for uint256; + using BeforeSwapDeltaLibrary for BeforeSwapDelta; + + struct CallbackDataCustom { + address sender; + int128 amount0; + int128 amount1; + } + + /** + * @dev Set the pool `PoolManager` address. + */ + constructor(IPoolManager _poolManager) BaseCustomAccounting(_poolManager) {} + + /** + * @dev Defines how the liquidity modification data is encoded and returned + * for an add liquidity request. + */ + function _getAddLiquidity(uint160, AddLiquidityParams memory params) + internal + virtual + override + returns (bytes memory, uint256) + { + (uint256 amount0, uint256 amount1, uint256 shares) = _getAmountIn(params); + return (abi.encode(amount0.toInt128(), amount1.toInt128()), shares); + } + + /** + * @dev Defines how the liquidity modification data is encoded and returned + * for a remove liquidity request. + */ + function _getRemoveLiquidity(RemoveLiquidityParams memory params) + internal + virtual + override + returns (bytes memory, uint256) + { + (uint256 amount0, uint256 amount1, uint256 shares) = _getAmountOut(params); + return (abi.encode(-amount0.toInt128(), -amount1.toInt128()), shares); + } + + /** + * @dev Overides the default swap logic of the `PoolManager` and calls the {_getUnspecifiedAmount} + * to get the amount of tokens to be sent to the receiver. + * + * NOTE: In order to take and settle tokens from the pool, the hook must hold the liquidity added + * via the {addLiquidity} function. + */ + function _beforeSwap(address sender, PoolKey calldata key, SwapParams calldata params, bytes calldata) + internal + virtual + override + returns (bytes4, BeforeSwapDelta, uint24) + { + // Determine if the swap is exact input or exact output + bool exactInput = params.amountSpecified < 0; + + // Determine which currency is specified and which is unspecified + (Currency specified, Currency unspecified) = + (params.zeroForOne == exactInput) ? (key.currency0, key.currency1) : (key.currency1, key.currency0); + + // Get the positive specified amount + uint256 specifiedAmount = exactInput ? uint256(-params.amountSpecified) : uint256(params.amountSpecified); + + // Get the amount of the unspecified currency to be taken or settled + (uint256 unspecifiedAmount) = _getUnspecifiedAmount(params); + + // Get the total amount of fees to be paid in the swap + uint256 swapFeeAmount = _getSwapFeeAmount(params, unspecifiedAmount); + + // New delta must be returned, so store in memory + BeforeSwapDelta returnDelta; + + if (exactInput) { + // For exact input swaps: + // 1. Take the specified input (user-given) amount from this contract's balance in the pool + specified.take(poolManager, address(this), specifiedAmount, true); + // 2. Send the calculated output amount to this contract's balance in the pool + unspecified.settle(poolManager, address(this), unspecifiedAmount, true); + + returnDelta = toBeforeSwapDelta(specifiedAmount.toInt128(), -unspecifiedAmount.toInt128()); + } else { + // For exact output swaps: + // 1. Take the calculated input amount from this contract's balance in the pool + unspecified.take(poolManager, address(this), unspecifiedAmount, true); + // 2. Send the specified (user-given) output amount to this contract's balance in the pool + specified.settle(poolManager, address(this), specifiedAmount, true); + + returnDelta = toBeforeSwapDelta(-specifiedAmount.toInt128(), unspecifiedAmount.toInt128()); + } + + // Emit the swap event with the amounts ordered correctly + // NOTE: the fee is paid in the input currency + if (specified == key.currency0) { + emit HookSwap( + PoolId.unwrap(key.toId()), + sender, + specifiedAmount.toInt128(), + unspecifiedAmount.toInt128(), + exactInput ? swapFeeAmount.toUint128() : 0, // if specified is currency0 and exactInput = true, the fee is paid in currency0 + exactInput ? 0 : swapFeeAmount.toUint128() // if specified is currency0 and exactInput = false, the fee is paid in currency1 + ); + } else { + emit HookSwap( + PoolId.unwrap(key.toId()), + sender, + unspecifiedAmount.toInt128(), + specifiedAmount.toInt128(), + exactInput ? 0 : swapFeeAmount.toUint128(), + exactInput ? swapFeeAmount.toUint128() : 0 + ); + } + + return (this.beforeSwap.selector, returnDelta, 0); + } + + /** + * @dev Overides the custom accounting logic to support the custom curve integer amounts. + * + * @param params The parameters for the liquidity modification, encoded in the + * {_getAddLiquidity} or {_getRemoveLiquidity} function. + * @return callerDelta The balance delta from the liquidity modification. This is the total of both principal and fee deltas. + * @return feesAccrued The balance delta of the fees generated in the liquidity range. + */ + function _modifyLiquidity(bytes memory params) + internal + virtual + override + returns (BalanceDelta callerDelta, BalanceDelta feesAccrued) + { + (int128 amount0, int128 amount1) = abi.decode(params, (int128, int128)); + (callerDelta, feesAccrued) = abi.decode( + poolManager.unlock(abi.encode(CallbackDataCustom(msg.sender, amount0, amount1))), + (BalanceDelta, BalanceDelta) + ); + } + + /** + * @dev Decodes the callback data and applies the liquidity modifications, overriding the custom + * accounting logic to mint and burn ERC-6909 claim tokens which are used in swaps. + * + * @param rawData The callback data encoded in the {_modifyLiquidity} function. + * @return returnData The encoded caller and fees accrued deltas. + */ + function unlockCallback(bytes calldata rawData) + external + virtual + override + onlyPoolManager + returns (bytes memory returnData) + { + CallbackDataCustom memory data = abi.decode(rawData, (CallbackDataCustom)); + + int128 amount0 = 0; + int128 amount1 = 0; + + // This section handles liquidity modifications (adding/removing) for both tokens in the pool + // The sign of data.amount0/1 determines if we're removing (-) or adding (+) liquidity + + PoolKey memory _poolKey = poolKey; + + // Remove liquidity if amount0 is negative + if (data.amount0 < 0) { + // Burns ERC-6909 tokens to receive tokens + _poolKey.currency0.settle(poolManager, address(this), uint256(int256(-data.amount0)), true); + // Sends tokens from the pool to the user + _poolKey.currency0.take(poolManager, data.sender, uint256(int256(-data.amount0)), false); + // Record the amount so that it can be then encoded into the delta + amount0 = -data.amount0; + } + + // Remove liquidity if amount1 is negative + if (data.amount1 < 0) { + // Burns ERC-6909 tokens to receive tokens + _poolKey.currency1.settle(poolManager, address(this), uint256(int256(-data.amount1)), true); + // Sends tokens from the pool to the user + _poolKey.currency1.take(poolManager, data.sender, uint256(int256(-data.amount1)), false); + // Record the amount so that it can be then encoded into the delta + amount1 = -data.amount1; + } + + // Add liquidity if amount0 is positive + if (data.amount0 > 0) { + // First settle (send) tokens from user to pool + _poolKey.currency0.settle(poolManager, data.sender, uint256(int256(data.amount0)), false); + // Take (mint) ERC-6909 tokens to be received by this hook + _poolKey.currency0.take(poolManager, address(this), uint256(int256(data.amount0)), true); + // Record the amount so that it can be then encoded into the delta + amount0 = -data.amount0; + } + + // Add liquidity if amount1 is positive + if (data.amount1 > 0) { + // First settle (send) tokens from user to pool + _poolKey.currency1.settle(poolManager, data.sender, uint256(int256(data.amount1)), false); + // Take (mint) ERC-6909 tokens to be received by this hook + _poolKey.currency1.take(poolManager, address(this), uint256(int256(data.amount1)), true); + // Record the amount so that it can be then encoded into the delta + amount1 = -data.amount1; + } + + emit HookModifyLiquidity(PoolId.unwrap(poolKey.toId()), data.sender, amount0, amount1); + + // Return the encoded caller and fees accrued (zero by default) deltas + return abi.encode(toBalanceDelta(amount0, amount1), BalanceDeltaLibrary.ZERO_DELTA); + } + + /** + * @dev Calculate the amount of the unspecified currency to be taken or settled from the swapper, depending on the swap + * direction and the fee amount to be paid to LPs. + * + * @param params The swap parameters. + * @return unspecifiedAmount The amount of the unspecified currency to be taken or settled. + */ + function _getUnspecifiedAmount(SwapParams calldata params) internal virtual returns (uint256 unspecifiedAmount); + + /** + * @dev Calculate the amount of fees to be paid to LPs in a swap. + * + * @param params The swap parameters. + * @param unspecifiedAmount The amount of the unspecified currency to be taken or settled. + * @return swapFeeAmount The amount of fees to be paid to LPs in the swap (in currency0 and currency1). + */ + function _getSwapFeeAmount(SwapParams calldata params, uint256 unspecifiedAmount) + internal + virtual + returns (uint256 swapFeeAmount); + + /** + * @dev Calculate the amount of tokens to use and liquidity shares to burn for a remove liquidity request. + * @return amount0 The amount of token0 to be received by the liquidity provider. + * @return amount1 The amount of token1 to be received by the liquidity provider. + * @return shares The amount of liquidity shares to be burned by the liquidity provider. + */ + function _getAmountOut(RemoveLiquidityParams memory params) + internal + virtual + returns (uint256 amount0, uint256 amount1, uint256 shares); + + /** + * @dev Calculate the amount of tokens to use and liquidity shares to mint for an add liquidity request. + * @return amount0 The amount of token0 to be sent by the liquidity provider. + * @return amount1 The amount of token1 to be sent by the liquidity provider. + * @return shares The amount of liquidity shares to be minted by the liquidity provider. + */ + function _getAmountIn(AddLiquidityParams memory params) + internal + virtual + returns (uint256 amount0, uint256 amount1, uint256 shares); + + /** + * @dev Set the hook permissions, specifically `beforeInitialize`, `beforeAddLiquidity`, `beforeRemoveLiquidity`, + * `beforeSwap`, and `beforeSwapReturnDelta` + * + * @return permissions The hook permissions. + */ + function getHookPermissions() public pure virtual override returns (Hooks.Permissions memory permissions) { + return Hooks.Permissions({ + beforeInitialize: true, + afterInitialize: false, + beforeAddLiquidity: true, + beforeRemoveLiquidity: true, + afterAddLiquidity: false, + afterRemoveLiquidity: false, + beforeSwap: true, + afterSwap: false, + beforeDonate: false, + afterDonate: false, + beforeSwapReturnDelta: true, + afterSwapReturnDelta: false, + afterAddLiquidityReturnDelta: false, + afterRemoveLiquidityReturnDelta: false + }); + } +} diff --git a/CVLByExample/UniswapHooks/lib/uniswap-hooks/src/base/BaseHook.sol b/CVLByExample/UniswapHooks/lib/uniswap-hooks/src/base/BaseHook.sol new file mode 100644 index 00000000..78eb1e1e --- /dev/null +++ b/CVLByExample/UniswapHooks/lib/uniswap-hooks/src/base/BaseHook.sol @@ -0,0 +1,344 @@ +// SPDX-License-Identifier: MIT +// OpenZeppelin Uniswap Hooks (last updated v0.1.0) (src/base/BaseHook.sol) + +pragma solidity ^0.8.24; + +import {IHooks} from "v4-core/src/interfaces/IHooks.sol"; +import {BalanceDelta} from "v4-core/src/types/BalanceDelta.sol"; +import {Hooks} from "v4-core/src/libraries/Hooks.sol"; +import {PoolKey} from "v4-core/src/types/PoolKey.sol"; +import {IPoolManager} from "v4-core/src/interfaces/IPoolManager.sol"; +import {BeforeSwapDelta} from "v4-core/src/types/BeforeSwapDelta.sol"; +import {SwapParams, ModifyLiquidityParams} from "v4-core/src/types/PoolOperation.sol"; + +/** + * @dev Base hook implementation. + * + * This contract defines all hook entry points, as well as security and permission helpers. + * Based on the https://github.com/Uniswap/v4-periphery/blob/main/src/base/hooks/BaseHook.sol[Uniswap v4 periphery implementation]. + * + * NOTE: Hook entry points must be overiden and implemented by the inheriting hook to be used. Their respective + * flags must be set to true in the `getHookPermissions` function as well. + * + * WARNING: This is experimental software and is provided on an "as is" and "as available" basis. We do + * not give any warranties and will not be liable for any losses incurred through any use of this code + * base. + * + * _Available since v0.1.0_ + */ +abstract contract BaseHook is IHooks { + IPoolManager public immutable poolManager; + + /** + * @dev The hook is not the caller. + */ + error NotSelf(); + + /** + * @dev The pool is not authorized to use this hook. + */ + error InvalidPool(); + + /** + * @dev The hook function is not implemented. + */ + error HookNotImplemented(); + + /** + * @notice Thrown when calling unlockCallback where the caller is not `PoolManager`. + */ + error NotPoolManager(); + + /** + * @dev Set the pool manager and check that the hook address matches the expected permissions and flags. + */ + constructor(IPoolManager _poolManager) { + poolManager = _poolManager; + validateHookAddress(this); + } + + /** + * @notice Only allow calls from the `PoolManager` contract + */ + modifier onlyPoolManager() { + if (msg.sender != address(poolManager)) revert NotPoolManager(); + _; + } + + /** + * @dev Restrict the function to only be callable by the hook itself. + */ + modifier onlySelf() { + if (msg.sender != address(this)) revert NotSelf(); + _; + } + + /** + * @dev Restrict the function to only be called for a valid pool. + */ + modifier onlyValidPools(IHooks hooks) { + if (hooks != this) revert InvalidPool(); + _; + } + + /** + * @dev Get the hook permissions to signal which hook functions are to be implemented. + * + * Used at deployment to validate the address correctly represents the expected permissions. + * + * @return permissions The hook permissions. + */ + function getHookPermissions() public pure virtual returns (Hooks.Permissions memory permissions); + + /** + * @dev Validate the hook address against the expected permissions. + */ + function validateHookAddress(BaseHook hook) internal pure { + Hooks.validateHookPermissions(hook, getHookPermissions()); + } + + /** + * @inheritdoc IHooks + */ + function beforeInitialize(address sender, PoolKey calldata key, uint160 sqrtPriceX96) + external + virtual + onlyPoolManager + returns (bytes4) + { + return _beforeInitialize(sender, key, sqrtPriceX96); + } + + /** + * @dev Hook implementation for `beforeInitialize`, to be overriden by the inheriting hook. The + * flag must be set to true in the `getHookPermissions` function. + */ + function _beforeInitialize(address, PoolKey calldata, uint160) internal virtual returns (bytes4) { + revert HookNotImplemented(); + } + + /** + * @inheritdoc IHooks + */ + function afterInitialize(address sender, PoolKey calldata key, uint160 sqrtPriceX96, int24 tick) + external + virtual + onlyPoolManager + returns (bytes4) + { + return _afterInitialize(sender, key, sqrtPriceX96, tick); + } + + /** + * @dev Hook implementation for `afterInitialize`, to be overriden by the inheriting hook. The + * flag must be set to true in the `getHookPermissions` function. + */ + function _afterInitialize(address, PoolKey calldata, uint160, int24) internal virtual returns (bytes4) { + revert HookNotImplemented(); + } + + /** + * @inheritdoc IHooks + */ + function beforeAddLiquidity( + address sender, + PoolKey calldata key, + ModifyLiquidityParams calldata params, + bytes calldata hookData + ) external virtual onlyPoolManager returns (bytes4) { + return _beforeAddLiquidity(sender, key, params, hookData); + } + + /** + * @dev Hook implementation for `beforeAddLiquidity`, to be overriden by the inheriting hook. The + * flag must be set to true in the `getHookPermissions` function. + */ + function _beforeAddLiquidity(address, PoolKey calldata, ModifyLiquidityParams calldata, bytes calldata) + internal + virtual + returns (bytes4) + { + revert HookNotImplemented(); + } + + /** + * @inheritdoc IHooks + */ + function beforeRemoveLiquidity( + address sender, + PoolKey calldata key, + ModifyLiquidityParams calldata params, + bytes calldata hookData + ) external virtual onlyPoolManager returns (bytes4) { + return _beforeRemoveLiquidity(sender, key, params, hookData); + } + + /** + * @dev Hook implementation for `beforeRemoveLiquidity`, to be overriden by the inheriting hook. The + * flag must be set to true in the `getHookPermissions` function. + */ + function _beforeRemoveLiquidity(address, PoolKey calldata, ModifyLiquidityParams calldata, bytes calldata) + internal + virtual + returns (bytes4) + { + revert HookNotImplemented(); + } + + /** + * @inheritdoc IHooks + */ + function afterAddLiquidity( + address sender, + PoolKey calldata key, + ModifyLiquidityParams calldata params, + BalanceDelta delta0, + BalanceDelta delta1, + bytes calldata hookData + ) external virtual onlyPoolManager returns (bytes4, BalanceDelta) { + return _afterAddLiquidity(sender, key, params, delta0, delta1, hookData); + } + + /** + * @dev Hook implementation for `afterAddLiquidity`, to be overriden by the inheriting hook. The + * flag must be set to true in the `getHookPermissions` function. + */ + function _afterAddLiquidity( + address, + PoolKey calldata, + ModifyLiquidityParams calldata, + BalanceDelta, + BalanceDelta, + bytes calldata + ) internal virtual returns (bytes4, BalanceDelta) { + revert HookNotImplemented(); + } + + /** + * @inheritdoc IHooks + */ + function afterRemoveLiquidity( + address sender, + PoolKey calldata key, + ModifyLiquidityParams calldata params, + BalanceDelta delta0, + BalanceDelta delta1, + bytes calldata hookData + ) external virtual onlyPoolManager returns (bytes4, BalanceDelta) { + return _afterRemoveLiquidity(sender, key, params, delta0, delta1, hookData); + } + + /** + * @dev Hook implementation for `afterRemoveLiquidity`, to be overriden by the inheriting hook. The + * flag must be set to true in the `getHookPermissions` function. + */ + function _afterRemoveLiquidity( + address, + PoolKey calldata, + ModifyLiquidityParams calldata, + BalanceDelta, + BalanceDelta, + bytes calldata + ) internal virtual returns (bytes4, BalanceDelta) { + revert HookNotImplemented(); + } + + /** + * @inheritdoc IHooks + */ + function beforeSwap(address sender, PoolKey calldata key, SwapParams calldata params, bytes calldata hookData) + external + virtual + onlyPoolManager + returns (bytes4, BeforeSwapDelta, uint24) + { + return _beforeSwap(sender, key, params, hookData); + } + + /** + * @dev Hook implementation for `beforeSwap`, to be overriden by the inheriting hook. The + * flag must be set to true in the `getHookPermissions` function. + */ + function _beforeSwap(address, PoolKey calldata, SwapParams calldata, bytes calldata) + internal + virtual + returns (bytes4, BeforeSwapDelta, uint24) + { + revert HookNotImplemented(); + } + + /** + * @inheritdoc IHooks + */ + function afterSwap( + address sender, + PoolKey calldata key, + SwapParams calldata params, + BalanceDelta delta, + bytes calldata hookData + ) external virtual onlyPoolManager returns (bytes4, int128) { + return _afterSwap(sender, key, params, delta, hookData); + } + + /** + * @dev Hook implementation for `afterSwap`, to be overriden by the inheriting hook. The + * flag must be set to true in the `getHookPermissions` function. + */ + function _afterSwap(address, PoolKey calldata, SwapParams calldata, BalanceDelta, bytes calldata) + internal + virtual + returns (bytes4, int128) + { + revert HookNotImplemented(); + } + + /** + * @inheritdoc IHooks + */ + function beforeDonate( + address sender, + PoolKey calldata key, + uint256 amount0, + uint256 amount1, + bytes calldata hookData + ) external virtual onlyPoolManager returns (bytes4) { + return _beforeDonate(sender, key, amount0, amount1, hookData); + } + + /** + * @dev Hook implementation for `beforeDonate`, to be overriden by the inheriting hook. The + * flag must be set to true in the `getHookPermissions` function. + */ + function _beforeDonate(address, PoolKey calldata, uint256, uint256, bytes calldata) + internal + virtual + returns (bytes4) + { + revert HookNotImplemented(); + } + + /** + * @inheritdoc IHooks + */ + function afterDonate( + address sender, + PoolKey calldata key, + uint256 amount0, + uint256 amount1, + bytes calldata hookData + ) external virtual onlyPoolManager returns (bytes4) { + return _afterDonate(sender, key, amount0, amount1, hookData); + } + + /** + * @dev Hook implementation for `afterDonate`, to be overriden by the inheriting hook. The + * flag must be set to true in the `getHookPermissions` function. + */ + function _afterDonate(address, PoolKey calldata, uint256, uint256, bytes calldata) + internal + virtual + returns (bytes4) + { + revert HookNotImplemented(); + } +} diff --git a/CVLByExample/UniswapHooks/lib/uniswap-hooks/src/base/README.adoc b/CVLByExample/UniswapHooks/lib/uniswap-hooks/src/base/README.adoc new file mode 100644 index 00000000..07ac5e9c --- /dev/null +++ b/CVLByExample/UniswapHooks/lib/uniswap-hooks/src/base/README.adoc @@ -0,0 +1,21 @@ += Base + +[.readme-notice] +NOTE: This document is better viewed on the docs page. + +Base contracts for building secure and modular Uniswap hooks, providing core functionality and common patterns for hook development. + + * {BaseCustomAccounting}: Base hook implementation for custom accounting, including support for swaps and liquidity management. + * {BaseCustomCurve}: Base hook implementation for custom curves. + * {BaseHook}: Base implementation for hooks. + * {BaseAsyncSwap}: Base hook implementation for asynchronous swaps. + +== Hooks + +{{BaseCustomAccounting}} + +{{BaseCustomCurve}} + +{{BaseHook}} + +{{BaseAsyncSwap}} diff --git a/CVLByExample/UniswapHooks/lib/uniswap-hooks/src/fee/BaseDynamicAfterFee.sol b/CVLByExample/UniswapHooks/lib/uniswap-hooks/src/fee/BaseDynamicAfterFee.sol new file mode 100644 index 00000000..75d01eb2 --- /dev/null +++ b/CVLByExample/UniswapHooks/lib/uniswap-hooks/src/fee/BaseDynamicAfterFee.sol @@ -0,0 +1,184 @@ +// SPDX-License-Identifier: MIT +// OpenZeppelin Uniswap Hooks (last updated v0.1.0) (src/fee/BaseDynamicAfterFee.sol) + +pragma solidity ^0.8.24; + +import {BaseHook} from "src/base/BaseHook.sol"; +import {BalanceDelta} from "v4-core/src/types/BalanceDelta.sol"; +import {Hooks} from "v4-core/src/libraries/Hooks.sol"; +import {PoolKey} from "v4-core/src/types/PoolKey.sol"; +import {IPoolManager} from "v4-core/src/interfaces/IPoolManager.sol"; +import {Currency} from "v4-core/src/types/Currency.sol"; +import {SafeCast} from "v4-core/src/libraries/SafeCast.sol"; +import {BeforeSwapDelta, BeforeSwapDeltaLibrary} from "v4-core/src/types/BeforeSwapDelta.sol"; +import {CurrencySettler} from "src/utils/CurrencySettler.sol"; +import {PoolId} from "v4-core/src/types/PoolId.sol"; +import {IHookEvents} from "src/interfaces/IHookEvents.sol"; +import {SwapParams} from "v4-core/src/types/PoolOperation.sol"; + +/** + * @dev Base implementation for dynamic fees applied after swaps. + * + * In order to use this hook, the inheriting contract must define the {_getTargetOutput} and + * {_afterSwapHandler} functions. The {_getTargetOutput} function returns the target output to + * apply to the swap depending on the given apply flag. The {_afterSwapHandler} function is called + * after the target output is applied to the swap and currency amount is received. + * + * WARNING: This is experimental software and is provided on an "as is" and "as available" basis. We do + * not give any warranties and will not be liable for any losses incurred through any use of this code + * base. + * + * _Available since v0.1.0_ + */ +abstract contract BaseDynamicAfterFee is BaseHook, IHookEvents { + using SafeCast for uint256; + using CurrencySettler for Currency; + + uint256 internal _targetOutput; + + bool internal _applyTargetOutput; + + /** + * @dev Target output exceeds swap amount. + */ + error TargetOutputExceeds(); + + /** + * @dev Set the `PoolManager` address. + */ + constructor(IPoolManager _poolManager) BaseHook(_poolManager) {} + + /** + * @dev Sets the target output and apply flag to be used in the `afterSwap` hook. + * + * NOTE: The target output is reset to 0 in the `afterSwap` hook regardless of the apply flag. + */ + function _beforeSwap(address sender, PoolKey calldata key, SwapParams calldata params, bytes calldata hookData) + internal + virtual + override + returns (bytes4, BeforeSwapDelta, uint24) + { + // Get the target output and apply flag + (uint256 targetOutput, bool applyTargetOutput) = _getTargetOutput(sender, key, params, hookData); + + // Set the target output and apply flag, overriding any previous values. + _applyTargetOutput = applyTargetOutput; + _targetOutput = targetOutput; + + return (this.beforeSwap.selector, BeforeSwapDeltaLibrary.ZERO_DELTA, 0); + } + + /** + * @dev Apply the target output to the unspecified currency of the swap using fees. + * The fees are minted as ERC-6909 tokens, which can then be redeemed in the + * {_afterSwapHandler} function. Note that if the underlying unspecified currency + * is native, the implementing contract must ensure that it can receive native tokens + * when redeeming. + * + * NOTE: The target output is reset to 0, both when the apply flag is set to `false` + * and when set to `true`. + */ + function _afterSwap( + address sender, + PoolKey calldata key, + SwapParams calldata params, + BalanceDelta delta, + bytes calldata + ) internal virtual override returns (bytes4, int128) { + uint256 targetOutput = _targetOutput; + + // Reset storage target output to 0 and use one stored in memory + _targetOutput = 0; + + // Skip if target output is not active + if (!_applyTargetOutput) { + return (this.afterSwap.selector, 0); + } + + // Fee defined in the unspecified currency of the swap + (Currency unspecified, int128 unspecifiedAmount) = (params.amountSpecified < 0 == params.zeroForOne) + ? (key.currency1, delta.amount1()) + : (key.currency0, delta.amount0()); + + // If fee is on output, get the absolute output amount + if (unspecifiedAmount < 0) unspecifiedAmount = -unspecifiedAmount; + + // Revert if the target output exceeds the swap amount + if (targetOutput > uint128(unspecifiedAmount)) revert TargetOutputExceeds(); + + // Calculate the fee amount, which is the difference between the swap amount and the target output + uint256 feeAmount = uint128(unspecifiedAmount) - targetOutput; + + // Mint ERC-6909 tokens for unspecified currency fee and call handler + if (feeAmount > 0) { + unspecified.take(poolManager, address(this), feeAmount, true); + _afterSwapHandler(key, params, delta, targetOutput, feeAmount); + } + + // Emit the swap event with the amounts ordered correctly + if (unspecified == key.currency0) { + emit HookFee(PoolId.unwrap(key.toId()), sender, feeAmount.toUint128(), 0); + } else { + emit HookFee(PoolId.unwrap(key.toId()), sender, 0, feeAmount.toUint128()); + } + + return (this.afterSwap.selector, feeAmount.toInt128()); + } + + /** + * @dev Return the target output to be enforced by the `afterSwap` hook using fees. + * + * IMPORTANT: The swap will revert if the target output exceeds the output unspecified amount from the swap. + * In order to consume all of the output from the swap, set the target output to equal the output unspecified + * amount and set the apply flag to `true`. + * + * @return targetOutput The target output, defined in the unspecified currency of the swap. + * @return applyTargetOutput The apply flag, which can be set to `false` to skip applying the target output. + */ + function _getTargetOutput(address sender, PoolKey calldata key, SwapParams calldata params, bytes calldata hookData) + internal + virtual + returns (uint256 targetOutput, bool applyTargetOutput); + + /** + * @dev Handler called after applying the target output to a swap and receiving the currency amount. + * + * @param key The pool key. + * @param params The swap parameters. + * @param delta The balance delta from the swap. + * @param targetOutput The target output, defined in the unspecified currency of the swap. + * @param feeAmount The amount of the unspecified currency taken from the swap. + */ + function _afterSwapHandler( + PoolKey calldata key, + SwapParams calldata params, + BalanceDelta delta, + uint256 targetOutput, + uint256 feeAmount + ) internal virtual; + + /** + * @dev Set the hook permissions, specifically {beforeSwap}, {afterSwap} and {afterSwapReturnDelta}. + * + * @return permissions The hook permissions. + */ + function getHookPermissions() public pure virtual override returns (Hooks.Permissions memory permissions) { + return Hooks.Permissions({ + beforeInitialize: false, + afterInitialize: false, + beforeAddLiquidity: false, + afterAddLiquidity: false, + beforeRemoveLiquidity: false, + afterRemoveLiquidity: false, + beforeSwap: true, + afterSwap: true, + beforeDonate: false, + afterDonate: false, + beforeSwapReturnDelta: false, + afterSwapReturnDelta: true, + afterAddLiquidityReturnDelta: false, + afterRemoveLiquidityReturnDelta: false + }); + } +} diff --git a/CVLByExample/UniswapHooks/lib/uniswap-hooks/src/fee/BaseDynamicFee.sol b/CVLByExample/UniswapHooks/lib/uniswap-hooks/src/fee/BaseDynamicFee.sol new file mode 100644 index 00000000..8254410a --- /dev/null +++ b/CVLByExample/UniswapHooks/lib/uniswap-hooks/src/fee/BaseDynamicFee.sol @@ -0,0 +1,98 @@ +// SPDX-License-Identifier: MIT +// OpenZeppelin Uniswap Hooks (last updated v0.1.0) (src/fee/BaseDynamicFee.sol) + +pragma solidity ^0.8.24; + +import {BaseHook} from "src/base/BaseHook.sol"; +import {Hooks} from "v4-core/src/libraries/Hooks.sol"; +import {PoolKey} from "v4-core/src/types/PoolKey.sol"; +import {IPoolManager} from "v4-core/src/interfaces/IPoolManager.sol"; +import {LPFeeLibrary} from "v4-core/src/libraries/LPFeeLibrary.sol"; +import {SwapParams} from "v4-core/src/types/PoolOperation.sol"; + +/** + * @dev Base implementation to apply a dynamic fee via the `PoolManager`'s `updateDynamicLPFee` function. + * + * WARNING: This is experimental software and is provided on an "as is" and "as available" basis. We do + * not give any warranties and will not be liable for any losses incurred through any use of this code + * base. + * + * _Available since v0.1.0_ + */ +abstract contract BaseDynamicFee is BaseHook { + using LPFeeLibrary for uint24; + + /** + * @dev The hook was attempted to be initialized with a non-dynamic fee. + */ + error NotDynamicFee(); + + /** + * @dev Set the `PoolManager` address. + */ + constructor(IPoolManager _poolManager) BaseHook(_poolManager) {} + + /** + * @dev Returns a fee, denominated in hundredths of a bip, to be applied to the pool after it is initialized. + */ + function _getFee(PoolKey calldata key) internal virtual returns (uint24); + + /** + * @dev Set the fee after the pool is initialized. + */ + function _afterInitialize(address, PoolKey calldata key, uint160, int24) + internal + virtual + override + returns (bytes4) + { + if (!key.fee.isDynamicFee()) revert NotDynamicFee(); + poolManager.updateDynamicLPFee(key, _getFee(key)); + return this.afterInitialize.selector; + } + + /** + * @dev Updates the dynamic LP fee for the given pool, which must have a key + * that contains this hook's address. + * + * WARNING: This function can be called by anyone at any time. If `_getFee` implementation + * depends on external conditions (e.g., oracle prices, other pool states, token balances), + * it may be vulnerable to manipulation. An attacker could potentially: + * 1. Manipulate the external conditions that `_getFee` depends on + * 2. Call `poke()` to update the fee to a more favorable rate + * 3. Execute trades at the manipulated fee rate + * + * Inheriting contracts should consider implementing access controls on this function, + * make the logic in `_getFee` resistant to short-term manipulation, or accept the risk + * of fee manipulation. + * + * @param key The pool key to update the dynamic LP fee for. + */ + function poke(PoolKey calldata key) external virtual onlyValidPools(key.hooks) { + poolManager.updateDynamicLPFee(key, _getFee(key)); + } + + /** + * @dev Set the hook permissions, specifically `afterInitialize`. + * + * @return permissions The hook permissions. + */ + function getHookPermissions() public pure virtual override returns (Hooks.Permissions memory permissions) { + return Hooks.Permissions({ + beforeInitialize: false, + afterInitialize: true, + beforeAddLiquidity: false, + afterAddLiquidity: false, + beforeRemoveLiquidity: false, + afterRemoveLiquidity: false, + beforeSwap: false, + afterSwap: false, + beforeDonate: false, + afterDonate: false, + beforeSwapReturnDelta: false, + afterSwapReturnDelta: false, + afterAddLiquidityReturnDelta: false, + afterRemoveLiquidityReturnDelta: false + }); + } +} diff --git a/CVLByExample/UniswapHooks/lib/uniswap-hooks/src/fee/BaseOverrideFee.sol b/CVLByExample/UniswapHooks/lib/uniswap-hooks/src/fee/BaseOverrideFee.sol new file mode 100644 index 00000000..e36a3879 --- /dev/null +++ b/CVLByExample/UniswapHooks/lib/uniswap-hooks/src/fee/BaseOverrideFee.sol @@ -0,0 +1,93 @@ +// SPDX-License-Identifier: MIT +// OpenZeppelin Uniswap Hooks (last updated v0.1.0) (src/fee/BaseOverrideFee.sol) + +pragma solidity ^0.8.24; + +import {BaseHook} from "src/base/BaseHook.sol"; +import {Hooks} from "v4-core/src/libraries/Hooks.sol"; +import {PoolKey} from "v4-core/src/types/PoolKey.sol"; +import {IPoolManager} from "v4-core/src/interfaces/IPoolManager.sol"; +import {BeforeSwapDelta, BeforeSwapDeltaLibrary} from "v4-core/src/types/BeforeSwapDelta.sol"; +import {LPFeeLibrary} from "v4-core/src/libraries/LPFeeLibrary.sol"; +import {SwapParams} from "v4-core/src/types/PoolOperation.sol"; + +/** + * @dev Base implementation for automatic dynamic fees applied before swaps. + * + * WARNING: This is experimental software and is provided on an "as is" and "as available" basis. We do + * not give any warranties and will not be liable for any losses incurred through any use of this code + * base. + * + * _Available since v0.1.0_ + */ +abstract contract BaseOverrideFee is BaseHook { + using LPFeeLibrary for uint24; + + /** + * @dev The hook was attempted to be initialized with a non-dynamic fee. + */ + error NotDynamicFee(); + + /** + * @dev Set the `PoolManager` address. + */ + constructor(IPoolManager _poolManager) BaseHook(_poolManager) {} + + /** + * @dev Check that the pool key has a dynamic fee. + */ + function _afterInitialize(address, PoolKey calldata key, uint160, int24) + internal + virtual + override + returns (bytes4) + { + if (!key.fee.isDynamicFee()) revert NotDynamicFee(); + return this.afterInitialize.selector; + } + + /** + * @dev Returns a fee, denominated in hundredths of a bip, to be applied to a swap. + */ + function _getFee(address sender, PoolKey calldata key, SwapParams calldata params, bytes calldata hookData) + internal + virtual + returns (uint24); + + /** + * @dev Set the fee before the swap is processed using the override fee flag. + */ + function _beforeSwap(address sender, PoolKey calldata key, SwapParams calldata params, bytes calldata hookData) + internal + virtual + override + returns (bytes4, BeforeSwapDelta, uint24) + { + uint24 fee = _getFee(sender, key, params, hookData); + return (this.beforeSwap.selector, BeforeSwapDeltaLibrary.ZERO_DELTA, fee | LPFeeLibrary.OVERRIDE_FEE_FLAG); + } + + /** + * @dev Set the hook permissions, specifically `afterInitialize` and `beforeSwap`. + * + * @return permissions The hook permissions. + */ + function getHookPermissions() public pure virtual override returns (Hooks.Permissions memory permissions) { + return Hooks.Permissions({ + beforeInitialize: false, + afterInitialize: true, + beforeAddLiquidity: false, + afterAddLiquidity: false, + beforeRemoveLiquidity: false, + afterRemoveLiquidity: false, + beforeSwap: true, + afterSwap: false, + beforeDonate: false, + afterDonate: false, + beforeSwapReturnDelta: false, + afterSwapReturnDelta: false, + afterAddLiquidityReturnDelta: false, + afterRemoveLiquidityReturnDelta: false + }); + } +} diff --git a/CVLByExample/UniswapHooks/lib/uniswap-hooks/src/fee/README.adoc b/CVLByExample/UniswapHooks/lib/uniswap-hooks/src/fee/README.adoc new file mode 100644 index 00000000..555feeb2 --- /dev/null +++ b/CVLByExample/UniswapHooks/lib/uniswap-hooks/src/fee/README.adoc @@ -0,0 +1,18 @@ += Fee + +[.readme-notice] +NOTE: This document is better viewed on the docs page. + +Hooks for managing and customizing pool fees, including dynamic fee adjustments, fee overrides, and post-swap fee calculations. + + * {BaseDynamicFee}: Hook to apply a manual dynamic fee via the Uniswap's `PoolManager` contract. + * {BaseOverrideFee}: Hook that overrides and applies a fee before swapping automatically. + * {BaseDynamicAfterFee}: Hook that overrides and applies a fee based on a delta after swapping. + +== Hooks + +{{BaseDynamicFee}} + +{{BaseOverrideFee}} + +{{BaseDynamicAfterFee}} diff --git a/CVLByExample/UniswapHooks/lib/uniswap-hooks/src/general/AntiSandwichHook.sol b/CVLByExample/UniswapHooks/lib/uniswap-hooks/src/general/AntiSandwichHook.sol new file mode 100644 index 00000000..de8d7d0b --- /dev/null +++ b/CVLByExample/UniswapHooks/lib/uniswap-hooks/src/general/AntiSandwichHook.sol @@ -0,0 +1,249 @@ +// SPDX-License-Identifier: MIT +// OpenZeppelin Uniswap Hooks (last updated v1.1.0) (src/general/AntiSandwichHook.sol) + +pragma solidity ^0.8.24; + +// Internal imports +import {BaseDynamicAfterFee} from "../fee/BaseDynamicAfterFee.sol"; +import {CurrencySettler} from "../utils/CurrencySettler.sol"; + +// External imports +import {BalanceDelta} from "v4-core/src/types/BalanceDelta.sol"; +import {Pool} from "v4-core/src/libraries/Pool.sol"; +import {PoolId} from "v4-core/src/types/PoolId.sol"; +import {Hooks} from "v4-core/src/libraries/Hooks.sol"; +import {PoolKey} from "v4-core/src/types/PoolKey.sol"; +import {IPoolManager} from "v4-core/src/interfaces/IPoolManager.sol"; +import {BeforeSwapDelta, BeforeSwapDeltaLibrary} from "v4-core/src/types/BeforeSwapDelta.sol"; +import {Slot0} from "v4-core/src/types/Slot0.sol"; +import {StateLibrary} from "v4-core/src/libraries/StateLibrary.sol"; +import {Currency} from "v4-core/src/types/Currency.sol"; +import {SwapParams} from "v4-core/src/types/PoolOperation.sol"; + +/** + * @dev Sandwich-resistant hook, based on + * https://github.com/cairoeth/sandwich-resistant-hook/blob/master/src/srHook.sol[this] + * implementation. + * + * This hook implements the sandwich-resistant AMM design introduced + * https://www.umbraresearch.xyz/writings/sandwich-resistant-amm[here]. Specifically, + * this hook guarantees that no swaps get filled at a price better than the price at + * the beginning of the slot window (i.e. one block). + * + * Within a slot window, swaps impact the pool asymmetrically for buys and sells. + * When a buy order is executed, the offer on the pool increases in accordance with + * the xy=k curve. However, the bid price remains constant, instead increasing the + * amount of liquidity on the bid. Subsequent sells eat into this liquidity, while + * decreasing the offer price according to xy=k. + * + * NOTE: Swaps in the other direction do not get the positive price difference + * compared to the initial price before the first swap in the block. + * + * WARNING: This is experimental software and is provided on an "as is" and "as available" basis. We do + * not give any warranties and will not be liable for any losses incurred through any use of this code + * base. + * + * _Available since v1.1.0_ + */ +contract AntiSandwichHook is BaseDynamicAfterFee { + using Pool for *; + using StateLibrary for IPoolManager; + using CurrencySettler for Currency; + + /// @dev Represents a checkpoint of the pool state at the beginning of a block. + struct Checkpoint { + uint48 blockNumber; + Slot0 slot0; + Pool.State state; + } + + mapping(PoolId id => Checkpoint) private _lastCheckpoints; + + constructor(IPoolManager _poolManager) BaseDynamicAfterFee(_poolManager) {} + + /** + * @dev Handles the before swap hook, setting up checkpoints at the beginning of blocks + * and calculating target outputs for subsequent swaps. + * + * For the first swap in a block: + * - Saves the current pool state as a checkpoint + * + * For subsequent swaps in the same block: + * - Calculates a target output based on the beginning-of-block state + * - Sets the inherited `_targetOutput` and `_applyTargetOutput` variables to enforce price limits + * + * NOTE: This implementation skips calling `super._beforeSwap` in the first swap of the block. Consider + * execution side effects might be missed if there is more than one definition for this function. + */ + function _beforeSwap(address sender, PoolKey calldata key, SwapParams calldata params, bytes calldata hookData) + internal + override + returns (bytes4, BeforeSwapDelta, uint24) + { + PoolId poolId = key.toId(); + Checkpoint storage _lastCheckpoint = _lastCheckpoints[poolId]; + + // update the top-of-block `slot0` if new block + if (_lastCheckpoint.blockNumber != uint48(block.number)) { + _lastCheckpoint.slot0 = Slot0.wrap(poolManager.extsload(StateLibrary._getPoolStateSlot(poolId))); + return (this.beforeSwap.selector, BeforeSwapDeltaLibrary.ZERO_DELTA, 0); + } + + return super._beforeSwap(sender, key, params, hookData); + } + + /** + * @dev Handles the after swap hook, initializing the full pool state checkpoint for the first + * swap in a block and updating the target output if needed. + * + * For the first swap in a block: + * - Saves a detailed checkpoint of the pool state including liquidity and tick information + * - This checkpoint will be used for subsequent swaps to calculate fair execution prices + * + * For all swaps: + * - Caps the target output to the actual swap amount to prevent excessive fee collection + */ + function _afterSwap( + address sender, + PoolKey calldata key, + SwapParams calldata params, + BalanceDelta delta, + bytes calldata hookData + ) internal override returns (bytes4, int128) { + uint48 blockNumber = uint48(block.number); + PoolId poolId = key.toId(); + Checkpoint storage _lastCheckpoint = _lastCheckpoints[poolId]; + + // after the first swap in block, initialize the temporary pool state + if (_lastCheckpoint.blockNumber != blockNumber) { + _lastCheckpoint.blockNumber = blockNumber; + + // iterate over ticks + (, int24 tickAfter,,) = poolManager.getSlot0(poolId); + for (int24 tick = _lastCheckpoint.slot0.tick(); tick < tickAfter; tick += key.tickSpacing) { + ( + uint128 liquidityGross, + int128 liquidityNet, + uint256 feeGrowthOutside0X128, + uint256 feeGrowthOutside1X128 + ) = poolManager.getTickInfo(poolId, tick); + + _lastCheckpoint.state.ticks[tick].liquidityGross = liquidityGross; + _lastCheckpoint.state.ticks[tick].liquidityNet = liquidityNet; + _lastCheckpoint.state.ticks[tick].feeGrowthOutside0X128 = feeGrowthOutside0X128; + _lastCheckpoint.state.ticks[tick].feeGrowthOutside1X128 = feeGrowthOutside1X128; + } + + // deep copy only values that are used and change in fair delta calculation + _lastCheckpoint.state.slot0 = Slot0.wrap(poolManager.extsload(StateLibrary._getPoolStateSlot(poolId))); + (_lastCheckpoint.state.feeGrowthGlobal0X128, _lastCheckpoint.state.feeGrowthGlobal1X128) = + poolManager.getFeeGrowthGlobals(poolId); + _lastCheckpoint.state.liquidity = poolManager.getLiquidity(poolId); + } + int128 unspecifiedAmount = (params.amountSpecified < 0 == params.zeroForOne) ? delta.amount1() : delta.amount0(); + + if (unspecifiedAmount < 0) { + unspecifiedAmount = -unspecifiedAmount; + } + + // update target output if it exceeds the swap amount + if (_targetOutput > uint128(unspecifiedAmount)) { + _targetOutput = uint128(unspecifiedAmount); + } + + return super._afterSwap(sender, key, params, delta, hookData); + } + + /** + * @dev Calculates the fair output amount based on the pool state at the beginning of the block. + * This prevents sandwich attacks by ensuring trades can't get better prices than what was available + * at the start of the block. + * + * The anti-sandwich mechanism works by: + * * For currency0 to currency1 swaps (zeroForOne = true): The pool behaves normally with xy=k curve + * * For currency1 to currency0 swaps (zeroForOne = false): The price is fixed at the beginning-of-block + * price, which prevents attackers from manipulating the price within a block + */ + function _getTargetOutput(address, PoolKey calldata key, SwapParams calldata params, bytes calldata) + internal + override + returns (uint256 targetOutput, bool applyTargetOutput) + { + PoolId poolId = key.toId(); + Checkpoint storage _lastCheckpoint = _lastCheckpoints[poolId]; + + // constant bid price + if (!params.zeroForOne) { + _lastCheckpoint.state.slot0 = _lastCheckpoint.slot0; + } + + // calculate target output + // NOTE: this functions does not execute the swap, it only calculates the output of a swap in the given state + (BalanceDelta targetDelta,,,) = Pool.swap( + _lastCheckpoint.state, + Pool.SwapParams({ + tickSpacing: key.tickSpacing, + zeroForOne: params.zeroForOne, + amountSpecified: params.amountSpecified, + sqrtPriceLimitX96: params.sqrtPriceLimitX96, + lpFeeOverride: 0 + }) + ); + + int128 target = + (params.amountSpecified < 0 == params.zeroForOne) ? targetDelta.amount1() : targetDelta.amount0(); + + targetOutput = uint256(uint128(target)); + applyTargetOutput = true; + } + + /** + * @dev Handles the excess tokens collected during the swap due to the anti-sandwich mechanism. + * When a swap executes at a worse price than what's currently available in the pool (due to + * enforcing the beginning-of-block price), the excess tokens are donated back to the pool + * to benefit all liquidity providers. + */ + function _afterSwapHandler( + PoolKey calldata key, + SwapParams calldata params, + BalanceDelta, + uint256, + uint256 feeAmount + ) internal override { + Currency unspecified = (params.amountSpecified < 0 == params.zeroForOne) ? (key.currency1) : (key.currency0); + (uint256 amount0, uint256 amount1) = unspecified == key.currency0 + ? (uint256(uint128(feeAmount)), uint256(0)) + : (uint256(0), uint256(uint128(feeAmount))); + + // reset apply flag + _applyTargetOutput = false; + + // settle and donate execess tokens to the pool + poolManager.donate(key, amount0, amount1, ""); + unspecified.settle(poolManager, address(this), feeAmount, true); + } + + /** + * @dev Set the hook permissions, specifically `beforeSwap`, `afterSwap`, and `afterSwapReturnDelta`. + * + * @return permissions The hook permissions. + */ + function getHookPermissions() public pure virtual override returns (Hooks.Permissions memory permissions) { + return Hooks.Permissions({ + beforeInitialize: false, + afterInitialize: false, + beforeAddLiquidity: false, + afterAddLiquidity: false, + beforeRemoveLiquidity: false, + afterRemoveLiquidity: false, + beforeSwap: true, + afterSwap: true, + beforeDonate: false, + afterDonate: false, + beforeSwapReturnDelta: false, + afterSwapReturnDelta: true, + afterAddLiquidityReturnDelta: false, + afterRemoveLiquidityReturnDelta: false + }); + } +} diff --git a/CVLByExample/UniswapHooks/lib/uniswap-hooks/src/general/LimitOrderHook.sol b/CVLByExample/UniswapHooks/lib/uniswap-hooks/src/general/LimitOrderHook.sol new file mode 100644 index 00000000..59098f7c --- /dev/null +++ b/CVLByExample/UniswapHooks/lib/uniswap-hooks/src/general/LimitOrderHook.sol @@ -0,0 +1,709 @@ +// SPDX-License-Identifier: MIT +// OpenZeppelin Uniswap Hooks (last updated v1.1.0) (src/general/LimitOrderHook.sol) + +pragma solidity ^0.8.24; + +// Internal imports +import {CurrencySettler} from "../utils/CurrencySettler.sol"; +import {BaseHook} from "../base/BaseHook.sol"; + +// External imports +import {Hooks} from "v4-core/src/libraries/Hooks.sol"; +import {FullMath} from "v4-core/src/libraries/FullMath.sol"; +import {StateLibrary} from "v4-core/src/libraries/StateLibrary.sol"; +import {TickMath} from "v4-core/src/libraries/TickMath.sol"; +import {PoolKey} from "v4-core/src/types/PoolKey.sol"; +import {Currency} from "v4-core/src/types/Currency.sol"; +import {PoolId} from "v4-core/src/types/PoolId.sol"; +import {BalanceDelta, toBalanceDelta} from "v4-core/src/types/BalanceDelta.sol"; +import {IPoolManager} from "v4-core/src/interfaces/IPoolManager.sol"; +import {IUnlockCallback} from "v4-core/src/interfaces/callback/IUnlockCallback.sol"; +import {SwapParams, ModifyLiquidityParams} from "v4-core/src/types/PoolOperation.sol"; + +/// @dev The order id library. +library OrderIdLibrary { + /// @dev The order id type. + type OrderId is uint232; + + /** + * @dev Compare two order ids for equality. Takes two `OrderId` values `a` and `b` and returns whether + * their underlying values are equal. + */ + function equals(OrderId a, OrderId b) internal pure returns (bool) { + return OrderId.unwrap(a) == OrderId.unwrap(b); + } + + /// @dev Increment the order id `a`. Might overflow. + function unsafeIncrement(OrderId a) internal pure returns (OrderId) { + unchecked { + return OrderId.wrap(OrderId.unwrap(a) + 1); + } + } +} + +/** + * @dev This hook implements a mechanism to place limit orders on a liquidity pool. Specifically, + * it allows users to place limit orders at a specific tick, which will be filled if the price of the pool + * crosses the tick. + * + * The hook implements the placing of orders by adding liquidity to the pool in a tick range out of range of the current price. + * Note that, given the way v4 pools work, if one adds liquidity out of range, the liquidity added will be in a single currency, + * instead of both, as in an in-range addition. + * + * Users can cancel their limit orders at any time until it is filled and liquidity is removed from the pool. Users can also withdraw + * their liquidity after the limit order is filled. + * + * WARNING: This is experimental software and is provided on an "as is" and "as available" basis. We do + * not give any warranties and will not be liable for any losses incurred through any use of this code + * base. + * + * _Available since v1.1.0_ + */ +contract LimitOrderHook is BaseHook, IUnlockCallback { + using StateLibrary for IPoolManager; + using OrderIdLibrary for OrderIdLibrary.OrderId; + using CurrencySettler for Currency; + + /// @dev The info for each order id. + struct OrderInfo { + bool filled; + Currency currency0; + Currency currency1; + uint256 currency0Total; + uint256 currency1Total; + uint128 liquidityTotal; + mapping(address => uint128) liquidity; + } + + /// @dev Enum of callbacks for the hook, used to determine the type of callback called from the poolManager to `{unlockCallback}` + enum Callbacks { + PlaceOrder, + CancelOrder, + Withdraw + } + + /// @dev Struct of callback data (sent from the poolManager to `{unlockCallback}`). + struct CallbackData { + Callbacks callbackType; + bytes data; + } + + /// @dev Struct of callback data for the place callback. + struct CallbackDataPlace { + PoolKey key; + address owner; + bool zeroForOne; + int24 tickLower; + uint128 liquidity; + } + + /// @dev Struct of callback data for the cancel callback. + struct CallbackDataCancel { + PoolKey key; + int24 tickLower; + int256 liquidityDelta; + address to; + bool removingAllLiquidity; + } + + /// @dev Struct of callback data for the withdraw callback + struct CallbackDataWithdraw { + Currency currency0; + Currency currency1; + uint256 currency0Amount; + uint256 currency1Amount; + address to; + } + + /// @dev The zero bytes. + bytes internal constant ZERO_BYTES = bytes(""); + + /// @dev The default order id, used to indicate that an order is not yet initialized. + OrderIdLibrary.OrderId private constant ORDER_ID_DEFAULT = OrderIdLibrary.OrderId.wrap(0); + + /// @dev The next order id to be used. + OrderIdLibrary.OrderId public orderIdNext = OrderIdLibrary.OrderId.wrap(1); + + /// @dev The last tick lower for each pool. + mapping(PoolId => int24) public tickLowerLasts; + + /// @dev Tracks each order id for a given identifier, defined by keccak256 of the key, tick lower, and zero for one. + mapping(bytes32 => OrderIdLibrary.OrderId) public orders; + + /// @dev Tracks the order info for each order id. + mapping(OrderIdLibrary.OrderId => OrderInfo) public orderInfos; + + /// @dev Zero liquidity was attempted to be added or removed. + error ZeroLiquidity(); + + /// @dev Limit order was placed in range. + error InRange(); + + /// @dev Limit order placed on the wrong side of the range. + error CrossedRange(); + + /// @dev Hook was already initialized. + error AlreadyInitialized(); + + /// @dev Limit order was already filled. + error Filled(); + + /// @dev Limit order is not filled. + error NotFilled(); + + /// @dev Event emitted when a limit order is placed. + event Place( + address indexed owner, + OrderIdLibrary.OrderId indexed orderId, + PoolKey key, + int24 tickLower, + bool zeroForOne, + uint128 liquidity + ); + + /// @dev Event emitted when a limit order is filled. + event Fill(OrderIdLibrary.OrderId indexed orderId, PoolKey key, int24 tickLower, bool zeroForOne); + + /// @dev Event emitted when a limit order is canceled. + event Cancel( + address indexed owner, + OrderIdLibrary.OrderId indexed orderId, + PoolKey key, + int24 tickLower, + bool zeroForOne, + uint128 liquidity + ); + + /// @dev Event emitted when a limit order is withdrawn. + event Withdraw(address indexed owner, OrderIdLibrary.OrderId indexed orderId, uint128 liquidity); + + /// @dev Set the `PoolManager` address. + constructor(IPoolManager _poolManager) BaseHook(_poolManager) {} + + /// @dev Hooks into the `afterInitialize` hook to set the last tick lower for the pool. + function _afterInitialize(address, PoolKey calldata key, uint160, int24 tick) internal override returns (bytes4) { + // set the last tick lower for the pool + tickLowerLasts[key.toId()] = getTickLower(tick, key.tickSpacing); + + return this.afterInitialize.selector; + } + + /// @dev Hooks into the `afterSwap` hook to get the ticks crossed by the swap and fill the orders that are crossed, filling them. + function _afterSwap(address, PoolKey calldata key, SwapParams calldata params, BalanceDelta, bytes calldata) + internal + virtual + override + returns (bytes4, int128) + { + (int24 tickLower, int24 lower, int24 upper) = _getCrossedTicks(key.toId(), key.tickSpacing); + + if (lower > upper) return (this.afterSwap.selector, 0); + + // set the last tick lower for the pool + tickLowerLasts[key.toId()] = tickLower; + + // note that a zeroForOne swap means that the pool is actually gaining token0, so limit + // order fills are the opposite of swap fills, hence the inversion below + bool zeroForOne = !params.zeroForOne; + for (; lower <= upper; lower += key.tickSpacing) { + _fillOrder(key, lower, zeroForOne); + } + + return (this.afterSwap.selector, 0); + } + + /** + * @dev Places a limit order by adding liquidity out of range at a specific tick. The order will be filled when the + * pool price crosses the specified `tick`. Takes a `PoolKey` `key`, target `tick`, direction `zeroForOne` indicating + * whether to buy currency0 or currency1, and amount of `liquidity` to place. The interaction with the `poolManager` is done + * via the `unlock` function, which will trigger the `{unlockCallback}` function. + */ + function placeOrder(PoolKey calldata key, int24 tick, bool zeroForOne, uint128 liquidity) external { + // revert if liquidity is 0 + if (liquidity == 0) revert ZeroLiquidity(); + + OrderInfo storage orderInfo; + + // get the order for the limit order + OrderIdLibrary.OrderId orderId = getOrderId(key, tick, zeroForOne); + + // if the order is not initialized, initialize it + if (orderId.equals(ORDER_ID_DEFAULT)) { + // initialize the order to the next order + unchecked { + setOrderId(key, tick, zeroForOne, orderId = orderIdNext); + + // increment the order id + orderIdNext = orderIdNext.unsafeIncrement(); + } + + // get the order info + orderInfo = orderInfos[orderId]; + + // set the currency0 and currency1 + orderInfo.currency0 = key.currency0; + orderInfo.currency1 = key.currency1; + } else { + // get the order info + orderInfo = orderInfos[orderId]; + } + + // add the liquidity to the order + unchecked { + orderInfo.liquidityTotal += liquidity; + orderInfo.liquidity[msg.sender] += liquidity; + } + + // unlock the callback to the poolManager, the callback will trigger `unlockCallback` + // note that multiple functions trigger `unlockCallback`, so the `callbackData.callbackType` will determine what happens + // in `unlockCallback`. In this case, it will add liquidity out of range. + // IMPORTANT: `tick` must be valid, i.e. within the range of `MIN_TICK` and `MAX_TICK`, defined in the `TickMath` library and it must be + // a multiple of `key.tickSpacing`. + poolManager.unlock( + abi.encode( + CallbackData( + Callbacks.PlaceOrder, abi.encode(CallbackDataPlace(key, msg.sender, zeroForOne, tick, liquidity)) + ) + ) + ); + + // emit the place event + emit Place(msg.sender, orderId, key, tick, zeroForOne, liquidity); + } + + /** + * @dev Cancels a limit order by removing liquidity from the pool. Takes a `PoolKey` `key`, `tickLower` of the order, + * direction `zeroForOne` indicating whether it was buying currency0 or currency1, and recipient address `to` for the + * removed liquidity. Note that partial cancellation is not supported - the entire liquidity added by the msg.sender will be removed. + * Note also that cancelling an order will cancel the order placed by the msg.sender, not orders placed by other users in the same tick range. + * The interaction with the `poolManager` is done via the `unlock` function, which will trigger the `{unlockCallback}` function. + */ + function cancelOrder(PoolKey calldata key, int24 tickLower, bool zeroForOne, address to) external { + // get the order + OrderIdLibrary.OrderId orderId = getOrderId(key, tickLower, zeroForOne); + OrderInfo storage orderInfo = orderInfos[orderId]; + + // revert if the order is already filled + if (orderInfo.filled) revert Filled(); + + // get the liquidity added by the msg.sender + uint128 liquidity = orderInfo.liquidity[msg.sender]; + + // revert if the liquidity is 0 + if (liquidity == 0) revert ZeroLiquidity(); + + // delete the liquidity from the order + delete orderInfo.liquidity[msg.sender]; + + // subtract the liquidity from the total liquidity + orderInfo.liquidityTotal -= liquidity; + + // unlock the callback to the poolManager, the callback will trigger `unlockCallback` + // and remove the liquidity from the pool. Note that this function will return the fees accrued + // by the position, since the limit order is a liquidity addition. + // Note that `amount0Fee` and `amount1Fee` are the fees accrued by the position and will not be transferred to + // the `to` address. Instead, they will be added to the order info (benefiting the remaining limit order placers). + (uint256 amount0Fee, uint256 amount1Fee) = abi.decode( + poolManager.unlock( + abi.encode( + CallbackData( + Callbacks.CancelOrder, + abi.encode( + CallbackDataCancel( + key, tickLower, -int256(uint256(liquidity)), to, liquidity == orderInfo.liquidityTotal + ) + ) + ) + ) + ), + (uint256, uint256) + ); + + // add the fees to the order info + // note that the currency totals must be updated after poolManager call as they depend on the returned values of the callback. + // This is safe as these functions are only callable on the trusted poolManager + unchecked { + // slither-disable-next-line reentrancy-no-eth + orderInfo.currency0Total += amount0Fee; + // slither-disable-next-line reentrancy-no-eth + orderInfo.currency1Total += amount1Fee; + } + + // emit the cancel event + emit Cancel(msg.sender, orderId, key, tickLower, zeroForOne, liquidity); + } + + /** + * @dev Withdraws liquidity from a filled order, sending it to address `to`. Takes an `OrderId` `orderId` of the filled + * order to withdraw from. Returns the withdrawn amounts as `(amount0, amount1)`. Can only be called after the order is + * filled - use `cancelOrder` to remove liquidity from unfilled orders. The interaction with the `poolManager` is done via the + * `unlock` function, which will trigger the `{unlockCallback}` function. + */ + function withdraw(OrderIdLibrary.OrderId orderId, address to) external returns (uint256 amount0, uint256 amount1) { + // get the order info + OrderInfo storage orderInfo = orderInfos[orderId]; + + // revert if the order is not filled + if (!orderInfo.filled) revert NotFilled(); + + // get the liquidity added by the msg.sender + uint128 liquidity = orderInfo.liquidity[msg.sender]; + + // revert if the liquidity is 0 + if (liquidity == 0) revert ZeroLiquidity(); + + // delete the liquidity from the order + delete orderInfo.liquidity[msg.sender]; + + // get the total liquidity in the order + uint128 liquidityTotal = orderInfo.liquidityTotal; + + // calculate the amount of currency0 and currency1 owed to the msg.sender + amount0 = FullMath.mulDiv(orderInfo.currency0Total, liquidity, liquidityTotal); + amount1 = FullMath.mulDiv(orderInfo.currency1Total, liquidity, liquidityTotal); + + // subtract the amount of currency0 and currency1 from the order info + orderInfo.currency0Total -= amount0; + orderInfo.currency1Total -= amount1; + + // unlock the callback to the poolManager, the callback will trigger `unlockCallback` + // and return the liquidity to the `to` address. + poolManager.unlock( + abi.encode( + CallbackData( + Callbacks.Withdraw, + abi.encode(CallbackDataWithdraw(orderInfo.currency0, orderInfo.currency1, amount0, amount1, to)) + ) + ) + ); + + // emit the withdraw event + emit Withdraw(msg.sender, orderId, liquidity); + } + + /** + * @dev Handles callbacks from the `PoolManager` for order operations. Takes encoded `rawData` containing the callback type + * and operation-specific data. Returns encoded data containing fees accrued for cancel operations, or empty bytes + * otherwise. Only callable by the PoolManager. + */ + function unlockCallback(bytes calldata rawData) + external + virtual + override + onlyPoolManager + returns (bytes memory returnData) + { + // decode the callback data + CallbackData memory callbackData = abi.decode(rawData, (CallbackData)); + + // handle the callback based on the type + if (callbackData.callbackType == Callbacks.PlaceOrder) { + // decode the callback data + CallbackDataPlace memory placeData = abi.decode(callbackData.data, (CallbackDataPlace)); + + _handlePlaceCallback(placeData); + } else if (callbackData.callbackType == Callbacks.CancelOrder) { + // decode the callback data + CallbackDataCancel memory cancelData = abi.decode(callbackData.data, (CallbackDataCancel)); + + (uint256 amount0Fee, uint256 amount1Fee) = _handleCancelCallback(cancelData); + + // return the fees accrued by the position encoded in the return data + return abi.encode(amount0Fee, amount1Fee); + } else if (callbackData.callbackType == Callbacks.Withdraw) { + CallbackDataWithdraw memory withdrawData = abi.decode(callbackData.data, (CallbackDataWithdraw)); + + _handleWithdrawCallback(withdrawData); + } + } + + /** + * @dev Internal handler for place order callbacks. Takes `placeData` containing the order details and adds the + * specified liquidity to the pool out of range. Reverts if the order would be placed in range or on the wrong + * side of the range. + */ + function _handlePlaceCallback(CallbackDataPlace memory placeData) internal { + // get the pool key + PoolKey memory key = placeData.key; + + // add the out of range liquidity to the pool + (BalanceDelta delta,) = poolManager.modifyLiquidity( + key, + ModifyLiquidityParams({ + tickLower: placeData.tickLower, + tickUpper: placeData.tickLower + key.tickSpacing, + liquidityDelta: int256(uint256(placeData.liquidity)), + salt: 0 + }), + ZERO_BYTES + ); + + // if the amount of currency0 is negative, the limit order is to sell `currency0` for `currency1` + if (delta.amount0() < 0) { + // if the amount of currency1 is not 0, the limit order is in range + if (delta.amount1() != 0) revert InRange(); + // if `zeroForOne` is false, the limit order is wrong side of the range + if (!placeData.zeroForOne) revert CrossedRange(); + + // settle the currency0 to the owner + key.currency0.settle(poolManager, placeData.owner, uint256(uint128(-delta.amount0())), false); + } else { + // if the amount of currency0 is not 0, the limit order is in range + if (delta.amount0() != 0) revert InRange(); + // if `zeroForOne` is true, the limit order is wrong side of the range + if (placeData.zeroForOne) revert CrossedRange(); + + // settle the currency1 to the owner + key.currency1.settle(poolManager, placeData.owner, uint256(uint128(-delta.amount1())), false); + } + } + + /** + * @dev Internal handler for cancel order callbacks. Takes `cancelData` containing the cancellation details and + * removes liquidity from the pool. Returns accrued fees `(amount0Fee, amount1Fee)` which are allocated to remaining + * limit order placers, or to the cancelling user if they're removing all liquidity. + */ + function _handleCancelCallback(CallbackDataCancel memory cancelData) + internal + returns (uint256 amount0Fee, uint256 amount1Fee) + { + // get the tick upper + int24 tickUpper = cancelData.tickLower + cancelData.key.tickSpacing; + + // remove the liquidity from the pool. The fees accrued by the position are included in the `cancelDelta` + (BalanceDelta cancelDelta, BalanceDelta feesAccrued) = poolManager.modifyLiquidity( + cancelData.key, + ModifyLiquidityParams({ + tickLower: cancelData.tickLower, + tickUpper: tickUpper, + liquidityDelta: cancelData.liquidityDelta, + salt: 0 + }), + ZERO_BYTES + ); + + BalanceDelta principalDelta; + + // because `modifyPosition` includes not just principal value but also fees, we cannot allocate + // the proceeds pro-rata. if we were to do so, users who have been in a limit order that's partially filled + // could be unfairly diluted by a user synchronously placing then canceling a limit order to skim off fees. + // to prevent this, we allocate all fee revenue to remaining limit order placers, unless this is the last order. + if (!cancelData.removingAllLiquidity) { + // if the amount of fees in currency0 is positive, mint currency0 to the hook + if (feesAccrued.amount0() > 0) { + poolManager.mint( + address(this), cancelData.key.currency0.toId(), amount0Fee = uint128(feesAccrued.amount0()) + ); + } + + // if the amount of fees in currency1 is positive, mint currency1 to the hook + if (feesAccrued.amount1() > 0) { + poolManager.mint( + address(this), cancelData.key.currency1.toId(), amount1Fee = uint128(feesAccrued.amount1()) + ); + } + + // if the `removingAllLiquidity` flag is false, the fees accrued will be allocated to the remaining limit order placers + // so we need to subtract the fees from the `cancelDelta` to get the principal delta + principalDelta = cancelDelta - feesAccrued; + } else { + // if the `removingAllLiquidity` flag is true, the fees accrued will be allocated to the placer of the last limit order being cancelled + // so we can just use the `cancelDelta` as the principal delta + principalDelta = cancelDelta; + } + + // if the amount of currency0 is positive, take the currency0 from the pool and send it to the `to` address + if (principalDelta.amount0() > 0) { + cancelData.key.currency0.take(poolManager, cancelData.to, uint256(uint128(principalDelta.amount0())), false); + } + + // if the amount of currency1 is positive, take the currency1 from the pool and send it to the `to` address + if (principalDelta.amount1() > 0) { + cancelData.key.currency1.take(poolManager, cancelData.to, uint256(uint128(principalDelta.amount1())), false); + } + } + + /** + * @dev Internal handler for withdraw callbacks. Takes `withdrawData` containing withdrawal amounts and recipient, + * burns the specified currency amounts from the hook, and transfers them to the recipient address. + */ + function _handleWithdrawCallback(CallbackDataWithdraw memory withdrawData) internal { + // if the amount of currency0 is positive, burn the currency0 from the hook + if (withdrawData.currency0Amount > 0) { + // burn the currency0 from the hook + poolManager.burn(address(this), withdrawData.currency0.toId(), withdrawData.currency0Amount); + // take the currency0 from the pool and send it to the `to` address + poolManager.take(withdrawData.currency0, withdrawData.to, withdrawData.currency0Amount); + } + + // if the amount of currency1 is positive, burn the currency1 from the hook + if (withdrawData.currency1Amount > 0) { + // burn the currency1 from the hook + poolManager.burn(address(this), withdrawData.currency1.toId(), withdrawData.currency1Amount); + // take the currency1 from the pool and send it to the `to` address + poolManager.take(withdrawData.currency1, withdrawData.to, withdrawData.currency1Amount); + } + } + + /** + * @dev Internal handler for filling limit orders when price crosses a tick. Takes a `PoolKey` `key`, target `tickLower`, + * and direction `zeroForOne`. Removes liquidity from filled orders, mints the received currencies to the hook, and + * updates order state to track filled amounts. + */ + function _fillOrder(PoolKey calldata key, int24 tickLower, bool zeroForOne) internal { + // get the order + OrderIdLibrary.OrderId orderId = getOrderId(key, tickLower, zeroForOne); + + // if the order is not default (not initialized), fill it + if (!orderId.equals(ORDER_ID_DEFAULT)) { + // get the order info + OrderInfo storage orderInfo = orderInfos[orderId]; + + // set the order as filled + orderInfo.filled = true; + + // set the order as default (inactive) + setOrderId(key, tickLower, zeroForOne, ORDER_ID_DEFAULT); + + // modify the liquidity to remove the order liquidity from the pool + (BalanceDelta delta,) = poolManager.modifyLiquidity( + key, + ModifyLiquidityParams({ + tickLower: tickLower, + tickUpper: tickLower + key.tickSpacing, + liquidityDelta: -int256(uint256(orderInfo.liquidityTotal)), + salt: 0 + }), + ZERO_BYTES + ); + + uint128 amount0 = 0; + uint128 amount1 = 0; + + // if the amount of currency0 is positive, mint the currency0 to the hook + if (delta.amount0() > 0) { + poolManager.mint(address(this), key.currency0.toId(), amount0 = uint128(delta.amount0())); + } + + // if the amount of currency1 is positive, mint the currency1 to the hook + if (delta.amount1() > 0) { + poolManager.mint(address(this), key.currency1.toId(), amount1 = uint128(delta.amount1())); + } + + // add the amount of currency0 and currency1 to the order info + // note that the currency totals must be updated after poolManager calls as they depend on the returned values. + // This is safe as these functions are only callable on the trusted poolManager + unchecked { + // slither-disable-next-line reentrancy-no-eth + orderInfo.currency0Total += amount0; + // slither-disable-next-line reentrancy-no-eth + orderInfo.currency1Total += amount1; + } + + // emit the fill event + emit Fill(orderId, key, tickLower, zeroForOne); + } + } + + /** + * @dev Internal helper that calculates the range of ticks crossed during a price change. Takes a `PoolId` `poolId` + * and `tickSpacing`, returns the current `tickLower` and the range of ticks crossed (`lower`, `upper`) that need + * to be checked for limit orders. + */ + function _getCrossedTicks(PoolId poolId, int24 tickSpacing) + internal + view + returns (int24 tickLower, int24 lower, int24 upper) + { + tickLower = getTickLower(getTick(poolId), tickSpacing); + int24 tickLowerLast = getTickLowerLast(poolId); + + if (tickLower < tickLowerLast) { + lower = tickLower + tickSpacing; + upper = tickLowerLast; + } else { + lower = tickLowerLast; + upper = tickLower - tickSpacing; + } + } + + /** + * @dev Returns the last recorded lower tick for a given pool. Takes a `PoolId` `poolId` and returns the + * stored `tickLowerLast` value. + */ + function getTickLowerLast(PoolId poolId) public view returns (int24) { + return tickLowerLasts[poolId]; + } + + /** + * @dev Retrieves the order id for a given pool position. Takes a `PoolKey` `key`, target `tickLower`, and direction + * `zeroForOne` indicating whether it's buying currency0 or currency1. Returns the {OrderId} associated with this + * position, or the default order id if no order exists. + */ + function getOrderId(PoolKey memory key, int24 tickLower, bool zeroForOne) + public + view + returns (OrderIdLibrary.OrderId) + { + return orders[keccak256(abi.encode(key, tickLower, zeroForOne))]; + } + + /** + * @dev Internal helper that updates the order ID mapping. Takes a `PoolKey` `key`, target `tickLower`, direction + * `zeroForOne`, and `orderId` to store. Associates the given order id with the pool position's hash. + */ + function setOrderId(PoolKey memory key, int24 tickLower, bool zeroForOne, OrderIdLibrary.OrderId orderId) private { + orders[keccak256(abi.encode(key, tickLower, zeroForOne))] = orderId; + } + + /** + * @dev Get the tick lower. Takes a `tick` and `tickSpacing` and returns the nearest valid tick boundary + * at or below the input tick, accounting for negative tick handling. + */ + function getTickLower(int24 tick, int24 tickSpacing) private pure returns (int24) { + // slither-disable-next-line divide-before-multiply + int24 compressed = tick / tickSpacing; + if (tick < 0 && tick % tickSpacing != 0) compressed--; // round towards negative infinity + return compressed * tickSpacing; + } + + /** + * @dev Get the liquidity of an order for a given order id and owner. Takes an {OrderId} `orderId` and `owner` address + * and returns the amount of liquidity the owner has contributed to the order. + */ + function getOrderLiquidity(OrderIdLibrary.OrderId orderId, address owner) external view returns (uint256) { + return orderInfos[orderId].liquidity[owner]; + } + + /** + * @dev Get the current tick for a given pool. Takes a `PoolId` `poolId` and returns the tick calculated + * from the pool's current sqrt price. + */ + function getTick(PoolId poolId) private view returns (int24 tick) { + (uint160 sqrtPriceX96,,,) = poolManager.getSlot0(poolId); + tick = TickMath.getTickAtSqrtPrice(sqrtPriceX96); + } + + /** + * @dev Get the hook permissions for this contract. Returns a `Hooks.Permissions` struct configured to enable + * `afterInitialize` and `afterSwap` hooks while disabling all other hooks. + */ + function getHookPermissions() public pure virtual override returns (Hooks.Permissions memory permissions) { + return Hooks.Permissions({ + beforeInitialize: false, + afterInitialize: true, + beforeAddLiquidity: false, + beforeRemoveLiquidity: false, + afterAddLiquidity: false, + afterRemoveLiquidity: false, + beforeSwap: false, + afterSwap: true, + beforeDonate: false, + afterDonate: false, + beforeSwapReturnDelta: false, + afterSwapReturnDelta: false, + afterAddLiquidityReturnDelta: false, + afterRemoveLiquidityReturnDelta: false + }); + } +} diff --git a/CVLByExample/UniswapHooks/lib/uniswap-hooks/src/general/LiquidityPenaltyHook.sol b/CVLByExample/UniswapHooks/lib/uniswap-hooks/src/general/LiquidityPenaltyHook.sol new file mode 100644 index 00000000..1853a81b --- /dev/null +++ b/CVLByExample/UniswapHooks/lib/uniswap-hooks/src/general/LiquidityPenaltyHook.sol @@ -0,0 +1,196 @@ +// SPDX-License-Identifier: MIT +// OpenZeppelin Uniswap Hooks (last updated v0.1.1) (src/general/LiquidityPenaltyHook.sol) + +pragma solidity ^0.8.24; + +import {BaseHook} from "src/base/BaseHook.sol"; +import {Pool} from "v4-core/src/libraries/Pool.sol"; +import {BalanceDelta, BalanceDeltaLibrary, toBalanceDelta} from "v4-core/src/types/BalanceDelta.sol"; +import {IPoolManager} from "v4-core/src/interfaces/IPoolManager.sol"; +import {Position} from "v4-core/src/libraries/Position.sol"; +import {SafeCast} from "v4-core/src/libraries/SafeCast.sol"; +import {StateLibrary} from "v4-core/src/libraries/StateLibrary.sol"; +import {PoolKey} from "v4-core/src/types/PoolKey.sol"; +import {Hooks} from "v4-core/src/libraries/Hooks.sol"; +import {PoolId} from "v4-core/src/types/PoolId.sol"; +import {FullMath} from "v4-core/src/libraries/FullMath.sol"; +import {CurrencySettler} from "src/utils/CurrencySettler.sol"; +import {Currency} from "v4-core/src/types/Currency.sol"; +import {SwapParams, ModifyLiquidityParams} from "v4-core/src/types/PoolOperation.sol"; + +/** + * @dev This hook implements a mechanism penalize liquidity provision based on time of adding and removal of liquidty. + * The main purpose is to prevent JIT (Just in Time) attacks on liquidity pools. Specifically, + * it checks if a liquidity position was added to the pool within a certain block number range (at least 1 block) + * and if so, it donates some of the fees to the pool (up to 100% of the fees). This way, the hook effectively taxes JIT attackers by donating their + * expected profits back to the pool. + * The hook calculates the fee donation based on the block number when the liquidity was added + * and the block number offset. + * + * At constructor, the hook requires a block number offset. This offset is the number of blocks at which the hook + * will donate the fees to the pool. The minimum value is 1. + * + * NOTE: The hook donates the fees to the current in range liquidity providers (at the time of liquidity removal). + * If the block number offset is much later than the actual block number when the liquidity was added, the + * liquidity providers who benefited from the fees will be the ones in range at the time of liquidity removal, not + * the ones in range at the time of liquidity addition. + * + * WARNING: This is experimental software and is provided on an "as is" and "as available" basis. We do + * not give any warranties and will not be liable for any losses incurred through any use of this code + * base. + * + * _Available since v0.1.1_ + */ +contract LiquidityPenaltyHook is BaseHook { + using CurrencySettler for Currency; + using StateLibrary for IPoolManager; + using SafeCast for uint256; + + /** + * @notice The minimum block number amount for the offset. + */ + uint256 public constant MIN_BLOCK_NUMBER_OFFSET = 1; + + /** + * @notice Tracks the last block number when a liquidity position was added to the pool. + */ + mapping(PoolId id => mapping(bytes32 positionKey => uint256 blockNumber)) public lastAddedLiquidity; + + /** + * @notice The block number offset before which if the liquidity is removed, the fees will be donated to the pool. + */ + uint256 public immutable blockNumberOffset; + + /** + * @dev Hook was attempted to be deployed with a block number offset that is too low. + */ + error BlockNumberOffsetTooLow(); + + /** + * @dev Set the `PoolManager` address and the block number offset. + */ + constructor(IPoolManager _poolManager, uint256 _blockNumberOffset) BaseHook(_poolManager) { + if (_blockNumberOffset < MIN_BLOCK_NUMBER_OFFSET) revert BlockNumberOffsetTooLow(); + blockNumberOffset = _blockNumberOffset; + } + + /** + * @dev Hooks into the `afterAddLiquidity` hook to record the block number when the liquidity was added to track + * JIT liquidity positions. + */ + function _afterAddLiquidity( + address sender, + PoolKey calldata key, + ModifyLiquidityParams calldata params, + BalanceDelta, + BalanceDelta, + bytes calldata + ) internal virtual override returns (bytes4, BalanceDelta) { + // Get the position key + bytes32 positionKey = Position.calculatePositionKey(sender, params.tickLower, params.tickUpper, params.salt); + + // Record the block number when the liquidity was added + lastAddedLiquidity[key.toId()][positionKey] = block.number; + + return (this.afterAddLiquidity.selector, BalanceDeltaLibrary.ZERO_DELTA); + } + + /** + * @dev Hooks into the `afterRemoveLiquidity` hook to donate accumulated fees for a JIT liquidity position created. + */ + function _afterRemoveLiquidity( + address sender, + PoolKey calldata key, + ModifyLiquidityParams calldata params, + BalanceDelta, + BalanceDelta feeDelta, + bytes calldata + ) internal virtual override returns (bytes4, BalanceDelta) { + PoolId id = key.toId(); + + bytes32 positionKey = Position.calculatePositionKey(sender, params.tickLower, params.tickUpper, params.salt); + + uint128 liquidity = poolManager.getLiquidity(id); + + // We need to check if the liquidity is greater than 0 to prevent donating when there are no liquidity positions. + if (block.number - lastAddedLiquidity[id][positionKey] < blockNumberOffset && liquidity > 0) { + // If the liquidity provider removes liquidity before the block number offset, the hook donates + // a part of the fees to the pool (i.e., in range liquidity providers at the time of liquidity removal). + + BalanceDelta liquidityPenalty = _calculateLiquidityPenalty(feeDelta, id, positionKey); + + BalanceDelta deltaHook = poolManager.donate( + key, uint256(int256(liquidityPenalty.amount0())), uint256(int256(liquidityPenalty.amount1())), "" + ); + + BalanceDelta returnDelta = toBalanceDelta(-deltaHook.amount0(), -deltaHook.amount1()); + + return (this.afterRemoveLiquidity.selector, returnDelta); + } + + return (this.afterRemoveLiquidity.selector, BalanceDeltaLibrary.ZERO_DELTA); + } + + /** + * @dev Calculates the fee donation when a liquidity position is removed before the block number offset. + * + * @param feeDelta The `BalanceDelta` of the fees from the position. + * @param poolId The `PoolId` of the pool. + * @param positionKey The `bytes32` key of the position. + * @return liquidityPenalty The `BalanceDelta` of the liquidity penalty. + */ + function _calculateLiquidityPenalty(BalanceDelta feeDelta, PoolId poolId, bytes32 positionKey) + internal + virtual + returns (BalanceDelta liquidityPenalty) + { + int128 amount0FeeDelta = feeDelta.amount0(); + int128 amount1FeeDelta = feeDelta.amount1(); + + // amount0 and amount1 are necesseraly greater than or equal to 0, since they are fee rewards + // This is the implementation of a linear penalty on the fees, where the penalty decreases linearly from 100% of the fees at the block + // where liquidity was added to the pool to 0% after the block number offset. + // The formula is: + // liquidityPenalty = feeDelta * ( 1 - (block.number - lastAddedLiquidity[id][positionKey]) / blockNumberOffset) + // NOTE: this function is called only if the liquidity is removed before the block number offset, i.e., + // block.number - lastAddedLiquidity[poolId][positionKey] < blockNumberOffset + // so the subtraction is safe and won't overflow + uint256 amount0LiquidityPenalty = FullMath.mulDiv( + SafeCast.toUint128(amount0FeeDelta), + blockNumberOffset - (block.number - lastAddedLiquidity[poolId][positionKey]), // wont't overflow, since block.number - lastAddedLiquidity[poolId][positionKey] < blockNumberOffset + blockNumberOffset + ); + uint256 amount1LiquidityPenalty = FullMath.mulDiv( + SafeCast.toUint128(amount1FeeDelta), + blockNumberOffset - (block.number - lastAddedLiquidity[poolId][positionKey]), + blockNumberOffset + ); + + // although the amounts are returned as uint256, they must fit in int128, since they are fee rewards + liquidityPenalty = toBalanceDelta(amount0LiquidityPenalty.toInt128(), amount1LiquidityPenalty.toInt128()); + } + + /** + * Set the hooks permissions, specifically `afterAddLiquidity`, `afterRemoveLiquidity` and `afterRemoveLiquidityReturnDelta`. + * + * @return permissions The permissions for the hook. + */ + function getHookPermissions() public pure virtual override returns (Hooks.Permissions memory permissions) { + return Hooks.Permissions({ + beforeInitialize: false, + afterInitialize: false, + beforeAddLiquidity: false, + afterAddLiquidity: true, + beforeRemoveLiquidity: false, + afterRemoveLiquidity: true, + beforeSwap: false, + afterSwap: false, + beforeDonate: false, + afterDonate: false, + beforeSwapReturnDelta: false, + afterSwapReturnDelta: false, + afterAddLiquidityReturnDelta: false, + afterRemoveLiquidityReturnDelta: true + }); + } +} diff --git a/CVLByExample/UniswapHooks/lib/uniswap-hooks/src/general/README.adoc b/CVLByExample/UniswapHooks/lib/uniswap-hooks/src/general/README.adoc new file mode 100644 index 00000000..fe25f871 --- /dev/null +++ b/CVLByExample/UniswapHooks/lib/uniswap-hooks/src/general/README.adoc @@ -0,0 +1,16 @@ += General + +[.readme-notice] +NOTE: This document is better viewed on the docs page. + +Ready-to-use hooks built on top of the base and fee abstract contracts + + * {LiquidityPenaltyHook}: Hook resistant to just-in-time liquidity attacks + * {AntiSandwichHook}: Hook resistant to sandwich attacks on swaps + * {LimitOrderHook}: Hook to enable limit order placing on liquidity pools + +== Hooks + +{{LiquidityPenaltyHook}} +{{AntiSandwichHook}} +{{LimitOrderHook}} diff --git a/CVLByExample/UniswapHooks/lib/uniswap-hooks/src/interfaces/IHookEvents.sol b/CVLByExample/UniswapHooks/lib/uniswap-hooks/src/interfaces/IHookEvents.sol new file mode 100644 index 00000000..67c489d8 --- /dev/null +++ b/CVLByExample/UniswapHooks/lib/uniswap-hooks/src/interfaces/IHookEvents.sol @@ -0,0 +1,38 @@ +// SPDX-License-Identifier: MIT +// OpenZeppelin Uniswap Hooks (last updated v0.1.0) (src/interfaces/IHookEvents.sol) + +pragma solidity ^0.8.24; + +/** + * @dev Interface for standard hook events emission. + * + * NOTE: Hooks should inherit from this interface to standardized event emission. + */ +interface IHookEvents { + /** + * @dev Event emitted when a swap is executed. + */ + event HookSwap( + bytes32 indexed id, + address indexed sender, + int128 amount0, + int128 amount1, + uint128 hookLPfeeAmount0, + uint128 hookLPfeeAmount1 + ); + + /** + * @dev Event emitted when a fee is collected. + */ + event HookFee(bytes32 indexed id, address indexed sender, uint128 feeAmount0, uint128 feeAmount1); + + /** + * @dev Event emitted when a liquidity modification is executed. + */ + event HookModifyLiquidity(bytes32 indexed id, address indexed sender, int128 amount0, int128 amount1); + + /** + * @dev Event emitted when a bonus is added to a swap. + */ + event HookBonus(bytes32 indexed id, uint128 amount0, uint128 amount1); +} diff --git a/CVLByExample/UniswapHooks/lib/uniswap-hooks/src/interfaces/README.adoc b/CVLByExample/UniswapHooks/lib/uniswap-hooks/src/interfaces/README.adoc new file mode 100644 index 00000000..4ce94a66 --- /dev/null +++ b/CVLByExample/UniswapHooks/lib/uniswap-hooks/src/interfaces/README.adoc @@ -0,0 +1,12 @@ += Interfaces + +[.readme-notice] +NOTE: This document is better viewed on the docs page. + +Interfaces to be used by Hooks + + * {IHookEvents}: Interface for standard hook events emission. + +== Interfaces + +{{IHookEvents}} diff --git a/CVLByExample/UniswapHooks/lib/uniswap-hooks/src/utils/CurrencySettler.sol b/CVLByExample/UniswapHooks/lib/uniswap-hooks/src/utils/CurrencySettler.sol new file mode 100644 index 00000000..84fbaa9f --- /dev/null +++ b/CVLByExample/UniswapHooks/lib/uniswap-hooks/src/utils/CurrencySettler.sol @@ -0,0 +1,69 @@ +// SPDX-License-Identifier: MIT +// OpenZeppelin Uniswap Hooks (last updated v0.1.0) (src/utils/CurrencySettler.sol) + +pragma solidity ^0.8.24; + +import {Currency} from "v4-core/src/types/Currency.sol"; +import {IPoolManager} from "v4-core/src/interfaces/IPoolManager.sol"; +import {IERC20} from "openzeppelin/token/ERC20/IERC20.sol"; +import {SafeERC20} from "openzeppelin/token/ERC20/utils/SafeERC20.sol"; + +/** + * @dev Library used to interact with the `PoolManager` to settle any open deltas. + * To settle a positive delta (a credit to the user), a user may take or mint. + * To settle a negative delta (a debt on the user), a user may transfer or burn to pay off a debt. + * + * Based on the https://github.com/Uniswap/v4-core/blob/main/test/utils/CurrencySettler.sol[Uniswap v4 test utils implementation]. + * + * NOTE: Deltas are synced before any ERC-20 transfers in {settle} function. + */ +library CurrencySettler { + using SafeERC20 for IERC20; + + /** + * @notice Settle (pay) a currency to the `PoolManager` + * @param currency Currency to settle + * @param poolManager `PoolManager` to settle to + * @param payer Address of the payer, which can be the hook itself or an external address. + * @param amount Amount to send + * @param burn If true, burn the ERC-6909 token, otherwise transfer ERC-20 to the `PoolManager` + */ + function settle(Currency currency, IPoolManager poolManager, address payer, uint256 amount, bool burn) internal { + // Early return when amount is 0 given that some tokens may revert in this case + if (amount == 0) return; + + // For native currencies or burns, calling sync is not required + // Short circuit for ERC-6909 burns to support ERC-6909-wrapped native tokens + if (burn) { + poolManager.burn(payer, currency.toId(), amount); + } else if (currency.isAddressZero()) { + poolManager.sync(currency); + poolManager.settle{value: amount}(); + } else { + poolManager.sync(currency); + if (payer != address(this)) { + IERC20(Currency.unwrap(currency)).safeTransferFrom(payer, address(poolManager), amount); + } else { + IERC20(Currency.unwrap(currency)).safeTransfer(address(poolManager), amount); + } + poolManager.settle(); + } + } + + /** + * @notice Take (receive) a currency from the `PoolManager` + * @param currency Currency to take + * @param poolManager `PoolManager` to take from + * @param recipient Address of the recipient of the ERC-6909 or ERC-20 token. + * @param amount Amount to receive + * @param claims If true, mint the ERC-6909 token, otherwise transfer ERC-20 from the `PoolManager` to recipient + */ + function take(Currency currency, IPoolManager poolManager, address recipient, uint256 amount, bool claims) + internal + { + // Early return when amount is 0 given that some tokens may revert in this case + if (amount == 0) return; + + claims ? poolManager.mint(recipient, currency.toId(), amount) : poolManager.take(currency, recipient, amount); + } +} diff --git a/CVLByExample/UniswapHooks/lib/uniswap-hooks/src/utils/README.adoc b/CVLByExample/UniswapHooks/lib/uniswap-hooks/src/utils/README.adoc new file mode 100644 index 00000000..07389fb5 --- /dev/null +++ b/CVLByExample/UniswapHooks/lib/uniswap-hooks/src/utils/README.adoc @@ -0,0 +1,12 @@ += Utilities + +[.readme-notice] +NOTE: This document is better viewed on the docs page. + +Libraries and general purpose utilities to help develop hooks. + + * {CurrencySettler}: Library used to interact with the `PoolManager` to settle any open deltas, with support for ERC-6909 and native currencies. + +== Libraries + +{{CurrencySettler}} From 2f5332787515d158a48357d22ff5fea1957b8b51 Mon Sep 17 00:00:00 2001 From: Jochen Hoenicke Date: Thu, 21 Aug 2025 14:15:10 +0200 Subject: [PATCH 3/5] Added solmate library --- .gitmodules | 0 .../lib/v4-core/lib/solmate/src/auth/Auth.sol | 64 + .../v4-core/lib/solmate/src/auth/Owned.sol | 44 + .../auth/authorities/MultiRolesAuthority.sol | 123 ++ .../src/auth/authorities/RolesAuthority.sol | 108 + .../lib/solmate/src/mixins/ERC4626.sol | 183 ++ .../v4-core/lib/solmate/src/test/Auth.t.sol | 192 ++ .../solmate/src/test/Bytes32AddressLib.t.sol | 22 + .../lib/solmate/src/test/CREATE3.t.sol | 99 + .../lib/solmate/src/test/DSTestPlus.t.sol | 72 + .../lib/solmate/src/test/ERC1155.t.sol | 1777 +++++++++++++++++ .../v4-core/lib/solmate/src/test/ERC20.t.sol | 531 +++++ .../lib/solmate/src/test/ERC4626.t.sol | 446 +++++ .../lib/solmate/src/test/ERC6909.t.sol | 378 ++++ .../v4-core/lib/solmate/src/test/ERC721.t.sol | 727 +++++++ .../solmate/src/test/FixedPointMathLib.t.sol | 360 ++++ .../lib/solmate/src/test/LibString.t.sol | 148 ++ .../lib/solmate/src/test/MerkleProofLib.t.sol | 50 + .../src/test/MultiRolesAuthority.t.sol | 321 +++ .../v4-core/lib/solmate/src/test/Owned.t.sol | 40 + .../solmate/src/test/ReentrancyGuard.t.sol | 56 + .../lib/solmate/src/test/RolesAuthority.t.sol | 148 ++ .../lib/solmate/src/test/SSTORE2.t.sol | 152 ++ .../lib/solmate/src/test/SafeCastLib.t.sol | 644 ++++++ .../solmate/src/test/SafeTransferLib.t.sol | 610 ++++++ .../lib/solmate/src/test/SignedWadMath.t.sol | 74 + .../v4-core/lib/solmate/src/test/WETH.t.sol | 146 ++ .../src/test/utils/DSInvariantTest.sol | 16 + .../lib/solmate/src/test/utils/DSTestPlus.sol | 179 ++ .../lib/solmate/src/test/utils/Hevm.sol | 107 + .../src/test/utils/mocks/MockAuthChild.sol | 12 + .../src/test/utils/mocks/MockAuthority.sol | 20 + .../src/test/utils/mocks/MockERC1155.sol | 42 + .../src/test/utils/mocks/MockERC20.sol | 20 + .../src/test/utils/mocks/MockERC4626.sol | 28 + .../src/test/utils/mocks/MockERC6909.sol | 22 + .../src/test/utils/mocks/MockERC721.sol | 30 + .../src/test/utils/mocks/MockOwned.sol | 12 + .../utils/weird-tokens/MissingReturnToken.sol | 83 + .../utils/weird-tokens/ReturnsFalseToken.sol | 61 + .../weird-tokens/ReturnsGarbageToken.sol | 115 ++ .../weird-tokens/ReturnsTooLittleToken.sol | 70 + .../weird-tokens/ReturnsTooMuchToken.sol | 98 + .../utils/weird-tokens/ReturnsTwoToken.sol | 61 + .../utils/weird-tokens/RevertingToken.sol | 61 + .../lib/solmate/src/tokens/ERC1155.sol | 257 +++ .../v4-core/lib/solmate/src/tokens/ERC20.sol | 206 ++ .../lib/solmate/src/tokens/ERC6909.sol | 118 ++ .../v4-core/lib/solmate/src/tokens/ERC721.sol | 231 +++ .../v4-core/lib/solmate/src/tokens/WETH.sol | 35 + .../solmate/src/utils/Bytes32AddressLib.sol | 14 + .../v4-core/lib/solmate/src/utils/CREATE3.sol | 87 + .../solmate/src/utils/FixedPointMathLib.sol | 255 +++ .../lib/solmate/src/utils/LibString.sol | 77 + .../lib/solmate/src/utils/MerkleProofLib.sol | 48 + .../lib/solmate/src/utils/ReentrancyGuard.sol | 19 + .../v4-core/lib/solmate/src/utils/SSTORE2.sol | 101 + .../lib/solmate/src/utils/SafeCastLib.sol | 193 ++ .../lib/solmate/src/utils/SafeTransferLib.sol | 128 ++ .../lib/solmate/src/utils/SignedWadMath.sol | 245 +++ 60 files changed, 10566 insertions(+) delete mode 100644 .gitmodules create mode 100644 CVLByExample/UniswapHooks/lib/uniswap-hooks/lib/v4-core/lib/solmate/src/auth/Auth.sol create mode 100644 CVLByExample/UniswapHooks/lib/uniswap-hooks/lib/v4-core/lib/solmate/src/auth/Owned.sol create mode 100644 CVLByExample/UniswapHooks/lib/uniswap-hooks/lib/v4-core/lib/solmate/src/auth/authorities/MultiRolesAuthority.sol create mode 100644 CVLByExample/UniswapHooks/lib/uniswap-hooks/lib/v4-core/lib/solmate/src/auth/authorities/RolesAuthority.sol create mode 100644 CVLByExample/UniswapHooks/lib/uniswap-hooks/lib/v4-core/lib/solmate/src/mixins/ERC4626.sol create mode 100644 CVLByExample/UniswapHooks/lib/uniswap-hooks/lib/v4-core/lib/solmate/src/test/Auth.t.sol create mode 100644 CVLByExample/UniswapHooks/lib/uniswap-hooks/lib/v4-core/lib/solmate/src/test/Bytes32AddressLib.t.sol create mode 100644 CVLByExample/UniswapHooks/lib/uniswap-hooks/lib/v4-core/lib/solmate/src/test/CREATE3.t.sol create mode 100644 CVLByExample/UniswapHooks/lib/uniswap-hooks/lib/v4-core/lib/solmate/src/test/DSTestPlus.t.sol create mode 100644 CVLByExample/UniswapHooks/lib/uniswap-hooks/lib/v4-core/lib/solmate/src/test/ERC1155.t.sol create mode 100644 CVLByExample/UniswapHooks/lib/uniswap-hooks/lib/v4-core/lib/solmate/src/test/ERC20.t.sol create mode 100644 CVLByExample/UniswapHooks/lib/uniswap-hooks/lib/v4-core/lib/solmate/src/test/ERC4626.t.sol create mode 100644 CVLByExample/UniswapHooks/lib/uniswap-hooks/lib/v4-core/lib/solmate/src/test/ERC6909.t.sol create mode 100644 CVLByExample/UniswapHooks/lib/uniswap-hooks/lib/v4-core/lib/solmate/src/test/ERC721.t.sol create mode 100644 CVLByExample/UniswapHooks/lib/uniswap-hooks/lib/v4-core/lib/solmate/src/test/FixedPointMathLib.t.sol create mode 100644 CVLByExample/UniswapHooks/lib/uniswap-hooks/lib/v4-core/lib/solmate/src/test/LibString.t.sol create mode 100644 CVLByExample/UniswapHooks/lib/uniswap-hooks/lib/v4-core/lib/solmate/src/test/MerkleProofLib.t.sol create mode 100644 CVLByExample/UniswapHooks/lib/uniswap-hooks/lib/v4-core/lib/solmate/src/test/MultiRolesAuthority.t.sol create mode 100644 CVLByExample/UniswapHooks/lib/uniswap-hooks/lib/v4-core/lib/solmate/src/test/Owned.t.sol create mode 100644 CVLByExample/UniswapHooks/lib/uniswap-hooks/lib/v4-core/lib/solmate/src/test/ReentrancyGuard.t.sol create mode 100644 CVLByExample/UniswapHooks/lib/uniswap-hooks/lib/v4-core/lib/solmate/src/test/RolesAuthority.t.sol create mode 100644 CVLByExample/UniswapHooks/lib/uniswap-hooks/lib/v4-core/lib/solmate/src/test/SSTORE2.t.sol create mode 100644 CVLByExample/UniswapHooks/lib/uniswap-hooks/lib/v4-core/lib/solmate/src/test/SafeCastLib.t.sol create mode 100644 CVLByExample/UniswapHooks/lib/uniswap-hooks/lib/v4-core/lib/solmate/src/test/SafeTransferLib.t.sol create mode 100644 CVLByExample/UniswapHooks/lib/uniswap-hooks/lib/v4-core/lib/solmate/src/test/SignedWadMath.t.sol create mode 100644 CVLByExample/UniswapHooks/lib/uniswap-hooks/lib/v4-core/lib/solmate/src/test/WETH.t.sol create mode 100644 CVLByExample/UniswapHooks/lib/uniswap-hooks/lib/v4-core/lib/solmate/src/test/utils/DSInvariantTest.sol create mode 100644 CVLByExample/UniswapHooks/lib/uniswap-hooks/lib/v4-core/lib/solmate/src/test/utils/DSTestPlus.sol create mode 100644 CVLByExample/UniswapHooks/lib/uniswap-hooks/lib/v4-core/lib/solmate/src/test/utils/Hevm.sol create mode 100644 CVLByExample/UniswapHooks/lib/uniswap-hooks/lib/v4-core/lib/solmate/src/test/utils/mocks/MockAuthChild.sol create mode 100644 CVLByExample/UniswapHooks/lib/uniswap-hooks/lib/v4-core/lib/solmate/src/test/utils/mocks/MockAuthority.sol create mode 100644 CVLByExample/UniswapHooks/lib/uniswap-hooks/lib/v4-core/lib/solmate/src/test/utils/mocks/MockERC1155.sol create mode 100644 CVLByExample/UniswapHooks/lib/uniswap-hooks/lib/v4-core/lib/solmate/src/test/utils/mocks/MockERC20.sol create mode 100644 CVLByExample/UniswapHooks/lib/uniswap-hooks/lib/v4-core/lib/solmate/src/test/utils/mocks/MockERC4626.sol create mode 100644 CVLByExample/UniswapHooks/lib/uniswap-hooks/lib/v4-core/lib/solmate/src/test/utils/mocks/MockERC6909.sol create mode 100644 CVLByExample/UniswapHooks/lib/uniswap-hooks/lib/v4-core/lib/solmate/src/test/utils/mocks/MockERC721.sol create mode 100644 CVLByExample/UniswapHooks/lib/uniswap-hooks/lib/v4-core/lib/solmate/src/test/utils/mocks/MockOwned.sol create mode 100644 CVLByExample/UniswapHooks/lib/uniswap-hooks/lib/v4-core/lib/solmate/src/test/utils/weird-tokens/MissingReturnToken.sol create mode 100644 CVLByExample/UniswapHooks/lib/uniswap-hooks/lib/v4-core/lib/solmate/src/test/utils/weird-tokens/ReturnsFalseToken.sol create mode 100644 CVLByExample/UniswapHooks/lib/uniswap-hooks/lib/v4-core/lib/solmate/src/test/utils/weird-tokens/ReturnsGarbageToken.sol create mode 100644 CVLByExample/UniswapHooks/lib/uniswap-hooks/lib/v4-core/lib/solmate/src/test/utils/weird-tokens/ReturnsTooLittleToken.sol create mode 100644 CVLByExample/UniswapHooks/lib/uniswap-hooks/lib/v4-core/lib/solmate/src/test/utils/weird-tokens/ReturnsTooMuchToken.sol create mode 100644 CVLByExample/UniswapHooks/lib/uniswap-hooks/lib/v4-core/lib/solmate/src/test/utils/weird-tokens/ReturnsTwoToken.sol create mode 100644 CVLByExample/UniswapHooks/lib/uniswap-hooks/lib/v4-core/lib/solmate/src/test/utils/weird-tokens/RevertingToken.sol create mode 100644 CVLByExample/UniswapHooks/lib/uniswap-hooks/lib/v4-core/lib/solmate/src/tokens/ERC1155.sol create mode 100644 CVLByExample/UniswapHooks/lib/uniswap-hooks/lib/v4-core/lib/solmate/src/tokens/ERC20.sol create mode 100644 CVLByExample/UniswapHooks/lib/uniswap-hooks/lib/v4-core/lib/solmate/src/tokens/ERC6909.sol create mode 100644 CVLByExample/UniswapHooks/lib/uniswap-hooks/lib/v4-core/lib/solmate/src/tokens/ERC721.sol create mode 100644 CVLByExample/UniswapHooks/lib/uniswap-hooks/lib/v4-core/lib/solmate/src/tokens/WETH.sol create mode 100644 CVLByExample/UniswapHooks/lib/uniswap-hooks/lib/v4-core/lib/solmate/src/utils/Bytes32AddressLib.sol create mode 100644 CVLByExample/UniswapHooks/lib/uniswap-hooks/lib/v4-core/lib/solmate/src/utils/CREATE3.sol create mode 100644 CVLByExample/UniswapHooks/lib/uniswap-hooks/lib/v4-core/lib/solmate/src/utils/FixedPointMathLib.sol create mode 100644 CVLByExample/UniswapHooks/lib/uniswap-hooks/lib/v4-core/lib/solmate/src/utils/LibString.sol create mode 100644 CVLByExample/UniswapHooks/lib/uniswap-hooks/lib/v4-core/lib/solmate/src/utils/MerkleProofLib.sol create mode 100644 CVLByExample/UniswapHooks/lib/uniswap-hooks/lib/v4-core/lib/solmate/src/utils/ReentrancyGuard.sol create mode 100644 CVLByExample/UniswapHooks/lib/uniswap-hooks/lib/v4-core/lib/solmate/src/utils/SSTORE2.sol create mode 100644 CVLByExample/UniswapHooks/lib/uniswap-hooks/lib/v4-core/lib/solmate/src/utils/SafeCastLib.sol create mode 100644 CVLByExample/UniswapHooks/lib/uniswap-hooks/lib/v4-core/lib/solmate/src/utils/SafeTransferLib.sol create mode 100644 CVLByExample/UniswapHooks/lib/uniswap-hooks/lib/v4-core/lib/solmate/src/utils/SignedWadMath.sol diff --git a/.gitmodules b/.gitmodules deleted file mode 100644 index e69de29b..00000000 diff --git a/CVLByExample/UniswapHooks/lib/uniswap-hooks/lib/v4-core/lib/solmate/src/auth/Auth.sol b/CVLByExample/UniswapHooks/lib/uniswap-hooks/lib/v4-core/lib/solmate/src/auth/Auth.sol new file mode 100644 index 00000000..3807ccda --- /dev/null +++ b/CVLByExample/UniswapHooks/lib/uniswap-hooks/lib/v4-core/lib/solmate/src/auth/Auth.sol @@ -0,0 +1,64 @@ +// SPDX-License-Identifier: AGPL-3.0-only +pragma solidity >=0.8.0; + +/// @notice Provides a flexible and updatable auth pattern which is completely separate from application logic. +/// @author Solmate (https://github.com/transmissions11/solmate/blob/main/src/auth/Auth.sol) +/// @author Modified from Dappsys (https://github.com/dapphub/ds-auth/blob/master/src/auth.sol) +abstract contract Auth { + event OwnershipTransferred(address indexed user, address indexed newOwner); + + event AuthorityUpdated(address indexed user, Authority indexed newAuthority); + + address public owner; + + Authority public authority; + + constructor(address _owner, Authority _authority) { + owner = _owner; + authority = _authority; + + emit OwnershipTransferred(msg.sender, _owner); + emit AuthorityUpdated(msg.sender, _authority); + } + + modifier requiresAuth() virtual { + require(isAuthorized(msg.sender, msg.sig), "UNAUTHORIZED"); + + _; + } + + function isAuthorized(address user, bytes4 functionSig) internal view virtual returns (bool) { + Authority auth = authority; // Memoizing authority saves us a warm SLOAD, around 100 gas. + + // Checking if the caller is the owner only after calling the authority saves gas in most cases, but be + // aware that this makes protected functions uncallable even to the owner if the authority is out of order. + return (address(auth) != address(0) && auth.canCall(user, address(this), functionSig)) || user == owner; + } + + function setAuthority(Authority newAuthority) public virtual { + // We check if the caller is the owner first because we want to ensure they can + // always swap out the authority even if it's reverting or using up a lot of gas. + require(msg.sender == owner || authority.canCall(msg.sender, address(this), msg.sig)); + + authority = newAuthority; + + emit AuthorityUpdated(msg.sender, newAuthority); + } + + function transferOwnership(address newOwner) public virtual requiresAuth { + owner = newOwner; + + emit OwnershipTransferred(msg.sender, newOwner); + } +} + +/// @notice A generic interface for a contract which provides authorization data to an Auth instance. +/// @author Solmate (https://github.com/transmissions11/solmate/blob/main/src/auth/Auth.sol) +/// @author Modified from Dappsys (https://github.com/dapphub/ds-auth/blob/master/src/auth.sol) +interface Authority { + function canCall( + address user, + address target, + bytes4 functionSig + ) external view returns (bool); +} diff --git a/CVLByExample/UniswapHooks/lib/uniswap-hooks/lib/v4-core/lib/solmate/src/auth/Owned.sol b/CVLByExample/UniswapHooks/lib/uniswap-hooks/lib/v4-core/lib/solmate/src/auth/Owned.sol new file mode 100644 index 00000000..e82b44d8 --- /dev/null +++ b/CVLByExample/UniswapHooks/lib/uniswap-hooks/lib/v4-core/lib/solmate/src/auth/Owned.sol @@ -0,0 +1,44 @@ +// SPDX-License-Identifier: AGPL-3.0-only +pragma solidity >=0.8.0; + +/// @notice Simple single owner authorization mixin. +/// @author Solmate (https://github.com/transmissions11/solmate/blob/main/src/auth/Owned.sol) +abstract contract Owned { + /*////////////////////////////////////////////////////////////// + EVENTS + //////////////////////////////////////////////////////////////*/ + + event OwnershipTransferred(address indexed user, address indexed newOwner); + + /*////////////////////////////////////////////////////////////// + OWNERSHIP STORAGE + //////////////////////////////////////////////////////////////*/ + + address public owner; + + modifier onlyOwner() virtual { + require(msg.sender == owner, "UNAUTHORIZED"); + + _; + } + + /*////////////////////////////////////////////////////////////// + CONSTRUCTOR + //////////////////////////////////////////////////////////////*/ + + constructor(address _owner) { + owner = _owner; + + emit OwnershipTransferred(address(0), _owner); + } + + /*////////////////////////////////////////////////////////////// + OWNERSHIP LOGIC + //////////////////////////////////////////////////////////////*/ + + function transferOwnership(address newOwner) public virtual onlyOwner { + owner = newOwner; + + emit OwnershipTransferred(msg.sender, newOwner); + } +} diff --git a/CVLByExample/UniswapHooks/lib/uniswap-hooks/lib/v4-core/lib/solmate/src/auth/authorities/MultiRolesAuthority.sol b/CVLByExample/UniswapHooks/lib/uniswap-hooks/lib/v4-core/lib/solmate/src/auth/authorities/MultiRolesAuthority.sol new file mode 100644 index 00000000..3ff80bb0 --- /dev/null +++ b/CVLByExample/UniswapHooks/lib/uniswap-hooks/lib/v4-core/lib/solmate/src/auth/authorities/MultiRolesAuthority.sol @@ -0,0 +1,123 @@ +// SPDX-License-Identifier: AGPL-3.0-only +pragma solidity >=0.8.0; + +import {Auth, Authority} from "../Auth.sol"; + +/// @notice Flexible and target agnostic role based Authority that supports up to 256 roles. +/// @author Solmate (https://github.com/transmissions11/solmate/blob/main/src/auth/authorities/MultiRolesAuthority.sol) +contract MultiRolesAuthority is Auth, Authority { + /*////////////////////////////////////////////////////////////// + EVENTS + //////////////////////////////////////////////////////////////*/ + + event UserRoleUpdated(address indexed user, uint8 indexed role, bool enabled); + + event PublicCapabilityUpdated(bytes4 indexed functionSig, bool enabled); + + event RoleCapabilityUpdated(uint8 indexed role, bytes4 indexed functionSig, bool enabled); + + event TargetCustomAuthorityUpdated(address indexed target, Authority indexed authority); + + /*////////////////////////////////////////////////////////////// + CONSTRUCTOR + //////////////////////////////////////////////////////////////*/ + + constructor(address _owner, Authority _authority) Auth(_owner, _authority) {} + + /*////////////////////////////////////////////////////////////// + CUSTOM TARGET AUTHORITY STORAGE + //////////////////////////////////////////////////////////////*/ + + mapping(address => Authority) public getTargetCustomAuthority; + + /*////////////////////////////////////////////////////////////// + ROLE/USER STORAGE + //////////////////////////////////////////////////////////////*/ + + mapping(address => bytes32) public getUserRoles; + + mapping(bytes4 => bool) public isCapabilityPublic; + + mapping(bytes4 => bytes32) public getRolesWithCapability; + + function doesUserHaveRole(address user, uint8 role) public view virtual returns (bool) { + return (uint256(getUserRoles[user]) >> role) & 1 != 0; + } + + function doesRoleHaveCapability(uint8 role, bytes4 functionSig) public view virtual returns (bool) { + return (uint256(getRolesWithCapability[functionSig]) >> role) & 1 != 0; + } + + /*////////////////////////////////////////////////////////////// + AUTHORIZATION LOGIC + //////////////////////////////////////////////////////////////*/ + + function canCall( + address user, + address target, + bytes4 functionSig + ) public view virtual override returns (bool) { + Authority customAuthority = getTargetCustomAuthority[target]; + + if (address(customAuthority) != address(0)) return customAuthority.canCall(user, target, functionSig); + + return + isCapabilityPublic[functionSig] || bytes32(0) != getUserRoles[user] & getRolesWithCapability[functionSig]; + } + + /*/////////////////////////////////////////////////////////////// + CUSTOM TARGET AUTHORITY CONFIGURATION LOGIC + //////////////////////////////////////////////////////////////*/ + + function setTargetCustomAuthority(address target, Authority customAuthority) public virtual requiresAuth { + getTargetCustomAuthority[target] = customAuthority; + + emit TargetCustomAuthorityUpdated(target, customAuthority); + } + + /*////////////////////////////////////////////////////////////// + PUBLIC CAPABILITY CONFIGURATION LOGIC + //////////////////////////////////////////////////////////////*/ + + function setPublicCapability(bytes4 functionSig, bool enabled) public virtual requiresAuth { + isCapabilityPublic[functionSig] = enabled; + + emit PublicCapabilityUpdated(functionSig, enabled); + } + + /*////////////////////////////////////////////////////////////// + USER ROLE ASSIGNMENT LOGIC + //////////////////////////////////////////////////////////////*/ + + function setUserRole( + address user, + uint8 role, + bool enabled + ) public virtual requiresAuth { + if (enabled) { + getUserRoles[user] |= bytes32(1 << role); + } else { + getUserRoles[user] &= ~bytes32(1 << role); + } + + emit UserRoleUpdated(user, role, enabled); + } + + /*////////////////////////////////////////////////////////////// + ROLE CAPABILITY CONFIGURATION LOGIC + //////////////////////////////////////////////////////////////*/ + + function setRoleCapability( + uint8 role, + bytes4 functionSig, + bool enabled + ) public virtual requiresAuth { + if (enabled) { + getRolesWithCapability[functionSig] |= bytes32(1 << role); + } else { + getRolesWithCapability[functionSig] &= ~bytes32(1 << role); + } + + emit RoleCapabilityUpdated(role, functionSig, enabled); + } +} diff --git a/CVLByExample/UniswapHooks/lib/uniswap-hooks/lib/v4-core/lib/solmate/src/auth/authorities/RolesAuthority.sol b/CVLByExample/UniswapHooks/lib/uniswap-hooks/lib/v4-core/lib/solmate/src/auth/authorities/RolesAuthority.sol new file mode 100644 index 00000000..aa5cc71c --- /dev/null +++ b/CVLByExample/UniswapHooks/lib/uniswap-hooks/lib/v4-core/lib/solmate/src/auth/authorities/RolesAuthority.sol @@ -0,0 +1,108 @@ +// SPDX-License-Identifier: AGPL-3.0-only +pragma solidity >=0.8.0; + +import {Auth, Authority} from "../Auth.sol"; + +/// @notice Role based Authority that supports up to 256 roles. +/// @author Solmate (https://github.com/transmissions11/solmate/blob/main/src/auth/authorities/RolesAuthority.sol) +/// @author Modified from Dappsys (https://github.com/dapphub/ds-roles/blob/master/src/roles.sol) +contract RolesAuthority is Auth, Authority { + /*////////////////////////////////////////////////////////////// + EVENTS + //////////////////////////////////////////////////////////////*/ + + event UserRoleUpdated(address indexed user, uint8 indexed role, bool enabled); + + event PublicCapabilityUpdated(address indexed target, bytes4 indexed functionSig, bool enabled); + + event RoleCapabilityUpdated(uint8 indexed role, address indexed target, bytes4 indexed functionSig, bool enabled); + + /*////////////////////////////////////////////////////////////// + CONSTRUCTOR + //////////////////////////////////////////////////////////////*/ + + constructor(address _owner, Authority _authority) Auth(_owner, _authority) {} + + /*////////////////////////////////////////////////////////////// + ROLE/USER STORAGE + //////////////////////////////////////////////////////////////*/ + + mapping(address => bytes32) public getUserRoles; + + mapping(address => mapping(bytes4 => bool)) public isCapabilityPublic; + + mapping(address => mapping(bytes4 => bytes32)) public getRolesWithCapability; + + function doesUserHaveRole(address user, uint8 role) public view virtual returns (bool) { + return (uint256(getUserRoles[user]) >> role) & 1 != 0; + } + + function doesRoleHaveCapability( + uint8 role, + address target, + bytes4 functionSig + ) public view virtual returns (bool) { + return (uint256(getRolesWithCapability[target][functionSig]) >> role) & 1 != 0; + } + + /*////////////////////////////////////////////////////////////// + AUTHORIZATION LOGIC + //////////////////////////////////////////////////////////////*/ + + function canCall( + address user, + address target, + bytes4 functionSig + ) public view virtual override returns (bool) { + return + isCapabilityPublic[target][functionSig] || + bytes32(0) != getUserRoles[user] & getRolesWithCapability[target][functionSig]; + } + + /*////////////////////////////////////////////////////////////// + ROLE CAPABILITY CONFIGURATION LOGIC + //////////////////////////////////////////////////////////////*/ + + function setPublicCapability( + address target, + bytes4 functionSig, + bool enabled + ) public virtual requiresAuth { + isCapabilityPublic[target][functionSig] = enabled; + + emit PublicCapabilityUpdated(target, functionSig, enabled); + } + + function setRoleCapability( + uint8 role, + address target, + bytes4 functionSig, + bool enabled + ) public virtual requiresAuth { + if (enabled) { + getRolesWithCapability[target][functionSig] |= bytes32(1 << role); + } else { + getRolesWithCapability[target][functionSig] &= ~bytes32(1 << role); + } + + emit RoleCapabilityUpdated(role, target, functionSig, enabled); + } + + /*////////////////////////////////////////////////////////////// + USER ROLE ASSIGNMENT LOGIC + //////////////////////////////////////////////////////////////*/ + + function setUserRole( + address user, + uint8 role, + bool enabled + ) public virtual requiresAuth { + if (enabled) { + getUserRoles[user] |= bytes32(1 << role); + } else { + getUserRoles[user] &= ~bytes32(1 << role); + } + + emit UserRoleUpdated(user, role, enabled); + } +} diff --git a/CVLByExample/UniswapHooks/lib/uniswap-hooks/lib/v4-core/lib/solmate/src/mixins/ERC4626.sol b/CVLByExample/UniswapHooks/lib/uniswap-hooks/lib/v4-core/lib/solmate/src/mixins/ERC4626.sol new file mode 100644 index 00000000..af56c156 --- /dev/null +++ b/CVLByExample/UniswapHooks/lib/uniswap-hooks/lib/v4-core/lib/solmate/src/mixins/ERC4626.sol @@ -0,0 +1,183 @@ +// SPDX-License-Identifier: AGPL-3.0-only +pragma solidity >=0.8.0; + +import {ERC20} from "../tokens/ERC20.sol"; +import {SafeTransferLib} from "../utils/SafeTransferLib.sol"; +import {FixedPointMathLib} from "../utils/FixedPointMathLib.sol"; + +/// @notice Minimal ERC4626 tokenized Vault implementation. +/// @author Solmate (https://github.com/transmissions11/solmate/blob/main/src/mixins/ERC4626.sol) +abstract contract ERC4626 is ERC20 { + using SafeTransferLib for ERC20; + using FixedPointMathLib for uint256; + + /*////////////////////////////////////////////////////////////// + EVENTS + //////////////////////////////////////////////////////////////*/ + + event Deposit(address indexed caller, address indexed owner, uint256 assets, uint256 shares); + + event Withdraw( + address indexed caller, + address indexed receiver, + address indexed owner, + uint256 assets, + uint256 shares + ); + + /*////////////////////////////////////////////////////////////// + IMMUTABLES + //////////////////////////////////////////////////////////////*/ + + ERC20 public immutable asset; + + constructor( + ERC20 _asset, + string memory _name, + string memory _symbol + ) ERC20(_name, _symbol, _asset.decimals()) { + asset = _asset; + } + + /*////////////////////////////////////////////////////////////// + DEPOSIT/WITHDRAWAL LOGIC + //////////////////////////////////////////////////////////////*/ + + function deposit(uint256 assets, address receiver) public virtual returns (uint256 shares) { + // Check for rounding error since we round down in previewDeposit. + require((shares = previewDeposit(assets)) != 0, "ZERO_SHARES"); + + // Need to transfer before minting or ERC777s could reenter. + asset.safeTransferFrom(msg.sender, address(this), assets); + + _mint(receiver, shares); + + emit Deposit(msg.sender, receiver, assets, shares); + + afterDeposit(assets, shares); + } + + function mint(uint256 shares, address receiver) public virtual returns (uint256 assets) { + assets = previewMint(shares); // No need to check for rounding error, previewMint rounds up. + + // Need to transfer before minting or ERC777s could reenter. + asset.safeTransferFrom(msg.sender, address(this), assets); + + _mint(receiver, shares); + + emit Deposit(msg.sender, receiver, assets, shares); + + afterDeposit(assets, shares); + } + + function withdraw( + uint256 assets, + address receiver, + address owner + ) public virtual returns (uint256 shares) { + shares = previewWithdraw(assets); // No need to check for rounding error, previewWithdraw rounds up. + + if (msg.sender != owner) { + uint256 allowed = allowance[owner][msg.sender]; // Saves gas for limited approvals. + + if (allowed != type(uint256).max) allowance[owner][msg.sender] = allowed - shares; + } + + beforeWithdraw(assets, shares); + + _burn(owner, shares); + + emit Withdraw(msg.sender, receiver, owner, assets, shares); + + asset.safeTransfer(receiver, assets); + } + + function redeem( + uint256 shares, + address receiver, + address owner + ) public virtual returns (uint256 assets) { + if (msg.sender != owner) { + uint256 allowed = allowance[owner][msg.sender]; // Saves gas for limited approvals. + + if (allowed != type(uint256).max) allowance[owner][msg.sender] = allowed - shares; + } + + // Check for rounding error since we round down in previewRedeem. + require((assets = previewRedeem(shares)) != 0, "ZERO_ASSETS"); + + beforeWithdraw(assets, shares); + + _burn(owner, shares); + + emit Withdraw(msg.sender, receiver, owner, assets, shares); + + asset.safeTransfer(receiver, assets); + } + + /*////////////////////////////////////////////////////////////// + ACCOUNTING LOGIC + //////////////////////////////////////////////////////////////*/ + + function totalAssets() public view virtual returns (uint256); + + function convertToShares(uint256 assets) public view virtual returns (uint256) { + uint256 supply = totalSupply; // Saves an extra SLOAD if totalSupply is non-zero. + + return supply == 0 ? assets : assets.mulDivDown(supply, totalAssets()); + } + + function convertToAssets(uint256 shares) public view virtual returns (uint256) { + uint256 supply = totalSupply; // Saves an extra SLOAD if totalSupply is non-zero. + + return supply == 0 ? shares : shares.mulDivDown(totalAssets(), supply); + } + + function previewDeposit(uint256 assets) public view virtual returns (uint256) { + return convertToShares(assets); + } + + function previewMint(uint256 shares) public view virtual returns (uint256) { + uint256 supply = totalSupply; // Saves an extra SLOAD if totalSupply is non-zero. + + return supply == 0 ? shares : shares.mulDivUp(totalAssets(), supply); + } + + function previewWithdraw(uint256 assets) public view virtual returns (uint256) { + uint256 supply = totalSupply; // Saves an extra SLOAD if totalSupply is non-zero. + + return supply == 0 ? assets : assets.mulDivUp(supply, totalAssets()); + } + + function previewRedeem(uint256 shares) public view virtual returns (uint256) { + return convertToAssets(shares); + } + + /*////////////////////////////////////////////////////////////// + DEPOSIT/WITHDRAWAL LIMIT LOGIC + //////////////////////////////////////////////////////////////*/ + + function maxDeposit(address) public view virtual returns (uint256) { + return type(uint256).max; + } + + function maxMint(address) public view virtual returns (uint256) { + return type(uint256).max; + } + + function maxWithdraw(address owner) public view virtual returns (uint256) { + return convertToAssets(balanceOf[owner]); + } + + function maxRedeem(address owner) public view virtual returns (uint256) { + return balanceOf[owner]; + } + + /*////////////////////////////////////////////////////////////// + INTERNAL HOOKS LOGIC + //////////////////////////////////////////////////////////////*/ + + function beforeWithdraw(uint256 assets, uint256 shares) internal virtual {} + + function afterDeposit(uint256 assets, uint256 shares) internal virtual {} +} diff --git a/CVLByExample/UniswapHooks/lib/uniswap-hooks/lib/v4-core/lib/solmate/src/test/Auth.t.sol b/CVLByExample/UniswapHooks/lib/uniswap-hooks/lib/v4-core/lib/solmate/src/test/Auth.t.sol new file mode 100644 index 00000000..9e315288 --- /dev/null +++ b/CVLByExample/UniswapHooks/lib/uniswap-hooks/lib/v4-core/lib/solmate/src/test/Auth.t.sol @@ -0,0 +1,192 @@ +// SPDX-License-Identifier: AGPL-3.0-only +pragma solidity 0.8.15; + +import {DSTestPlus} from "./utils/DSTestPlus.sol"; +import {MockAuthChild} from "./utils/mocks/MockAuthChild.sol"; +import {MockAuthority} from "./utils/mocks/MockAuthority.sol"; + +import {Authority} from "../auth/Auth.sol"; + +contract OutOfOrderAuthority is Authority { + function canCall( + address, + address, + bytes4 + ) public pure override returns (bool) { + revert("OUT_OF_ORDER"); + } +} + +contract AuthTest is DSTestPlus { + MockAuthChild mockAuthChild; + + function setUp() public { + mockAuthChild = new MockAuthChild(); + } + + function testTransferOwnershipAsOwner() public { + mockAuthChild.transferOwnership(address(0xBEEF)); + assertEq(mockAuthChild.owner(), address(0xBEEF)); + } + + function testSetAuthorityAsOwner() public { + mockAuthChild.setAuthority(Authority(address(0xBEEF))); + assertEq(address(mockAuthChild.authority()), address(0xBEEF)); + } + + function testCallFunctionAsOwner() public { + mockAuthChild.updateFlag(); + } + + function testTransferOwnershipWithPermissiveAuthority() public { + mockAuthChild.setAuthority(new MockAuthority(true)); + mockAuthChild.transferOwnership(address(0)); + mockAuthChild.transferOwnership(address(this)); + } + + function testSetAuthorityWithPermissiveAuthority() public { + mockAuthChild.setAuthority(new MockAuthority(true)); + mockAuthChild.transferOwnership(address(0)); + mockAuthChild.setAuthority(Authority(address(0xBEEF))); + } + + function testCallFunctionWithPermissiveAuthority() public { + mockAuthChild.setAuthority(new MockAuthority(true)); + mockAuthChild.transferOwnership(address(0)); + mockAuthChild.updateFlag(); + } + + function testSetAuthorityAsOwnerWithOutOfOrderAuthority() public { + mockAuthChild.setAuthority(new OutOfOrderAuthority()); + mockAuthChild.setAuthority(new MockAuthority(true)); + } + + function testFailTransferOwnershipAsNonOwner() public { + mockAuthChild.transferOwnership(address(0)); + mockAuthChild.transferOwnership(address(0xBEEF)); + } + + function testFailSetAuthorityAsNonOwner() public { + mockAuthChild.transferOwnership(address(0)); + mockAuthChild.setAuthority(Authority(address(0xBEEF))); + } + + function testFailCallFunctionAsNonOwner() public { + mockAuthChild.transferOwnership(address(0)); + mockAuthChild.updateFlag(); + } + + function testFailTransferOwnershipWithRestrictiveAuthority() public { + mockAuthChild.setAuthority(new MockAuthority(false)); + mockAuthChild.transferOwnership(address(0)); + mockAuthChild.transferOwnership(address(this)); + } + + function testFailSetAuthorityWithRestrictiveAuthority() public { + mockAuthChild.setAuthority(new MockAuthority(false)); + mockAuthChild.transferOwnership(address(0)); + mockAuthChild.setAuthority(Authority(address(0xBEEF))); + } + + function testFailCallFunctionWithRestrictiveAuthority() public { + mockAuthChild.setAuthority(new MockAuthority(false)); + mockAuthChild.transferOwnership(address(0)); + mockAuthChild.updateFlag(); + } + + function testFailTransferOwnershipAsOwnerWithOutOfOrderAuthority() public { + mockAuthChild.setAuthority(new OutOfOrderAuthority()); + mockAuthChild.transferOwnership(address(0)); + } + + function testFailCallFunctionAsOwnerWithOutOfOrderAuthority() public { + mockAuthChild.setAuthority(new OutOfOrderAuthority()); + mockAuthChild.updateFlag(); + } + + function testTransferOwnershipAsOwner(address newOwner) public { + mockAuthChild.transferOwnership(newOwner); + assertEq(mockAuthChild.owner(), newOwner); + } + + function testSetAuthorityAsOwner(Authority newAuthority) public { + mockAuthChild.setAuthority(newAuthority); + assertEq(address(mockAuthChild.authority()), address(newAuthority)); + } + + function testTransferOwnershipWithPermissiveAuthority(address deadOwner, address newOwner) public { + if (deadOwner == address(this)) deadOwner = address(0); + + mockAuthChild.setAuthority(new MockAuthority(true)); + mockAuthChild.transferOwnership(deadOwner); + mockAuthChild.transferOwnership(newOwner); + } + + function testSetAuthorityWithPermissiveAuthority(address deadOwner, Authority newAuthority) public { + if (deadOwner == address(this)) deadOwner = address(0); + + mockAuthChild.setAuthority(new MockAuthority(true)); + mockAuthChild.transferOwnership(deadOwner); + mockAuthChild.setAuthority(newAuthority); + } + + function testCallFunctionWithPermissiveAuthority(address deadOwner) public { + if (deadOwner == address(this)) deadOwner = address(0); + + mockAuthChild.setAuthority(new MockAuthority(true)); + mockAuthChild.transferOwnership(deadOwner); + mockAuthChild.updateFlag(); + } + + function testFailTransferOwnershipAsNonOwner(address deadOwner, address newOwner) public { + if (deadOwner == address(this)) deadOwner = address(0); + + mockAuthChild.transferOwnership(deadOwner); + mockAuthChild.transferOwnership(newOwner); + } + + function testFailSetAuthorityAsNonOwner(address deadOwner, Authority newAuthority) public { + if (deadOwner == address(this)) deadOwner = address(0); + + mockAuthChild.transferOwnership(deadOwner); + mockAuthChild.setAuthority(newAuthority); + } + + function testFailCallFunctionAsNonOwner(address deadOwner) public { + if (deadOwner == address(this)) deadOwner = address(0); + + mockAuthChild.transferOwnership(deadOwner); + mockAuthChild.updateFlag(); + } + + function testFailTransferOwnershipWithRestrictiveAuthority(address deadOwner, address newOwner) public { + if (deadOwner == address(this)) deadOwner = address(0); + + mockAuthChild.setAuthority(new MockAuthority(false)); + mockAuthChild.transferOwnership(deadOwner); + mockAuthChild.transferOwnership(newOwner); + } + + function testFailSetAuthorityWithRestrictiveAuthority(address deadOwner, Authority newAuthority) public { + if (deadOwner == address(this)) deadOwner = address(0); + + mockAuthChild.setAuthority(new MockAuthority(false)); + mockAuthChild.transferOwnership(deadOwner); + mockAuthChild.setAuthority(newAuthority); + } + + function testFailCallFunctionWithRestrictiveAuthority(address deadOwner) public { + if (deadOwner == address(this)) deadOwner = address(0); + + mockAuthChild.setAuthority(new MockAuthority(false)); + mockAuthChild.transferOwnership(deadOwner); + mockAuthChild.updateFlag(); + } + + function testFailTransferOwnershipAsOwnerWithOutOfOrderAuthority(address deadOwner) public { + if (deadOwner == address(this)) deadOwner = address(0); + + mockAuthChild.setAuthority(new OutOfOrderAuthority()); + mockAuthChild.transferOwnership(deadOwner); + } +} diff --git a/CVLByExample/UniswapHooks/lib/uniswap-hooks/lib/v4-core/lib/solmate/src/test/Bytes32AddressLib.t.sol b/CVLByExample/UniswapHooks/lib/uniswap-hooks/lib/v4-core/lib/solmate/src/test/Bytes32AddressLib.t.sol new file mode 100644 index 00000000..6c0a3d8e --- /dev/null +++ b/CVLByExample/UniswapHooks/lib/uniswap-hooks/lib/v4-core/lib/solmate/src/test/Bytes32AddressLib.t.sol @@ -0,0 +1,22 @@ +// SPDX-License-Identifier: AGPL-3.0-only +pragma solidity 0.8.15; + +import {DSTestPlus} from "./utils/DSTestPlus.sol"; + +import {Bytes32AddressLib} from "../utils/Bytes32AddressLib.sol"; + +contract Bytes32AddressLibTest is DSTestPlus { + function testFillLast12Bytes() public { + assertEq( + Bytes32AddressLib.fillLast12Bytes(0xfEEDFaCEcaFeBEEFfEEDFACecaFEBeeFfeEdfAce), + 0xfeedfacecafebeeffeedfacecafebeeffeedface000000000000000000000000 + ); + } + + function testFromLast20Bytes() public { + assertEq( + Bytes32AddressLib.fromLast20Bytes(0xfeedfacecafebeeffeedfacecafebeeffeedfacecafebeeffeedfacecafebeef), + 0xCAfeBeefFeedfAceCAFeBEEffEEDfaCecafEBeeF + ); + } +} diff --git a/CVLByExample/UniswapHooks/lib/uniswap-hooks/lib/v4-core/lib/solmate/src/test/CREATE3.t.sol b/CVLByExample/UniswapHooks/lib/uniswap-hooks/lib/v4-core/lib/solmate/src/test/CREATE3.t.sol new file mode 100644 index 00000000..7ee8fc0d --- /dev/null +++ b/CVLByExample/UniswapHooks/lib/uniswap-hooks/lib/v4-core/lib/solmate/src/test/CREATE3.t.sol @@ -0,0 +1,99 @@ +// SPDX-License-Identifier: AGPL-3.0-only +pragma solidity 0.8.15; + +import {WETH} from "../tokens/WETH.sol"; +import {DSTestPlus} from "./utils/DSTestPlus.sol"; +import {MockERC20} from "./utils/mocks/MockERC20.sol"; +import {MockAuthChild} from "./utils/mocks/MockAuthChild.sol"; + +import {CREATE3} from "../utils/CREATE3.sol"; + +contract Factory { + function deploy(bytes32 salt) public returns (address deployed) { + deployed = CREATE3.deploy( + salt, + abi.encodePacked(type(MockERC20).creationCode, abi.encode("Mock Token", "MOCK", 18)), + 0 + ); + } +} + +contract CREATE3Test is DSTestPlus { + function testDeployERC20() public { + bytes32 salt = keccak256(bytes("A salt!")); + MockERC20 deployed = MockERC20( + CREATE3.deploy( + salt, + abi.encodePacked(type(MockERC20).creationCode, abi.encode("Mock Token", "MOCK", 18)), + 0 + ) + ); + + assertEq(address(deployed), CREATE3.getDeployed(salt)); + + assertEq(deployed.name(), "Mock Token"); + assertEq(deployed.symbol(), "MOCK"); + assertEq(deployed.decimals(), 18); + } + + function testPredictDeployERC20() public { + bytes32 salt = keccak256(bytes("A salt!")); + Factory factory = new Factory(); + + MockERC20 deployed = MockERC20( + factory.deploy(salt) + ); + + assertEq(address(deployed), CREATE3.getDeployed(salt, address(factory))); + assertTrue(address(deployed) != CREATE3.getDeployed(salt)); + + assertEq(deployed.name(), "Mock Token"); + assertEq(deployed.symbol(), "MOCK"); + assertEq(deployed.decimals(), 18); + } + + function testFailDoubleDeploySameBytecode() public { + bytes32 salt = keccak256(bytes("Salty...")); + + CREATE3.deploy(salt, type(MockAuthChild).creationCode, 0); + CREATE3.deploy(salt, type(MockAuthChild).creationCode, 0); + } + + function testFailDoubleDeployDifferentBytecode() public { + bytes32 salt = keccak256(bytes("and sweet!")); + + CREATE3.deploy(salt, type(WETH).creationCode, 0); + CREATE3.deploy(salt, type(MockAuthChild).creationCode, 0); + } + + function testDeployERC20( + bytes32 salt, + string calldata name, + string calldata symbol, + uint8 decimals + ) public { + MockERC20 deployed = MockERC20( + CREATE3.deploy(salt, abi.encodePacked(type(MockERC20).creationCode, abi.encode(name, symbol, decimals)), 0) + ); + + assertEq(address(deployed), CREATE3.getDeployed(salt)); + + assertEq(deployed.name(), name); + assertEq(deployed.symbol(), symbol); + assertEq(deployed.decimals(), decimals); + } + + function testFailDoubleDeploySameBytecode(bytes32 salt, bytes calldata bytecode) public { + CREATE3.deploy(salt, bytecode, 0); + CREATE3.deploy(salt, bytecode, 0); + } + + function testFailDoubleDeployDifferentBytecode( + bytes32 salt, + bytes calldata bytecode1, + bytes calldata bytecode2 + ) public { + CREATE3.deploy(salt, bytecode1, 0); + CREATE3.deploy(salt, bytecode2, 0); + } +} diff --git a/CVLByExample/UniswapHooks/lib/uniswap-hooks/lib/v4-core/lib/solmate/src/test/DSTestPlus.t.sol b/CVLByExample/UniswapHooks/lib/uniswap-hooks/lib/v4-core/lib/solmate/src/test/DSTestPlus.t.sol new file mode 100644 index 00000000..db108600 --- /dev/null +++ b/CVLByExample/UniswapHooks/lib/uniswap-hooks/lib/v4-core/lib/solmate/src/test/DSTestPlus.t.sol @@ -0,0 +1,72 @@ +// SPDX-License-Identifier: AGPL-3.0-only +pragma solidity 0.8.15; + +import {DSTestPlus} from "./utils/DSTestPlus.sol"; + +contract DSTestPlusTest is DSTestPlus { + function testBound() public { + assertEq(bound(0, 69, 69), 69); + assertEq(bound(0, 68, 69), 68); + assertEq(bound(5, 0, 4), 0); + assertEq(bound(9999, 1337, 6666), 6006); + assertEq(bound(0, type(uint256).max - 6, type(uint256).max), type(uint256).max - 6); + assertEq(bound(6, type(uint256).max - 6, type(uint256).max), type(uint256).max); + } + + function testFailBoundMinBiggerThanMax() public { + bound(5, 100, 10); + } + + function testRelApproxEqBothZeroesPasses() public { + assertRelApproxEq(0, 0, 1e18); + assertRelApproxEq(0, 0, 0); + } + + function testBound( + uint256 num, + uint256 min, + uint256 max + ) public { + if (min > max) (min, max) = (max, min); + + uint256 bounded = bound(num, min, max); + + assertGe(bounded, min); + assertLe(bounded, max); + } + + function testFailBoundMinBiggerThanMax( + uint256 num, + uint256 min, + uint256 max + ) public { + if (max == min) { + unchecked { + min++; // Overflow is handled below. + } + } + + if (max > min) (min, max) = (max, min); + + bound(num, min, max); + } + + function testBrutalizeMemory() public brutalizeMemory("FEEDFACECAFEBEEFFEEDFACECAFEBEEF") { + bytes32 scratchSpace1; + bytes32 scratchSpace2; + bytes32 freeMem1; + bytes32 freeMem2; + + assembly { + scratchSpace1 := mload(0) + scratchSpace2 := mload(32) + freeMem1 := mload(mload(0x40)) + freeMem2 := mload(add(mload(0x40), 32)) + } + + assertGt(uint256(freeMem1), 0); + assertGt(uint256(freeMem2), 0); + assertGt(uint256(scratchSpace1), 0); + assertGt(uint256(scratchSpace2), 0); + } +} diff --git a/CVLByExample/UniswapHooks/lib/uniswap-hooks/lib/v4-core/lib/solmate/src/test/ERC1155.t.sol b/CVLByExample/UniswapHooks/lib/uniswap-hooks/lib/v4-core/lib/solmate/src/test/ERC1155.t.sol new file mode 100644 index 00000000..9e32d88b --- /dev/null +++ b/CVLByExample/UniswapHooks/lib/uniswap-hooks/lib/v4-core/lib/solmate/src/test/ERC1155.t.sol @@ -0,0 +1,1777 @@ +// SPDX-License-Identifier: AGPL-3.0-only +pragma solidity 0.8.15; + +import {DSTestPlus} from "./utils/DSTestPlus.sol"; +import {DSInvariantTest} from "./utils/DSInvariantTest.sol"; + +import {MockERC1155} from "./utils/mocks/MockERC1155.sol"; + +import {ERC1155TokenReceiver} from "../tokens/ERC1155.sol"; + +contract ERC1155Recipient is ERC1155TokenReceiver { + address public operator; + address public from; + uint256 public id; + uint256 public amount; + bytes public mintData; + + function onERC1155Received( + address _operator, + address _from, + uint256 _id, + uint256 _amount, + bytes calldata _data + ) public override returns (bytes4) { + operator = _operator; + from = _from; + id = _id; + amount = _amount; + mintData = _data; + + return ERC1155TokenReceiver.onERC1155Received.selector; + } + + address public batchOperator; + address public batchFrom; + uint256[] internal _batchIds; + uint256[] internal _batchAmounts; + bytes public batchData; + + function batchIds() external view returns (uint256[] memory) { + return _batchIds; + } + + function batchAmounts() external view returns (uint256[] memory) { + return _batchAmounts; + } + + function onERC1155BatchReceived( + address _operator, + address _from, + uint256[] calldata _ids, + uint256[] calldata _amounts, + bytes calldata _data + ) external override returns (bytes4) { + batchOperator = _operator; + batchFrom = _from; + _batchIds = _ids; + _batchAmounts = _amounts; + batchData = _data; + + return ERC1155TokenReceiver.onERC1155BatchReceived.selector; + } +} + +contract RevertingERC1155Recipient is ERC1155TokenReceiver { + function onERC1155Received( + address, + address, + uint256, + uint256, + bytes calldata + ) public pure override returns (bytes4) { + revert(string(abi.encodePacked(ERC1155TokenReceiver.onERC1155Received.selector))); + } + + function onERC1155BatchReceived( + address, + address, + uint256[] calldata, + uint256[] calldata, + bytes calldata + ) external pure override returns (bytes4) { + revert(string(abi.encodePacked(ERC1155TokenReceiver.onERC1155BatchReceived.selector))); + } +} + +contract WrongReturnDataERC1155Recipient is ERC1155TokenReceiver { + function onERC1155Received( + address, + address, + uint256, + uint256, + bytes calldata + ) public pure override returns (bytes4) { + return 0xCAFEBEEF; + } + + function onERC1155BatchReceived( + address, + address, + uint256[] calldata, + uint256[] calldata, + bytes calldata + ) external pure override returns (bytes4) { + return 0xCAFEBEEF; + } +} + +contract NonERC1155Recipient {} + +contract ERC1155Test is DSTestPlus, ERC1155TokenReceiver { + MockERC1155 token; + + mapping(address => mapping(uint256 => uint256)) public userMintAmounts; + mapping(address => mapping(uint256 => uint256)) public userTransferOrBurnAmounts; + + function setUp() public { + token = new MockERC1155(); + } + + function testMintToEOA() public { + token.mint(address(0xBEEF), 1337, 1, ""); + + assertEq(token.balanceOf(address(0xBEEF), 1337), 1); + } + + function testMintToERC1155Recipient() public { + ERC1155Recipient to = new ERC1155Recipient(); + + token.mint(address(to), 1337, 1, "testing 123"); + + assertEq(token.balanceOf(address(to), 1337), 1); + + assertEq(to.operator(), address(this)); + assertEq(to.from(), address(0)); + assertEq(to.id(), 1337); + assertBytesEq(to.mintData(), "testing 123"); + } + + function testBatchMintToEOA() public { + uint256[] memory ids = new uint256[](5); + ids[0] = 1337; + ids[1] = 1338; + ids[2] = 1339; + ids[3] = 1340; + ids[4] = 1341; + + uint256[] memory amounts = new uint256[](5); + amounts[0] = 100; + amounts[1] = 200; + amounts[2] = 300; + amounts[3] = 400; + amounts[4] = 500; + + token.batchMint(address(0xBEEF), ids, amounts, ""); + + assertEq(token.balanceOf(address(0xBEEF), 1337), 100); + assertEq(token.balanceOf(address(0xBEEF), 1338), 200); + assertEq(token.balanceOf(address(0xBEEF), 1339), 300); + assertEq(token.balanceOf(address(0xBEEF), 1340), 400); + assertEq(token.balanceOf(address(0xBEEF), 1341), 500); + } + + function testBatchMintToERC1155Recipient() public { + ERC1155Recipient to = new ERC1155Recipient(); + + uint256[] memory ids = new uint256[](5); + ids[0] = 1337; + ids[1] = 1338; + ids[2] = 1339; + ids[3] = 1340; + ids[4] = 1341; + + uint256[] memory amounts = new uint256[](5); + amounts[0] = 100; + amounts[1] = 200; + amounts[2] = 300; + amounts[3] = 400; + amounts[4] = 500; + + token.batchMint(address(to), ids, amounts, "testing 123"); + + assertEq(to.batchOperator(), address(this)); + assertEq(to.batchFrom(), address(0)); + assertUintArrayEq(to.batchIds(), ids); + assertUintArrayEq(to.batchAmounts(), amounts); + assertBytesEq(to.batchData(), "testing 123"); + + assertEq(token.balanceOf(address(to), 1337), 100); + assertEq(token.balanceOf(address(to), 1338), 200); + assertEq(token.balanceOf(address(to), 1339), 300); + assertEq(token.balanceOf(address(to), 1340), 400); + assertEq(token.balanceOf(address(to), 1341), 500); + } + + function testBurn() public { + token.mint(address(0xBEEF), 1337, 100, ""); + + token.burn(address(0xBEEF), 1337, 70); + + assertEq(token.balanceOf(address(0xBEEF), 1337), 30); + } + + function testBatchBurn() public { + uint256[] memory ids = new uint256[](5); + ids[0] = 1337; + ids[1] = 1338; + ids[2] = 1339; + ids[3] = 1340; + ids[4] = 1341; + + uint256[] memory mintAmounts = new uint256[](5); + mintAmounts[0] = 100; + mintAmounts[1] = 200; + mintAmounts[2] = 300; + mintAmounts[3] = 400; + mintAmounts[4] = 500; + + uint256[] memory burnAmounts = new uint256[](5); + burnAmounts[0] = 50; + burnAmounts[1] = 100; + burnAmounts[2] = 150; + burnAmounts[3] = 200; + burnAmounts[4] = 250; + + token.batchMint(address(0xBEEF), ids, mintAmounts, ""); + + token.batchBurn(address(0xBEEF), ids, burnAmounts); + + assertEq(token.balanceOf(address(0xBEEF), 1337), 50); + assertEq(token.balanceOf(address(0xBEEF), 1338), 100); + assertEq(token.balanceOf(address(0xBEEF), 1339), 150); + assertEq(token.balanceOf(address(0xBEEF), 1340), 200); + assertEq(token.balanceOf(address(0xBEEF), 1341), 250); + } + + function testApproveAll() public { + token.setApprovalForAll(address(0xBEEF), true); + + assertTrue(token.isApprovedForAll(address(this), address(0xBEEF))); + } + + function testSafeTransferFromToEOA() public { + address from = address(0xABCD); + + token.mint(from, 1337, 100, ""); + + hevm.prank(from); + token.setApprovalForAll(address(this), true); + + token.safeTransferFrom(from, address(0xBEEF), 1337, 70, ""); + + assertEq(token.balanceOf(address(0xBEEF), 1337), 70); + assertEq(token.balanceOf(from, 1337), 30); + } + + function testSafeTransferFromToERC1155Recipient() public { + ERC1155Recipient to = new ERC1155Recipient(); + + address from = address(0xABCD); + + token.mint(from, 1337, 100, ""); + + hevm.prank(from); + token.setApprovalForAll(address(this), true); + + token.safeTransferFrom(from, address(to), 1337, 70, "testing 123"); + + assertEq(to.operator(), address(this)); + assertEq(to.from(), from); + assertEq(to.id(), 1337); + assertBytesEq(to.mintData(), "testing 123"); + + assertEq(token.balanceOf(address(to), 1337), 70); + assertEq(token.balanceOf(from, 1337), 30); + } + + function testSafeTransferFromSelf() public { + token.mint(address(this), 1337, 100, ""); + + token.safeTransferFrom(address(this), address(0xBEEF), 1337, 70, ""); + + assertEq(token.balanceOf(address(0xBEEF), 1337), 70); + assertEq(token.balanceOf(address(this), 1337), 30); + } + + function testSafeBatchTransferFromToEOA() public { + address from = address(0xABCD); + + uint256[] memory ids = new uint256[](5); + ids[0] = 1337; + ids[1] = 1338; + ids[2] = 1339; + ids[3] = 1340; + ids[4] = 1341; + + uint256[] memory mintAmounts = new uint256[](5); + mintAmounts[0] = 100; + mintAmounts[1] = 200; + mintAmounts[2] = 300; + mintAmounts[3] = 400; + mintAmounts[4] = 500; + + uint256[] memory transferAmounts = new uint256[](5); + transferAmounts[0] = 50; + transferAmounts[1] = 100; + transferAmounts[2] = 150; + transferAmounts[3] = 200; + transferAmounts[4] = 250; + + token.batchMint(from, ids, mintAmounts, ""); + + hevm.prank(from); + token.setApprovalForAll(address(this), true); + + token.safeBatchTransferFrom(from, address(0xBEEF), ids, transferAmounts, ""); + + assertEq(token.balanceOf(from, 1337), 50); + assertEq(token.balanceOf(address(0xBEEF), 1337), 50); + + assertEq(token.balanceOf(from, 1338), 100); + assertEq(token.balanceOf(address(0xBEEF), 1338), 100); + + assertEq(token.balanceOf(from, 1339), 150); + assertEq(token.balanceOf(address(0xBEEF), 1339), 150); + + assertEq(token.balanceOf(from, 1340), 200); + assertEq(token.balanceOf(address(0xBEEF), 1340), 200); + + assertEq(token.balanceOf(from, 1341), 250); + assertEq(token.balanceOf(address(0xBEEF), 1341), 250); + } + + function testSafeBatchTransferFromToERC1155Recipient() public { + address from = address(0xABCD); + + ERC1155Recipient to = new ERC1155Recipient(); + + uint256[] memory ids = new uint256[](5); + ids[0] = 1337; + ids[1] = 1338; + ids[2] = 1339; + ids[3] = 1340; + ids[4] = 1341; + + uint256[] memory mintAmounts = new uint256[](5); + mintAmounts[0] = 100; + mintAmounts[1] = 200; + mintAmounts[2] = 300; + mintAmounts[3] = 400; + mintAmounts[4] = 500; + + uint256[] memory transferAmounts = new uint256[](5); + transferAmounts[0] = 50; + transferAmounts[1] = 100; + transferAmounts[2] = 150; + transferAmounts[3] = 200; + transferAmounts[4] = 250; + + token.batchMint(from, ids, mintAmounts, ""); + + hevm.prank(from); + token.setApprovalForAll(address(this), true); + + token.safeBatchTransferFrom(from, address(to), ids, transferAmounts, "testing 123"); + + assertEq(to.batchOperator(), address(this)); + assertEq(to.batchFrom(), from); + assertUintArrayEq(to.batchIds(), ids); + assertUintArrayEq(to.batchAmounts(), transferAmounts); + assertBytesEq(to.batchData(), "testing 123"); + + assertEq(token.balanceOf(from, 1337), 50); + assertEq(token.balanceOf(address(to), 1337), 50); + + assertEq(token.balanceOf(from, 1338), 100); + assertEq(token.balanceOf(address(to), 1338), 100); + + assertEq(token.balanceOf(from, 1339), 150); + assertEq(token.balanceOf(address(to), 1339), 150); + + assertEq(token.balanceOf(from, 1340), 200); + assertEq(token.balanceOf(address(to), 1340), 200); + + assertEq(token.balanceOf(from, 1341), 250); + assertEq(token.balanceOf(address(to), 1341), 250); + } + + function testBatchBalanceOf() public { + address[] memory tos = new address[](5); + tos[0] = address(0xBEEF); + tos[1] = address(0xCAFE); + tos[2] = address(0xFACE); + tos[3] = address(0xDEAD); + tos[4] = address(0xFEED); + + uint256[] memory ids = new uint256[](5); + ids[0] = 1337; + ids[1] = 1338; + ids[2] = 1339; + ids[3] = 1340; + ids[4] = 1341; + + token.mint(address(0xBEEF), 1337, 100, ""); + token.mint(address(0xCAFE), 1338, 200, ""); + token.mint(address(0xFACE), 1339, 300, ""); + token.mint(address(0xDEAD), 1340, 400, ""); + token.mint(address(0xFEED), 1341, 500, ""); + + uint256[] memory balances = token.balanceOfBatch(tos, ids); + + assertEq(balances[0], 100); + assertEq(balances[1], 200); + assertEq(balances[2], 300); + assertEq(balances[3], 400); + assertEq(balances[4], 500); + } + + function testFailMintToZero() public { + token.mint(address(0), 1337, 1, ""); + } + + function testFailMintToNonERC155Recipient() public { + token.mint(address(new NonERC1155Recipient()), 1337, 1, ""); + } + + function testFailMintToRevertingERC155Recipient() public { + token.mint(address(new RevertingERC1155Recipient()), 1337, 1, ""); + } + + function testFailMintToWrongReturnDataERC155Recipient() public { + token.mint(address(new RevertingERC1155Recipient()), 1337, 1, ""); + } + + function testFailBurnInsufficientBalance() public { + token.mint(address(0xBEEF), 1337, 70, ""); + token.burn(address(0xBEEF), 1337, 100); + } + + function testFailSafeTransferFromInsufficientBalance() public { + address from = address(0xABCD); + + token.mint(from, 1337, 70, ""); + + hevm.prank(from); + token.setApprovalForAll(address(this), true); + + token.safeTransferFrom(from, address(0xBEEF), 1337, 100, ""); + } + + function testFailSafeTransferFromSelfInsufficientBalance() public { + token.mint(address(this), 1337, 70, ""); + token.safeTransferFrom(address(this), address(0xBEEF), 1337, 100, ""); + } + + function testFailSafeTransferFromToZero() public { + token.mint(address(this), 1337, 100, ""); + token.safeTransferFrom(address(this), address(0), 1337, 70, ""); + } + + function testFailSafeTransferFromToNonERC155Recipient() public { + token.mint(address(this), 1337, 100, ""); + token.safeTransferFrom(address(this), address(new NonERC1155Recipient()), 1337, 70, ""); + } + + function testFailSafeTransferFromToRevertingERC1155Recipient() public { + token.mint(address(this), 1337, 100, ""); + token.safeTransferFrom(address(this), address(new RevertingERC1155Recipient()), 1337, 70, ""); + } + + function testFailSafeTransferFromToWrongReturnDataERC1155Recipient() public { + token.mint(address(this), 1337, 100, ""); + token.safeTransferFrom(address(this), address(new WrongReturnDataERC1155Recipient()), 1337, 70, ""); + } + + function testFailSafeBatchTransferInsufficientBalance() public { + address from = address(0xABCD); + + uint256[] memory ids = new uint256[](5); + ids[0] = 1337; + ids[1] = 1338; + ids[2] = 1339; + ids[3] = 1340; + ids[4] = 1341; + + uint256[] memory mintAmounts = new uint256[](5); + + mintAmounts[0] = 50; + mintAmounts[1] = 100; + mintAmounts[2] = 150; + mintAmounts[3] = 200; + mintAmounts[4] = 250; + + uint256[] memory transferAmounts = new uint256[](5); + transferAmounts[0] = 100; + transferAmounts[1] = 200; + transferAmounts[2] = 300; + transferAmounts[3] = 400; + transferAmounts[4] = 500; + + token.batchMint(from, ids, mintAmounts, ""); + + hevm.prank(from); + token.setApprovalForAll(address(this), true); + + token.safeBatchTransferFrom(from, address(0xBEEF), ids, transferAmounts, ""); + } + + function testFailSafeBatchTransferFromToZero() public { + address from = address(0xABCD); + + uint256[] memory ids = new uint256[](5); + ids[0] = 1337; + ids[1] = 1338; + ids[2] = 1339; + ids[3] = 1340; + ids[4] = 1341; + + uint256[] memory mintAmounts = new uint256[](5); + mintAmounts[0] = 100; + mintAmounts[1] = 200; + mintAmounts[2] = 300; + mintAmounts[3] = 400; + mintAmounts[4] = 500; + + uint256[] memory transferAmounts = new uint256[](5); + transferAmounts[0] = 50; + transferAmounts[1] = 100; + transferAmounts[2] = 150; + transferAmounts[3] = 200; + transferAmounts[4] = 250; + + token.batchMint(from, ids, mintAmounts, ""); + + hevm.prank(from); + token.setApprovalForAll(address(this), true); + + token.safeBatchTransferFrom(from, address(0), ids, transferAmounts, ""); + } + + function testFailSafeBatchTransferFromToNonERC1155Recipient() public { + address from = address(0xABCD); + + uint256[] memory ids = new uint256[](5); + ids[0] = 1337; + ids[1] = 1338; + ids[2] = 1339; + ids[3] = 1340; + ids[4] = 1341; + + uint256[] memory mintAmounts = new uint256[](5); + mintAmounts[0] = 100; + mintAmounts[1] = 200; + mintAmounts[2] = 300; + mintAmounts[3] = 400; + mintAmounts[4] = 500; + + uint256[] memory transferAmounts = new uint256[](5); + transferAmounts[0] = 50; + transferAmounts[1] = 100; + transferAmounts[2] = 150; + transferAmounts[3] = 200; + transferAmounts[4] = 250; + + token.batchMint(from, ids, mintAmounts, ""); + + hevm.prank(from); + token.setApprovalForAll(address(this), true); + + token.safeBatchTransferFrom(from, address(new NonERC1155Recipient()), ids, transferAmounts, ""); + } + + function testFailSafeBatchTransferFromToRevertingERC1155Recipient() public { + address from = address(0xABCD); + + uint256[] memory ids = new uint256[](5); + ids[0] = 1337; + ids[1] = 1338; + ids[2] = 1339; + ids[3] = 1340; + ids[4] = 1341; + + uint256[] memory mintAmounts = new uint256[](5); + mintAmounts[0] = 100; + mintAmounts[1] = 200; + mintAmounts[2] = 300; + mintAmounts[3] = 400; + mintAmounts[4] = 500; + + uint256[] memory transferAmounts = new uint256[](5); + transferAmounts[0] = 50; + transferAmounts[1] = 100; + transferAmounts[2] = 150; + transferAmounts[3] = 200; + transferAmounts[4] = 250; + + token.batchMint(from, ids, mintAmounts, ""); + + hevm.prank(from); + token.setApprovalForAll(address(this), true); + + token.safeBatchTransferFrom(from, address(new RevertingERC1155Recipient()), ids, transferAmounts, ""); + } + + function testFailSafeBatchTransferFromToWrongReturnDataERC1155Recipient() public { + address from = address(0xABCD); + + uint256[] memory ids = new uint256[](5); + ids[0] = 1337; + ids[1] = 1338; + ids[2] = 1339; + ids[3] = 1340; + ids[4] = 1341; + + uint256[] memory mintAmounts = new uint256[](5); + mintAmounts[0] = 100; + mintAmounts[1] = 200; + mintAmounts[2] = 300; + mintAmounts[3] = 400; + mintAmounts[4] = 500; + + uint256[] memory transferAmounts = new uint256[](5); + transferAmounts[0] = 50; + transferAmounts[1] = 100; + transferAmounts[2] = 150; + transferAmounts[3] = 200; + transferAmounts[4] = 250; + + token.batchMint(from, ids, mintAmounts, ""); + + hevm.prank(from); + token.setApprovalForAll(address(this), true); + + token.safeBatchTransferFrom(from, address(new WrongReturnDataERC1155Recipient()), ids, transferAmounts, ""); + } + + function testFailSafeBatchTransferFromWithArrayLengthMismatch() public { + address from = address(0xABCD); + + uint256[] memory ids = new uint256[](5); + ids[0] = 1337; + ids[1] = 1338; + ids[2] = 1339; + ids[3] = 1340; + ids[4] = 1341; + + uint256[] memory mintAmounts = new uint256[](5); + mintAmounts[0] = 100; + mintAmounts[1] = 200; + mintAmounts[2] = 300; + mintAmounts[3] = 400; + mintAmounts[4] = 500; + + uint256[] memory transferAmounts = new uint256[](4); + transferAmounts[0] = 50; + transferAmounts[1] = 100; + transferAmounts[2] = 150; + transferAmounts[3] = 200; + + token.batchMint(from, ids, mintAmounts, ""); + + hevm.prank(from); + token.setApprovalForAll(address(this), true); + + token.safeBatchTransferFrom(from, address(0xBEEF), ids, transferAmounts, ""); + } + + function testFailBatchMintToZero() public { + uint256[] memory ids = new uint256[](5); + ids[0] = 1337; + ids[1] = 1338; + ids[2] = 1339; + ids[3] = 1340; + ids[4] = 1341; + + uint256[] memory mintAmounts = new uint256[](5); + mintAmounts[0] = 100; + mintAmounts[1] = 200; + mintAmounts[2] = 300; + mintAmounts[3] = 400; + mintAmounts[4] = 500; + + token.batchMint(address(0), ids, mintAmounts, ""); + } + + function testFailBatchMintToNonERC1155Recipient() public { + NonERC1155Recipient to = new NonERC1155Recipient(); + + uint256[] memory ids = new uint256[](5); + ids[0] = 1337; + ids[1] = 1338; + ids[2] = 1339; + ids[3] = 1340; + ids[4] = 1341; + + uint256[] memory mintAmounts = new uint256[](5); + mintAmounts[0] = 100; + mintAmounts[1] = 200; + mintAmounts[2] = 300; + mintAmounts[3] = 400; + mintAmounts[4] = 500; + + token.batchMint(address(to), ids, mintAmounts, ""); + } + + function testFailBatchMintToRevertingERC1155Recipient() public { + RevertingERC1155Recipient to = new RevertingERC1155Recipient(); + + uint256[] memory ids = new uint256[](5); + ids[0] = 1337; + ids[1] = 1338; + ids[2] = 1339; + ids[3] = 1340; + ids[4] = 1341; + + uint256[] memory mintAmounts = new uint256[](5); + mintAmounts[0] = 100; + mintAmounts[1] = 200; + mintAmounts[2] = 300; + mintAmounts[3] = 400; + mintAmounts[4] = 500; + + token.batchMint(address(to), ids, mintAmounts, ""); + } + + function testFailBatchMintToWrongReturnDataERC1155Recipient() public { + WrongReturnDataERC1155Recipient to = new WrongReturnDataERC1155Recipient(); + + uint256[] memory ids = new uint256[](5); + ids[0] = 1337; + ids[1] = 1338; + ids[2] = 1339; + ids[3] = 1340; + ids[4] = 1341; + + uint256[] memory mintAmounts = new uint256[](5); + mintAmounts[0] = 100; + mintAmounts[1] = 200; + mintAmounts[2] = 300; + mintAmounts[3] = 400; + mintAmounts[4] = 500; + + token.batchMint(address(to), ids, mintAmounts, ""); + } + + function testFailBatchMintWithArrayMismatch() public { + uint256[] memory ids = new uint256[](5); + ids[0] = 1337; + ids[1] = 1338; + ids[2] = 1339; + ids[3] = 1340; + ids[4] = 1341; + + uint256[] memory amounts = new uint256[](4); + amounts[0] = 100; + amounts[1] = 200; + amounts[2] = 300; + amounts[3] = 400; + + token.batchMint(address(0xBEEF), ids, amounts, ""); + } + + function testFailBatchBurnInsufficientBalance() public { + uint256[] memory ids = new uint256[](5); + ids[0] = 1337; + ids[1] = 1338; + ids[2] = 1339; + ids[3] = 1340; + ids[4] = 1341; + + uint256[] memory mintAmounts = new uint256[](5); + mintAmounts[0] = 50; + mintAmounts[1] = 100; + mintAmounts[2] = 150; + mintAmounts[3] = 200; + mintAmounts[4] = 250; + + uint256[] memory burnAmounts = new uint256[](5); + burnAmounts[0] = 100; + burnAmounts[1] = 200; + burnAmounts[2] = 300; + burnAmounts[3] = 400; + burnAmounts[4] = 500; + + token.batchMint(address(0xBEEF), ids, mintAmounts, ""); + + token.batchBurn(address(0xBEEF), ids, burnAmounts); + } + + function testFailBatchBurnWithArrayLengthMismatch() public { + uint256[] memory ids = new uint256[](5); + ids[0] = 1337; + ids[1] = 1338; + ids[2] = 1339; + ids[3] = 1340; + ids[4] = 1341; + + uint256[] memory mintAmounts = new uint256[](5); + mintAmounts[0] = 100; + mintAmounts[1] = 200; + mintAmounts[2] = 300; + mintAmounts[3] = 400; + mintAmounts[4] = 500; + + uint256[] memory burnAmounts = new uint256[](4); + burnAmounts[0] = 50; + burnAmounts[1] = 100; + burnAmounts[2] = 150; + burnAmounts[3] = 200; + + token.batchMint(address(0xBEEF), ids, mintAmounts, ""); + + token.batchBurn(address(0xBEEF), ids, burnAmounts); + } + + function testFailBalanceOfBatchWithArrayMismatch() public view { + address[] memory tos = new address[](5); + tos[0] = address(0xBEEF); + tos[1] = address(0xCAFE); + tos[2] = address(0xFACE); + tos[3] = address(0xDEAD); + tos[4] = address(0xFEED); + + uint256[] memory ids = new uint256[](4); + ids[0] = 1337; + ids[1] = 1338; + ids[2] = 1339; + ids[3] = 1340; + + token.balanceOfBatch(tos, ids); + } + + function testMintToEOA( + address to, + uint256 id, + uint256 amount, + bytes memory mintData + ) public { + if (to == address(0)) to = address(0xBEEF); + + if (uint256(uint160(to)) <= 18 || to.code.length > 0) return; + + token.mint(to, id, amount, mintData); + + assertEq(token.balanceOf(to, id), amount); + } + + function testMintToERC1155Recipient( + uint256 id, + uint256 amount, + bytes memory mintData + ) public { + ERC1155Recipient to = new ERC1155Recipient(); + + token.mint(address(to), id, amount, mintData); + + assertEq(token.balanceOf(address(to), id), amount); + + assertEq(to.operator(), address(this)); + assertEq(to.from(), address(0)); + assertEq(to.id(), id); + assertBytesEq(to.mintData(), mintData); + } + + function testBatchMintToEOA( + address to, + uint256[] memory ids, + uint256[] memory amounts, + bytes memory mintData + ) public { + if (to == address(0)) to = address(0xBEEF); + + if (uint256(uint160(to)) <= 18 || to.code.length > 0) return; + + uint256 minLength = min2(ids.length, amounts.length); + + uint256[] memory normalizedIds = new uint256[](minLength); + uint256[] memory normalizedAmounts = new uint256[](minLength); + + for (uint256 i = 0; i < minLength; i++) { + uint256 id = ids[i]; + + uint256 remainingMintAmountForId = type(uint256).max - userMintAmounts[to][id]; + + uint256 mintAmount = bound(amounts[i], 0, remainingMintAmountForId); + + normalizedIds[i] = id; + normalizedAmounts[i] = mintAmount; + + userMintAmounts[to][id] += mintAmount; + } + + token.batchMint(to, normalizedIds, normalizedAmounts, mintData); + + for (uint256 i = 0; i < normalizedIds.length; i++) { + uint256 id = normalizedIds[i]; + + assertEq(token.balanceOf(to, id), userMintAmounts[to][id]); + } + } + + function testBatchMintToERC1155Recipient( + uint256[] memory ids, + uint256[] memory amounts, + bytes memory mintData + ) public { + ERC1155Recipient to = new ERC1155Recipient(); + + uint256 minLength = min2(ids.length, amounts.length); + + uint256[] memory normalizedIds = new uint256[](minLength); + uint256[] memory normalizedAmounts = new uint256[](minLength); + + for (uint256 i = 0; i < minLength; i++) { + uint256 id = ids[i]; + + uint256 remainingMintAmountForId = type(uint256).max - userMintAmounts[address(to)][id]; + + uint256 mintAmount = bound(amounts[i], 0, remainingMintAmountForId); + + normalizedIds[i] = id; + normalizedAmounts[i] = mintAmount; + + userMintAmounts[address(to)][id] += mintAmount; + } + + token.batchMint(address(to), normalizedIds, normalizedAmounts, mintData); + + assertEq(to.batchOperator(), address(this)); + assertEq(to.batchFrom(), address(0)); + assertUintArrayEq(to.batchIds(), normalizedIds); + assertUintArrayEq(to.batchAmounts(), normalizedAmounts); + assertBytesEq(to.batchData(), mintData); + + for (uint256 i = 0; i < normalizedIds.length; i++) { + uint256 id = normalizedIds[i]; + + assertEq(token.balanceOf(address(to), id), userMintAmounts[address(to)][id]); + } + } + + function testBurn( + address to, + uint256 id, + uint256 mintAmount, + bytes memory mintData, + uint256 burnAmount + ) public { + if (to == address(0)) to = address(0xBEEF); + + if (uint256(uint160(to)) <= 18 || to.code.length > 0) return; + + burnAmount = bound(burnAmount, 0, mintAmount); + + token.mint(to, id, mintAmount, mintData); + + token.burn(to, id, burnAmount); + + assertEq(token.balanceOf(address(to), id), mintAmount - burnAmount); + } + + function testBatchBurn( + address to, + uint256[] memory ids, + uint256[] memory mintAmounts, + uint256[] memory burnAmounts, + bytes memory mintData + ) public { + if (to == address(0)) to = address(0xBEEF); + + if (uint256(uint160(to)) <= 18 || to.code.length > 0) return; + + uint256 minLength = min3(ids.length, mintAmounts.length, burnAmounts.length); + + uint256[] memory normalizedIds = new uint256[](minLength); + uint256[] memory normalizedMintAmounts = new uint256[](minLength); + uint256[] memory normalizedBurnAmounts = new uint256[](minLength); + + for (uint256 i = 0; i < minLength; i++) { + uint256 id = ids[i]; + + uint256 remainingMintAmountForId = type(uint256).max - userMintAmounts[address(to)][id]; + + normalizedIds[i] = id; + normalizedMintAmounts[i] = bound(mintAmounts[i], 0, remainingMintAmountForId); + normalizedBurnAmounts[i] = bound(burnAmounts[i], 0, normalizedMintAmounts[i]); + + userMintAmounts[address(to)][id] += normalizedMintAmounts[i]; + userTransferOrBurnAmounts[address(to)][id] += normalizedBurnAmounts[i]; + } + + token.batchMint(to, normalizedIds, normalizedMintAmounts, mintData); + + token.batchBurn(to, normalizedIds, normalizedBurnAmounts); + + for (uint256 i = 0; i < normalizedIds.length; i++) { + uint256 id = normalizedIds[i]; + + assertEq(token.balanceOf(to, id), userMintAmounts[to][id] - userTransferOrBurnAmounts[to][id]); + } + } + + function testApproveAll(address to, bool approved) public { + token.setApprovalForAll(to, approved); + + assertBoolEq(token.isApprovedForAll(address(this), to), approved); + } + + function testSafeTransferFromToEOA( + uint256 id, + uint256 mintAmount, + bytes memory mintData, + uint256 transferAmount, + address to, + bytes memory transferData + ) public { + if (to == address(0)) to = address(0xBEEF); + + if (uint256(uint160(to)) <= 18 || to.code.length > 0) return; + + transferAmount = bound(transferAmount, 0, mintAmount); + + address from = address(0xABCD); + + token.mint(from, id, mintAmount, mintData); + + hevm.prank(from); + token.setApprovalForAll(address(this), true); + + token.safeTransferFrom(from, to, id, transferAmount, transferData); + + if (to == from) { + assertEq(token.balanceOf(to, id), mintAmount); + } else { + assertEq(token.balanceOf(to, id), transferAmount); + assertEq(token.balanceOf(from, id), mintAmount - transferAmount); + } + } + + function testSafeTransferFromToERC1155Recipient( + uint256 id, + uint256 mintAmount, + bytes memory mintData, + uint256 transferAmount, + bytes memory transferData + ) public { + ERC1155Recipient to = new ERC1155Recipient(); + + address from = address(0xABCD); + + transferAmount = bound(transferAmount, 0, mintAmount); + + token.mint(from, id, mintAmount, mintData); + + hevm.prank(from); + token.setApprovalForAll(address(this), true); + + token.safeTransferFrom(from, address(to), id, transferAmount, transferData); + + assertEq(to.operator(), address(this)); + assertEq(to.from(), from); + assertEq(to.id(), id); + assertBytesEq(to.mintData(), transferData); + + assertEq(token.balanceOf(address(to), id), transferAmount); + assertEq(token.balanceOf(from, id), mintAmount - transferAmount); + } + + function testSafeTransferFromSelf( + uint256 id, + uint256 mintAmount, + bytes memory mintData, + uint256 transferAmount, + address to, + bytes memory transferData + ) public { + if (to == address(0)) to = address(0xBEEF); + + if (uint256(uint160(to)) <= 18 || to.code.length > 0) return; + + transferAmount = bound(transferAmount, 0, mintAmount); + + token.mint(address(this), id, mintAmount, mintData); + + token.safeTransferFrom(address(this), to, id, transferAmount, transferData); + + assertEq(token.balanceOf(to, id), transferAmount); + assertEq(token.balanceOf(address(this), id), mintAmount - transferAmount); + } + + function testSafeBatchTransferFromToEOA( + address to, + uint256[] memory ids, + uint256[] memory mintAmounts, + uint256[] memory transferAmounts, + bytes memory mintData, + bytes memory transferData + ) public { + if (to == address(0)) to = address(0xBEEF); + + if (uint256(uint160(to)) <= 18 || to.code.length > 0) return; + + address from = address(0xABCD); + + uint256 minLength = min3(ids.length, mintAmounts.length, transferAmounts.length); + + uint256[] memory normalizedIds = new uint256[](minLength); + uint256[] memory normalizedMintAmounts = new uint256[](minLength); + uint256[] memory normalizedTransferAmounts = new uint256[](minLength); + + for (uint256 i = 0; i < minLength; i++) { + uint256 id = ids[i]; + + uint256 remainingMintAmountForId = type(uint256).max - userMintAmounts[from][id]; + + uint256 mintAmount = bound(mintAmounts[i], 0, remainingMintAmountForId); + uint256 transferAmount = bound(transferAmounts[i], 0, mintAmount); + + normalizedIds[i] = id; + normalizedMintAmounts[i] = mintAmount; + normalizedTransferAmounts[i] = transferAmount; + + userMintAmounts[from][id] += mintAmount; + userTransferOrBurnAmounts[from][id] += transferAmount; + } + + token.batchMint(from, normalizedIds, normalizedMintAmounts, mintData); + + hevm.prank(from); + token.setApprovalForAll(address(this), true); + + token.safeBatchTransferFrom(from, to, normalizedIds, normalizedTransferAmounts, transferData); + + for (uint256 i = 0; i < normalizedIds.length; i++) { + uint256 id = normalizedIds[i]; + + assertEq(token.balanceOf(address(to), id), userTransferOrBurnAmounts[from][id]); + assertEq(token.balanceOf(from, id), userMintAmounts[from][id] - userTransferOrBurnAmounts[from][id]); + } + } + + function testSafeBatchTransferFromToERC1155Recipient( + uint256[] memory ids, + uint256[] memory mintAmounts, + uint256[] memory transferAmounts, + bytes memory mintData, + bytes memory transferData + ) public { + address from = address(0xABCD); + + ERC1155Recipient to = new ERC1155Recipient(); + + uint256 minLength = min3(ids.length, mintAmounts.length, transferAmounts.length); + + uint256[] memory normalizedIds = new uint256[](minLength); + uint256[] memory normalizedMintAmounts = new uint256[](minLength); + uint256[] memory normalizedTransferAmounts = new uint256[](minLength); + + for (uint256 i = 0; i < minLength; i++) { + uint256 id = ids[i]; + + uint256 remainingMintAmountForId = type(uint256).max - userMintAmounts[from][id]; + + uint256 mintAmount = bound(mintAmounts[i], 0, remainingMintAmountForId); + uint256 transferAmount = bound(transferAmounts[i], 0, mintAmount); + + normalizedIds[i] = id; + normalizedMintAmounts[i] = mintAmount; + normalizedTransferAmounts[i] = transferAmount; + + userMintAmounts[from][id] += mintAmount; + userTransferOrBurnAmounts[from][id] += transferAmount; + } + + token.batchMint(from, normalizedIds, normalizedMintAmounts, mintData); + + hevm.prank(from); + token.setApprovalForAll(address(this), true); + + token.safeBatchTransferFrom(from, address(to), normalizedIds, normalizedTransferAmounts, transferData); + + assertEq(to.batchOperator(), address(this)); + assertEq(to.batchFrom(), from); + assertUintArrayEq(to.batchIds(), normalizedIds); + assertUintArrayEq(to.batchAmounts(), normalizedTransferAmounts); + assertBytesEq(to.batchData(), transferData); + + for (uint256 i = 0; i < normalizedIds.length; i++) { + uint256 id = normalizedIds[i]; + uint256 transferAmount = userTransferOrBurnAmounts[from][id]; + + assertEq(token.balanceOf(address(to), id), transferAmount); + assertEq(token.balanceOf(from, id), userMintAmounts[from][id] - transferAmount); + } + } + + function testBatchBalanceOf( + address[] memory tos, + uint256[] memory ids, + uint256[] memory amounts, + bytes memory mintData + ) public { + uint256 minLength = min3(tos.length, ids.length, amounts.length); + + address[] memory normalizedTos = new address[](minLength); + uint256[] memory normalizedIds = new uint256[](minLength); + + for (uint256 i = 0; i < minLength; i++) { + uint256 id = ids[i]; + address to = tos[i] == address(0) || tos[i].code.length > 0 ? address(0xBEEF) : tos[i]; + + uint256 remainingMintAmountForId = type(uint256).max - userMintAmounts[to][id]; + + normalizedTos[i] = to; + normalizedIds[i] = id; + + uint256 mintAmount = bound(amounts[i], 0, remainingMintAmountForId); + + token.mint(to, id, mintAmount, mintData); + + userMintAmounts[to][id] += mintAmount; + } + + uint256[] memory balances = token.balanceOfBatch(normalizedTos, normalizedIds); + + for (uint256 i = 0; i < normalizedTos.length; i++) { + assertEq(balances[i], token.balanceOf(normalizedTos[i], normalizedIds[i])); + } + } + + function testFailMintToZero( + uint256 id, + uint256 amount, + bytes memory data + ) public { + token.mint(address(0), id, amount, data); + } + + function testFailMintToNonERC155Recipient( + uint256 id, + uint256 mintAmount, + bytes memory mintData + ) public { + token.mint(address(new NonERC1155Recipient()), id, mintAmount, mintData); + } + + function testFailMintToRevertingERC155Recipient( + uint256 id, + uint256 mintAmount, + bytes memory mintData + ) public { + token.mint(address(new RevertingERC1155Recipient()), id, mintAmount, mintData); + } + + function testFailMintToWrongReturnDataERC155Recipient( + uint256 id, + uint256 mintAmount, + bytes memory mintData + ) public { + token.mint(address(new RevertingERC1155Recipient()), id, mintAmount, mintData); + } + + function testFailBurnInsufficientBalance( + address to, + uint256 id, + uint256 mintAmount, + uint256 burnAmount, + bytes memory mintData + ) public { + burnAmount = bound(burnAmount, mintAmount + 1, type(uint256).max); + + token.mint(to, id, mintAmount, mintData); + token.burn(to, id, burnAmount); + } + + function testFailSafeTransferFromInsufficientBalance( + address to, + uint256 id, + uint256 mintAmount, + uint256 transferAmount, + bytes memory mintData, + bytes memory transferData + ) public { + address from = address(0xABCD); + + transferAmount = bound(transferAmount, mintAmount + 1, type(uint256).max); + + token.mint(from, id, mintAmount, mintData); + + hevm.prank(from); + token.setApprovalForAll(address(this), true); + + token.safeTransferFrom(from, to, id, transferAmount, transferData); + } + + function testFailSafeTransferFromSelfInsufficientBalance( + address to, + uint256 id, + uint256 mintAmount, + uint256 transferAmount, + bytes memory mintData, + bytes memory transferData + ) public { + transferAmount = bound(transferAmount, mintAmount + 1, type(uint256).max); + + token.mint(address(this), id, mintAmount, mintData); + token.safeTransferFrom(address(this), to, id, transferAmount, transferData); + } + + function testFailSafeTransferFromToZero( + uint256 id, + uint256 mintAmount, + uint256 transferAmount, + bytes memory mintData, + bytes memory transferData + ) public { + transferAmount = bound(transferAmount, 0, mintAmount); + + token.mint(address(this), id, mintAmount, mintData); + token.safeTransferFrom(address(this), address(0), id, transferAmount, transferData); + } + + function testFailSafeTransferFromToNonERC155Recipient( + uint256 id, + uint256 mintAmount, + uint256 transferAmount, + bytes memory mintData, + bytes memory transferData + ) public { + transferAmount = bound(transferAmount, 0, mintAmount); + + token.mint(address(this), id, mintAmount, mintData); + token.safeTransferFrom(address(this), address(new NonERC1155Recipient()), id, transferAmount, transferData); + } + + function testFailSafeTransferFromToRevertingERC1155Recipient( + uint256 id, + uint256 mintAmount, + uint256 transferAmount, + bytes memory mintData, + bytes memory transferData + ) public { + transferAmount = bound(transferAmount, 0, mintAmount); + + token.mint(address(this), id, mintAmount, mintData); + token.safeTransferFrom( + address(this), + address(new RevertingERC1155Recipient()), + id, + transferAmount, + transferData + ); + } + + function testFailSafeTransferFromToWrongReturnDataERC1155Recipient( + uint256 id, + uint256 mintAmount, + uint256 transferAmount, + bytes memory mintData, + bytes memory transferData + ) public { + transferAmount = bound(transferAmount, 0, mintAmount); + + token.mint(address(this), id, mintAmount, mintData); + token.safeTransferFrom( + address(this), + address(new WrongReturnDataERC1155Recipient()), + id, + transferAmount, + transferData + ); + } + + function testFailSafeBatchTransferInsufficientBalance( + address to, + uint256[] memory ids, + uint256[] memory mintAmounts, + uint256[] memory transferAmounts, + bytes memory mintData, + bytes memory transferData + ) public { + address from = address(0xABCD); + + uint256 minLength = min3(ids.length, mintAmounts.length, transferAmounts.length); + + if (minLength == 0) revert(); + + uint256[] memory normalizedIds = new uint256[](minLength); + uint256[] memory normalizedMintAmounts = new uint256[](minLength); + uint256[] memory normalizedTransferAmounts = new uint256[](minLength); + + for (uint256 i = 0; i < minLength; i++) { + uint256 id = ids[i]; + + uint256 remainingMintAmountForId = type(uint256).max - userMintAmounts[from][id]; + + uint256 mintAmount = bound(mintAmounts[i], 0, remainingMintAmountForId); + uint256 transferAmount = bound(transferAmounts[i], mintAmount + 1, type(uint256).max); + + normalizedIds[i] = id; + normalizedMintAmounts[i] = mintAmount; + normalizedTransferAmounts[i] = transferAmount; + + userMintAmounts[from][id] += mintAmount; + } + + token.batchMint(from, normalizedIds, normalizedMintAmounts, mintData); + + hevm.prank(from); + token.setApprovalForAll(address(this), true); + + token.safeBatchTransferFrom(from, to, normalizedIds, normalizedTransferAmounts, transferData); + } + + function testFailSafeBatchTransferFromToZero( + uint256[] memory ids, + uint256[] memory mintAmounts, + uint256[] memory transferAmounts, + bytes memory mintData, + bytes memory transferData + ) public { + address from = address(0xABCD); + + uint256 minLength = min3(ids.length, mintAmounts.length, transferAmounts.length); + + uint256[] memory normalizedIds = new uint256[](minLength); + uint256[] memory normalizedMintAmounts = new uint256[](minLength); + uint256[] memory normalizedTransferAmounts = new uint256[](minLength); + + for (uint256 i = 0; i < minLength; i++) { + uint256 id = ids[i]; + + uint256 remainingMintAmountForId = type(uint256).max - userMintAmounts[from][id]; + + uint256 mintAmount = bound(mintAmounts[i], 0, remainingMintAmountForId); + uint256 transferAmount = bound(transferAmounts[i], 0, mintAmount); + + normalizedIds[i] = id; + normalizedMintAmounts[i] = mintAmount; + normalizedTransferAmounts[i] = transferAmount; + + userMintAmounts[from][id] += mintAmount; + } + + token.batchMint(from, normalizedIds, normalizedMintAmounts, mintData); + + hevm.prank(from); + token.setApprovalForAll(address(this), true); + + token.safeBatchTransferFrom(from, address(0), normalizedIds, normalizedTransferAmounts, transferData); + } + + function testFailSafeBatchTransferFromToNonERC1155Recipient( + uint256[] memory ids, + uint256[] memory mintAmounts, + uint256[] memory transferAmounts, + bytes memory mintData, + bytes memory transferData + ) public { + address from = address(0xABCD); + + uint256 minLength = min3(ids.length, mintAmounts.length, transferAmounts.length); + + uint256[] memory normalizedIds = new uint256[](minLength); + uint256[] memory normalizedMintAmounts = new uint256[](minLength); + uint256[] memory normalizedTransferAmounts = new uint256[](minLength); + + for (uint256 i = 0; i < minLength; i++) { + uint256 id = ids[i]; + + uint256 remainingMintAmountForId = type(uint256).max - userMintAmounts[from][id]; + + uint256 mintAmount = bound(mintAmounts[i], 0, remainingMintAmountForId); + uint256 transferAmount = bound(transferAmounts[i], 0, mintAmount); + + normalizedIds[i] = id; + normalizedMintAmounts[i] = mintAmount; + normalizedTransferAmounts[i] = transferAmount; + + userMintAmounts[from][id] += mintAmount; + } + + token.batchMint(from, normalizedIds, normalizedMintAmounts, mintData); + + hevm.prank(from); + token.setApprovalForAll(address(this), true); + + token.safeBatchTransferFrom( + from, + address(new NonERC1155Recipient()), + normalizedIds, + normalizedTransferAmounts, + transferData + ); + } + + function testFailSafeBatchTransferFromToRevertingERC1155Recipient( + uint256[] memory ids, + uint256[] memory mintAmounts, + uint256[] memory transferAmounts, + bytes memory mintData, + bytes memory transferData + ) public { + address from = address(0xABCD); + + uint256 minLength = min3(ids.length, mintAmounts.length, transferAmounts.length); + + uint256[] memory normalizedIds = new uint256[](minLength); + uint256[] memory normalizedMintAmounts = new uint256[](minLength); + uint256[] memory normalizedTransferAmounts = new uint256[](minLength); + + for (uint256 i = 0; i < minLength; i++) { + uint256 id = ids[i]; + + uint256 remainingMintAmountForId = type(uint256).max - userMintAmounts[from][id]; + + uint256 mintAmount = bound(mintAmounts[i], 0, remainingMintAmountForId); + uint256 transferAmount = bound(transferAmounts[i], 0, mintAmount); + + normalizedIds[i] = id; + normalizedMintAmounts[i] = mintAmount; + normalizedTransferAmounts[i] = transferAmount; + + userMintAmounts[from][id] += mintAmount; + } + + token.batchMint(from, normalizedIds, normalizedMintAmounts, mintData); + + hevm.prank(from); + token.setApprovalForAll(address(this), true); + + token.safeBatchTransferFrom( + from, + address(new RevertingERC1155Recipient()), + normalizedIds, + normalizedTransferAmounts, + transferData + ); + } + + function testFailSafeBatchTransferFromToWrongReturnDataERC1155Recipient( + uint256[] memory ids, + uint256[] memory mintAmounts, + uint256[] memory transferAmounts, + bytes memory mintData, + bytes memory transferData + ) public { + address from = address(0xABCD); + + uint256 minLength = min3(ids.length, mintAmounts.length, transferAmounts.length); + + uint256[] memory normalizedIds = new uint256[](minLength); + uint256[] memory normalizedMintAmounts = new uint256[](minLength); + uint256[] memory normalizedTransferAmounts = new uint256[](minLength); + + for (uint256 i = 0; i < minLength; i++) { + uint256 id = ids[i]; + + uint256 remainingMintAmountForId = type(uint256).max - userMintAmounts[from][id]; + + uint256 mintAmount = bound(mintAmounts[i], 0, remainingMintAmountForId); + uint256 transferAmount = bound(transferAmounts[i], 0, mintAmount); + + normalizedIds[i] = id; + normalizedMintAmounts[i] = mintAmount; + normalizedTransferAmounts[i] = transferAmount; + + userMintAmounts[from][id] += mintAmount; + } + + token.batchMint(from, normalizedIds, normalizedMintAmounts, mintData); + + hevm.prank(from); + token.setApprovalForAll(address(this), true); + + token.safeBatchTransferFrom( + from, + address(new WrongReturnDataERC1155Recipient()), + normalizedIds, + normalizedTransferAmounts, + transferData + ); + } + + function testFailSafeBatchTransferFromWithArrayLengthMismatch( + address to, + uint256[] memory ids, + uint256[] memory mintAmounts, + uint256[] memory transferAmounts, + bytes memory mintData, + bytes memory transferData + ) public { + address from = address(0xABCD); + + if (ids.length == transferAmounts.length) revert(); + + token.batchMint(from, ids, mintAmounts, mintData); + + hevm.prank(from); + token.setApprovalForAll(address(this), true); + + token.safeBatchTransferFrom(from, to, ids, transferAmounts, transferData); + } + + function testFailBatchMintToZero( + uint256[] memory ids, + uint256[] memory amounts, + bytes memory mintData + ) public { + uint256 minLength = min2(ids.length, amounts.length); + + uint256[] memory normalizedIds = new uint256[](minLength); + uint256[] memory normalizedAmounts = new uint256[](minLength); + + for (uint256 i = 0; i < minLength; i++) { + uint256 id = ids[i]; + + uint256 remainingMintAmountForId = type(uint256).max - userMintAmounts[address(0)][id]; + + uint256 mintAmount = bound(amounts[i], 0, remainingMintAmountForId); + + normalizedIds[i] = id; + normalizedAmounts[i] = mintAmount; + + userMintAmounts[address(0)][id] += mintAmount; + } + + token.batchMint(address(0), normalizedIds, normalizedAmounts, mintData); + } + + function testFailBatchMintToNonERC1155Recipient( + uint256[] memory ids, + uint256[] memory amounts, + bytes memory mintData + ) public { + NonERC1155Recipient to = new NonERC1155Recipient(); + + uint256 minLength = min2(ids.length, amounts.length); + + uint256[] memory normalizedIds = new uint256[](minLength); + uint256[] memory normalizedAmounts = new uint256[](minLength); + + for (uint256 i = 0; i < minLength; i++) { + uint256 id = ids[i]; + + uint256 remainingMintAmountForId = type(uint256).max - userMintAmounts[address(to)][id]; + + uint256 mintAmount = bound(amounts[i], 0, remainingMintAmountForId); + + normalizedIds[i] = id; + normalizedAmounts[i] = mintAmount; + + userMintAmounts[address(to)][id] += mintAmount; + } + + token.batchMint(address(to), normalizedIds, normalizedAmounts, mintData); + } + + function testFailBatchMintToRevertingERC1155Recipient( + uint256[] memory ids, + uint256[] memory amounts, + bytes memory mintData + ) public { + RevertingERC1155Recipient to = new RevertingERC1155Recipient(); + + uint256 minLength = min2(ids.length, amounts.length); + + uint256[] memory normalizedIds = new uint256[](minLength); + uint256[] memory normalizedAmounts = new uint256[](minLength); + + for (uint256 i = 0; i < minLength; i++) { + uint256 id = ids[i]; + + uint256 remainingMintAmountForId = type(uint256).max - userMintAmounts[address(to)][id]; + + uint256 mintAmount = bound(amounts[i], 0, remainingMintAmountForId); + + normalizedIds[i] = id; + normalizedAmounts[i] = mintAmount; + + userMintAmounts[address(to)][id] += mintAmount; + } + + token.batchMint(address(to), normalizedIds, normalizedAmounts, mintData); + } + + function testFailBatchMintToWrongReturnDataERC1155Recipient( + uint256[] memory ids, + uint256[] memory amounts, + bytes memory mintData + ) public { + WrongReturnDataERC1155Recipient to = new WrongReturnDataERC1155Recipient(); + + uint256 minLength = min2(ids.length, amounts.length); + + uint256[] memory normalizedIds = new uint256[](minLength); + uint256[] memory normalizedAmounts = new uint256[](minLength); + + for (uint256 i = 0; i < minLength; i++) { + uint256 id = ids[i]; + + uint256 remainingMintAmountForId = type(uint256).max - userMintAmounts[address(to)][id]; + + uint256 mintAmount = bound(amounts[i], 0, remainingMintAmountForId); + + normalizedIds[i] = id; + normalizedAmounts[i] = mintAmount; + + userMintAmounts[address(to)][id] += mintAmount; + } + + token.batchMint(address(to), normalizedIds, normalizedAmounts, mintData); + } + + function testFailBatchMintWithArrayMismatch( + address to, + uint256[] memory ids, + uint256[] memory amounts, + bytes memory mintData + ) public { + if (ids.length == amounts.length) revert(); + + token.batchMint(address(to), ids, amounts, mintData); + } + + function testFailBatchBurnInsufficientBalance( + address to, + uint256[] memory ids, + uint256[] memory mintAmounts, + uint256[] memory burnAmounts, + bytes memory mintData + ) public { + uint256 minLength = min3(ids.length, mintAmounts.length, burnAmounts.length); + + if (minLength == 0) revert(); + + uint256[] memory normalizedIds = new uint256[](minLength); + uint256[] memory normalizedMintAmounts = new uint256[](minLength); + uint256[] memory normalizedBurnAmounts = new uint256[](minLength); + + for (uint256 i = 0; i < minLength; i++) { + uint256 id = ids[i]; + + uint256 remainingMintAmountForId = type(uint256).max - userMintAmounts[to][id]; + + normalizedIds[i] = id; + normalizedMintAmounts[i] = bound(mintAmounts[i], 0, remainingMintAmountForId); + normalizedBurnAmounts[i] = bound(burnAmounts[i], normalizedMintAmounts[i] + 1, type(uint256).max); + + userMintAmounts[to][id] += normalizedMintAmounts[i]; + } + + token.batchMint(to, normalizedIds, normalizedMintAmounts, mintData); + + token.batchBurn(to, normalizedIds, normalizedBurnAmounts); + } + + function testFailBatchBurnWithArrayLengthMismatch( + address to, + uint256[] memory ids, + uint256[] memory mintAmounts, + uint256[] memory burnAmounts, + bytes memory mintData + ) public { + if (ids.length == burnAmounts.length) revert(); + + token.batchMint(to, ids, mintAmounts, mintData); + + token.batchBurn(to, ids, burnAmounts); + } + + function testFailBalanceOfBatchWithArrayMismatch(address[] memory tos, uint256[] memory ids) public view { + if (tos.length == ids.length) revert(); + + token.balanceOfBatch(tos, ids); + } +} diff --git a/CVLByExample/UniswapHooks/lib/uniswap-hooks/lib/v4-core/lib/solmate/src/test/ERC20.t.sol b/CVLByExample/UniswapHooks/lib/uniswap-hooks/lib/v4-core/lib/solmate/src/test/ERC20.t.sol new file mode 100644 index 00000000..1506d8cf --- /dev/null +++ b/CVLByExample/UniswapHooks/lib/uniswap-hooks/lib/v4-core/lib/solmate/src/test/ERC20.t.sol @@ -0,0 +1,531 @@ +// SPDX-License-Identifier: AGPL-3.0-only +pragma solidity 0.8.15; + +import {DSTestPlus} from "./utils/DSTestPlus.sol"; +import {DSInvariantTest} from "./utils/DSInvariantTest.sol"; + +import {MockERC20} from "./utils/mocks/MockERC20.sol"; + +contract ERC20Test is DSTestPlus { + MockERC20 token; + + bytes32 constant PERMIT_TYPEHASH = + keccak256("Permit(address owner,address spender,uint256 value,uint256 nonce,uint256 deadline)"); + + function setUp() public { + token = new MockERC20("Token", "TKN", 18); + } + + function invariantMetadata() public { + assertEq(token.name(), "Token"); + assertEq(token.symbol(), "TKN"); + assertEq(token.decimals(), 18); + } + + function testMint() public { + token.mint(address(0xBEEF), 1e18); + + assertEq(token.totalSupply(), 1e18); + assertEq(token.balanceOf(address(0xBEEF)), 1e18); + } + + function testBurn() public { + token.mint(address(0xBEEF), 1e18); + token.burn(address(0xBEEF), 0.9e18); + + assertEq(token.totalSupply(), 1e18 - 0.9e18); + assertEq(token.balanceOf(address(0xBEEF)), 0.1e18); + } + + function testApprove() public { + assertTrue(token.approve(address(0xBEEF), 1e18)); + + assertEq(token.allowance(address(this), address(0xBEEF)), 1e18); + } + + function testTransfer() public { + token.mint(address(this), 1e18); + + assertTrue(token.transfer(address(0xBEEF), 1e18)); + assertEq(token.totalSupply(), 1e18); + + assertEq(token.balanceOf(address(this)), 0); + assertEq(token.balanceOf(address(0xBEEF)), 1e18); + } + + function testTransferFrom() public { + address from = address(0xABCD); + + token.mint(from, 1e18); + + hevm.prank(from); + token.approve(address(this), 1e18); + + assertTrue(token.transferFrom(from, address(0xBEEF), 1e18)); + assertEq(token.totalSupply(), 1e18); + + assertEq(token.allowance(from, address(this)), 0); + + assertEq(token.balanceOf(from), 0); + assertEq(token.balanceOf(address(0xBEEF)), 1e18); + } + + function testInfiniteApproveTransferFrom() public { + address from = address(0xABCD); + + token.mint(from, 1e18); + + hevm.prank(from); + token.approve(address(this), type(uint256).max); + + assertTrue(token.transferFrom(from, address(0xBEEF), 1e18)); + assertEq(token.totalSupply(), 1e18); + + assertEq(token.allowance(from, address(this)), type(uint256).max); + + assertEq(token.balanceOf(from), 0); + assertEq(token.balanceOf(address(0xBEEF)), 1e18); + } + + function testPermit() public { + uint256 privateKey = 0xBEEF; + address owner = hevm.addr(privateKey); + + (uint8 v, bytes32 r, bytes32 s) = hevm.sign( + privateKey, + keccak256( + abi.encodePacked( + "\x19\x01", + token.DOMAIN_SEPARATOR(), + keccak256(abi.encode(PERMIT_TYPEHASH, owner, address(0xCAFE), 1e18, 0, block.timestamp)) + ) + ) + ); + + token.permit(owner, address(0xCAFE), 1e18, block.timestamp, v, r, s); + + assertEq(token.allowance(owner, address(0xCAFE)), 1e18); + assertEq(token.nonces(owner), 1); + } + + function testFailTransferInsufficientBalance() public { + token.mint(address(this), 0.9e18); + token.transfer(address(0xBEEF), 1e18); + } + + function testFailTransferFromInsufficientAllowance() public { + address from = address(0xABCD); + + token.mint(from, 1e18); + + hevm.prank(from); + token.approve(address(this), 0.9e18); + + token.transferFrom(from, address(0xBEEF), 1e18); + } + + function testFailTransferFromInsufficientBalance() public { + address from = address(0xABCD); + + token.mint(from, 0.9e18); + + hevm.prank(from); + token.approve(address(this), 1e18); + + token.transferFrom(from, address(0xBEEF), 1e18); + } + + function testFailPermitBadNonce() public { + uint256 privateKey = 0xBEEF; + address owner = hevm.addr(privateKey); + + (uint8 v, bytes32 r, bytes32 s) = hevm.sign( + privateKey, + keccak256( + abi.encodePacked( + "\x19\x01", + token.DOMAIN_SEPARATOR(), + keccak256(abi.encode(PERMIT_TYPEHASH, owner, address(0xCAFE), 1e18, 1, block.timestamp)) + ) + ) + ); + + token.permit(owner, address(0xCAFE), 1e18, block.timestamp, v, r, s); + } + + function testFailPermitBadDeadline() public { + uint256 privateKey = 0xBEEF; + address owner = hevm.addr(privateKey); + + (uint8 v, bytes32 r, bytes32 s) = hevm.sign( + privateKey, + keccak256( + abi.encodePacked( + "\x19\x01", + token.DOMAIN_SEPARATOR(), + keccak256(abi.encode(PERMIT_TYPEHASH, owner, address(0xCAFE), 1e18, 0, block.timestamp)) + ) + ) + ); + + token.permit(owner, address(0xCAFE), 1e18, block.timestamp + 1, v, r, s); + } + + function testFailPermitPastDeadline() public { + uint256 oldTimestamp = block.timestamp; + uint256 privateKey = 0xBEEF; + address owner = hevm.addr(privateKey); + + (uint8 v, bytes32 r, bytes32 s) = hevm.sign( + privateKey, + keccak256( + abi.encodePacked( + "\x19\x01", + token.DOMAIN_SEPARATOR(), + keccak256(abi.encode(PERMIT_TYPEHASH, owner, address(0xCAFE), 1e18, 0, oldTimestamp)) + ) + ) + ); + + hevm.warp(block.timestamp + 1); + token.permit(owner, address(0xCAFE), 1e18, oldTimestamp, v, r, s); + } + + function testFailPermitReplay() public { + uint256 privateKey = 0xBEEF; + address owner = hevm.addr(privateKey); + + (uint8 v, bytes32 r, bytes32 s) = hevm.sign( + privateKey, + keccak256( + abi.encodePacked( + "\x19\x01", + token.DOMAIN_SEPARATOR(), + keccak256(abi.encode(PERMIT_TYPEHASH, owner, address(0xCAFE), 1e18, 0, block.timestamp)) + ) + ) + ); + + token.permit(owner, address(0xCAFE), 1e18, block.timestamp, v, r, s); + token.permit(owner, address(0xCAFE), 1e18, block.timestamp, v, r, s); + } + + function testMetadata( + string calldata name, + string calldata symbol, + uint8 decimals + ) public { + MockERC20 tkn = new MockERC20(name, symbol, decimals); + assertEq(tkn.name(), name); + assertEq(tkn.symbol(), symbol); + assertEq(tkn.decimals(), decimals); + } + + function testMint(address from, uint256 amount) public { + token.mint(from, amount); + + assertEq(token.totalSupply(), amount); + assertEq(token.balanceOf(from), amount); + } + + function testBurn( + address from, + uint256 mintAmount, + uint256 burnAmount + ) public { + burnAmount = bound(burnAmount, 0, mintAmount); + + token.mint(from, mintAmount); + token.burn(from, burnAmount); + + assertEq(token.totalSupply(), mintAmount - burnAmount); + assertEq(token.balanceOf(from), mintAmount - burnAmount); + } + + function testApprove(address to, uint256 amount) public { + assertTrue(token.approve(to, amount)); + + assertEq(token.allowance(address(this), to), amount); + } + + function testTransfer(address from, uint256 amount) public { + token.mint(address(this), amount); + + assertTrue(token.transfer(from, amount)); + assertEq(token.totalSupply(), amount); + + if (address(this) == from) { + assertEq(token.balanceOf(address(this)), amount); + } else { + assertEq(token.balanceOf(address(this)), 0); + assertEq(token.balanceOf(from), amount); + } + } + + function testTransferFrom( + address to, + uint256 approval, + uint256 amount + ) public { + amount = bound(amount, 0, approval); + + address from = address(0xABCD); + + token.mint(from, amount); + + hevm.prank(from); + token.approve(address(this), approval); + + assertTrue(token.transferFrom(from, to, amount)); + assertEq(token.totalSupply(), amount); + + uint256 app = from == address(this) || approval == type(uint256).max ? approval : approval - amount; + assertEq(token.allowance(from, address(this)), app); + + if (from == to) { + assertEq(token.balanceOf(from), amount); + } else { + assertEq(token.balanceOf(from), 0); + assertEq(token.balanceOf(to), amount); + } + } + + function testPermit( + uint248 privKey, + address to, + uint256 amount, + uint256 deadline + ) public { + uint256 privateKey = privKey; + if (deadline < block.timestamp) deadline = block.timestamp; + if (privateKey == 0) privateKey = 1; + + address owner = hevm.addr(privateKey); + + (uint8 v, bytes32 r, bytes32 s) = hevm.sign( + privateKey, + keccak256( + abi.encodePacked( + "\x19\x01", + token.DOMAIN_SEPARATOR(), + keccak256(abi.encode(PERMIT_TYPEHASH, owner, to, amount, 0, deadline)) + ) + ) + ); + + token.permit(owner, to, amount, deadline, v, r, s); + + assertEq(token.allowance(owner, to), amount); + assertEq(token.nonces(owner), 1); + } + + function testFailBurnInsufficientBalance( + address to, + uint256 mintAmount, + uint256 burnAmount + ) public { + burnAmount = bound(burnAmount, mintAmount + 1, type(uint256).max); + + token.mint(to, mintAmount); + token.burn(to, burnAmount); + } + + function testFailTransferInsufficientBalance( + address to, + uint256 mintAmount, + uint256 sendAmount + ) public { + sendAmount = bound(sendAmount, mintAmount + 1, type(uint256).max); + + token.mint(address(this), mintAmount); + token.transfer(to, sendAmount); + } + + function testFailTransferFromInsufficientAllowance( + address to, + uint256 approval, + uint256 amount + ) public { + amount = bound(amount, approval + 1, type(uint256).max); + + address from = address(0xABCD); + + token.mint(from, amount); + + hevm.prank(from); + token.approve(address(this), approval); + + token.transferFrom(from, to, amount); + } + + function testFailTransferFromInsufficientBalance( + address to, + uint256 mintAmount, + uint256 sendAmount + ) public { + sendAmount = bound(sendAmount, mintAmount + 1, type(uint256).max); + + address from = address(0xABCD); + + token.mint(from, mintAmount); + + hevm.prank(from); + token.approve(address(this), sendAmount); + + token.transferFrom(from, to, sendAmount); + } + + function testFailPermitBadNonce( + uint256 privateKey, + address to, + uint256 amount, + uint256 deadline, + uint256 nonce + ) public { + if (deadline < block.timestamp) deadline = block.timestamp; + if (privateKey == 0) privateKey = 1; + if (nonce == 0) nonce = 1; + + address owner = hevm.addr(privateKey); + + (uint8 v, bytes32 r, bytes32 s) = hevm.sign( + privateKey, + keccak256( + abi.encodePacked( + "\x19\x01", + token.DOMAIN_SEPARATOR(), + keccak256(abi.encode(PERMIT_TYPEHASH, owner, to, amount, nonce, deadline)) + ) + ) + ); + + token.permit(owner, to, amount, deadline, v, r, s); + } + + function testFailPermitBadDeadline( + uint256 privateKey, + address to, + uint256 amount, + uint256 deadline + ) public { + if (deadline < block.timestamp) deadline = block.timestamp; + if (privateKey == 0) privateKey = 1; + + address owner = hevm.addr(privateKey); + + (uint8 v, bytes32 r, bytes32 s) = hevm.sign( + privateKey, + keccak256( + abi.encodePacked( + "\x19\x01", + token.DOMAIN_SEPARATOR(), + keccak256(abi.encode(PERMIT_TYPEHASH, owner, to, amount, 0, deadline)) + ) + ) + ); + + token.permit(owner, to, amount, deadline + 1, v, r, s); + } + + function testFailPermitPastDeadline( + uint256 privateKey, + address to, + uint256 amount, + uint256 deadline + ) public { + deadline = bound(deadline, 0, block.timestamp - 1); + if (privateKey == 0) privateKey = 1; + + address owner = hevm.addr(privateKey); + + (uint8 v, bytes32 r, bytes32 s) = hevm.sign( + privateKey, + keccak256( + abi.encodePacked( + "\x19\x01", + token.DOMAIN_SEPARATOR(), + keccak256(abi.encode(PERMIT_TYPEHASH, owner, to, amount, 0, deadline)) + ) + ) + ); + + token.permit(owner, to, amount, deadline, v, r, s); + } + + function testFailPermitReplay( + uint256 privateKey, + address to, + uint256 amount, + uint256 deadline + ) public { + if (deadline < block.timestamp) deadline = block.timestamp; + if (privateKey == 0) privateKey = 1; + + address owner = hevm.addr(privateKey); + + (uint8 v, bytes32 r, bytes32 s) = hevm.sign( + privateKey, + keccak256( + abi.encodePacked( + "\x19\x01", + token.DOMAIN_SEPARATOR(), + keccak256(abi.encode(PERMIT_TYPEHASH, owner, to, amount, 0, deadline)) + ) + ) + ); + + token.permit(owner, to, amount, deadline, v, r, s); + token.permit(owner, to, amount, deadline, v, r, s); + } +} + +contract ERC20Invariants is DSTestPlus, DSInvariantTest { + BalanceSum balanceSum; + MockERC20 token; + + function setUp() public { + token = new MockERC20("Token", "TKN", 18); + balanceSum = new BalanceSum(token); + + addTargetContract(address(balanceSum)); + } + + function invariantBalanceSum() public { + assertEq(token.totalSupply(), balanceSum.sum()); + } +} + +contract BalanceSum { + MockERC20 token; + uint256 public sum; + + constructor(MockERC20 _token) { + token = _token; + } + + function mint(address from, uint256 amount) public { + token.mint(from, amount); + sum += amount; + } + + function burn(address from, uint256 amount) public { + token.burn(from, amount); + sum -= amount; + } + + function approve(address to, uint256 amount) public { + token.approve(to, amount); + } + + function transferFrom( + address from, + address to, + uint256 amount + ) public { + token.transferFrom(from, to, amount); + } + + function transfer(address to, uint256 amount) public { + token.transfer(to, amount); + } +} diff --git a/CVLByExample/UniswapHooks/lib/uniswap-hooks/lib/v4-core/lib/solmate/src/test/ERC4626.t.sol b/CVLByExample/UniswapHooks/lib/uniswap-hooks/lib/v4-core/lib/solmate/src/test/ERC4626.t.sol new file mode 100644 index 00000000..816c8e48 --- /dev/null +++ b/CVLByExample/UniswapHooks/lib/uniswap-hooks/lib/v4-core/lib/solmate/src/test/ERC4626.t.sol @@ -0,0 +1,446 @@ +// SPDX-License-Identifier: AGPL-3.0-only +pragma solidity 0.8.15; + +import {DSTestPlus} from "./utils/DSTestPlus.sol"; + +import {MockERC20} from "./utils/mocks/MockERC20.sol"; +import {MockERC4626} from "./utils/mocks/MockERC4626.sol"; + +contract ERC4626Test is DSTestPlus { + MockERC20 underlying; + MockERC4626 vault; + + function setUp() public { + underlying = new MockERC20("Mock Token", "TKN", 18); + vault = new MockERC4626(underlying, "Mock Token Vault", "vwTKN"); + } + + function invariantMetadata() public { + assertEq(vault.name(), "Mock Token Vault"); + assertEq(vault.symbol(), "vwTKN"); + assertEq(vault.decimals(), 18); + } + + function testMetadata(string calldata name, string calldata symbol) public { + MockERC4626 vlt = new MockERC4626(underlying, name, symbol); + assertEq(vlt.name(), name); + assertEq(vlt.symbol(), symbol); + assertEq(address(vlt.asset()), address(underlying)); + } + + function testSingleDepositWithdraw(uint128 amount) public { + if (amount == 0) amount = 1; + + uint256 aliceUnderlyingAmount = amount; + + address alice = address(0xABCD); + + underlying.mint(alice, aliceUnderlyingAmount); + + hevm.prank(alice); + underlying.approve(address(vault), aliceUnderlyingAmount); + assertEq(underlying.allowance(alice, address(vault)), aliceUnderlyingAmount); + + uint256 alicePreDepositBal = underlying.balanceOf(alice); + + hevm.prank(alice); + uint256 aliceShareAmount = vault.deposit(aliceUnderlyingAmount, alice); + + assertEq(vault.afterDepositHookCalledCounter(), 1); + + // Expect exchange rate to be 1:1 on initial deposit. + assertEq(aliceUnderlyingAmount, aliceShareAmount); + assertEq(vault.previewWithdraw(aliceShareAmount), aliceUnderlyingAmount); + assertEq(vault.previewDeposit(aliceUnderlyingAmount), aliceShareAmount); + assertEq(vault.totalSupply(), aliceShareAmount); + assertEq(vault.totalAssets(), aliceUnderlyingAmount); + assertEq(vault.balanceOf(alice), aliceShareAmount); + assertEq(vault.convertToAssets(vault.balanceOf(alice)), aliceUnderlyingAmount); + assertEq(underlying.balanceOf(alice), alicePreDepositBal - aliceUnderlyingAmount); + + hevm.prank(alice); + vault.withdraw(aliceUnderlyingAmount, alice, alice); + + assertEq(vault.beforeWithdrawHookCalledCounter(), 1); + + assertEq(vault.totalAssets(), 0); + assertEq(vault.balanceOf(alice), 0); + assertEq(vault.convertToAssets(vault.balanceOf(alice)), 0); + assertEq(underlying.balanceOf(alice), alicePreDepositBal); + } + + function testSingleMintRedeem(uint128 amount) public { + if (amount == 0) amount = 1; + + uint256 aliceShareAmount = amount; + + address alice = address(0xABCD); + + underlying.mint(alice, aliceShareAmount); + + hevm.prank(alice); + underlying.approve(address(vault), aliceShareAmount); + assertEq(underlying.allowance(alice, address(vault)), aliceShareAmount); + + uint256 alicePreDepositBal = underlying.balanceOf(alice); + + hevm.prank(alice); + uint256 aliceUnderlyingAmount = vault.mint(aliceShareAmount, alice); + + assertEq(vault.afterDepositHookCalledCounter(), 1); + + // Expect exchange rate to be 1:1 on initial mint. + assertEq(aliceShareAmount, aliceUnderlyingAmount); + assertEq(vault.previewWithdraw(aliceShareAmount), aliceUnderlyingAmount); + assertEq(vault.previewDeposit(aliceUnderlyingAmount), aliceShareAmount); + assertEq(vault.totalSupply(), aliceShareAmount); + assertEq(vault.totalAssets(), aliceUnderlyingAmount); + assertEq(vault.balanceOf(alice), aliceUnderlyingAmount); + assertEq(vault.convertToAssets(vault.balanceOf(alice)), aliceUnderlyingAmount); + assertEq(underlying.balanceOf(alice), alicePreDepositBal - aliceUnderlyingAmount); + + hevm.prank(alice); + vault.redeem(aliceShareAmount, alice, alice); + + assertEq(vault.beforeWithdrawHookCalledCounter(), 1); + + assertEq(vault.totalAssets(), 0); + assertEq(vault.balanceOf(alice), 0); + assertEq(vault.convertToAssets(vault.balanceOf(alice)), 0); + assertEq(underlying.balanceOf(alice), alicePreDepositBal); + } + + function testMultipleMintDepositRedeemWithdraw() public { + // Scenario: + // A = Alice, B = Bob + // ________________________________________________________ + // | Vault shares | A share | A assets | B share | B assets | + // |========================================================| + // | 1. Alice mints 2000 shares (costs 2000 tokens) | + // |--------------|---------|----------|---------|----------| + // | 2000 | 2000 | 2000 | 0 | 0 | + // |--------------|---------|----------|---------|----------| + // | 2. Bob deposits 4000 tokens (mints 4000 shares) | + // |--------------|---------|----------|---------|----------| + // | 6000 | 2000 | 2000 | 4000 | 4000 | + // |--------------|---------|----------|---------|----------| + // | 3. Vault mutates by +3000 tokens... | + // | (simulated yield returned from strategy)... | + // |--------------|---------|----------|---------|----------| + // | 6000 | 2000 | 3000 | 4000 | 6000 | + // |--------------|---------|----------|---------|----------| + // | 4. Alice deposits 2000 tokens (mints 1333 shares) | + // |--------------|---------|----------|---------|----------| + // | 7333 | 3333 | 4999 | 4000 | 6000 | + // |--------------|---------|----------|---------|----------| + // | 5. Bob mints 2000 shares (costs 3001 assets) | + // | NOTE: Bob's assets spent got rounded up | + // | NOTE: Alice's vault assets got rounded up | + // |--------------|---------|----------|---------|----------| + // | 9333 | 3333 | 5000 | 6000 | 9000 | + // |--------------|---------|----------|---------|----------| + // | 6. Vault mutates by +3000 tokens... | + // | (simulated yield returned from strategy) | + // | NOTE: Vault holds 17001 tokens, but sum of | + // | assetsOf() is 17000. | + // |--------------|---------|----------|---------|----------| + // | 9333 | 3333 | 6071 | 6000 | 10929 | + // |--------------|---------|----------|---------|----------| + // | 7. Alice redeem 1333 shares (2428 assets) | + // |--------------|---------|----------|---------|----------| + // | 8000 | 2000 | 3643 | 6000 | 10929 | + // |--------------|---------|----------|---------|----------| + // | 8. Bob withdraws 2928 assets (1608 shares) | + // |--------------|---------|----------|---------|----------| + // | 6392 | 2000 | 3643 | 4392 | 8000 | + // |--------------|---------|----------|---------|----------| + // | 9. Alice withdraws 3643 assets (2000 shares) | + // | NOTE: Bob's assets have been rounded back up | + // |--------------|---------|----------|---------|----------| + // | 4392 | 0 | 0 | 4392 | 8001 | + // |--------------|---------|----------|---------|----------| + // | 10. Bob redeem 4392 shares (8001 tokens) | + // |--------------|---------|----------|---------|----------| + // | 0 | 0 | 0 | 0 | 0 | + // |______________|_________|__________|_________|__________| + + address alice = address(0xABCD); + address bob = address(0xDCBA); + + uint256 mutationUnderlyingAmount = 3000; + + underlying.mint(alice, 4000); + + hevm.prank(alice); + underlying.approve(address(vault), 4000); + + assertEq(underlying.allowance(alice, address(vault)), 4000); + + underlying.mint(bob, 7001); + + hevm.prank(bob); + underlying.approve(address(vault), 7001); + + assertEq(underlying.allowance(bob, address(vault)), 7001); + + // 1. Alice mints 2000 shares (costs 2000 tokens) + hevm.prank(alice); + uint256 aliceUnderlyingAmount = vault.mint(2000, alice); + + uint256 aliceShareAmount = vault.previewDeposit(aliceUnderlyingAmount); + assertEq(vault.afterDepositHookCalledCounter(), 1); + + // Expect to have received the requested mint amount. + assertEq(aliceShareAmount, 2000); + assertEq(vault.balanceOf(alice), aliceShareAmount); + assertEq(vault.convertToAssets(vault.balanceOf(alice)), aliceUnderlyingAmount); + assertEq(vault.convertToShares(aliceUnderlyingAmount), vault.balanceOf(alice)); + + // Expect a 1:1 ratio before mutation. + assertEq(aliceUnderlyingAmount, 2000); + + // Sanity check. + assertEq(vault.totalSupply(), aliceShareAmount); + assertEq(vault.totalAssets(), aliceUnderlyingAmount); + + // 2. Bob deposits 4000 tokens (mints 4000 shares) + hevm.prank(bob); + uint256 bobShareAmount = vault.deposit(4000, bob); + uint256 bobUnderlyingAmount = vault.previewWithdraw(bobShareAmount); + assertEq(vault.afterDepositHookCalledCounter(), 2); + + // Expect to have received the requested underlying amount. + assertEq(bobUnderlyingAmount, 4000); + assertEq(vault.balanceOf(bob), bobShareAmount); + assertEq(vault.convertToAssets(vault.balanceOf(bob)), bobUnderlyingAmount); + assertEq(vault.convertToShares(bobUnderlyingAmount), vault.balanceOf(bob)); + + // Expect a 1:1 ratio before mutation. + assertEq(bobShareAmount, bobUnderlyingAmount); + + // Sanity check. + uint256 preMutationShareBal = aliceShareAmount + bobShareAmount; + uint256 preMutationBal = aliceUnderlyingAmount + bobUnderlyingAmount; + assertEq(vault.totalSupply(), preMutationShareBal); + assertEq(vault.totalAssets(), preMutationBal); + assertEq(vault.totalSupply(), 6000); + assertEq(vault.totalAssets(), 6000); + + // 3. Vault mutates by +3000 tokens... | + // (simulated yield returned from strategy)... + // The Vault now contains more tokens than deposited which causes the exchange rate to change. + // Alice share is 33.33% of the Vault, Bob 66.66% of the Vault. + // Alice's share count stays the same but the underlying amount changes from 2000 to 3000. + // Bob's share count stays the same but the underlying amount changes from 4000 to 6000. + underlying.mint(address(vault), mutationUnderlyingAmount); + assertEq(vault.totalSupply(), preMutationShareBal); + assertEq(vault.totalAssets(), preMutationBal + mutationUnderlyingAmount); + assertEq(vault.balanceOf(alice), aliceShareAmount); + assertEq( + vault.convertToAssets(vault.balanceOf(alice)), + aliceUnderlyingAmount + (mutationUnderlyingAmount / 3) * 1 + ); + assertEq(vault.balanceOf(bob), bobShareAmount); + assertEq(vault.convertToAssets(vault.balanceOf(bob)), bobUnderlyingAmount + (mutationUnderlyingAmount / 3) * 2); + + // 4. Alice deposits 2000 tokens (mints 1333 shares) + hevm.prank(alice); + vault.deposit(2000, alice); + + assertEq(vault.totalSupply(), 7333); + assertEq(vault.balanceOf(alice), 3333); + assertEq(vault.convertToAssets(vault.balanceOf(alice)), 4999); + assertEq(vault.balanceOf(bob), 4000); + assertEq(vault.convertToAssets(vault.balanceOf(bob)), 6000); + + // 5. Bob mints 2000 shares (costs 3001 assets) + // NOTE: Bob's assets spent got rounded up + // NOTE: Alices's vault assets got rounded up + hevm.prank(bob); + vault.mint(2000, bob); + + assertEq(vault.totalSupply(), 9333); + assertEq(vault.balanceOf(alice), 3333); + assertEq(vault.convertToAssets(vault.balanceOf(alice)), 5000); + assertEq(vault.balanceOf(bob), 6000); + assertEq(vault.convertToAssets(vault.balanceOf(bob)), 9000); + + // Sanity checks: + // Alice and bob should have spent all their tokens now + assertEq(underlying.balanceOf(alice), 0); + assertEq(underlying.balanceOf(bob), 0); + // Assets in vault: 4k (alice) + 7k (bob) + 3k (yield) + 1 (round up) + assertEq(vault.totalAssets(), 14001); + + // 6. Vault mutates by +3000 tokens + // NOTE: Vault holds 17001 tokens, but sum of assetsOf() is 17000. + underlying.mint(address(vault), mutationUnderlyingAmount); + assertEq(vault.totalAssets(), 17001); + assertEq(vault.convertToAssets(vault.balanceOf(alice)), 6071); + assertEq(vault.convertToAssets(vault.balanceOf(bob)), 10929); + + // 7. Alice redeem 1333 shares (2428 assets) + hevm.prank(alice); + vault.redeem(1333, alice, alice); + + assertEq(underlying.balanceOf(alice), 2428); + assertEq(vault.totalSupply(), 8000); + assertEq(vault.totalAssets(), 14573); + assertEq(vault.balanceOf(alice), 2000); + assertEq(vault.convertToAssets(vault.balanceOf(alice)), 3643); + assertEq(vault.balanceOf(bob), 6000); + assertEq(vault.convertToAssets(vault.balanceOf(bob)), 10929); + + // 8. Bob withdraws 2929 assets (1608 shares) + hevm.prank(bob); + vault.withdraw(2929, bob, bob); + + assertEq(underlying.balanceOf(bob), 2929); + assertEq(vault.totalSupply(), 6392); + assertEq(vault.totalAssets(), 11644); + assertEq(vault.balanceOf(alice), 2000); + assertEq(vault.convertToAssets(vault.balanceOf(alice)), 3643); + assertEq(vault.balanceOf(bob), 4392); + assertEq(vault.convertToAssets(vault.balanceOf(bob)), 8000); + + // 9. Alice withdraws 3643 assets (2000 shares) + // NOTE: Bob's assets have been rounded back up + hevm.prank(alice); + vault.withdraw(3643, alice, alice); + + assertEq(underlying.balanceOf(alice), 6071); + assertEq(vault.totalSupply(), 4392); + assertEq(vault.totalAssets(), 8001); + assertEq(vault.balanceOf(alice), 0); + assertEq(vault.convertToAssets(vault.balanceOf(alice)), 0); + assertEq(vault.balanceOf(bob), 4392); + assertEq(vault.convertToAssets(vault.balanceOf(bob)), 8001); + + // 10. Bob redeem 4392 shares (8001 tokens) + hevm.prank(bob); + vault.redeem(4392, bob, bob); + assertEq(underlying.balanceOf(bob), 10930); + assertEq(vault.totalSupply(), 0); + assertEq(vault.totalAssets(), 0); + assertEq(vault.balanceOf(alice), 0); + assertEq(vault.convertToAssets(vault.balanceOf(alice)), 0); + assertEq(vault.balanceOf(bob), 0); + assertEq(vault.convertToAssets(vault.balanceOf(bob)), 0); + + // Sanity check + assertEq(underlying.balanceOf(address(vault)), 0); + } + + function testFailDepositWithNotEnoughApproval() public { + underlying.mint(address(this), 0.5e18); + underlying.approve(address(vault), 0.5e18); + assertEq(underlying.allowance(address(this), address(vault)), 0.5e18); + + vault.deposit(1e18, address(this)); + } + + function testFailWithdrawWithNotEnoughUnderlyingAmount() public { + underlying.mint(address(this), 0.5e18); + underlying.approve(address(vault), 0.5e18); + + vault.deposit(0.5e18, address(this)); + + vault.withdraw(1e18, address(this), address(this)); + } + + function testFailRedeemWithNotEnoughShareAmount() public { + underlying.mint(address(this), 0.5e18); + underlying.approve(address(vault), 0.5e18); + + vault.deposit(0.5e18, address(this)); + + vault.redeem(1e18, address(this), address(this)); + } + + function testFailWithdrawWithNoUnderlyingAmount() public { + vault.withdraw(1e18, address(this), address(this)); + } + + function testFailRedeemWithNoShareAmount() public { + vault.redeem(1e18, address(this), address(this)); + } + + function testFailDepositWithNoApproval() public { + vault.deposit(1e18, address(this)); + } + + function testFailMintWithNoApproval() public { + vault.mint(1e18, address(this)); + } + + function testFailDepositZero() public { + vault.deposit(0, address(this)); + } + + function testMintZero() public { + vault.mint(0, address(this)); + + assertEq(vault.balanceOf(address(this)), 0); + assertEq(vault.convertToAssets(vault.balanceOf(address(this))), 0); + assertEq(vault.totalSupply(), 0); + assertEq(vault.totalAssets(), 0); + } + + function testFailRedeemZero() public { + vault.redeem(0, address(this), address(this)); + } + + function testWithdrawZero() public { + vault.withdraw(0, address(this), address(this)); + + assertEq(vault.balanceOf(address(this)), 0); + assertEq(vault.convertToAssets(vault.balanceOf(address(this))), 0); + assertEq(vault.totalSupply(), 0); + assertEq(vault.totalAssets(), 0); + } + + function testVaultInteractionsForSomeoneElse() public { + // init 2 users with a 1e18 balance + address alice = address(0xABCD); + address bob = address(0xDCBA); + underlying.mint(alice, 1e18); + underlying.mint(bob, 1e18); + + hevm.prank(alice); + underlying.approve(address(vault), 1e18); + + hevm.prank(bob); + underlying.approve(address(vault), 1e18); + + // alice deposits 1e18 for bob + hevm.prank(alice); + vault.deposit(1e18, bob); + + assertEq(vault.balanceOf(alice), 0); + assertEq(vault.balanceOf(bob), 1e18); + assertEq(underlying.balanceOf(alice), 0); + + // bob mint 1e18 for alice + hevm.prank(bob); + vault.mint(1e18, alice); + assertEq(vault.balanceOf(alice), 1e18); + assertEq(vault.balanceOf(bob), 1e18); + assertEq(underlying.balanceOf(bob), 0); + + // alice redeem 1e18 for bob + hevm.prank(alice); + vault.redeem(1e18, bob, alice); + + assertEq(vault.balanceOf(alice), 0); + assertEq(vault.balanceOf(bob), 1e18); + assertEq(underlying.balanceOf(bob), 1e18); + + // bob withdraw 1e18 for alice + hevm.prank(bob); + vault.withdraw(1e18, alice, bob); + + assertEq(vault.balanceOf(alice), 0); + assertEq(vault.balanceOf(bob), 0); + assertEq(underlying.balanceOf(alice), 1e18); + } +} diff --git a/CVLByExample/UniswapHooks/lib/uniswap-hooks/lib/v4-core/lib/solmate/src/test/ERC6909.t.sol b/CVLByExample/UniswapHooks/lib/uniswap-hooks/lib/v4-core/lib/solmate/src/test/ERC6909.t.sol new file mode 100644 index 00000000..57cdb738 --- /dev/null +++ b/CVLByExample/UniswapHooks/lib/uniswap-hooks/lib/v4-core/lib/solmate/src/test/ERC6909.t.sol @@ -0,0 +1,378 @@ +// SPDX-License-Identifier: MIT +pragma solidity 0.8.15; + +import {DSTestPlus} from "./utils/DSTestPlus.sol"; +import {DSInvariantTest} from "./utils/DSInvariantTest.sol"; + +import {MockERC6909} from "./utils/mocks/MockERC6909.sol"; + +contract ERC6909Test is DSTestPlus { + MockERC6909 token; + + mapping(address => mapping(uint256 => uint256)) public userMintAmounts; + mapping(address => mapping(uint256 => uint256)) public userTransferOrBurnAmounts; + + function setUp() public { + token = new MockERC6909(); + } + + function testMint() public { + token.mint(address(0xBEEF), 1337, 100); + + assertEq(token.balanceOf(address(0xBEEF), 1337), 100); + } + + function testBurn() public { + token.mint(address(0xBEEF), 1337, 100); + token.burn(address(0xBEEF), 1337, 70); + + assertEq(token.balanceOf(address(0xBEEF), 1337), 30); + } + + function testSetOperator() public { + token.setOperator(address(0xBEEF), true); + + assertTrue(token.isOperator(address(this), address(0xBEEF))); + } + + function testApprove() public { + token.approve(address(0xBEEF), 1337, 100); + + assertEq(token.allowance(address(this), address(0xBEEF), 1337), 100); + } + + function testTransfer() public { + address sender = address(0xABCD); + + token.mint(sender, 1337, 100); + + hevm.prank(sender); + token.transfer(address(0xBEEF), 1337, 70); + + assertEq(token.balanceOf(sender, 1337), 30); + assertEq(token.balanceOf(address(0xBEEF), 1337), 70); + } + + function testTransferFromWithApproval() public { + address sender = address(0xABCD); + address receiver = address(0xBEEF); + + token.mint(sender, 1337, 100); + + hevm.prank(sender); + token.approve(address(this), 1337, 100); + + token.transferFrom(sender, receiver, 1337, 70); + + assertEq(token.allowance(sender, address(this), 1337), 30); + assertEq(token.balanceOf(sender, 1337), 30); + assertEq(token.balanceOf(receiver, 1337), 70); + } + + function testTransferFromWithInfiniteApproval() public { + address sender = address(0xABCD); + address receiver = address(0xBEEF); + + token.mint(sender, 1337, 100); + + hevm.prank(sender); + token.approve(address(this), 1337, type(uint256).max); + + token.transferFrom(sender, receiver, 1337, 70); + + assertEq(token.allowance(sender, address(this), 1337), type(uint256).max); + assertEq(token.balanceOf(sender, 1337), 30); + assertEq(token.balanceOf(receiver, 1337), 70); + } + + function testTransferFromAsOperator() public { + address sender = address(0xABCD); + address receiver = address(0xBEEF); + + token.mint(sender, 1337, 100); + + hevm.prank(sender); + token.setOperator(address(this), true); + + token.transferFrom(sender, receiver, 1337, 70); + + assertEq(token.balanceOf(sender, 1337), 30); + assertEq(token.balanceOf(receiver, 1337), 70); + } + + function testFailMintBalanceOverflow() public { + token.mint(address(0xDEAD), 1337, type(uint256).max); + token.mint(address(0xDEAD), 1337, 1); + } + + function testFailTransferBalanceUnderflow() public { + address sender = address(0xABCD); + address receiver = address(0xBEEF); + + hevm.prank(sender); + token.transferFrom(sender, receiver, 1337, 1); + } + + function testFailTransferBalanceOverflow() public { + address sender = address(0xABCD); + address receiver = address(0xBEEF); + + token.mint(sender, 1337, type(uint256).max); + + hevm.prank(sender); + token.transferFrom(sender, receiver, 1337, type(uint256).max); + + token.mint(sender, 1337, 1); + + hevm.prank(sender); + token.transferFrom(sender, receiver, 1337, 1); + } + + function testFailTransferFromBalanceUnderflow() public { + address sender = address(0xABCD); + address receiver = address(0xBEEF); + + hevm.prank(sender); + token.transferFrom(sender, receiver, 1337, 1); + } + + function testFailTransferFromBalanceOverflow() public { + address sender = address(0xABCD); + address receiver = address(0xBEEF); + + token.mint(sender, 1337, type(uint256).max); + + hevm.prank(sender); + token.transferFrom(sender, receiver, 1337, type(uint256).max); + + token.mint(sender, 1337, 1); + + hevm.prank(sender); + token.transferFrom(sender, receiver, 1337, 1); + } + + function testFailTransferFromNotAuthorized() public { + address sender = address(0xABCD); + address receiver = address(0xBEEF); + + token.mint(sender, 1337, 100); + + token.transferFrom(sender, receiver, 1337, 100); + } + + function testMint( + address receiver, + uint256 id, + uint256 amount + ) public { + token.mint(receiver, id, amount); + + assertEq(token.balanceOf(receiver, id), amount); + } + + function testBurn( + address sender, + uint256 id, + uint256 amount + ) public { + token.mint(sender, id, amount); + token.burn(sender, id, amount); + + assertEq(token.balanceOf(sender, id), 0); + } + + function testSetOperator(address operator, bool approved) public { + token.setOperator(operator, approved); + + assertBoolEq(token.isOperator(address(this), operator), approved); + } + + function testApprove( + address spender, + uint256 id, + uint256 amount + ) public { + token.approve(spender, id, amount); + + assertEq(token.allowance(address(this), spender, id), amount); + } + + function testTransfer( + address sender, + address receiver, + uint256 id, + uint256 mintAmount, + uint256 transferAmount + ) public { + transferAmount = bound(transferAmount, 0, mintAmount); + + token.mint(sender, id, mintAmount); + + hevm.prank(sender); + token.transfer(receiver, id, transferAmount); + + if (sender == receiver) { + assertEq(token.balanceOf(sender, id), mintAmount); + } else { + assertEq(token.balanceOf(sender, id), mintAmount - transferAmount); + assertEq(token.balanceOf(receiver, id), transferAmount); + } + } + + function testTransferFromWithApproval( + address sender, + address receiver, + uint256 id, + uint256 mintAmount, + uint256 transferAmount + ) public { + transferAmount = bound(transferAmount, 0, mintAmount); + + token.mint(sender, id, mintAmount); + + hevm.prank(sender); + token.approve(address(this), id, mintAmount); + + token.transferFrom(sender, receiver, id, transferAmount); + + if (mintAmount == type(uint256).max) { + assertEq(token.allowance(sender, address(this), id), type(uint256).max); + } else { + assertEq(token.allowance(sender, address(this), id), mintAmount - transferAmount); + } + + if (sender == receiver) { + assertEq(token.balanceOf(sender, id), mintAmount); + } else { + assertEq(token.balanceOf(sender, id), mintAmount - transferAmount); + assertEq(token.balanceOf(receiver, id), transferAmount); + } + } + + function testTransferFromWithInfiniteApproval( + address sender, + address receiver, + uint256 id, + uint256 mintAmount, + uint256 transferAmount + ) public { + transferAmount = bound(transferAmount, 0, mintAmount); + + token.mint(sender, id, mintAmount); + + hevm.prank(sender); + token.approve(address(this), id, type(uint256).max); + + token.transferFrom(sender, receiver, id, transferAmount); + + assertEq(token.allowance(sender, address(this), id), type(uint256).max); + + if (sender == receiver) { + assertEq(token.balanceOf(sender, id), mintAmount); + } else { + assertEq(token.balanceOf(sender, id), mintAmount - transferAmount); + assertEq(token.balanceOf(receiver, id), transferAmount); + } + } + + function testTransferFromAsOperator( + address sender, + address receiver, + uint256 id, + uint256 mintAmount, + uint256 transferAmount + ) public { + transferAmount = bound(transferAmount, 0, mintAmount); + + token.mint(sender, id, mintAmount); + + hevm.prank(sender); + token.setOperator(address(this), true); + + token.transferFrom(sender, receiver, id, transferAmount); + + if (sender == receiver) { + assertEq(token.balanceOf(sender, id), mintAmount); + } else { + assertEq(token.balanceOf(sender, id), mintAmount - transferAmount); + assertEq(token.balanceOf(receiver, id), transferAmount); + } + } + + function testFailTransferBalanceUnderflow( + address sender, + address receiver, + uint256 id, + uint256 amount + ) public { + amount = bound(amount, 1, type(uint256).max); + + hevm.prank(sender); + token.transfer(receiver, id, amount); + } + + function testFailTransferBalanceOverflow( + address sender, + address receiver, + uint256 id, + uint256 amount + ) public { + amount = bound(amount, 1, type(uint256).max); + uint256 overflowAmount = type(uint256).max - amount + 1; + + token.mint(sender, id, amount); + + hevm.prank(sender); + token.transfer(receiver, id, amount); + + token.mint(sender, id, overflowAmount); + + hevm.prank(sender); + token.transfer(receiver, id, overflowAmount); + } + + function testFailTransferFromBalanceUnderflow( + address sender, + address receiver, + uint256 id, + uint256 amount + ) public { + amount = bound(amount, 1, type(uint256).max); + + hevm.prank(sender); + token.transferFrom(sender, receiver, id, amount); + } + + function testFailTransferFromBalanceOverflow( + address sender, + address receiver, + uint256 id, + uint256 amount + ) public { + amount = bound(amount, 1, type(uint256).max); + uint256 overflowAmount = type(uint256).max - amount + 1; + + token.mint(sender, id, amount); + + hevm.prank(sender); + token.transferFrom(sender, receiver, id, amount); + + token.mint(sender, id, overflowAmount); + + hevm.prank(sender); + token.transferFrom(sender, receiver, id, overflowAmount); + } + + function testFailTransferFromNotAuthorized( + address sender, + address receiver, + uint256 id, + uint256 amount + ) public { + amount = bound(amount, 1, type(uint256).max); + + token.mint(sender, id, amount); + + token.transferFrom(sender, receiver, id, amount); + } +} diff --git a/CVLByExample/UniswapHooks/lib/uniswap-hooks/lib/v4-core/lib/solmate/src/test/ERC721.t.sol b/CVLByExample/UniswapHooks/lib/uniswap-hooks/lib/v4-core/lib/solmate/src/test/ERC721.t.sol new file mode 100644 index 00000000..81de0ff6 --- /dev/null +++ b/CVLByExample/UniswapHooks/lib/uniswap-hooks/lib/v4-core/lib/solmate/src/test/ERC721.t.sol @@ -0,0 +1,727 @@ +// SPDX-License-Identifier: AGPL-3.0-only +pragma solidity 0.8.15; + +import {DSTestPlus} from "./utils/DSTestPlus.sol"; +import {DSInvariantTest} from "./utils/DSInvariantTest.sol"; + +import {MockERC721} from "./utils/mocks/MockERC721.sol"; + +import {ERC721TokenReceiver} from "../tokens/ERC721.sol"; + +contract ERC721Recipient is ERC721TokenReceiver { + address public operator; + address public from; + uint256 public id; + bytes public data; + + function onERC721Received( + address _operator, + address _from, + uint256 _id, + bytes calldata _data + ) public virtual override returns (bytes4) { + operator = _operator; + from = _from; + id = _id; + data = _data; + + return ERC721TokenReceiver.onERC721Received.selector; + } +} + +contract RevertingERC721Recipient is ERC721TokenReceiver { + function onERC721Received( + address, + address, + uint256, + bytes calldata + ) public virtual override returns (bytes4) { + revert(string(abi.encodePacked(ERC721TokenReceiver.onERC721Received.selector))); + } +} + +contract WrongReturnDataERC721Recipient is ERC721TokenReceiver { + function onERC721Received( + address, + address, + uint256, + bytes calldata + ) public virtual override returns (bytes4) { + return 0xCAFEBEEF; + } +} + +contract NonERC721Recipient {} + +contract ERC721Test is DSTestPlus { + MockERC721 token; + + function setUp() public { + token = new MockERC721("Token", "TKN"); + } + + function invariantMetadata() public { + assertEq(token.name(), "Token"); + assertEq(token.symbol(), "TKN"); + } + + function testMint() public { + token.mint(address(0xBEEF), 1337); + + assertEq(token.balanceOf(address(0xBEEF)), 1); + assertEq(token.ownerOf(1337), address(0xBEEF)); + } + + function testBurn() public { + token.mint(address(0xBEEF), 1337); + token.burn(1337); + + assertEq(token.balanceOf(address(0xBEEF)), 0); + + hevm.expectRevert("NOT_MINTED"); + token.ownerOf(1337); + } + + function testApprove() public { + token.mint(address(this), 1337); + + token.approve(address(0xBEEF), 1337); + + assertEq(token.getApproved(1337), address(0xBEEF)); + } + + function testApproveBurn() public { + token.mint(address(this), 1337); + + token.approve(address(0xBEEF), 1337); + + token.burn(1337); + + assertEq(token.balanceOf(address(this)), 0); + assertEq(token.getApproved(1337), address(0)); + + hevm.expectRevert("NOT_MINTED"); + token.ownerOf(1337); + } + + function testApproveAll() public { + token.setApprovalForAll(address(0xBEEF), true); + + assertTrue(token.isApprovedForAll(address(this), address(0xBEEF))); + } + + function testTransferFrom() public { + address from = address(0xABCD); + + token.mint(from, 1337); + + hevm.prank(from); + token.approve(address(this), 1337); + + token.transferFrom(from, address(0xBEEF), 1337); + + assertEq(token.getApproved(1337), address(0)); + assertEq(token.ownerOf(1337), address(0xBEEF)); + assertEq(token.balanceOf(address(0xBEEF)), 1); + assertEq(token.balanceOf(from), 0); + } + + function testTransferFromSelf() public { + token.mint(address(this), 1337); + + token.transferFrom(address(this), address(0xBEEF), 1337); + + assertEq(token.getApproved(1337), address(0)); + assertEq(token.ownerOf(1337), address(0xBEEF)); + assertEq(token.balanceOf(address(0xBEEF)), 1); + assertEq(token.balanceOf(address(this)), 0); + } + + function testTransferFromApproveAll() public { + address from = address(0xABCD); + + token.mint(from, 1337); + + hevm.prank(from); + token.setApprovalForAll(address(this), true); + + token.transferFrom(from, address(0xBEEF), 1337); + + assertEq(token.getApproved(1337), address(0)); + assertEq(token.ownerOf(1337), address(0xBEEF)); + assertEq(token.balanceOf(address(0xBEEF)), 1); + assertEq(token.balanceOf(from), 0); + } + + function testSafeTransferFromToEOA() public { + address from = address(0xABCD); + + token.mint(from, 1337); + + hevm.prank(from); + token.setApprovalForAll(address(this), true); + + token.safeTransferFrom(from, address(0xBEEF), 1337); + + assertEq(token.getApproved(1337), address(0)); + assertEq(token.ownerOf(1337), address(0xBEEF)); + assertEq(token.balanceOf(address(0xBEEF)), 1); + assertEq(token.balanceOf(from), 0); + } + + function testSafeTransferFromToERC721Recipient() public { + address from = address(0xABCD); + ERC721Recipient recipient = new ERC721Recipient(); + + token.mint(from, 1337); + + hevm.prank(from); + token.setApprovalForAll(address(this), true); + + token.safeTransferFrom(from, address(recipient), 1337); + + assertEq(token.getApproved(1337), address(0)); + assertEq(token.ownerOf(1337), address(recipient)); + assertEq(token.balanceOf(address(recipient)), 1); + assertEq(token.balanceOf(from), 0); + + assertEq(recipient.operator(), address(this)); + assertEq(recipient.from(), from); + assertEq(recipient.id(), 1337); + assertBytesEq(recipient.data(), ""); + } + + function testSafeTransferFromToERC721RecipientWithData() public { + address from = address(0xABCD); + ERC721Recipient recipient = new ERC721Recipient(); + + token.mint(from, 1337); + + hevm.prank(from); + token.setApprovalForAll(address(this), true); + + token.safeTransferFrom(from, address(recipient), 1337, "testing 123"); + + assertEq(token.getApproved(1337), address(0)); + assertEq(token.ownerOf(1337), address(recipient)); + assertEq(token.balanceOf(address(recipient)), 1); + assertEq(token.balanceOf(from), 0); + + assertEq(recipient.operator(), address(this)); + assertEq(recipient.from(), from); + assertEq(recipient.id(), 1337); + assertBytesEq(recipient.data(), "testing 123"); + } + + function testSafeMintToEOA() public { + token.safeMint(address(0xBEEF), 1337); + + assertEq(token.ownerOf(1337), address(address(0xBEEF))); + assertEq(token.balanceOf(address(address(0xBEEF))), 1); + } + + function testSafeMintToERC721Recipient() public { + ERC721Recipient to = new ERC721Recipient(); + + token.safeMint(address(to), 1337); + + assertEq(token.ownerOf(1337), address(to)); + assertEq(token.balanceOf(address(to)), 1); + + assertEq(to.operator(), address(this)); + assertEq(to.from(), address(0)); + assertEq(to.id(), 1337); + assertBytesEq(to.data(), ""); + } + + function testSafeMintToERC721RecipientWithData() public { + ERC721Recipient to = new ERC721Recipient(); + + token.safeMint(address(to), 1337, "testing 123"); + + assertEq(token.ownerOf(1337), address(to)); + assertEq(token.balanceOf(address(to)), 1); + + assertEq(to.operator(), address(this)); + assertEq(to.from(), address(0)); + assertEq(to.id(), 1337); + assertBytesEq(to.data(), "testing 123"); + } + + function testFailMintToZero() public { + token.mint(address(0), 1337); + } + + function testFailDoubleMint() public { + token.mint(address(0xBEEF), 1337); + token.mint(address(0xBEEF), 1337); + } + + function testFailBurnUnMinted() public { + token.burn(1337); + } + + function testFailDoubleBurn() public { + token.mint(address(0xBEEF), 1337); + + token.burn(1337); + token.burn(1337); + } + + function testFailApproveUnMinted() public { + token.approve(address(0xBEEF), 1337); + } + + function testFailApproveUnAuthorized() public { + token.mint(address(0xCAFE), 1337); + + token.approve(address(0xBEEF), 1337); + } + + function testFailTransferFromUnOwned() public { + token.transferFrom(address(0xFEED), address(0xBEEF), 1337); + } + + function testFailTransferFromWrongFrom() public { + token.mint(address(0xCAFE), 1337); + + token.transferFrom(address(0xFEED), address(0xBEEF), 1337); + } + + function testFailTransferFromToZero() public { + token.mint(address(this), 1337); + + token.transferFrom(address(this), address(0), 1337); + } + + function testFailTransferFromNotOwner() public { + token.mint(address(0xFEED), 1337); + + token.transferFrom(address(0xFEED), address(0xBEEF), 1337); + } + + function testFailSafeTransferFromToNonERC721Recipient() public { + token.mint(address(this), 1337); + + token.safeTransferFrom(address(this), address(new NonERC721Recipient()), 1337); + } + + function testFailSafeTransferFromToNonERC721RecipientWithData() public { + token.mint(address(this), 1337); + + token.safeTransferFrom(address(this), address(new NonERC721Recipient()), 1337, "testing 123"); + } + + function testFailSafeTransferFromToRevertingERC721Recipient() public { + token.mint(address(this), 1337); + + token.safeTransferFrom(address(this), address(new RevertingERC721Recipient()), 1337); + } + + function testFailSafeTransferFromToRevertingERC721RecipientWithData() public { + token.mint(address(this), 1337); + + token.safeTransferFrom(address(this), address(new RevertingERC721Recipient()), 1337, "testing 123"); + } + + function testFailSafeTransferFromToERC721RecipientWithWrongReturnData() public { + token.mint(address(this), 1337); + + token.safeTransferFrom(address(this), address(new WrongReturnDataERC721Recipient()), 1337); + } + + function testFailSafeTransferFromToERC721RecipientWithWrongReturnDataWithData() public { + token.mint(address(this), 1337); + + token.safeTransferFrom(address(this), address(new WrongReturnDataERC721Recipient()), 1337, "testing 123"); + } + + function testFailSafeMintToNonERC721Recipient() public { + token.safeMint(address(new NonERC721Recipient()), 1337); + } + + function testFailSafeMintToNonERC721RecipientWithData() public { + token.safeMint(address(new NonERC721Recipient()), 1337, "testing 123"); + } + + function testFailSafeMintToRevertingERC721Recipient() public { + token.safeMint(address(new RevertingERC721Recipient()), 1337); + } + + function testFailSafeMintToRevertingERC721RecipientWithData() public { + token.safeMint(address(new RevertingERC721Recipient()), 1337, "testing 123"); + } + + function testFailSafeMintToERC721RecipientWithWrongReturnData() public { + token.safeMint(address(new WrongReturnDataERC721Recipient()), 1337); + } + + function testFailSafeMintToERC721RecipientWithWrongReturnDataWithData() public { + token.safeMint(address(new WrongReturnDataERC721Recipient()), 1337, "testing 123"); + } + + function testFailBalanceOfZeroAddress() public view { + token.balanceOf(address(0)); + } + + function testFailOwnerOfUnminted() public view { + token.ownerOf(1337); + } + + function testMetadata(string memory name, string memory symbol) public { + MockERC721 tkn = new MockERC721(name, symbol); + + assertEq(tkn.name(), name); + assertEq(tkn.symbol(), symbol); + } + + function testMint(address to, uint256 id) public { + if (to == address(0)) to = address(0xBEEF); + + token.mint(to, id); + + assertEq(token.balanceOf(to), 1); + assertEq(token.ownerOf(id), to); + } + + function testBurn(address to, uint256 id) public { + if (to == address(0)) to = address(0xBEEF); + + token.mint(to, id); + token.burn(id); + + assertEq(token.balanceOf(to), 0); + + hevm.expectRevert("NOT_MINTED"); + token.ownerOf(id); + } + + function testApprove(address to, uint256 id) public { + if (to == address(0)) to = address(0xBEEF); + + token.mint(address(this), id); + + token.approve(to, id); + + assertEq(token.getApproved(id), to); + } + + function testApproveBurn(address to, uint256 id) public { + token.mint(address(this), id); + + token.approve(address(to), id); + + token.burn(id); + + assertEq(token.balanceOf(address(this)), 0); + assertEq(token.getApproved(id), address(0)); + + hevm.expectRevert("NOT_MINTED"); + token.ownerOf(id); + } + + function testApproveAll(address to, bool approved) public { + token.setApprovalForAll(to, approved); + + assertBoolEq(token.isApprovedForAll(address(this), to), approved); + } + + function testTransferFrom(uint256 id, address to) public { + address from = address(0xABCD); + + if (to == address(0) || to == from) to = address(0xBEEF); + + token.mint(from, id); + + hevm.prank(from); + token.approve(address(this), id); + + token.transferFrom(from, to, id); + + assertEq(token.getApproved(id), address(0)); + assertEq(token.ownerOf(id), to); + assertEq(token.balanceOf(to), 1); + assertEq(token.balanceOf(from), 0); + } + + function testTransferFromSelf(uint256 id, address to) public { + if (to == address(0) || to == address(this)) to = address(0xBEEF); + + token.mint(address(this), id); + + token.transferFrom(address(this), to, id); + + assertEq(token.getApproved(id), address(0)); + assertEq(token.ownerOf(id), to); + assertEq(token.balanceOf(to), 1); + assertEq(token.balanceOf(address(this)), 0); + } + + function testTransferFromApproveAll(uint256 id, address to) public { + address from = address(0xABCD); + + if (to == address(0) || to == from) to = address(0xBEEF); + + token.mint(from, id); + + hevm.prank(from); + token.setApprovalForAll(address(this), true); + + token.transferFrom(from, to, id); + + assertEq(token.getApproved(id), address(0)); + assertEq(token.ownerOf(id), to); + assertEq(token.balanceOf(to), 1); + assertEq(token.balanceOf(from), 0); + } + + function testSafeTransferFromToEOA(uint256 id, address to) public { + address from = address(0xABCD); + + if (to == address(0) || to == from) to = address(0xBEEF); + + if (uint256(uint160(to)) <= 18 || to.code.length > 0) return; + + token.mint(from, id); + + hevm.prank(from); + token.setApprovalForAll(address(this), true); + + token.safeTransferFrom(from, to, id); + + assertEq(token.getApproved(id), address(0)); + assertEq(token.ownerOf(id), to); + assertEq(token.balanceOf(to), 1); + assertEq(token.balanceOf(from), 0); + } + + function testSafeTransferFromToERC721Recipient(uint256 id) public { + address from = address(0xABCD); + + ERC721Recipient recipient = new ERC721Recipient(); + + token.mint(from, id); + + hevm.prank(from); + token.setApprovalForAll(address(this), true); + + token.safeTransferFrom(from, address(recipient), id); + + assertEq(token.getApproved(id), address(0)); + assertEq(token.ownerOf(id), address(recipient)); + assertEq(token.balanceOf(address(recipient)), 1); + assertEq(token.balanceOf(from), 0); + + assertEq(recipient.operator(), address(this)); + assertEq(recipient.from(), from); + assertEq(recipient.id(), id); + assertBytesEq(recipient.data(), ""); + } + + function testSafeTransferFromToERC721RecipientWithData(uint256 id, bytes calldata data) public { + address from = address(0xABCD); + ERC721Recipient recipient = new ERC721Recipient(); + + token.mint(from, id); + + hevm.prank(from); + token.setApprovalForAll(address(this), true); + + token.safeTransferFrom(from, address(recipient), id, data); + + assertEq(token.getApproved(id), address(0)); + assertEq(token.ownerOf(id), address(recipient)); + assertEq(token.balanceOf(address(recipient)), 1); + assertEq(token.balanceOf(from), 0); + + assertEq(recipient.operator(), address(this)); + assertEq(recipient.from(), from); + assertEq(recipient.id(), id); + assertBytesEq(recipient.data(), data); + } + + function testSafeMintToEOA(uint256 id, address to) public { + if (to == address(0)) to = address(0xBEEF); + + if (uint256(uint160(to)) <= 18 || to.code.length > 0) return; + + token.safeMint(to, id); + + assertEq(token.ownerOf(id), address(to)); + assertEq(token.balanceOf(address(to)), 1); + } + + function testSafeMintToERC721Recipient(uint256 id) public { + ERC721Recipient to = new ERC721Recipient(); + + token.safeMint(address(to), id); + + assertEq(token.ownerOf(id), address(to)); + assertEq(token.balanceOf(address(to)), 1); + + assertEq(to.operator(), address(this)); + assertEq(to.from(), address(0)); + assertEq(to.id(), id); + assertBytesEq(to.data(), ""); + } + + function testSafeMintToERC721RecipientWithData(uint256 id, bytes calldata data) public { + ERC721Recipient to = new ERC721Recipient(); + + token.safeMint(address(to), id, data); + + assertEq(token.ownerOf(id), address(to)); + assertEq(token.balanceOf(address(to)), 1); + + assertEq(to.operator(), address(this)); + assertEq(to.from(), address(0)); + assertEq(to.id(), id); + assertBytesEq(to.data(), data); + } + + function testFailMintToZero(uint256 id) public { + token.mint(address(0), id); + } + + function testFailDoubleMint(uint256 id, address to) public { + if (to == address(0)) to = address(0xBEEF); + + token.mint(to, id); + token.mint(to, id); + } + + function testFailBurnUnMinted(uint256 id) public { + token.burn(id); + } + + function testFailDoubleBurn(uint256 id, address to) public { + if (to == address(0)) to = address(0xBEEF); + + token.mint(to, id); + + token.burn(id); + token.burn(id); + } + + function testFailApproveUnMinted(uint256 id, address to) public { + token.approve(to, id); + } + + function testFailApproveUnAuthorized( + address owner, + uint256 id, + address to + ) public { + if (owner == address(0) || owner == address(this)) owner = address(0xBEEF); + + token.mint(owner, id); + + token.approve(to, id); + } + + function testFailTransferFromUnOwned( + address from, + address to, + uint256 id + ) public { + token.transferFrom(from, to, id); + } + + function testFailTransferFromWrongFrom( + address owner, + address from, + address to, + uint256 id + ) public { + if (owner == address(0)) to = address(0xBEEF); + if (from == owner) revert(); + + token.mint(owner, id); + + token.transferFrom(from, to, id); + } + + function testFailTransferFromToZero(uint256 id) public { + token.mint(address(this), id); + + token.transferFrom(address(this), address(0), id); + } + + function testFailTransferFromNotOwner( + address from, + address to, + uint256 id + ) public { + if (from == address(this)) from = address(0xBEEF); + + token.mint(from, id); + + token.transferFrom(from, to, id); + } + + function testFailSafeTransferFromToNonERC721Recipient(uint256 id) public { + token.mint(address(this), id); + + token.safeTransferFrom(address(this), address(new NonERC721Recipient()), id); + } + + function testFailSafeTransferFromToNonERC721RecipientWithData(uint256 id, bytes calldata data) public { + token.mint(address(this), id); + + token.safeTransferFrom(address(this), address(new NonERC721Recipient()), id, data); + } + + function testFailSafeTransferFromToRevertingERC721Recipient(uint256 id) public { + token.mint(address(this), id); + + token.safeTransferFrom(address(this), address(new RevertingERC721Recipient()), id); + } + + function testFailSafeTransferFromToRevertingERC721RecipientWithData(uint256 id, bytes calldata data) public { + token.mint(address(this), id); + + token.safeTransferFrom(address(this), address(new RevertingERC721Recipient()), id, data); + } + + function testFailSafeTransferFromToERC721RecipientWithWrongReturnData(uint256 id) public { + token.mint(address(this), id); + + token.safeTransferFrom(address(this), address(new WrongReturnDataERC721Recipient()), id); + } + + function testFailSafeTransferFromToERC721RecipientWithWrongReturnDataWithData(uint256 id, bytes calldata data) + public + { + token.mint(address(this), id); + + token.safeTransferFrom(address(this), address(new WrongReturnDataERC721Recipient()), id, data); + } + + function testFailSafeMintToNonERC721Recipient(uint256 id) public { + token.safeMint(address(new NonERC721Recipient()), id); + } + + function testFailSafeMintToNonERC721RecipientWithData(uint256 id, bytes calldata data) public { + token.safeMint(address(new NonERC721Recipient()), id, data); + } + + function testFailSafeMintToRevertingERC721Recipient(uint256 id) public { + token.safeMint(address(new RevertingERC721Recipient()), id); + } + + function testFailSafeMintToRevertingERC721RecipientWithData(uint256 id, bytes calldata data) public { + token.safeMint(address(new RevertingERC721Recipient()), id, data); + } + + function testFailSafeMintToERC721RecipientWithWrongReturnData(uint256 id) public { + token.safeMint(address(new WrongReturnDataERC721Recipient()), id); + } + + function testFailSafeMintToERC721RecipientWithWrongReturnDataWithData(uint256 id, bytes calldata data) public { + token.safeMint(address(new WrongReturnDataERC721Recipient()), id, data); + } + + function testFailOwnerOfUnminted(uint256 id) public view { + token.ownerOf(id); + } +} diff --git a/CVLByExample/UniswapHooks/lib/uniswap-hooks/lib/v4-core/lib/solmate/src/test/FixedPointMathLib.t.sol b/CVLByExample/UniswapHooks/lib/uniswap-hooks/lib/v4-core/lib/solmate/src/test/FixedPointMathLib.t.sol new file mode 100644 index 00000000..789f957a --- /dev/null +++ b/CVLByExample/UniswapHooks/lib/uniswap-hooks/lib/v4-core/lib/solmate/src/test/FixedPointMathLib.t.sol @@ -0,0 +1,360 @@ +// SPDX-License-Identifier: AGPL-3.0-only +pragma solidity 0.8.15; + +import {DSTestPlus} from "./utils/DSTestPlus.sol"; + +import {FixedPointMathLib} from "../utils/FixedPointMathLib.sol"; + +contract FixedPointMathLibTest is DSTestPlus { + function testMulWadDown() public { + assertEq(FixedPointMathLib.mulWadDown(2.5e18, 0.5e18), 1.25e18); + assertEq(FixedPointMathLib.mulWadDown(3e18, 1e18), 3e18); + assertEq(FixedPointMathLib.mulWadDown(369, 271), 0); + } + + function testMulWadDownEdgeCases() public { + assertEq(FixedPointMathLib.mulWadDown(0, 1e18), 0); + assertEq(FixedPointMathLib.mulWadDown(1e18, 0), 0); + assertEq(FixedPointMathLib.mulWadDown(0, 0), 0); + } + + function testMulWadUp() public { + assertEq(FixedPointMathLib.mulWadUp(2.5e18, 0.5e18), 1.25e18); + assertEq(FixedPointMathLib.mulWadUp(3e18, 1e18), 3e18); + assertEq(FixedPointMathLib.mulWadUp(369, 271), 1); + } + + function testMulWadUpEdgeCases() public { + assertEq(FixedPointMathLib.mulWadUp(0, 1e18), 0); + assertEq(FixedPointMathLib.mulWadUp(1e18, 0), 0); + assertEq(FixedPointMathLib.mulWadUp(0, 0), 0); + } + + function testDivWadDown() public { + assertEq(FixedPointMathLib.divWadDown(1.25e18, 0.5e18), 2.5e18); + assertEq(FixedPointMathLib.divWadDown(3e18, 1e18), 3e18); + assertEq(FixedPointMathLib.divWadDown(2, 100000000000000e18), 0); + } + + function testDivWadDownEdgeCases() public { + assertEq(FixedPointMathLib.divWadDown(0, 1e18), 0); + } + + function testFailDivWadDownZeroDenominator() public pure { + FixedPointMathLib.divWadDown(1e18, 0); + } + + function testDivWadUp() public { + assertEq(FixedPointMathLib.divWadUp(1.25e18, 0.5e18), 2.5e18); + assertEq(FixedPointMathLib.divWadUp(3e18, 1e18), 3e18); + assertEq(FixedPointMathLib.divWadUp(2, 100000000000000e18), 1); + } + + function testDivWadUpEdgeCases() public { + assertEq(FixedPointMathLib.divWadUp(0, 1e18), 0); + } + + function testFailDivWadUpZeroDenominator() public pure { + FixedPointMathLib.divWadUp(1e18, 0); + } + + function testMulDivDown() public { + assertEq(FixedPointMathLib.mulDivDown(2.5e27, 0.5e27, 1e27), 1.25e27); + assertEq(FixedPointMathLib.mulDivDown(2.5e18, 0.5e18, 1e18), 1.25e18); + assertEq(FixedPointMathLib.mulDivDown(2.5e8, 0.5e8, 1e8), 1.25e8); + assertEq(FixedPointMathLib.mulDivDown(369, 271, 1e2), 999); + + assertEq(FixedPointMathLib.mulDivDown(1e27, 1e27, 2e27), 0.5e27); + assertEq(FixedPointMathLib.mulDivDown(1e18, 1e18, 2e18), 0.5e18); + assertEq(FixedPointMathLib.mulDivDown(1e8, 1e8, 2e8), 0.5e8); + + assertEq(FixedPointMathLib.mulDivDown(2e27, 3e27, 2e27), 3e27); + assertEq(FixedPointMathLib.mulDivDown(3e18, 2e18, 3e18), 2e18); + assertEq(FixedPointMathLib.mulDivDown(2e8, 3e8, 2e8), 3e8); + } + + function testMulDivDownEdgeCases() public { + assertEq(FixedPointMathLib.mulDivDown(0, 1e18, 1e18), 0); + assertEq(FixedPointMathLib.mulDivDown(1e18, 0, 1e18), 0); + assertEq(FixedPointMathLib.mulDivDown(0, 0, 1e18), 0); + } + + function testFailMulDivDownZeroDenominator() public pure { + FixedPointMathLib.mulDivDown(1e18, 1e18, 0); + } + + function testMulDivUp() public { + assertEq(FixedPointMathLib.mulDivUp(2.5e27, 0.5e27, 1e27), 1.25e27); + assertEq(FixedPointMathLib.mulDivUp(2.5e18, 0.5e18, 1e18), 1.25e18); + assertEq(FixedPointMathLib.mulDivUp(2.5e8, 0.5e8, 1e8), 1.25e8); + assertEq(FixedPointMathLib.mulDivUp(369, 271, 1e2), 1000); + + assertEq(FixedPointMathLib.mulDivUp(1e27, 1e27, 2e27), 0.5e27); + assertEq(FixedPointMathLib.mulDivUp(1e18, 1e18, 2e18), 0.5e18); + assertEq(FixedPointMathLib.mulDivUp(1e8, 1e8, 2e8), 0.5e8); + + assertEq(FixedPointMathLib.mulDivUp(2e27, 3e27, 2e27), 3e27); + assertEq(FixedPointMathLib.mulDivUp(3e18, 2e18, 3e18), 2e18); + assertEq(FixedPointMathLib.mulDivUp(2e8, 3e8, 2e8), 3e8); + } + + function testMulDivUpEdgeCases() public { + assertEq(FixedPointMathLib.mulDivUp(0, 1e18, 1e18), 0); + assertEq(FixedPointMathLib.mulDivUp(1e18, 0, 1e18), 0); + assertEq(FixedPointMathLib.mulDivUp(0, 0, 1e18), 0); + } + + function testFailMulDivUpZeroDenominator() public pure { + FixedPointMathLib.mulDivUp(1e18, 1e18, 0); + } + + function testRPow() public { + assertEq(FixedPointMathLib.rpow(2e27, 2, 1e27), 4e27); + assertEq(FixedPointMathLib.rpow(2e18, 2, 1e18), 4e18); + assertEq(FixedPointMathLib.rpow(2e8, 2, 1e8), 4e8); + assertEq(FixedPointMathLib.rpow(8, 3, 1), 512); + } + + function testSqrt() public { + assertEq(FixedPointMathLib.sqrt(0), 0); + assertEq(FixedPointMathLib.sqrt(1), 1); + assertEq(FixedPointMathLib.sqrt(2704), 52); + assertEq(FixedPointMathLib.sqrt(110889), 333); + assertEq(FixedPointMathLib.sqrt(32239684), 5678); + assertEq(FixedPointMathLib.sqrt(type(uint256).max), 340282366920938463463374607431768211455); + } + + function testSqrtBackHashedSingle() public { + testSqrtBackHashed(123); + } + + function testMulWadDown(uint256 x, uint256 y) public { + // Ignore cases where x * y overflows. + unchecked { + if (x != 0 && (x * y) / x != y) return; + } + + assertEq(FixedPointMathLib.mulWadDown(x, y), (x * y) / 1e18); + } + + function testFailMulWadDownOverflow(uint256 x, uint256 y) public pure { + // Ignore cases where x * y does not overflow. + unchecked { + if ((x * y) / x == y) revert(); + } + + FixedPointMathLib.mulWadDown(x, y); + } + + function testMulWadUp(uint256 x, uint256 y) public { + // Ignore cases where x * y overflows. + unchecked { + if (x != 0 && (x * y) / x != y) return; + } + + assertEq(FixedPointMathLib.mulWadUp(x, y), x * y == 0 ? 0 : (x * y - 1) / 1e18 + 1); + } + + function testFailMulWadUpOverflow(uint256 x, uint256 y) public pure { + // Ignore cases where x * y does not overflow. + unchecked { + if ((x * y) / x == y) revert(); + } + + FixedPointMathLib.mulWadUp(x, y); + } + + function testDivWadDown(uint256 x, uint256 y) public { + // Ignore cases where x * WAD overflows or y is 0. + unchecked { + if (y == 0 || (x != 0 && (x * 1e18) / 1e18 != x)) return; + } + + assertEq(FixedPointMathLib.divWadDown(x, y), (x * 1e18) / y); + } + + function testFailDivWadDownOverflow(uint256 x, uint256 y) public pure { + // Ignore cases where x * WAD does not overflow or y is 0. + unchecked { + if (y == 0 || (x * 1e18) / 1e18 == x) revert(); + } + + FixedPointMathLib.divWadDown(x, y); + } + + function testFailDivWadDownZeroDenominator(uint256 x) public pure { + FixedPointMathLib.divWadDown(x, 0); + } + + function testDivWadUp(uint256 x, uint256 y) public { + // Ignore cases where x * WAD overflows or y is 0. + unchecked { + if (y == 0 || (x != 0 && (x * 1e18) / 1e18 != x)) return; + } + + assertEq(FixedPointMathLib.divWadUp(x, y), x == 0 ? 0 : (x * 1e18 - 1) / y + 1); + } + + function testFailDivWadUpOverflow(uint256 x, uint256 y) public pure { + // Ignore cases where x * WAD does not overflow or y is 0. + unchecked { + if (y == 0 || (x * 1e18) / 1e18 == x) revert(); + } + + FixedPointMathLib.divWadUp(x, y); + } + + function testFailDivWadUpZeroDenominator(uint256 x) public pure { + FixedPointMathLib.divWadUp(x, 0); + } + + function testMulDivDown( + uint256 x, + uint256 y, + uint256 denominator + ) public { + // Ignore cases where x * y overflows or denominator is 0. + unchecked { + if (denominator == 0 || (x != 0 && (x * y) / x != y)) return; + } + + assertEq(FixedPointMathLib.mulDivDown(x, y, denominator), (x * y) / denominator); + } + + function testFailMulDivDownOverflow( + uint256 x, + uint256 y, + uint256 denominator + ) public pure { + // Ignore cases where x * y does not overflow or denominator is 0. + unchecked { + if (denominator == 0 || (x * y) / x == y) revert(); + } + + FixedPointMathLib.mulDivDown(x, y, denominator); + } + + function testFailMulDivDownZeroDenominator(uint256 x, uint256 y) public pure { + FixedPointMathLib.mulDivDown(x, y, 0); + } + + function testMulDivUp( + uint256 x, + uint256 y, + uint256 denominator + ) public { + // Ignore cases where x * y overflows or denominator is 0. + unchecked { + if (denominator == 0 || (x != 0 && (x * y) / x != y)) return; + } + + assertEq(FixedPointMathLib.mulDivUp(x, y, denominator), x * y == 0 ? 0 : (x * y - 1) / denominator + 1); + } + + function testFailMulDivUpOverflow( + uint256 x, + uint256 y, + uint256 denominator + ) public pure { + // Ignore cases where x * y does not overflow or denominator is 0. + unchecked { + if (denominator == 0 || (x * y) / x == y) revert(); + } + + FixedPointMathLib.mulDivUp(x, y, denominator); + } + + function testFailMulDivUpZeroDenominator(uint256 x, uint256 y) public pure { + FixedPointMathLib.mulDivUp(x, y, 0); + } + + function testDifferentiallyFuzzSqrt(uint256 x) public { + assertEq(FixedPointMathLib.sqrt(x), uniswapSqrt(x)); + assertEq(FixedPointMathLib.sqrt(x), abdkSqrt(x)); + } + + function testSqrt(uint256 x) public { + uint256 root = FixedPointMathLib.sqrt(x); + uint256 next = root + 1; + + // Ignore cases where next * next overflows. + unchecked { + if (next * next < next) return; + } + + assertTrue(root * root <= x && next * next > x); + } + + function testSqrtBack(uint256 x) public { + unchecked { + x >>= 128; + while (x != 0) { + assertEq(FixedPointMathLib.sqrt(x * x), x); + x >>= 1; + } + } + } + + function testSqrtBackHashed(uint256 x) public { + testSqrtBack(uint256(keccak256(abi.encode(x)))); + } + + function uniswapSqrt(uint256 y) internal pure returns (uint256 z) { + if (y > 3) { + z = y; + uint256 x = y / 2 + 1; + while (x < z) { + z = x; + x = (y / x + x) / 2; + } + } else if (y != 0) { + z = 1; + } + } + + function abdkSqrt(uint256 x) private pure returns (uint256) { + unchecked { + if (x == 0) return 0; + else { + uint256 xx = x; + uint256 r = 1; + if (xx >= 0x100000000000000000000000000000000) { + xx >>= 128; + r <<= 64; + } + if (xx >= 0x10000000000000000) { + xx >>= 64; + r <<= 32; + } + if (xx >= 0x100000000) { + xx >>= 32; + r <<= 16; + } + if (xx >= 0x10000) { + xx >>= 16; + r <<= 8; + } + if (xx >= 0x100) { + xx >>= 8; + r <<= 4; + } + if (xx >= 0x10) { + xx >>= 4; + r <<= 2; + } + if (xx >= 0x8) { + r <<= 1; + } + r = (r + x / r) >> 1; + r = (r + x / r) >> 1; + r = (r + x / r) >> 1; + r = (r + x / r) >> 1; + r = (r + x / r) >> 1; + r = (r + x / r) >> 1; + r = (r + x / r) >> 1; // Seven iterations should be enough + uint256 r1 = x / r; + return r < r1 ? r : r1; + } + } + } +} diff --git a/CVLByExample/UniswapHooks/lib/uniswap-hooks/lib/v4-core/lib/solmate/src/test/LibString.t.sol b/CVLByExample/UniswapHooks/lib/uniswap-hooks/lib/v4-core/lib/solmate/src/test/LibString.t.sol new file mode 100644 index 00000000..36ed435d --- /dev/null +++ b/CVLByExample/UniswapHooks/lib/uniswap-hooks/lib/v4-core/lib/solmate/src/test/LibString.t.sol @@ -0,0 +1,148 @@ +// SPDX-License-Identifier: MIT +pragma solidity >=0.8.0; + +import {DSTestPlus} from "./utils/DSTestPlus.sol"; + +import {LibString} from "../utils/LibString.sol"; + +contract LibStringTest is DSTestPlus { + function testToString() public { + assertEq(LibString.toString(uint256(0)), "0"); + assertEq(LibString.toString(uint256(1)), "1"); + assertEq(LibString.toString(uint256(17)), "17"); + assertEq(LibString.toString(uint256(99999999)), "99999999"); + assertEq(LibString.toString(uint256(99999999999)), "99999999999"); + assertEq(LibString.toString(uint256(2342343923423)), "2342343923423"); + assertEq(LibString.toString(uint256(98765685434567)), "98765685434567"); + } + + function testToStringIntPositive() public { + assertEq(LibString.toString(int256(0)), "0"); + assertEq(LibString.toString(int256(1)), "1"); + assertEq(LibString.toString(int256(17)), "17"); + assertEq(LibString.toString(int256(99999999)), "99999999"); + assertEq(LibString.toString(int256(99999999999)), "99999999999"); + assertEq(LibString.toString(int256(2342343923423)), "2342343923423"); + assertEq(LibString.toString(int256(98765685434567)), "98765685434567"); + } + + function testToStringIntNegative() public { + assertEq(LibString.toString(int256(-0)), "0"); + assertEq(LibString.toString(int256(-17)), "-17"); + assertEq(LibString.toString(int256(-99999999)), "-99999999"); + assertEq(LibString.toString(int256(-99999999999)), "-99999999999"); + assertEq(LibString.toString(int256(-2342343923423)), "-2342343923423"); + assertEq(LibString.toString(int256(-98765685434567)), "-98765685434567"); + } + + function testDifferentiallyFuzzToString(uint256 value, bytes calldata brutalizeWith) + public + brutalizeMemory(brutalizeWith) + { + string memory libString = LibString.toString(value); + string memory oz = toStringOZ(value); + + assertEq(bytes(libString).length, bytes(oz).length); + assertEq(libString, oz); + } + + function testDifferentiallyFuzzToStringInt(int256 value, bytes calldata brutalizeWith) + public + brutalizeMemory(brutalizeWith) + { + string memory libString = LibString.toString(value); + string memory oz = toStringOZ(value); + + assertEq(bytes(libString).length, bytes(oz).length); + assertEq(libString, oz); + } + + function testToStringOverwrite() public { + string memory str = LibString.toString(uint256(1)); + + bytes32 data; + bytes32 expected; + + assembly { + // Imagine a high level allocation writing something to the current free memory. + // Should have sufficient higher order bits for this to be visible + mstore(mload(0x40), not(0)) + // Correctly allocate 32 more bytes, to avoid more interference + mstore(0x40, add(mload(0x40), 32)) + data := mload(add(str, 32)) + + // the expected value should be the uft-8 encoding of 1 (49), + // followed by clean bits. We achieve this by taking the value and + // shifting left to the end of the 32 byte word + expected := shl(248, 49) + } + + assertEq(data, expected); + } + + function testToStringDirty() public { + uint256 freememptr; + // Make the next 4 bytes of the free memory dirty + assembly { + let dirty := not(0) + freememptr := mload(0x40) + mstore(freememptr, dirty) + mstore(add(freememptr, 32), dirty) + mstore(add(freememptr, 64), dirty) + mstore(add(freememptr, 96), dirty) + mstore(add(freememptr, 128), dirty) + } + string memory str = LibString.toString(uint256(1)); + uint256 len; + bytes32 data; + bytes32 expected; + assembly { + freememptr := str + len := mload(str) + data := mload(add(str, 32)) + // the expected value should be the uft-8 encoding of 1 (49), + // followed by clean bits. We achieve this by taking the value and + // shifting left to the end of the 32 byte word + expected := shl(248, 49) + } + emit log_named_uint("str: ", freememptr); + emit log_named_uint("len: ", len); + emit log_named_bytes32("data: ", data); + assembly { + freememptr := mload(0x40) + } + emit log_named_uint("memptr: ", freememptr); + + assertEq(data, expected); + } +} + +function toStringOZ(int256 value) pure returns (string memory) { + return string(abi.encodePacked(value < 0 ? "-" : "", toStringOZ(absOZ(value)))); +} + +function toStringOZ(uint256 value) pure returns (string memory) { + if (value == 0) { + return "0"; + } + uint256 temp = value; + uint256 digits; + while (temp != 0) { + digits++; + temp /= 10; + } + bytes memory buffer = new bytes(digits); + while (value != 0) { + digits -= 1; + buffer[digits] = bytes1(uint8(48 + uint256(value % 10))); + value /= 10; + } + return string(buffer); +} + +function absOZ(int256 n) pure returns (uint256) { + unchecked { + // must be unchecked in order to support `n = type(int256).min` + return uint256(n >= 0 ? n : -n); + } +} diff --git a/CVLByExample/UniswapHooks/lib/uniswap-hooks/lib/v4-core/lib/solmate/src/test/MerkleProofLib.t.sol b/CVLByExample/UniswapHooks/lib/uniswap-hooks/lib/v4-core/lib/solmate/src/test/MerkleProofLib.t.sol new file mode 100644 index 00000000..52796798 --- /dev/null +++ b/CVLByExample/UniswapHooks/lib/uniswap-hooks/lib/v4-core/lib/solmate/src/test/MerkleProofLib.t.sol @@ -0,0 +1,50 @@ +// SPDX-License-Identifier: MIT +pragma solidity >=0.8.0; + +import {DSTestPlus} from "./utils/DSTestPlus.sol"; + +import {MerkleProofLib} from "../utils/MerkleProofLib.sol"; + +contract MerkleProofLibTest is DSTestPlus { + function testVerifyEmptyMerkleProofSuppliedLeafAndRootSame() public { + bytes32[] memory proof; + assertBoolEq(this.verify(proof, 0x00, 0x00), true); + } + + function testVerifyEmptyMerkleProofSuppliedLeafAndRootDifferent() public { + bytes32[] memory proof; + bytes32 leaf = "a"; + assertBoolEq(this.verify(proof, 0x00, leaf), false); + } + + function testValidProofSupplied() public { + // Merkle tree created from leaves ['a', 'b', 'c']. + // Leaf is 'a'. + bytes32[] memory proof = new bytes32[](2); + proof[0] = 0xb5553de315e0edf504d9150af82dafa5c4667fa618ed0a6f19c69b41166c5510; + proof[1] = 0x0b42b6393c1f53060fe3ddbfcd7aadcca894465a5a438f69c87d790b2299b9b2; + bytes32 root = 0x5842148bc6ebeb52af882a317c765fccd3ae80589b21a9b8cbf21abb630e46a7; + bytes32 leaf = 0x3ac225168df54212a25c1c01fd35bebfea408fdac2e31ddd6f80a4bbf9a5f1cb; + assertBoolEq(this.verify(proof, root, leaf), true); + } + + function testVerifyInvalidProofSupplied() public { + // Merkle tree created from leaves ['a', 'b', 'c']. + // Leaf is 'a'. + // Proof is same as testValidProofSupplied but last byte of first element is modified. + bytes32[] memory proof = new bytes32[](2); + proof[0] = 0xb5553de315e0edf504d9150af82dafa5c4667fa618ed0a6f19c69b41166c5511; + proof[1] = 0x0b42b6393c1f53060fe3ddbfcd7aadcca894465a5a438f69c87d790b2299b9b2; + bytes32 root = 0x5842148bc6ebeb52af882a317c765fccd3ae80589b21a9b8cbf21abb630e46a7; + bytes32 leaf = 0x3ac225168df54212a25c1c01fd35bebfea408fdac2e31ddd6f80a4bbf9a5f1cb; + assertBoolEq(this.verify(proof, root, leaf), false); + } + + function verify( + bytes32[] calldata proof, + bytes32 root, + bytes32 leaf + ) external pure returns (bool) { + return MerkleProofLib.verify(proof, root, leaf); + } +} diff --git a/CVLByExample/UniswapHooks/lib/uniswap-hooks/lib/v4-core/lib/solmate/src/test/MultiRolesAuthority.t.sol b/CVLByExample/UniswapHooks/lib/uniswap-hooks/lib/v4-core/lib/solmate/src/test/MultiRolesAuthority.t.sol new file mode 100644 index 00000000..eedf6a08 --- /dev/null +++ b/CVLByExample/UniswapHooks/lib/uniswap-hooks/lib/v4-core/lib/solmate/src/test/MultiRolesAuthority.t.sol @@ -0,0 +1,321 @@ +// SPDX-License-Identifier: AGPL-3.0-only +pragma solidity 0.8.15; + +import {DSTestPlus} from "./utils/DSTestPlus.sol"; +import {MockAuthority} from "./utils/mocks/MockAuthority.sol"; + +import {Authority} from "../auth/Auth.sol"; + +import {MultiRolesAuthority} from "../auth/authorities/MultiRolesAuthority.sol"; + +contract MultiRolesAuthorityTest is DSTestPlus { + MultiRolesAuthority multiRolesAuthority; + + function setUp() public { + multiRolesAuthority = new MultiRolesAuthority(address(this), Authority(address(0))); + } + + function testSetRoles() public { + assertFalse(multiRolesAuthority.doesUserHaveRole(address(0xBEEF), 0)); + + multiRolesAuthority.setUserRole(address(0xBEEF), 0, true); + assertTrue(multiRolesAuthority.doesUserHaveRole(address(0xBEEF), 0)); + + multiRolesAuthority.setUserRole(address(0xBEEF), 0, false); + assertFalse(multiRolesAuthority.doesUserHaveRole(address(0xBEEF), 0)); + } + + function testSetRoleCapabilities() public { + assertFalse(multiRolesAuthority.doesRoleHaveCapability(0, 0xBEEFCAFE)); + + multiRolesAuthority.setRoleCapability(0, 0xBEEFCAFE, true); + assertTrue(multiRolesAuthority.doesRoleHaveCapability(0, 0xBEEFCAFE)); + + multiRolesAuthority.setRoleCapability(0, 0xBEEFCAFE, false); + assertFalse(multiRolesAuthority.doesRoleHaveCapability(0, 0xBEEFCAFE)); + } + + function testSetPublicCapabilities() public { + assertFalse(multiRolesAuthority.isCapabilityPublic(0xBEEFCAFE)); + + multiRolesAuthority.setPublicCapability(0xBEEFCAFE, true); + assertTrue(multiRolesAuthority.isCapabilityPublic(0xBEEFCAFE)); + + multiRolesAuthority.setPublicCapability(0xBEEFCAFE, false); + assertFalse(multiRolesAuthority.isCapabilityPublic(0xBEEFCAFE)); + } + + function testSetTargetCustomAuthority() public { + assertEq(address(multiRolesAuthority.getTargetCustomAuthority(address(0xBEEF))), address(0)); + + multiRolesAuthority.setTargetCustomAuthority(address(0xBEEF), Authority(address(0xCAFE))); + assertEq(address(multiRolesAuthority.getTargetCustomAuthority(address(0xBEEF))), address(0xCAFE)); + + multiRolesAuthority.setTargetCustomAuthority(address(0xBEEF), Authority(address(0))); + assertEq(address(multiRolesAuthority.getTargetCustomAuthority(address(0xBEEF))), address(0)); + } + + function testCanCallWithAuthorizedRole() public { + assertFalse(multiRolesAuthority.canCall(address(0xBEEF), address(0xCAFE), 0xBEEFCAFE)); + + multiRolesAuthority.setUserRole(address(0xBEEF), 0, true); + assertFalse(multiRolesAuthority.canCall(address(0xBEEF), address(0xCAFE), 0xBEEFCAFE)); + + multiRolesAuthority.setRoleCapability(0, 0xBEEFCAFE, true); + assertTrue(multiRolesAuthority.canCall(address(0xBEEF), address(0xCAFE), 0xBEEFCAFE)); + + multiRolesAuthority.setRoleCapability(0, 0xBEEFCAFE, false); + assertFalse(multiRolesAuthority.canCall(address(0xBEEF), address(0xCAFE), 0xBEEFCAFE)); + + multiRolesAuthority.setRoleCapability(0, 0xBEEFCAFE, true); + assertTrue(multiRolesAuthority.canCall(address(0xBEEF), address(0xCAFE), 0xBEEFCAFE)); + + multiRolesAuthority.setUserRole(address(0xBEEF), 0, false); + assertFalse(multiRolesAuthority.canCall(address(0xBEEF), address(0xCAFE), 0xBEEFCAFE)); + } + + function testCanCallPublicCapability() public { + assertFalse(multiRolesAuthority.canCall(address(0xBEEF), address(0xCAFE), 0xBEEFCAFE)); + + multiRolesAuthority.setPublicCapability(0xBEEFCAFE, true); + assertTrue(multiRolesAuthority.canCall(address(0xBEEF), address(0xCAFE), 0xBEEFCAFE)); + + multiRolesAuthority.setPublicCapability(0xBEEFCAFE, false); + assertFalse(multiRolesAuthority.canCall(address(0xBEEF), address(0xCAFE), 0xBEEFCAFE)); + } + + function testCanCallWithCustomAuthority() public { + assertFalse(multiRolesAuthority.canCall(address(0xBEEF), address(0xCAFE), 0xBEEFCAFE)); + + multiRolesAuthority.setTargetCustomAuthority(address(0xCAFE), new MockAuthority(false)); + assertFalse(multiRolesAuthority.canCall(address(0xBEEF), address(0xCAFE), 0xBEEFCAFE)); + + multiRolesAuthority.setTargetCustomAuthority(address(0xCAFE), new MockAuthority(true)); + assertTrue(multiRolesAuthority.canCall(address(0xBEEF), address(0xCAFE), 0xBEEFCAFE)); + + multiRolesAuthority.setTargetCustomAuthority(address(0xCAFE), new MockAuthority(false)); + assertFalse(multiRolesAuthority.canCall(address(0xBEEF), address(0xCAFE), 0xBEEFCAFE)); + + multiRolesAuthority.setTargetCustomAuthority(address(0xCAFE), Authority(address(0))); + assertFalse(multiRolesAuthority.canCall(address(0xBEEF), address(0xCAFE), 0xBEEFCAFE)); + + multiRolesAuthority.setTargetCustomAuthority(address(0xCAFE), new MockAuthority(true)); + assertTrue(multiRolesAuthority.canCall(address(0xBEEF), address(0xCAFE), 0xBEEFCAFE)); + } + + function testCanCallWithCustomAuthorityOverridesPublicCapability() public { + assertFalse(multiRolesAuthority.canCall(address(0xBEEF), address(0xCAFE), 0xBEEFCAFE)); + + multiRolesAuthority.setPublicCapability(0xBEEFCAFE, true); + assertTrue(multiRolesAuthority.canCall(address(0xBEEF), address(0xCAFE), 0xBEEFCAFE)); + + multiRolesAuthority.setTargetCustomAuthority(address(0xCAFE), new MockAuthority(false)); + assertFalse(multiRolesAuthority.canCall(address(0xBEEF), address(0xCAFE), 0xBEEFCAFE)); + + multiRolesAuthority.setTargetCustomAuthority(address(0xCAFE), new MockAuthority(true)); + assertTrue(multiRolesAuthority.canCall(address(0xBEEF), address(0xCAFE), 0xBEEFCAFE)); + + multiRolesAuthority.setPublicCapability(0xBEEFCAFE, false); + assertTrue(multiRolesAuthority.canCall(address(0xBEEF), address(0xCAFE), 0xBEEFCAFE)); + + multiRolesAuthority.setTargetCustomAuthority(address(0xCAFE), Authority(address(0))); + assertFalse(multiRolesAuthority.canCall(address(0xBEEF), address(0xCAFE), 0xBEEFCAFE)); + + multiRolesAuthority.setPublicCapability(0xBEEFCAFE, true); + assertTrue(multiRolesAuthority.canCall(address(0xBEEF), address(0xCAFE), 0xBEEFCAFE)); + } + + function testCanCallWithCustomAuthorityOverridesUserWithRole() public { + assertFalse(multiRolesAuthority.canCall(address(0xBEEF), address(0xCAFE), 0xBEEFCAFE)); + + multiRolesAuthority.setUserRole(address(0xBEEF), 0, true); + assertFalse(multiRolesAuthority.canCall(address(0xBEEF), address(0xCAFE), 0xBEEFCAFE)); + + multiRolesAuthority.setRoleCapability(0, 0xBEEFCAFE, true); + assertTrue(multiRolesAuthority.canCall(address(0xBEEF), address(0xCAFE), 0xBEEFCAFE)); + + multiRolesAuthority.setTargetCustomAuthority(address(0xCAFE), new MockAuthority(false)); + assertFalse(multiRolesAuthority.canCall(address(0xBEEF), address(0xCAFE), 0xBEEFCAFE)); + + multiRolesAuthority.setTargetCustomAuthority(address(0xCAFE), new MockAuthority(true)); + assertTrue(multiRolesAuthority.canCall(address(0xBEEF), address(0xCAFE), 0xBEEFCAFE)); + + multiRolesAuthority.setUserRole(address(0xBEEF), 0, false); + assertTrue(multiRolesAuthority.canCall(address(0xBEEF), address(0xCAFE), 0xBEEFCAFE)); + + multiRolesAuthority.setTargetCustomAuthority(address(0xCAFE), Authority(address(0))); + assertFalse(multiRolesAuthority.canCall(address(0xBEEF), address(0xCAFE), 0xBEEFCAFE)); + + multiRolesAuthority.setUserRole(address(0xBEEF), 0, true); + assertTrue(multiRolesAuthority.canCall(address(0xBEEF), address(0xCAFE), 0xBEEFCAFE)); + + multiRolesAuthority.setRoleCapability(0, 0xBEEFCAFE, false); + assertFalse(multiRolesAuthority.canCall(address(0xBEEF), address(0xCAFE), 0xBEEFCAFE)); + + multiRolesAuthority.setUserRole(address(0xBEEF), 0, false); + assertFalse(multiRolesAuthority.canCall(address(0xBEEF), address(0xCAFE), 0xBEEFCAFE)); + } + + function testSetRoles(address user, uint8 role) public { + assertFalse(multiRolesAuthority.doesUserHaveRole(user, role)); + + multiRolesAuthority.setUserRole(user, role, true); + assertTrue(multiRolesAuthority.doesUserHaveRole(user, role)); + + multiRolesAuthority.setUserRole(user, role, false); + assertFalse(multiRolesAuthority.doesUserHaveRole(user, role)); + } + + function testSetRoleCapabilities(uint8 role, bytes4 functionSig) public { + assertFalse(multiRolesAuthority.doesRoleHaveCapability(role, functionSig)); + + multiRolesAuthority.setRoleCapability(role, functionSig, true); + assertTrue(multiRolesAuthority.doesRoleHaveCapability(role, functionSig)); + + multiRolesAuthority.setRoleCapability(role, functionSig, false); + assertFalse(multiRolesAuthority.doesRoleHaveCapability(role, functionSig)); + } + + function testSetPublicCapabilities(bytes4 functionSig) public { + assertFalse(multiRolesAuthority.isCapabilityPublic(functionSig)); + + multiRolesAuthority.setPublicCapability(functionSig, true); + assertTrue(multiRolesAuthority.isCapabilityPublic(functionSig)); + + multiRolesAuthority.setPublicCapability(functionSig, false); + assertFalse(multiRolesAuthority.isCapabilityPublic(functionSig)); + } + + function testSetTargetCustomAuthority(address user, Authority customAuthority) public { + assertEq(address(multiRolesAuthority.getTargetCustomAuthority(user)), address(0)); + + multiRolesAuthority.setTargetCustomAuthority(user, customAuthority); + assertEq(address(multiRolesAuthority.getTargetCustomAuthority(user)), address(customAuthority)); + + multiRolesAuthority.setTargetCustomAuthority(user, Authority(address(0))); + assertEq(address(multiRolesAuthority.getTargetCustomAuthority(user)), address(0)); + } + + function testCanCallWithAuthorizedRole( + address user, + uint8 role, + address target, + bytes4 functionSig + ) public { + assertFalse(multiRolesAuthority.canCall(user, target, functionSig)); + + multiRolesAuthority.setUserRole(user, role, true); + assertFalse(multiRolesAuthority.canCall(user, target, functionSig)); + + multiRolesAuthority.setRoleCapability(role, functionSig, true); + assertTrue(multiRolesAuthority.canCall(user, target, functionSig)); + + multiRolesAuthority.setRoleCapability(role, functionSig, false); + assertFalse(multiRolesAuthority.canCall(user, target, functionSig)); + + multiRolesAuthority.setRoleCapability(role, functionSig, true); + assertTrue(multiRolesAuthority.canCall(user, target, functionSig)); + + multiRolesAuthority.setUserRole(user, role, false); + assertFalse(multiRolesAuthority.canCall(user, target, functionSig)); + } + + function testCanCallPublicCapability( + address user, + address target, + bytes4 functionSig + ) public { + assertFalse(multiRolesAuthority.canCall(user, target, functionSig)); + + multiRolesAuthority.setPublicCapability(functionSig, true); + assertTrue(multiRolesAuthority.canCall(user, target, functionSig)); + + multiRolesAuthority.setPublicCapability(functionSig, false); + assertFalse(multiRolesAuthority.canCall(user, target, functionSig)); + } + + function testCanCallWithCustomAuthority( + address user, + address target, + bytes4 functionSig + ) public { + assertFalse(multiRolesAuthority.canCall(user, target, functionSig)); + + multiRolesAuthority.setTargetCustomAuthority(target, new MockAuthority(false)); + assertFalse(multiRolesAuthority.canCall(user, target, functionSig)); + + multiRolesAuthority.setTargetCustomAuthority(target, new MockAuthority(true)); + assertTrue(multiRolesAuthority.canCall(user, target, functionSig)); + + multiRolesAuthority.setTargetCustomAuthority(target, new MockAuthority(false)); + assertFalse(multiRolesAuthority.canCall(user, target, functionSig)); + + multiRolesAuthority.setTargetCustomAuthority(target, Authority(address(0))); + assertFalse(multiRolesAuthority.canCall(user, target, functionSig)); + + multiRolesAuthority.setTargetCustomAuthority(target, new MockAuthority(true)); + assertTrue(multiRolesAuthority.canCall(user, target, functionSig)); + } + + function testCanCallWithCustomAuthorityOverridesPublicCapability( + address user, + address target, + bytes4 functionSig + ) public { + assertFalse(multiRolesAuthority.canCall(user, target, functionSig)); + + multiRolesAuthority.setPublicCapability(functionSig, true); + assertTrue(multiRolesAuthority.canCall(user, target, functionSig)); + + multiRolesAuthority.setTargetCustomAuthority(target, new MockAuthority(false)); + assertFalse(multiRolesAuthority.canCall(user, target, functionSig)); + + multiRolesAuthority.setTargetCustomAuthority(target, new MockAuthority(true)); + assertTrue(multiRolesAuthority.canCall(user, target, functionSig)); + + multiRolesAuthority.setPublicCapability(functionSig, false); + assertTrue(multiRolesAuthority.canCall(user, target, functionSig)); + + multiRolesAuthority.setTargetCustomAuthority(target, Authority(address(0))); + assertFalse(multiRolesAuthority.canCall(user, target, functionSig)); + + multiRolesAuthority.setPublicCapability(functionSig, true); + assertTrue(multiRolesAuthority.canCall(user, target, functionSig)); + } + + function testCanCallWithCustomAuthorityOverridesUserWithRole( + address user, + uint8 role, + address target, + bytes4 functionSig + ) public { + assertFalse(multiRolesAuthority.canCall(user, target, functionSig)); + + multiRolesAuthority.setUserRole(user, role, true); + assertFalse(multiRolesAuthority.canCall(user, target, functionSig)); + + multiRolesAuthority.setRoleCapability(role, functionSig, true); + assertTrue(multiRolesAuthority.canCall(user, target, functionSig)); + + multiRolesAuthority.setTargetCustomAuthority(target, new MockAuthority(false)); + assertFalse(multiRolesAuthority.canCall(user, target, functionSig)); + + multiRolesAuthority.setTargetCustomAuthority(target, new MockAuthority(true)); + assertTrue(multiRolesAuthority.canCall(user, target, functionSig)); + + multiRolesAuthority.setUserRole(user, role, false); + assertTrue(multiRolesAuthority.canCall(user, target, functionSig)); + + multiRolesAuthority.setTargetCustomAuthority(target, Authority(address(0))); + assertFalse(multiRolesAuthority.canCall(user, target, functionSig)); + + multiRolesAuthority.setUserRole(user, role, true); + assertTrue(multiRolesAuthority.canCall(user, target, functionSig)); + + multiRolesAuthority.setRoleCapability(role, functionSig, false); + assertFalse(multiRolesAuthority.canCall(user, target, functionSig)); + + multiRolesAuthority.setUserRole(user, role, false); + assertFalse(multiRolesAuthority.canCall(user, target, functionSig)); + } +} diff --git a/CVLByExample/UniswapHooks/lib/uniswap-hooks/lib/v4-core/lib/solmate/src/test/Owned.t.sol b/CVLByExample/UniswapHooks/lib/uniswap-hooks/lib/v4-core/lib/solmate/src/test/Owned.t.sol new file mode 100644 index 00000000..08a02392 --- /dev/null +++ b/CVLByExample/UniswapHooks/lib/uniswap-hooks/lib/v4-core/lib/solmate/src/test/Owned.t.sol @@ -0,0 +1,40 @@ +// SPDX-License-Identifier: AGPL-3.0-only +pragma solidity 0.8.15; + +import {DSTestPlus} from "./utils/DSTestPlus.sol"; +import {MockOwned} from "./utils/mocks/MockOwned.sol"; + +contract OwnedTest is DSTestPlus { + MockOwned mockOwned; + + function setUp() public { + mockOwned = new MockOwned(); + } + + function testTransferOwnership() public { + testTransferOwnership(address(0xBEEF)); + } + + function testCallFunctionAsNonOwner() public { + testCallFunctionAsNonOwner(address(0)); + } + + function testCallFunctionAsOwner() public { + mockOwned.updateFlag(); + } + + function testTransferOwnership(address newOwner) public { + mockOwned.transferOwnership(newOwner); + + assertEq(mockOwned.owner(), newOwner); + } + + function testCallFunctionAsNonOwner(address owner) public { + hevm.assume(owner != address(this)); + + mockOwned.transferOwnership(owner); + + hevm.expectRevert("UNAUTHORIZED"); + mockOwned.updateFlag(); + } +} diff --git a/CVLByExample/UniswapHooks/lib/uniswap-hooks/lib/v4-core/lib/solmate/src/test/ReentrancyGuard.t.sol b/CVLByExample/UniswapHooks/lib/uniswap-hooks/lib/v4-core/lib/solmate/src/test/ReentrancyGuard.t.sol new file mode 100644 index 00000000..842e3672 --- /dev/null +++ b/CVLByExample/UniswapHooks/lib/uniswap-hooks/lib/v4-core/lib/solmate/src/test/ReentrancyGuard.t.sol @@ -0,0 +1,56 @@ +// SPDX-License-Identifier: AGPL-3.0-only +pragma solidity 0.8.15; + +import {DSTestPlus} from "./utils/DSTestPlus.sol"; + +import {ReentrancyGuard} from "../utils/ReentrancyGuard.sol"; + +contract RiskyContract is ReentrancyGuard { + uint256 public enterTimes; + + function unprotectedCall() public { + enterTimes++; + + if (enterTimes > 1) return; + + this.protectedCall(); + } + + function protectedCall() public nonReentrant { + enterTimes++; + + if (enterTimes > 1) return; + + this.protectedCall(); + } + + function overprotectedCall() public nonReentrant {} +} + +contract ReentrancyGuardTest is DSTestPlus { + RiskyContract riskyContract; + + function setUp() public { + riskyContract = new RiskyContract(); + } + + function invariantReentrancyStatusAlways1() public { + assertEq(uint256(hevm.load(address(riskyContract), 0)), 1); + } + + function testFailUnprotectedCall() public { + riskyContract.unprotectedCall(); + + assertEq(riskyContract.enterTimes(), 1); + } + + function testProtectedCall() public { + try riskyContract.protectedCall() { + fail("Reentrancy Guard Failed To Stop Attacker"); + } catch {} + } + + function testNoReentrancy() public { + riskyContract.overprotectedCall(); + } +} diff --git a/CVLByExample/UniswapHooks/lib/uniswap-hooks/lib/v4-core/lib/solmate/src/test/RolesAuthority.t.sol b/CVLByExample/UniswapHooks/lib/uniswap-hooks/lib/v4-core/lib/solmate/src/test/RolesAuthority.t.sol new file mode 100644 index 00000000..e01db7e5 --- /dev/null +++ b/CVLByExample/UniswapHooks/lib/uniswap-hooks/lib/v4-core/lib/solmate/src/test/RolesAuthority.t.sol @@ -0,0 +1,148 @@ +// SPDX-License-Identifier: AGPL-3.0-only +pragma solidity 0.8.15; + +import {DSTestPlus} from "./utils/DSTestPlus.sol"; +import {MockAuthority} from "./utils/mocks/MockAuthority.sol"; + +import {Authority} from "../auth/Auth.sol"; + +import {RolesAuthority} from "../auth/authorities/RolesAuthority.sol"; + +contract RolesAuthorityTest is DSTestPlus { + RolesAuthority rolesAuthority; + + function setUp() public { + rolesAuthority = new RolesAuthority(address(this), Authority(address(0))); + } + + function testSetRoles() public { + assertFalse(rolesAuthority.doesUserHaveRole(address(0xBEEF), 0)); + + rolesAuthority.setUserRole(address(0xBEEF), 0, true); + assertTrue(rolesAuthority.doesUserHaveRole(address(0xBEEF), 0)); + + rolesAuthority.setUserRole(address(0xBEEF), 0, false); + assertFalse(rolesAuthority.doesUserHaveRole(address(0xBEEF), 0)); + } + + function testSetRoleCapabilities() public { + assertFalse(rolesAuthority.doesRoleHaveCapability(0, address(0xCAFE), 0xBEEFCAFE)); + + rolesAuthority.setRoleCapability(0, address(0xCAFE), 0xBEEFCAFE, true); + assertTrue(rolesAuthority.doesRoleHaveCapability(0, address(0xCAFE), 0xBEEFCAFE)); + + rolesAuthority.setRoleCapability(0, address(0xCAFE), 0xBEEFCAFE, false); + assertFalse(rolesAuthority.doesRoleHaveCapability(0, address(0xCAFE), 0xBEEFCAFE)); + } + + function testSetPublicCapabilities() public { + assertFalse(rolesAuthority.isCapabilityPublic(address(0xCAFE), 0xBEEFCAFE)); + + rolesAuthority.setPublicCapability(address(0xCAFE), 0xBEEFCAFE, true); + assertTrue(rolesAuthority.isCapabilityPublic(address(0xCAFE), 0xBEEFCAFE)); + + rolesAuthority.setPublicCapability(address(0xCAFE), 0xBEEFCAFE, false); + assertFalse(rolesAuthority.isCapabilityPublic(address(0xCAFE), 0xBEEFCAFE)); + } + + function testCanCallWithAuthorizedRole() public { + assertFalse(rolesAuthority.canCall(address(0xBEEF), address(0xCAFE), 0xBEEFCAFE)); + + rolesAuthority.setUserRole(address(0xBEEF), 0, true); + assertFalse(rolesAuthority.canCall(address(0xBEEF), address(0xCAFE), 0xBEEFCAFE)); + + rolesAuthority.setRoleCapability(0, address(0xCAFE), 0xBEEFCAFE, true); + assertTrue(rolesAuthority.canCall(address(0xBEEF), address(0xCAFE), 0xBEEFCAFE)); + + rolesAuthority.setRoleCapability(0, address(0xCAFE), 0xBEEFCAFE, false); + assertFalse(rolesAuthority.canCall(address(0xBEEF), address(0xCAFE), 0xBEEFCAFE)); + + rolesAuthority.setRoleCapability(0, address(0xCAFE), 0xBEEFCAFE, true); + assertTrue(rolesAuthority.canCall(address(0xBEEF), address(0xCAFE), 0xBEEFCAFE)); + + rolesAuthority.setUserRole(address(0xBEEF), 0, false); + assertFalse(rolesAuthority.canCall(address(0xBEEF), address(0xCAFE), 0xBEEFCAFE)); + } + + function testCanCallPublicCapability() public { + assertFalse(rolesAuthority.canCall(address(0xBEEF), address(0xCAFE), 0xBEEFCAFE)); + + rolesAuthority.setPublicCapability(address(0xCAFE), 0xBEEFCAFE, true); + assertTrue(rolesAuthority.canCall(address(0xBEEF), address(0xCAFE), 0xBEEFCAFE)); + + rolesAuthority.setPublicCapability(address(0xCAFE), 0xBEEFCAFE, false); + assertFalse(rolesAuthority.canCall(address(0xBEEF), address(0xCAFE), 0xBEEFCAFE)); + } + + function testSetRoles(address user, uint8 role) public { + assertFalse(rolesAuthority.doesUserHaveRole(user, role)); + + rolesAuthority.setUserRole(user, role, true); + assertTrue(rolesAuthority.doesUserHaveRole(user, role)); + + rolesAuthority.setUserRole(user, role, false); + assertFalse(rolesAuthority.doesUserHaveRole(user, role)); + } + + function testSetRoleCapabilities( + uint8 role, + address target, + bytes4 functionSig + ) public { + assertFalse(rolesAuthority.doesRoleHaveCapability(role, target, functionSig)); + + rolesAuthority.setRoleCapability(role, target, functionSig, true); + assertTrue(rolesAuthority.doesRoleHaveCapability(role, target, functionSig)); + + rolesAuthority.setRoleCapability(role, target, functionSig, false); + assertFalse(rolesAuthority.doesRoleHaveCapability(role, target, functionSig)); + } + + function testSetPublicCapabilities(address target, bytes4 functionSig) public { + assertFalse(rolesAuthority.isCapabilityPublic(target, functionSig)); + + rolesAuthority.setPublicCapability(target, functionSig, true); + assertTrue(rolesAuthority.isCapabilityPublic(target, functionSig)); + + rolesAuthority.setPublicCapability(target, functionSig, false); + assertFalse(rolesAuthority.isCapabilityPublic(target, functionSig)); + } + + function testCanCallWithAuthorizedRole( + address user, + uint8 role, + address target, + bytes4 functionSig + ) public { + assertFalse(rolesAuthority.canCall(user, target, functionSig)); + + rolesAuthority.setUserRole(user, role, true); + assertFalse(rolesAuthority.canCall(user, target, functionSig)); + + rolesAuthority.setRoleCapability(role, target, functionSig, true); + assertTrue(rolesAuthority.canCall(user, target, functionSig)); + + rolesAuthority.setRoleCapability(role, target, functionSig, false); + assertFalse(rolesAuthority.canCall(user, target, functionSig)); + + rolesAuthority.setRoleCapability(role, target, functionSig, true); + assertTrue(rolesAuthority.canCall(user, target, functionSig)); + + rolesAuthority.setUserRole(user, role, false); + assertFalse(rolesAuthority.canCall(user, target, functionSig)); + } + + function testCanCallPublicCapability( + address user, + address target, + bytes4 functionSig + ) public { + assertFalse(rolesAuthority.canCall(user, target, functionSig)); + + rolesAuthority.setPublicCapability(target, functionSig, true); + assertTrue(rolesAuthority.canCall(user, target, functionSig)); + + rolesAuthority.setPublicCapability(target, functionSig, false); + assertFalse(rolesAuthority.canCall(user, target, functionSig)); + } +} diff --git a/CVLByExample/UniswapHooks/lib/uniswap-hooks/lib/v4-core/lib/solmate/src/test/SSTORE2.t.sol b/CVLByExample/UniswapHooks/lib/uniswap-hooks/lib/v4-core/lib/solmate/src/test/SSTORE2.t.sol new file mode 100644 index 00000000..5d97ed7f --- /dev/null +++ b/CVLByExample/UniswapHooks/lib/uniswap-hooks/lib/v4-core/lib/solmate/src/test/SSTORE2.t.sol @@ -0,0 +1,152 @@ +// SPDX-License-Identifier: AGPL-3.0-only +pragma solidity 0.8.15; + +import {DSTestPlus} from "./utils/DSTestPlus.sol"; + +import {SSTORE2} from "../utils/SSTORE2.sol"; + +contract SSTORE2Test is DSTestPlus { + function testWriteRead() public { + bytes memory testBytes = abi.encode("this is a test"); + + address pointer = SSTORE2.write(testBytes); + + assertBytesEq(SSTORE2.read(pointer), testBytes); + } + + function testWriteReadFullStartBound() public { + assertBytesEq(SSTORE2.read(SSTORE2.write(hex"11223344"), 0), hex"11223344"); + } + + function testWriteReadCustomStartBound() public { + assertBytesEq(SSTORE2.read(SSTORE2.write(hex"11223344"), 1), hex"223344"); + } + + function testWriteReadFullBoundedRead() public { + bytes memory testBytes = abi.encode("this is a test"); + + assertBytesEq(SSTORE2.read(SSTORE2.write(testBytes), 0, testBytes.length), testBytes); + } + + function testWriteReadCustomBounds() public { + assertBytesEq(SSTORE2.read(SSTORE2.write(hex"11223344"), 1, 3), hex"2233"); + } + + function testWriteReadEmptyBound() public { + SSTORE2.read(SSTORE2.write(hex"11223344"), 3, 3); + } + + function testFailReadInvalidPointer() public view { + SSTORE2.read(DEAD_ADDRESS); + } + + function testFailReadInvalidPointerCustomStartBound() public view { + SSTORE2.read(DEAD_ADDRESS, 1); + } + + function testFailReadInvalidPointerCustomBounds() public view { + SSTORE2.read(DEAD_ADDRESS, 2, 4); + } + + function testFailWriteReadOutOfStartBound() public { + SSTORE2.read(SSTORE2.write(hex"11223344"), 41000); + } + + function testFailWriteReadEmptyOutOfBounds() public { + SSTORE2.read(SSTORE2.write(hex"11223344"), 42000, 42000); + } + + function testFailWriteReadOutOfBounds() public { + SSTORE2.read(SSTORE2.write(hex"11223344"), 41000, 42000); + } + + function testWriteRead(bytes calldata testBytes, bytes calldata brutalizeWith) + public + brutalizeMemory(brutalizeWith) + { + assertBytesEq(SSTORE2.read(SSTORE2.write(testBytes)), testBytes); + } + + function testWriteReadCustomStartBound( + bytes calldata testBytes, + uint256 startIndex, + bytes calldata brutalizeWith + ) public brutalizeMemory(brutalizeWith) { + if (testBytes.length == 0) return; + + startIndex = bound(startIndex, 0, testBytes.length); + + assertBytesEq(SSTORE2.read(SSTORE2.write(testBytes), startIndex), bytes(testBytes[startIndex:])); + } + + function testWriteReadCustomBounds( + bytes calldata testBytes, + uint256 startIndex, + uint256 endIndex, + bytes calldata brutalizeWith + ) public brutalizeMemory(brutalizeWith) { + if (testBytes.length == 0) return; + + endIndex = bound(endIndex, 0, testBytes.length); + startIndex = bound(startIndex, 0, testBytes.length); + + if (startIndex > endIndex) return; + + assertBytesEq( + SSTORE2.read(SSTORE2.write(testBytes), startIndex, endIndex), + bytes(testBytes[startIndex:endIndex]) + ); + } + + function testFailReadInvalidPointer(address pointer, bytes calldata brutalizeWith) + public + view + brutalizeMemory(brutalizeWith) + { + if (pointer.code.length > 0) revert(); + + SSTORE2.read(pointer); + } + + function testFailReadInvalidPointerCustomStartBound( + address pointer, + uint256 startIndex, + bytes calldata brutalizeWith + ) public view brutalizeMemory(brutalizeWith) { + if (pointer.code.length > 0) revert(); + + SSTORE2.read(pointer, startIndex); + } + + function testFailReadInvalidPointerCustomBounds( + address pointer, + uint256 startIndex, + uint256 endIndex, + bytes calldata brutalizeWith + ) public view brutalizeMemory(brutalizeWith) { + if (pointer.code.length > 0) revert(); + + SSTORE2.read(pointer, startIndex, endIndex); + } + + function testFailWriteReadCustomStartBoundOutOfRange( + bytes calldata testBytes, + uint256 startIndex, + bytes calldata brutalizeWith + ) public brutalizeMemory(brutalizeWith) { + startIndex = bound(startIndex, testBytes.length + 1, type(uint256).max); + + SSTORE2.read(SSTORE2.write(testBytes), startIndex); + } + + function testFailWriteReadCustomBoundsOutOfRange( + bytes calldata testBytes, + uint256 startIndex, + uint256 endIndex, + bytes calldata brutalizeWith + ) public brutalizeMemory(brutalizeWith) { + endIndex = bound(endIndex, testBytes.length + 1, type(uint256).max); + + SSTORE2.read(SSTORE2.write(testBytes), startIndex, endIndex); + } +} diff --git a/CVLByExample/UniswapHooks/lib/uniswap-hooks/lib/v4-core/lib/solmate/src/test/SafeCastLib.t.sol b/CVLByExample/UniswapHooks/lib/uniswap-hooks/lib/v4-core/lib/solmate/src/test/SafeCastLib.t.sol new file mode 100644 index 00000000..52a6c070 --- /dev/null +++ b/CVLByExample/UniswapHooks/lib/uniswap-hooks/lib/v4-core/lib/solmate/src/test/SafeCastLib.t.sol @@ -0,0 +1,644 @@ +// SPDX-License-Identifier: AGPL-3.0-only +pragma solidity 0.8.15; + +import {DSTestPlus} from "./utils/DSTestPlus.sol"; + +import {SafeCastLib} from "../utils/SafeCastLib.sol"; + +contract SafeCastLibTest is DSTestPlus { + function testSafeCastTo248() public { + assertEq(SafeCastLib.safeCastTo248(2.5e45), 2.5e45); + assertEq(SafeCastLib.safeCastTo248(2.5e27), 2.5e27); + } + + function testSafeCastTo240() public { + assertEq(SafeCastLib.safeCastTo240(2.5e45), 2.5e45); + assertEq(SafeCastLib.safeCastTo240(2.5e27), 2.5e27); + } + + function testSafeCastTo232() public { + assertEq(SafeCastLib.safeCastTo232(2.5e45), 2.5e45); + assertEq(SafeCastLib.safeCastTo232(2.5e27), 2.5e27); + } + + function testSafeCastTo224() public { + assertEq(SafeCastLib.safeCastTo224(2.5e36), 2.5e36); + assertEq(SafeCastLib.safeCastTo224(2.5e27), 2.5e27); + } + + function testSafeCastTo216() public { + assertEq(SafeCastLib.safeCastTo216(2.5e36), 2.5e36); + assertEq(SafeCastLib.safeCastTo216(2.5e27), 2.5e27); + } + + function testSafeCastTo208() public { + assertEq(SafeCastLib.safeCastTo208(2.5e36), 2.5e36); + assertEq(SafeCastLib.safeCastTo208(2.5e27), 2.5e27); + } + + function testSafeCastTo200() public { + assertEq(SafeCastLib.safeCastTo200(2.5e36), 2.5e36); + assertEq(SafeCastLib.safeCastTo200(2.5e27), 2.5e27); + } + + function testSafeCastTo192() public { + assertEq(SafeCastLib.safeCastTo192(2.5e36), 2.5e36); + assertEq(SafeCastLib.safeCastTo192(2.5e27), 2.5e27); + } + + function testSafeCastTo184() public { + assertEq(SafeCastLib.safeCastTo184(2.5e36), 2.5e36); + assertEq(SafeCastLib.safeCastTo184(2.5e27), 2.5e27); + } + + function testSafeCastTo176() public { + assertEq(SafeCastLib.safeCastTo176(2.5e36), 2.5e36); + assertEq(SafeCastLib.safeCastTo176(2.5e27), 2.5e27); + } + + function testSafeCastTo168() public { + assertEq(SafeCastLib.safeCastTo168(2.5e36), 2.5e36); + assertEq(SafeCastLib.safeCastTo168(2.5e27), 2.5e27); + } + + function testSafeCastTo160() public { + assertEq(SafeCastLib.safeCastTo160(2.5e36), 2.5e36); + assertEq(SafeCastLib.safeCastTo160(2.5e27), 2.5e27); + } + + function testSafeCastTo152() public { + assertEq(SafeCastLib.safeCastTo152(2.5e36), 2.5e36); + assertEq(SafeCastLib.safeCastTo152(2.5e27), 2.5e27); + } + + function testSafeCastTo144() public { + assertEq(SafeCastLib.safeCastTo144(2.5e36), 2.5e36); + assertEq(SafeCastLib.safeCastTo144(2.5e27), 2.5e27); + } + + function testSafeCastTo136() public { + assertEq(SafeCastLib.safeCastTo136(2.5e36), 2.5e36); + assertEq(SafeCastLib.safeCastTo136(2.5e27), 2.5e27); + } + + function testSafeCastTo128() public { + assertEq(SafeCastLib.safeCastTo128(2.5e27), 2.5e27); + assertEq(SafeCastLib.safeCastTo128(2.5e18), 2.5e18); + } + + function testSafeCastTo120() public { + assertEq(SafeCastLib.safeCastTo120(2.5e27), 2.5e27); + assertEq(SafeCastLib.safeCastTo120(2.5e18), 2.5e18); + } + + function testSafeCastTo112() public { + assertEq(SafeCastLib.safeCastTo112(2.5e27), 2.5e27); + assertEq(SafeCastLib.safeCastTo112(2.5e18), 2.5e18); + } + + function testSafeCastTo104() public { + assertEq(SafeCastLib.safeCastTo104(2.5e27), 2.5e27); + assertEq(SafeCastLib.safeCastTo104(2.5e18), 2.5e18); + } + + function testSafeCastTo96() public { + assertEq(SafeCastLib.safeCastTo96(2.5e18), 2.5e18); + assertEq(SafeCastLib.safeCastTo96(2.5e17), 2.5e17); + } + + function testSafeCastTo64() public { + assertEq(SafeCastLib.safeCastTo64(2.5e18), 2.5e18); + assertEq(SafeCastLib.safeCastTo64(2.5e17), 2.5e17); + } + + function testSafeCastTo56() public { + assertEq(SafeCastLib.safeCastTo56(2.5e16), 2.5e16); + assertEq(SafeCastLib.safeCastTo56(2.5e15), 2.5e15); + } + + function testSafeCastTo48() public { + assertEq(SafeCastLib.safeCastTo48(2.5e12), 2.5e12); + assertEq(SafeCastLib.safeCastTo48(2.5e11), 2.5e11); + } + + function testSafeCastTo40() public { + assertEq(SafeCastLib.safeCastTo40(2.5e10), 2.5e10); + assertEq(SafeCastLib.safeCastTo40(2.5e9), 2.5e9); + } + + function testSafeCastTo32() public { + assertEq(SafeCastLib.safeCastTo32(2.5e8), 2.5e8); + assertEq(SafeCastLib.safeCastTo32(2.5e7), 2.5e7); + } + + function testSafeCastTo24() public { + assertEq(SafeCastLib.safeCastTo24(2.5e4), 2.5e4); + assertEq(SafeCastLib.safeCastTo24(2.5e3), 2.5e3); + } + + function testSafeCastTo16() public { + assertEq(SafeCastLib.safeCastTo16(2.5e3), 2.5e3); + assertEq(SafeCastLib.safeCastTo16(2.5e2), 2.5e2); + } + + function testSafeCastTo8() public { + assertEq(SafeCastLib.safeCastTo8(100), 100); + assertEq(SafeCastLib.safeCastTo8(250), 250); + } + + function testFailSafeCastTo248() public pure { + SafeCastLib.safeCastTo248(type(uint248).max + 1); + } + + function testFailSafeCastTo240() public pure { + SafeCastLib.safeCastTo240(type(uint240).max + 1); + } + + function testFailSafeCastTo232() public pure { + SafeCastLib.safeCastTo232(type(uint232).max + 1); + } + + function testFailSafeCastTo224() public pure { + SafeCastLib.safeCastTo224(type(uint224).max + 1); + } + + function testFailSafeCastTo216() public pure { + SafeCastLib.safeCastTo216(type(uint216).max + 1); + } + + function testFailSafeCastTo208() public pure { + SafeCastLib.safeCastTo208(type(uint208).max + 1); + } + + function testFailSafeCastTo200() public pure { + SafeCastLib.safeCastTo200(type(uint200).max + 1); + } + + function testFailSafeCastTo192() public pure { + SafeCastLib.safeCastTo192(type(uint192).max + 1); + } + + function testFailSafeCastTo184() public pure { + SafeCastLib.safeCastTo184(type(uint184).max + 1); + } + + function testFailSafeCastTo176() public pure { + SafeCastLib.safeCastTo176(type(uint176).max + 1); + } + + function testFailSafeCastTo168() public pure { + SafeCastLib.safeCastTo168(type(uint168).max + 1); + } + + function testFailSafeCastTo160() public pure { + SafeCastLib.safeCastTo160(type(uint160).max + 1); + } + + function testFailSafeCastTo152() public pure { + SafeCastLib.safeCastTo152(type(uint152).max + 1); + } + + function testFailSafeCastTo144() public pure { + SafeCastLib.safeCastTo144(type(uint144).max + 1); + } + + function testFailSafeCastTo136() public pure { + SafeCastLib.safeCastTo136(type(uint136).max + 1); + } + + function testFailSafeCastTo128() public pure { + SafeCastLib.safeCastTo128(type(uint128).max + 1); + } + + function testFailSafeCastTo120() public pure { + SafeCastLib.safeCastTo120(type(uint120).max + 1); + } + + function testFailSafeCastTo112() public pure { + SafeCastLib.safeCastTo112(type(uint112).max + 1); + } + + function testFailSafeCastTo104() public pure { + SafeCastLib.safeCastTo104(type(uint104).max + 1); + } + + function testFailSafeCastTo96() public pure { + SafeCastLib.safeCastTo96(type(uint96).max + 1); + } + + function testFailSafeCastTo88() public pure { + SafeCastLib.safeCastTo88(type(uint88).max + 1); + } + + function testFailSafeCastTo80() public pure { + SafeCastLib.safeCastTo80(type(uint80).max + 1); + } + + function testFailSafeCastTo72() public pure { + SafeCastLib.safeCastTo72(type(uint72).max + 1); + } + + function testFailSafeCastTo64() public pure { + SafeCastLib.safeCastTo64(type(uint64).max + 1); + } + + function testFailSafeCastTo56() public pure { + SafeCastLib.safeCastTo56(type(uint56).max + 1); + } + + function testFailSafeCastTo48() public pure { + SafeCastLib.safeCastTo48(type(uint48).max + 1); + } + + function testFailSafeCastTo40() public pure { + SafeCastLib.safeCastTo40(type(uint40).max + 1); + } + + function testFailSafeCastTo32() public pure { + SafeCastLib.safeCastTo32(type(uint32).max + 1); + } + + function testFailSafeCastTo24() public pure { + SafeCastLib.safeCastTo24(type(uint24).max + 1); + } + + function testFailSafeCastTo16() public pure { + SafeCastLib.safeCastTo16(type(uint16).max + 1); + } + + function testFailSafeCastTo8() public pure { + SafeCastLib.safeCastTo8(type(uint8).max + 1); + } + + function testSafeCastTo248(uint256 x) public { + x = bound(x, 0, type(uint248).max); + + assertEq(SafeCastLib.safeCastTo248(x), x); + } + + function testSafeCastTo240(uint256 x) public { + x = bound(x, 0, type(uint240).max); + + assertEq(SafeCastLib.safeCastTo240(x), x); + } + + function testSafeCastTo232(uint256 x) public { + x = bound(x, 0, type(uint232).max); + + assertEq(SafeCastLib.safeCastTo232(x), x); + } + + function testSafeCastTo224(uint256 x) public { + x = bound(x, 0, type(uint224).max); + + assertEq(SafeCastLib.safeCastTo224(x), x); + } + + function testSafeCastTo216(uint256 x) public { + x = bound(x, 0, type(uint216).max); + + assertEq(SafeCastLib.safeCastTo216(x), x); + } + + function testSafeCastTo208(uint256 x) public { + x = bound(x, 0, type(uint208).max); + + assertEq(SafeCastLib.safeCastTo208(x), x); + } + + function testSafeCastTo200(uint256 x) public { + x = bound(x, 0, type(uint200).max); + + assertEq(SafeCastLib.safeCastTo200(x), x); + } + + function testSafeCastTo192(uint256 x) public { + x = bound(x, 0, type(uint192).max); + + assertEq(SafeCastLib.safeCastTo192(x), x); + } + + function testSafeCastTo184(uint256 x) public { + x = bound(x, 0, type(uint184).max); + + assertEq(SafeCastLib.safeCastTo184(x), x); + } + + function testSafeCastTo176(uint256 x) public { + x = bound(x, 0, type(uint176).max); + + assertEq(SafeCastLib.safeCastTo176(x), x); + } + + function testSafeCastTo168(uint256 x) public { + x = bound(x, 0, type(uint168).max); + + assertEq(SafeCastLib.safeCastTo168(x), x); + } + + function testSafeCastTo160(uint256 x) public { + x = bound(x, 0, type(uint160).max); + + assertEq(SafeCastLib.safeCastTo160(x), x); + } + + function testSafeCastTo152(uint256 x) public { + x = bound(x, 0, type(uint152).max); + + assertEq(SafeCastLib.safeCastTo152(x), x); + } + + function testSafeCastTo144(uint256 x) public { + x = bound(x, 0, type(uint144).max); + + assertEq(SafeCastLib.safeCastTo144(x), x); + } + + function testSafeCastTo136(uint256 x) public { + x = bound(x, 0, type(uint136).max); + + assertEq(SafeCastLib.safeCastTo136(x), x); + } + + function testSafeCastTo128(uint256 x) public { + x = bound(x, 0, type(uint128).max); + + assertEq(SafeCastLib.safeCastTo128(x), x); + } + + function testSafeCastTo120(uint256 x) public { + x = bound(x, 0, type(uint120).max); + + assertEq(SafeCastLib.safeCastTo120(x), x); + } + + function testSafeCastTo112(uint256 x) public { + x = bound(x, 0, type(uint112).max); + + assertEq(SafeCastLib.safeCastTo112(x), x); + } + + function testSafeCastTo104(uint256 x) public { + x = bound(x, 0, type(uint104).max); + + assertEq(SafeCastLib.safeCastTo104(x), x); + } + + function testSafeCastTo96(uint256 x) public { + x = bound(x, 0, type(uint96).max); + + assertEq(SafeCastLib.safeCastTo96(x), x); + } + + function testSafeCastTo88(uint256 x) public { + x = bound(x, 0, type(uint88).max); + + assertEq(SafeCastLib.safeCastTo88(x), x); + } + + function testSafeCastTo80(uint256 x) public { + x = bound(x, 0, type(uint80).max); + + assertEq(SafeCastLib.safeCastTo80(x), x); + } + + function testSafeCastTo72(uint256 x) public { + x = bound(x, 0, type(uint72).max); + + assertEq(SafeCastLib.safeCastTo72(x), x); + } + + function testSafeCastTo64(uint256 x) public { + x = bound(x, 0, type(uint64).max); + + assertEq(SafeCastLib.safeCastTo64(x), x); + } + + function testSafeCastTo56(uint256 x) public { + x = bound(x, 0, type(uint56).max); + + assertEq(SafeCastLib.safeCastTo56(x), x); + } + + function testSafeCastTo48(uint256 x) public { + x = bound(x, 0, type(uint48).max); + + assertEq(SafeCastLib.safeCastTo48(x), x); + } + + function testSafeCastTo40(uint256 x) public { + x = bound(x, 0, type(uint40).max); + + assertEq(SafeCastLib.safeCastTo40(x), x); + } + + function testSafeCastTo32(uint256 x) public { + x = bound(x, 0, type(uint32).max); + + assertEq(SafeCastLib.safeCastTo32(x), x); + } + + function testSafeCastTo24(uint256 x) public { + x = bound(x, 0, type(uint24).max); + + assertEq(SafeCastLib.safeCastTo24(x), x); + } + + function testSafeCastTo16(uint256 x) public { + x = bound(x, 0, type(uint16).max); + + assertEq(SafeCastLib.safeCastTo16(x), x); + } + + function testSafeCastTo8(uint256 x) public { + x = bound(x, 0, type(uint8).max); + + assertEq(SafeCastLib.safeCastTo8(x), x); + } + + function testFailSafeCastTo248(uint256 x) public { + x = bound(x, type(uint248).max + 1, type(uint256).max); + + SafeCastLib.safeCastTo248(x); + } + + function testFailSafeCastTo240(uint256 x) public { + x = bound(x, type(uint240).max + 1, type(uint256).max); + + SafeCastLib.safeCastTo240(x); + } + + function testFailSafeCastTo232(uint256 x) public { + x = bound(x, type(uint232).max + 1, type(uint256).max); + + SafeCastLib.safeCastTo232(x); + } + + function testFailSafeCastTo224(uint256 x) public { + x = bound(x, type(uint224).max + 1, type(uint256).max); + + SafeCastLib.safeCastTo224(x); + } + + function testFailSafeCastTo216(uint256 x) public { + x = bound(x, type(uint216).max + 1, type(uint256).max); + + SafeCastLib.safeCastTo216(x); + } + + function testFailSafeCastTo208(uint256 x) public { + x = bound(x, type(uint208).max + 1, type(uint256).max); + + SafeCastLib.safeCastTo208(x); + } + + function testFailSafeCastTo200(uint256 x) public { + x = bound(x, type(uint200).max + 1, type(uint256).max); + + SafeCastLib.safeCastTo200(x); + } + + function testFailSafeCastTo192(uint256 x) public { + x = bound(x, type(uint192).max + 1, type(uint256).max); + + SafeCastLib.safeCastTo192(x); + } + + function testFailSafeCastTo184(uint256 x) public { + x = bound(x, type(uint184).max + 1, type(uint256).max); + + SafeCastLib.safeCastTo184(x); + } + + function testFailSafeCastTo176(uint256 x) public { + x = bound(x, type(uint176).max + 1, type(uint256).max); + + SafeCastLib.safeCastTo176(x); + } + + function testFailSafeCastTo168(uint256 x) public { + x = bound(x, type(uint168).max + 1, type(uint256).max); + + SafeCastLib.safeCastTo168(x); + } + + function testFailSafeCastTo160(uint256 x) public { + x = bound(x, type(uint160).max + 1, type(uint256).max); + + SafeCastLib.safeCastTo160(x); + } + + function testFailSafeCastTo152(uint256 x) public { + x = bound(x, type(uint152).max + 1, type(uint256).max); + + SafeCastLib.safeCastTo152(x); + } + + function testFailSafeCastTo144(uint256 x) public { + x = bound(x, type(uint144).max + 1, type(uint256).max); + + SafeCastLib.safeCastTo144(x); + } + + function testFailSafeCastTo136(uint256 x) public { + x = bound(x, type(uint136).max + 1, type(uint256).max); + + SafeCastLib.safeCastTo136(x); + } + + function testFailSafeCastTo128(uint256 x) public { + x = bound(x, type(uint128).max + 1, type(uint256).max); + + SafeCastLib.safeCastTo128(x); + } + + function testFailSafeCastTo120(uint256 x) public { + x = bound(x, type(uint120).max + 1, type(uint256).max); + + SafeCastLib.safeCastTo120(x); + } + + function testFailSafeCastTo112(uint256 x) public { + x = bound(x, type(uint112).max + 1, type(uint256).max); + + SafeCastLib.safeCastTo112(x); + } + + function testFailSafeCastTo104(uint256 x) public { + x = bound(x, type(uint104).max + 1, type(uint256).max); + + SafeCastLib.safeCastTo104(x); + } + + function testFailSafeCastTo96(uint256 x) public { + x = bound(x, type(uint96).max + 1, type(uint256).max); + + SafeCastLib.safeCastTo96(x); + } + + function testFailSafeCastTo88(uint256 x) public { + x = bound(x, type(uint88).max + 1, type(uint256).max); + + SafeCastLib.safeCastTo88(x); + } + + function testFailSafeCastTo80(uint256 x) public { + x = bound(x, type(uint80).max + 1, type(uint256).max); + + SafeCastLib.safeCastTo80(x); + } + + function testFailSafeCastTo72(uint256 x) public { + x = bound(x, type(uint72).max + 1, type(uint256).max); + + SafeCastLib.safeCastTo72(x); + } + + function testFailSafeCastTo64(uint256 x) public { + x = bound(x, type(uint64).max + 1, type(uint256).max); + + SafeCastLib.safeCastTo64(x); + } + + function testFailSafeCastTo56(uint256 x) public { + x = bound(x, type(uint56).max + 1, type(uint256).max); + + SafeCastLib.safeCastTo56(x); + } + + function testFailSafeCastTo48(uint256 x) public { + x = bound(x, type(uint48).max + 1, type(uint256).max); + + SafeCastLib.safeCastTo48(x); + } + + function testFailSafeCastTo40(uint256 x) public { + x = bound(x, type(uint40).max + 1, type(uint256).max); + + SafeCastLib.safeCastTo40(x); + } + + function testFailSafeCastTo32(uint256 x) public { + x = bound(x, type(uint32).max + 1, type(uint256).max); + + SafeCastLib.safeCastTo32(x); + } + + function testFailSafeCastTo24(uint256 x) public { + x = bound(x, type(uint24).max + 1, type(uint256).max); + + SafeCastLib.safeCastTo24(x); + } + + function testFailSafeCastTo16(uint256 x) public { + x = bound(x, type(uint16).max + 1, type(uint256).max); + + SafeCastLib.safeCastTo16(x); + } + + function testFailSafeCastTo8(uint256 x) public { + x = bound(x, type(uint8).max + 1, type(uint256).max); + + SafeCastLib.safeCastTo8(x); + } +} diff --git a/CVLByExample/UniswapHooks/lib/uniswap-hooks/lib/v4-core/lib/solmate/src/test/SafeTransferLib.t.sol b/CVLByExample/UniswapHooks/lib/uniswap-hooks/lib/v4-core/lib/solmate/src/test/SafeTransferLib.t.sol new file mode 100644 index 00000000..b976e9f6 --- /dev/null +++ b/CVLByExample/UniswapHooks/lib/uniswap-hooks/lib/v4-core/lib/solmate/src/test/SafeTransferLib.t.sol @@ -0,0 +1,610 @@ +// SPDX-License-Identifier: AGPL-3.0-only +pragma solidity 0.8.15; + +import {MockERC20} from "./utils/mocks/MockERC20.sol"; +import {RevertingToken} from "./utils/weird-tokens/RevertingToken.sol"; +import {ReturnsTwoToken} from "./utils/weird-tokens/ReturnsTwoToken.sol"; +import {ReturnsFalseToken} from "./utils/weird-tokens/ReturnsFalseToken.sol"; +import {MissingReturnToken} from "./utils/weird-tokens/MissingReturnToken.sol"; +import {ReturnsTooMuchToken} from "./utils/weird-tokens/ReturnsTooMuchToken.sol"; +import {ReturnsGarbageToken} from "./utils/weird-tokens/ReturnsGarbageToken.sol"; +import {ReturnsTooLittleToken} from "./utils/weird-tokens/ReturnsTooLittleToken.sol"; + +import {DSTestPlus} from "./utils/DSTestPlus.sol"; + +import {ERC20} from "../tokens/ERC20.sol"; +import {SafeTransferLib} from "../utils/SafeTransferLib.sol"; + +contract SafeTransferLibTest is DSTestPlus { + RevertingToken reverting; + ReturnsTwoToken returnsTwo; + ReturnsFalseToken returnsFalse; + MissingReturnToken missingReturn; + ReturnsTooMuchToken returnsTooMuch; + ReturnsGarbageToken returnsGarbage; + ReturnsTooLittleToken returnsTooLittle; + + MockERC20 erc20; + + function setUp() public { + reverting = new RevertingToken(); + returnsTwo = new ReturnsTwoToken(); + returnsFalse = new ReturnsFalseToken(); + missingReturn = new MissingReturnToken(); + returnsTooMuch = new ReturnsTooMuchToken(); + returnsGarbage = new ReturnsGarbageToken(); + returnsTooLittle = new ReturnsTooLittleToken(); + + erc20 = new MockERC20("StandardToken", "ST", 18); + erc20.mint(address(this), type(uint256).max); + } + + function testTransferWithMissingReturn() public { + verifySafeTransfer(address(missingReturn), address(0xBEEF), 1e18); + } + + function testTransferWithStandardERC20() public { + verifySafeTransfer(address(erc20), address(0xBEEF), 1e18); + } + + function testTransferWithReturnsTooMuch() public { + verifySafeTransfer(address(returnsTooMuch), address(0xBEEF), 1e18); + } + + function testTransferWithNonContract() public { + SafeTransferLib.safeTransfer(ERC20(address(0xBADBEEF)), address(0xBEEF), 1e18); + } + + function testTransferFromWithMissingReturn() public { + verifySafeTransferFrom(address(missingReturn), address(0xFEED), address(0xBEEF), 1e18); + } + + function testTransferFromWithStandardERC20() public { + verifySafeTransferFrom(address(erc20), address(0xFEED), address(0xBEEF), 1e18); + } + + function testTransferFromWithReturnsTooMuch() public { + verifySafeTransferFrom(address(returnsTooMuch), address(0xFEED), address(0xBEEF), 1e18); + } + + function testTransferFromWithNonContract() public { + SafeTransferLib.safeTransferFrom(ERC20(address(0xBADBEEF)), address(0xFEED), address(0xBEEF), 1e18); + } + + function testApproveWithMissingReturn() public { + verifySafeApprove(address(missingReturn), address(0xBEEF), 1e18); + } + + function testApproveWithStandardERC20() public { + verifySafeApprove(address(erc20), address(0xBEEF), 1e18); + } + + function testApproveWithReturnsTooMuch() public { + verifySafeApprove(address(returnsTooMuch), address(0xBEEF), 1e18); + } + + function testApproveWithNonContract() public { + SafeTransferLib.safeApprove(ERC20(address(0xBADBEEF)), address(0xBEEF), 1e18); + } + + function testTransferETH() public { + SafeTransferLib.safeTransferETH(address(0xBEEF), 1e18); + } + + function testFailTransferWithReturnsFalse() public { + verifySafeTransfer(address(returnsFalse), address(0xBEEF), 1e18); + } + + function testFailTransferWithReverting() public { + verifySafeTransfer(address(reverting), address(0xBEEF), 1e18); + } + + function testFailTransferWithReturnsTooLittle() public { + verifySafeTransfer(address(returnsTooLittle), address(0xBEEF), 1e18); + } + + function testFailTransferFromWithReturnsFalse() public { + verifySafeTransferFrom(address(returnsFalse), address(0xFEED), address(0xBEEF), 1e18); + } + + function testFailTransferFromWithReverting() public { + verifySafeTransferFrom(address(reverting), address(0xFEED), address(0xBEEF), 1e18); + } + + function testFailTransferFromWithReturnsTooLittle() public { + verifySafeTransferFrom(address(returnsTooLittle), address(0xFEED), address(0xBEEF), 1e18); + } + + function testFailApproveWithReturnsFalse() public { + verifySafeApprove(address(returnsFalse), address(0xBEEF), 1e18); + } + + function testFailApproveWithReverting() public { + verifySafeApprove(address(reverting), address(0xBEEF), 1e18); + } + + function testFailApproveWithReturnsTooLittle() public { + verifySafeApprove(address(returnsTooLittle), address(0xBEEF), 1e18); + } + + function testTransferWithMissingReturn( + address to, + uint256 amount, + bytes calldata brutalizeWith + ) public brutalizeMemory(brutalizeWith) { + verifySafeTransfer(address(missingReturn), to, amount); + } + + function testTransferWithStandardERC20( + address to, + uint256 amount, + bytes calldata brutalizeWith + ) public brutalizeMemory(brutalizeWith) { + verifySafeTransfer(address(erc20), to, amount); + } + + function testTransferWithReturnsTooMuch( + address to, + uint256 amount, + bytes calldata brutalizeWith + ) public brutalizeMemory(brutalizeWith) { + verifySafeTransfer(address(returnsTooMuch), to, amount); + } + + function testTransferWithGarbage( + address to, + uint256 amount, + bytes memory garbage, + bytes calldata brutalizeWith + ) public brutalizeMemory(brutalizeWith) { + if ( + (garbage.length < 32 || + (garbage[0] != 0 || + garbage[1] != 0 || + garbage[2] != 0 || + garbage[3] != 0 || + garbage[4] != 0 || + garbage[5] != 0 || + garbage[6] != 0 || + garbage[7] != 0 || + garbage[8] != 0 || + garbage[9] != 0 || + garbage[10] != 0 || + garbage[11] != 0 || + garbage[12] != 0 || + garbage[13] != 0 || + garbage[14] != 0 || + garbage[15] != 0 || + garbage[16] != 0 || + garbage[17] != 0 || + garbage[18] != 0 || + garbage[19] != 0 || + garbage[20] != 0 || + garbage[21] != 0 || + garbage[22] != 0 || + garbage[23] != 0 || + garbage[24] != 0 || + garbage[25] != 0 || + garbage[26] != 0 || + garbage[27] != 0 || + garbage[28] != 0 || + garbage[29] != 0 || + garbage[30] != 0 || + garbage[31] != bytes1(0x01))) && garbage.length != 0 + ) return; + + returnsGarbage.setGarbage(garbage); + + verifySafeTransfer(address(returnsGarbage), to, amount); + } + + function testTransferWithNonContract( + address nonContract, + address to, + uint256 amount, + bytes calldata brutalizeWith + ) public brutalizeMemory(brutalizeWith) { + if (uint256(uint160(nonContract)) <= 18 || nonContract.code.length > 0) return; + + SafeTransferLib.safeTransfer(ERC20(nonContract), to, amount); + } + + function testFailTransferETHToContractWithoutFallback() public { + SafeTransferLib.safeTransferETH(address(this), 1e18); + } + + function testTransferFromWithMissingReturn( + address from, + address to, + uint256 amount, + bytes calldata brutalizeWith + ) public brutalizeMemory(brutalizeWith) { + verifySafeTransferFrom(address(missingReturn), from, to, amount); + } + + function testTransferFromWithStandardERC20( + address from, + address to, + uint256 amount, + bytes calldata brutalizeWith + ) public brutalizeMemory(brutalizeWith) { + verifySafeTransferFrom(address(erc20), from, to, amount); + } + + function testTransferFromWithReturnsTooMuch( + address from, + address to, + uint256 amount, + bytes calldata brutalizeWith + ) public brutalizeMemory(brutalizeWith) { + verifySafeTransferFrom(address(returnsTooMuch), from, to, amount); + } + + function testTransferFromWithGarbage( + address from, + address to, + uint256 amount, + bytes memory garbage, + bytes calldata brutalizeWith + ) public brutalizeMemory(brutalizeWith) { + if ( + (garbage.length < 32 || + (garbage[0] != 0 || + garbage[1] != 0 || + garbage[2] != 0 || + garbage[3] != 0 || + garbage[4] != 0 || + garbage[5] != 0 || + garbage[6] != 0 || + garbage[7] != 0 || + garbage[8] != 0 || + garbage[9] != 0 || + garbage[10] != 0 || + garbage[11] != 0 || + garbage[12] != 0 || + garbage[13] != 0 || + garbage[14] != 0 || + garbage[15] != 0 || + garbage[16] != 0 || + garbage[17] != 0 || + garbage[18] != 0 || + garbage[19] != 0 || + garbage[20] != 0 || + garbage[21] != 0 || + garbage[22] != 0 || + garbage[23] != 0 || + garbage[24] != 0 || + garbage[25] != 0 || + garbage[26] != 0 || + garbage[27] != 0 || + garbage[28] != 0 || + garbage[29] != 0 || + garbage[30] != 0 || + garbage[31] != bytes1(0x01))) && garbage.length != 0 + ) return; + + returnsGarbage.setGarbage(garbage); + + verifySafeTransferFrom(address(returnsGarbage), from, to, amount); + } + + function testTransferFromWithNonContract( + address nonContract, + address from, + address to, + uint256 amount, + bytes calldata brutalizeWith + ) public brutalizeMemory(brutalizeWith) { + if (uint256(uint160(nonContract)) <= 18 || nonContract.code.length > 0) return; + + SafeTransferLib.safeTransferFrom(ERC20(nonContract), from, to, amount); + } + + function testApproveWithMissingReturn( + address to, + uint256 amount, + bytes calldata brutalizeWith + ) public brutalizeMemory(brutalizeWith) { + verifySafeApprove(address(missingReturn), to, amount); + } + + function testApproveWithStandardERC20( + address to, + uint256 amount, + bytes calldata brutalizeWith + ) public brutalizeMemory(brutalizeWith) { + verifySafeApprove(address(erc20), to, amount); + } + + function testApproveWithReturnsTooMuch( + address to, + uint256 amount, + bytes calldata brutalizeWith + ) public brutalizeMemory(brutalizeWith) { + verifySafeApprove(address(returnsTooMuch), to, amount); + } + + function testApproveWithGarbage( + address to, + uint256 amount, + bytes memory garbage, + bytes calldata brutalizeWith + ) public brutalizeMemory(brutalizeWith) { + if ( + (garbage.length < 32 || + (garbage[0] != 0 || + garbage[1] != 0 || + garbage[2] != 0 || + garbage[3] != 0 || + garbage[4] != 0 || + garbage[5] != 0 || + garbage[6] != 0 || + garbage[7] != 0 || + garbage[8] != 0 || + garbage[9] != 0 || + garbage[10] != 0 || + garbage[11] != 0 || + garbage[12] != 0 || + garbage[13] != 0 || + garbage[14] != 0 || + garbage[15] != 0 || + garbage[16] != 0 || + garbage[17] != 0 || + garbage[18] != 0 || + garbage[19] != 0 || + garbage[20] != 0 || + garbage[21] != 0 || + garbage[22] != 0 || + garbage[23] != 0 || + garbage[24] != 0 || + garbage[25] != 0 || + garbage[26] != 0 || + garbage[27] != 0 || + garbage[28] != 0 || + garbage[29] != 0 || + garbage[30] != 0 || + garbage[31] != bytes1(0x01))) && garbage.length != 0 + ) return; + + returnsGarbage.setGarbage(garbage); + + verifySafeApprove(address(returnsGarbage), to, amount); + } + + function testApproveWithNonContract( + address nonContract, + address to, + uint256 amount, + bytes calldata brutalizeWith + ) public brutalizeMemory(brutalizeWith) { + if (uint256(uint160(nonContract)) <= 18 || nonContract.code.length > 0) return; + + SafeTransferLib.safeApprove(ERC20(nonContract), to, amount); + } + + function testTransferETH( + address recipient, + uint256 amount, + bytes calldata brutalizeWith + ) public brutalizeMemory(brutalizeWith) { + // Transferring to msg.sender can fail because it's possible to overflow their ETH balance as it begins non-zero. + if (recipient.code.length > 0 || uint256(uint160(recipient)) <= 18 || recipient == msg.sender) return; + + amount = bound(amount, 0, address(this).balance); + + SafeTransferLib.safeTransferETH(recipient, amount); + } + + function testFailTransferWithReturnsFalse( + address to, + uint256 amount, + bytes calldata brutalizeWith + ) public brutalizeMemory(brutalizeWith) { + verifySafeTransfer(address(returnsFalse), to, amount); + } + + function testFailTransferWithReverting( + address to, + uint256 amount, + bytes calldata brutalizeWith + ) public brutalizeMemory(brutalizeWith) { + verifySafeTransfer(address(reverting), to, amount); + } + + function testFailTransferWithReturnsTooLittle( + address to, + uint256 amount, + bytes calldata brutalizeWith + ) public brutalizeMemory(brutalizeWith) { + verifySafeTransfer(address(returnsTooLittle), to, amount); + } + + function testFailTransferWithReturnsTwo( + address to, + uint256 amount, + bytes calldata brutalizeWith + ) public brutalizeMemory(brutalizeWith) { + verifySafeTransfer(address(returnsTwo), to, amount); + } + + function testFailTransferWithGarbage( + address to, + uint256 amount, + bytes memory garbage, + bytes calldata brutalizeWith + ) public brutalizeMemory(brutalizeWith) { + require(garbage.length != 0 && (garbage.length < 32 || garbage[31] != bytes1(0x01))); + + returnsGarbage.setGarbage(garbage); + + verifySafeTransfer(address(returnsGarbage), to, amount); + } + + function testFailTransferFromWithReturnsFalse( + address from, + address to, + uint256 amount, + bytes calldata brutalizeWith + ) public brutalizeMemory(brutalizeWith) { + verifySafeTransferFrom(address(returnsFalse), from, to, amount); + } + + function testFailTransferFromWithReverting( + address from, + address to, + uint256 amount, + bytes calldata brutalizeWith + ) public brutalizeMemory(brutalizeWith) { + verifySafeTransferFrom(address(reverting), from, to, amount); + } + + function testFailTransferFromWithReturnsTooLittle( + address from, + address to, + uint256 amount, + bytes calldata brutalizeWith + ) public brutalizeMemory(brutalizeWith) { + verifySafeTransferFrom(address(returnsTooLittle), from, to, amount); + } + + function testFailTransferFromWithReturnsTwo( + address from, + address to, + uint256 amount, + bytes calldata brutalizeWith + ) public brutalizeMemory(brutalizeWith) { + verifySafeTransferFrom(address(returnsTwo), from, to, amount); + } + + function testFailTransferFromWithGarbage( + address from, + address to, + uint256 amount, + bytes memory garbage, + bytes calldata brutalizeWith + ) public brutalizeMemory(brutalizeWith) { + require(garbage.length != 0 && (garbage.length < 32 || garbage[31] != bytes1(0x01))); + + returnsGarbage.setGarbage(garbage); + + verifySafeTransferFrom(address(returnsGarbage), from, to, amount); + } + + function testFailApproveWithReturnsFalse( + address to, + uint256 amount, + bytes calldata brutalizeWith + ) public brutalizeMemory(brutalizeWith) { + verifySafeApprove(address(returnsFalse), to, amount); + } + + function testFailApproveWithReverting( + address to, + uint256 amount, + bytes calldata brutalizeWith + ) public brutalizeMemory(brutalizeWith) { + verifySafeApprove(address(reverting), to, amount); + } + + function testFailApproveWithReturnsTooLittle( + address to, + uint256 amount, + bytes calldata brutalizeWith + ) public brutalizeMemory(brutalizeWith) { + verifySafeApprove(address(returnsTooLittle), to, amount); + } + + function testFailApproveWithReturnsTwo( + address to, + uint256 amount, + bytes calldata brutalizeWith + ) public brutalizeMemory(brutalizeWith) { + verifySafeApprove(address(returnsTwo), to, amount); + } + + function testFailApproveWithGarbage( + address to, + uint256 amount, + bytes memory garbage, + bytes calldata brutalizeWith + ) public brutalizeMemory(brutalizeWith) { + require(garbage.length != 0 && (garbage.length < 32 || garbage[31] != bytes1(0x01))); + + returnsGarbage.setGarbage(garbage); + + verifySafeApprove(address(returnsGarbage), to, amount); + } + + function testFailTransferETHToContractWithoutFallback(uint256 amount, bytes calldata brutalizeWith) + public + brutalizeMemory(brutalizeWith) + { + SafeTransferLib.safeTransferETH(address(this), amount); + } + + function verifySafeTransfer( + address token, + address to, + uint256 amount + ) internal { + uint256 preBal = ERC20(token).balanceOf(to); + SafeTransferLib.safeTransfer(ERC20(address(token)), to, amount); + uint256 postBal = ERC20(token).balanceOf(to); + + if (to == address(this)) { + assertEq(preBal, postBal); + } else { + assertEq(postBal - preBal, amount); + } + } + + function verifySafeTransferFrom( + address token, + address from, + address to, + uint256 amount + ) internal { + forceApprove(token, from, address(this), amount); + + // We cast to MissingReturnToken here because it won't check + // that there was return data, which accommodates all tokens. + MissingReturnToken(token).transfer(from, amount); + + uint256 preBal = ERC20(token).balanceOf(to); + SafeTransferLib.safeTransferFrom(ERC20(token), from, to, amount); + uint256 postBal = ERC20(token).balanceOf(to); + + if (from == to) { + assertEq(preBal, postBal); + } else { + assertEq(postBal - preBal, amount); + } + } + + function verifySafeApprove( + address token, + address to, + uint256 amount + ) internal { + SafeTransferLib.safeApprove(ERC20(address(token)), to, amount); + + assertEq(ERC20(token).allowance(address(this), to), amount); + } + + function forceApprove( + address token, + address from, + address to, + uint256 amount + ) internal { + uint256 slot = token == address(erc20) ? 4 : 2; // Standard ERC20 name and symbol aren't constant. + + hevm.store( + token, + keccak256(abi.encode(to, keccak256(abi.encode(from, uint256(slot))))), + bytes32(uint256(amount)) + ); + + assertEq(ERC20(token).allowance(from, to), amount, "wrong allowance"); + } +} diff --git a/CVLByExample/UniswapHooks/lib/uniswap-hooks/lib/v4-core/lib/solmate/src/test/SignedWadMath.t.sol b/CVLByExample/UniswapHooks/lib/uniswap-hooks/lib/v4-core/lib/solmate/src/test/SignedWadMath.t.sol new file mode 100644 index 00000000..48494480 --- /dev/null +++ b/CVLByExample/UniswapHooks/lib/uniswap-hooks/lib/v4-core/lib/solmate/src/test/SignedWadMath.t.sol @@ -0,0 +1,74 @@ +// SPDX-License-Identifier: MIT +pragma solidity >=0.8.0; + +import {DSTestPlus} from "./utils/DSTestPlus.sol"; + +import {wadMul, wadDiv} from "../utils/SignedWadMath.sol"; + +contract SignedWadMathTest is DSTestPlus { + function testWadMul( + uint256 x, + uint256 y, + bool negX, + bool negY + ) public { + x = bound(x, 0, 99999999999999e18); + y = bound(x, 0, 99999999999999e18); + + int256 xPrime = negX ? -int256(x) : int256(x); + int256 yPrime = negY ? -int256(y) : int256(y); + + assertEq(wadMul(xPrime, yPrime), (xPrime * yPrime) / 1e18); + } + + function testFailWadMulEdgeCase() public pure { + int256 x = -1; + int256 y = type(int256).min; + + wadMul(x, y); + } + + function testFailWadMulEdgeCase2() public pure { + int256 x = type(int256).min; + int256 y = -1; + + wadMul(x, y); + } + + function testFailWadMulOverflow(int256 x, int256 y) public pure { + // Ignore cases where x * y does not overflow. + unchecked { + if ((x * y) / x == y) revert(); + } + + wadMul(x, y); + } + + function testWadDiv( + uint256 x, + uint256 y, + bool negX, + bool negY + ) public { + x = bound(x, 0, 99999999e18); + y = bound(x, 1, 99999999e18); + + int256 xPrime = negX ? -int256(x) : int256(x); + int256 yPrime = negY ? -int256(y) : int256(y); + + assertEq(wadDiv(xPrime, yPrime), (xPrime * 1e18) / yPrime); + } + + function testFailWadDivOverflow(int256 x, int256 y) public pure { + // Ignore cases where x * WAD does not overflow or y is 0. + unchecked { + if (y == 0 || (x * 1e18) / 1e18 == x) revert(); + } + + wadDiv(x, y); + } + + function testFailWadDivZeroDenominator(int256 x) public pure { + wadDiv(x, 0); + } +} diff --git a/CVLByExample/UniswapHooks/lib/uniswap-hooks/lib/v4-core/lib/solmate/src/test/WETH.t.sol b/CVLByExample/UniswapHooks/lib/uniswap-hooks/lib/v4-core/lib/solmate/src/test/WETH.t.sol new file mode 100644 index 00000000..a13761ee --- /dev/null +++ b/CVLByExample/UniswapHooks/lib/uniswap-hooks/lib/v4-core/lib/solmate/src/test/WETH.t.sol @@ -0,0 +1,146 @@ +// SPDX-License-Identifier: AGPL-3.0-only +pragma solidity 0.8.15; + +import {DSTestPlus} from "./utils/DSTestPlus.sol"; +import {DSInvariantTest} from "./utils/DSInvariantTest.sol"; + +import {SafeTransferLib} from "../utils/SafeTransferLib.sol"; + +import {WETH} from "../tokens/WETH.sol"; + +contract WETHTest is DSTestPlus { + WETH weth; + + function setUp() public { + weth = new WETH(); + } + + function testFallbackDeposit() public { + assertEq(weth.balanceOf(address(this)), 0); + assertEq(weth.totalSupply(), 0); + + SafeTransferLib.safeTransferETH(address(weth), 1 ether); + + assertEq(weth.balanceOf(address(this)), 1 ether); + assertEq(weth.totalSupply(), 1 ether); + } + + function testDeposit() public { + assertEq(weth.balanceOf(address(this)), 0); + assertEq(weth.totalSupply(), 0); + + weth.deposit{value: 1 ether}(); + + assertEq(weth.balanceOf(address(this)), 1 ether); + assertEq(weth.totalSupply(), 1 ether); + } + + function testWithdraw() public { + uint256 startingBalance = address(this).balance; + + weth.deposit{value: 1 ether}(); + + weth.withdraw(1 ether); + + uint256 balanceAfterWithdraw = address(this).balance; + + assertEq(balanceAfterWithdraw, startingBalance); + assertEq(weth.balanceOf(address(this)), 0); + assertEq(weth.totalSupply(), 0); + } + + function testPartialWithdraw() public { + weth.deposit{value: 1 ether}(); + + uint256 balanceBeforeWithdraw = address(this).balance; + + weth.withdraw(0.5 ether); + + uint256 balanceAfterWithdraw = address(this).balance; + + assertEq(balanceAfterWithdraw, balanceBeforeWithdraw + 0.5 ether); + assertEq(weth.balanceOf(address(this)), 0.5 ether); + assertEq(weth.totalSupply(), 0.5 ether); + } + + function testFallbackDeposit(uint256 amount) public { + amount = bound(amount, 0, address(this).balance); + + assertEq(weth.balanceOf(address(this)), 0); + assertEq(weth.totalSupply(), 0); + + SafeTransferLib.safeTransferETH(address(weth), amount); + + assertEq(weth.balanceOf(address(this)), amount); + assertEq(weth.totalSupply(), amount); + } + + function testDeposit(uint256 amount) public { + amount = bound(amount, 0, address(this).balance); + + assertEq(weth.balanceOf(address(this)), 0); + assertEq(weth.totalSupply(), 0); + + weth.deposit{value: amount}(); + + assertEq(weth.balanceOf(address(this)), amount); + assertEq(weth.totalSupply(), amount); + } + + function testWithdraw(uint256 depositAmount, uint256 withdrawAmount) public { + depositAmount = bound(depositAmount, 0, address(this).balance); + withdrawAmount = bound(withdrawAmount, 0, depositAmount); + + weth.deposit{value: depositAmount}(); + + uint256 balanceBeforeWithdraw = address(this).balance; + + weth.withdraw(withdrawAmount); + + uint256 balanceAfterWithdraw = address(this).balance; + + assertEq(balanceAfterWithdraw, balanceBeforeWithdraw + withdrawAmount); + assertEq(weth.balanceOf(address(this)), depositAmount - withdrawAmount); + assertEq(weth.totalSupply(), depositAmount - withdrawAmount); + } + + receive() external payable {} +} + +contract WETHInvariants is DSTestPlus, DSInvariantTest { + WETHTester wethTester; + WETH weth; + + function setUp() public { + weth = new WETH(); + wethTester = new WETHTester{value: address(this).balance}(weth); + + addTargetContract(address(wethTester)); + } + + function invariantTotalSupplyEqualsBalance() public { + assertEq(address(weth).balance, weth.totalSupply()); + } +} + +contract WETHTester { + WETH weth; + + constructor(WETH _weth) payable { + weth = _weth; + } + + function deposit(uint256 amount) public { + weth.deposit{value: amount}(); + } + + function fallbackDeposit(uint256 amount) public { + SafeTransferLib.safeTransferETH(address(weth), amount); + } + + function withdraw(uint256 amount) public { + weth.withdraw(amount); + } + + receive() external payable {} +} diff --git a/CVLByExample/UniswapHooks/lib/uniswap-hooks/lib/v4-core/lib/solmate/src/test/utils/DSInvariantTest.sol b/CVLByExample/UniswapHooks/lib/uniswap-hooks/lib/v4-core/lib/solmate/src/test/utils/DSInvariantTest.sol new file mode 100644 index 00000000..820775c5 --- /dev/null +++ b/CVLByExample/UniswapHooks/lib/uniswap-hooks/lib/v4-core/lib/solmate/src/test/utils/DSInvariantTest.sol @@ -0,0 +1,16 @@ +// SPDX-License-Identifier: AGPL-3.0-only +pragma solidity >=0.8.0; + +contract DSInvariantTest { + address[] private targets; + + function targetContracts() public view virtual returns (address[] memory) { + require(targets.length > 0, "NO_TARGET_CONTRACTS"); + + return targets; + } + + function addTargetContract(address newTargetContract) internal virtual { + targets.push(newTargetContract); + } +} diff --git a/CVLByExample/UniswapHooks/lib/uniswap-hooks/lib/v4-core/lib/solmate/src/test/utils/DSTestPlus.sol b/CVLByExample/UniswapHooks/lib/uniswap-hooks/lib/v4-core/lib/solmate/src/test/utils/DSTestPlus.sol new file mode 100644 index 00000000..b56d4c9b --- /dev/null +++ b/CVLByExample/UniswapHooks/lib/uniswap-hooks/lib/v4-core/lib/solmate/src/test/utils/DSTestPlus.sol @@ -0,0 +1,179 @@ +// SPDX-License-Identifier: AGPL-3.0-only +pragma solidity >=0.8.0; + +import {DSTest} from "ds-test/test.sol"; + +import {Hevm} from "./Hevm.sol"; + +/// @notice Extended testing framework for DappTools projects. +/// @author Solmate (https://github.com/transmissions11/solmate/blob/main/src/test/utils/DSTestPlus.sol) +contract DSTestPlus is DSTest { + Hevm internal constant hevm = Hevm(HEVM_ADDRESS); + + address internal constant DEAD_ADDRESS = 0xDeaDbeefdEAdbeefdEadbEEFdeadbeEFdEaDbeeF; + + string private checkpointLabel; + uint256 private checkpointGasLeft = 1; // Start the slot warm. + + modifier brutalizeMemory(bytes memory brutalizeWith) { + /// @solidity memory-safe-assembly + assembly { + // Fill the 64 bytes of scratch space with the data. + pop( + staticcall( + gas(), // Pass along all the gas in the call. + 0x04, // Call the identity precompile address. + brutalizeWith, // Offset is the bytes' pointer. + 64, // Copy enough to only fill the scratch space. + 0, // Store the return value in the scratch space. + 64 // Scratch space is only 64 bytes in size, we don't want to write further. + ) + ) + + let size := add(mload(brutalizeWith), 32) // Add 32 to include the 32 byte length slot. + + // Fill the free memory pointer's destination with the data. + pop( + staticcall( + gas(), // Pass along all the gas in the call. + 0x04, // Call the identity precompile address. + brutalizeWith, // Offset is the bytes' pointer. + size, // We want to pass the length of the bytes. + mload(0x40), // Store the return value at the free memory pointer. + size // Since the precompile just returns its input, we reuse size. + ) + ) + } + + _; + } + + function startMeasuringGas(string memory label) internal virtual { + checkpointLabel = label; + + checkpointGasLeft = gasleft(); + } + + function stopMeasuringGas() internal virtual { + uint256 checkpointGasLeft2 = gasleft(); + + // Subtract 100 to account for the warm SLOAD in startMeasuringGas. + uint256 gasDelta = checkpointGasLeft - checkpointGasLeft2 - 100; + + emit log_named_uint(string(abi.encodePacked(checkpointLabel, " Gas")), gasDelta); + } + + function fail(string memory err) internal virtual { + emit log_named_string("Error", err); + fail(); + } + + function assertFalse(bool data) internal virtual { + assertTrue(!data); + } + + function assertUint128Eq(uint128 a, uint128 b) internal virtual { + assertEq(uint256(a), uint256(b)); + } + + function assertUint64Eq(uint64 a, uint64 b) internal virtual { + assertEq(uint256(a), uint256(b)); + } + + function assertUint96Eq(uint96 a, uint96 b) internal virtual { + assertEq(uint256(a), uint256(b)); + } + + function assertUint32Eq(uint32 a, uint32 b) internal virtual { + assertEq(uint256(a), uint256(b)); + } + + function assertBoolEq(bool a, bool b) internal virtual { + b ? assertTrue(a) : assertFalse(a); + } + + function assertApproxEq( + uint256 a, + uint256 b, + uint256 maxDelta + ) internal virtual { + uint256 delta = a > b ? a - b : b - a; + + if (delta > maxDelta) { + emit log("Error: a ~= b not satisfied [uint]"); + emit log_named_uint(" Expected", b); + emit log_named_uint(" Actual", a); + emit log_named_uint(" Max Delta", maxDelta); + emit log_named_uint(" Delta", delta); + fail(); + } + } + + function assertRelApproxEq( + uint256 a, + uint256 b, + uint256 maxPercentDelta // An 18 decimal fixed point number, where 1e18 == 100% + ) internal virtual { + if (b == 0) return assertEq(a, b); // If the expected is 0, actual must be too. + + uint256 percentDelta = ((a > b ? a - b : b - a) * 1e18) / b; + + if (percentDelta > maxPercentDelta) { + emit log("Error: a ~= b not satisfied [uint]"); + emit log_named_uint(" Expected", b); + emit log_named_uint(" Actual", a); + emit log_named_decimal_uint(" Max % Delta", maxPercentDelta, 18); + emit log_named_decimal_uint(" % Delta", percentDelta, 18); + fail(); + } + } + + function assertBytesEq(bytes memory a, bytes memory b) internal virtual { + if (keccak256(a) != keccak256(b)) { + emit log("Error: a == b not satisfied [bytes]"); + emit log_named_bytes(" Expected", b); + emit log_named_bytes(" Actual", a); + fail(); + } + } + + function assertUintArrayEq(uint256[] memory a, uint256[] memory b) internal virtual { + require(a.length == b.length, "LENGTH_MISMATCH"); + + for (uint256 i = 0; i < a.length; i++) { + assertEq(a[i], b[i]); + } + } + + function bound( + uint256 x, + uint256 min, + uint256 max + ) internal virtual returns (uint256 result) { + require(max >= min, "MAX_LESS_THAN_MIN"); + + uint256 size = max - min; + + if (size == 0) result = min; + else if (size == type(uint256).max) result = x; + else { + ++size; // Make max inclusive. + uint256 mod = x % size; + result = min + mod; + } + + emit log_named_uint("Bound Result", result); + } + + function min3( + uint256 a, + uint256 b, + uint256 c + ) internal pure returns (uint256) { + return a > b ? (b > c ? c : b) : (a > c ? c : a); + } + + function min2(uint256 a, uint256 b) internal pure returns (uint256) { + return a > b ? b : a; + } +} diff --git a/CVLByExample/UniswapHooks/lib/uniswap-hooks/lib/v4-core/lib/solmate/src/test/utils/Hevm.sol b/CVLByExample/UniswapHooks/lib/uniswap-hooks/lib/v4-core/lib/solmate/src/test/utils/Hevm.sol new file mode 100644 index 00000000..8ca0eff9 --- /dev/null +++ b/CVLByExample/UniswapHooks/lib/uniswap-hooks/lib/v4-core/lib/solmate/src/test/utils/Hevm.sol @@ -0,0 +1,107 @@ +// SPDX-License-Identifier: AGPL-3.0-only +pragma solidity >=0.8.0; + +interface Hevm { + /// @notice Sets the block timestamp. + function warp(uint256) external; + + /// @notice Sets the block height. + function roll(uint256) external; + + /// @notice Sets the block base fee. + function fee(uint256) external; + + /// @notice Loads a storage slot from an address. + function load(address, bytes32) external returns (bytes32); + + /// @notice Stores a value to an address' storage slot. + function store( + address, + bytes32, + bytes32 + ) external; + + /// @notice Signs a digest with a private key, returns v r s. + function sign(uint256, bytes32) + external + returns ( + uint8, + bytes32, + bytes32 + ); + + /// @notice Gets address for a given private key. + function addr(uint256) external returns (address); + + /// @notice Performs a foreign function call via a terminal call. + function ffi(string[] calldata) external returns (bytes memory); + + /// @notice Sets the next call's msg.sender to be the input address. + function prank(address) external; + + /// @notice Sets all subsequent calls' msg.sender to be the input address until stopPrank is called. + function startPrank(address) external; + + /// @notice Sets the next call's msg.sender to be the input address and the tx.origin to be the second input. + function prank(address, address) external; + + /// @notice Sets all subsequent calls' msg.sender to be the input address and + /// sets tx.origin to be the second address inputted until stopPrank is called. + function startPrank(address, address) external; + + /// @notice Resets msg.sender to its original value before a prank. + function stopPrank() external; + + /// @notice Sets an address' balance. + function deal(address, uint256) external; + + /// @notice Sets an address' code. + function etch(address, bytes calldata) external; + + /// @notice Expects an error from the next call. + function expectRevert(bytes calldata) external; + + /// @notice Expects a revert from the next call. + function expectRevert(bytes4) external; + + /// @notice Record all storage reads and writes. + function record() external; + + /// @notice Gets all accessed reads and write slots from a recording session, for a given address. + function accesses(address) external returns (bytes32[] memory reads, bytes32[] memory writes); + + /// @notice Prepare an expected log with (bool checkTopic1, bool checkTopic2, bool checkTopic3, bool checkData). + /// @notice Call this function, then emit an event, then call a function. Internally after the call, we check + /// if logs were emitted in the expected order with the expected topics and data as specified by the booleans. + function expectEmit( + bool, + bool, + bool, + bool + ) external; + + /// @notice Mocks the behavior of a contract call, setting the input and output for a function. + /// @notice Calldata can either be strict or a partial match, e.g. if only passed + /// a selector to the expected calldata, then the entire function will be mocked. + function mockCall( + address, + bytes calldata, + bytes calldata + ) external; + + /// @notice Clears all mocked calls. + function clearMockedCalls() external; + + /// @notice Expect a call to an address with the specified calldata. + /// @notice Calldata can either be strict or a partial match. + function expectCall(address, bytes calldata) external; + + /// @notice Fetches the contract bytecode from its artifact file. + function getCode(string calldata) external returns (bytes memory); + + /// @notice Label an address in test traces. + function label(address addr, string calldata label) external; + + /// @notice When fuzzing, generate new inputs if the input conditional is not met. + function assume(bool) external; +} diff --git a/CVLByExample/UniswapHooks/lib/uniswap-hooks/lib/v4-core/lib/solmate/src/test/utils/mocks/MockAuthChild.sol b/CVLByExample/UniswapHooks/lib/uniswap-hooks/lib/v4-core/lib/solmate/src/test/utils/mocks/MockAuthChild.sol new file mode 100644 index 00000000..d2c32760 --- /dev/null +++ b/CVLByExample/UniswapHooks/lib/uniswap-hooks/lib/v4-core/lib/solmate/src/test/utils/mocks/MockAuthChild.sol @@ -0,0 +1,12 @@ +// SPDX-License-Identifier: AGPL-3.0-only +pragma solidity >=0.8.0; + +import {Auth, Authority} from "../../../auth/Auth.sol"; + +contract MockAuthChild is Auth(msg.sender, Authority(address(0))) { + bool public flag; + + function updateFlag() public virtual requiresAuth { + flag = true; + } +} diff --git a/CVLByExample/UniswapHooks/lib/uniswap-hooks/lib/v4-core/lib/solmate/src/test/utils/mocks/MockAuthority.sol b/CVLByExample/UniswapHooks/lib/uniswap-hooks/lib/v4-core/lib/solmate/src/test/utils/mocks/MockAuthority.sol new file mode 100644 index 00000000..acb36892 --- /dev/null +++ b/CVLByExample/UniswapHooks/lib/uniswap-hooks/lib/v4-core/lib/solmate/src/test/utils/mocks/MockAuthority.sol @@ -0,0 +1,20 @@ +// SPDX-License-Identifier: AGPL-3.0-only +pragma solidity >=0.8.0; + +import {Authority} from "../../../auth/Auth.sol"; + +contract MockAuthority is Authority { + bool immutable allowCalls; + + constructor(bool _allowCalls) { + allowCalls = _allowCalls; + } + + function canCall( + address, + address, + bytes4 + ) public view override returns (bool) { + return allowCalls; + } +} diff --git a/CVLByExample/UniswapHooks/lib/uniswap-hooks/lib/v4-core/lib/solmate/src/test/utils/mocks/MockERC1155.sol b/CVLByExample/UniswapHooks/lib/uniswap-hooks/lib/v4-core/lib/solmate/src/test/utils/mocks/MockERC1155.sol new file mode 100644 index 00000000..ede086db --- /dev/null +++ b/CVLByExample/UniswapHooks/lib/uniswap-hooks/lib/v4-core/lib/solmate/src/test/utils/mocks/MockERC1155.sol @@ -0,0 +1,42 @@ +// SPDX-License-Identifier: AGPL-3.0-only +pragma solidity >=0.8.0; + +import {ERC1155} from "../../../tokens/ERC1155.sol"; + +contract MockERC1155 is ERC1155 { + function uri(uint256) public pure virtual override returns (string memory) {} + + function mint( + address to, + uint256 id, + uint256 amount, + bytes memory data + ) public virtual { + _mint(to, id, amount, data); + } + + function batchMint( + address to, + uint256[] memory ids, + uint256[] memory amounts, + bytes memory data + ) public virtual { + _batchMint(to, ids, amounts, data); + } + + function burn( + address from, + uint256 id, + uint256 amount + ) public virtual { + _burn(from, id, amount); + } + + function batchBurn( + address from, + uint256[] memory ids, + uint256[] memory amounts + ) public virtual { + _batchBurn(from, ids, amounts); + } +} diff --git a/CVLByExample/UniswapHooks/lib/uniswap-hooks/lib/v4-core/lib/solmate/src/test/utils/mocks/MockERC20.sol b/CVLByExample/UniswapHooks/lib/uniswap-hooks/lib/v4-core/lib/solmate/src/test/utils/mocks/MockERC20.sol new file mode 100644 index 00000000..fbbaef5a --- /dev/null +++ b/CVLByExample/UniswapHooks/lib/uniswap-hooks/lib/v4-core/lib/solmate/src/test/utils/mocks/MockERC20.sol @@ -0,0 +1,20 @@ +// SPDX-License-Identifier: AGPL-3.0-only +pragma solidity >=0.8.0; + +import {ERC20} from "../../../tokens/ERC20.sol"; + +contract MockERC20 is ERC20 { + constructor( + string memory _name, + string memory _symbol, + uint8 _decimals + ) ERC20(_name, _symbol, _decimals) {} + + function mint(address to, uint256 value) public virtual { + _mint(to, value); + } + + function burn(address from, uint256 value) public virtual { + _burn(from, value); + } +} diff --git a/CVLByExample/UniswapHooks/lib/uniswap-hooks/lib/v4-core/lib/solmate/src/test/utils/mocks/MockERC4626.sol b/CVLByExample/UniswapHooks/lib/uniswap-hooks/lib/v4-core/lib/solmate/src/test/utils/mocks/MockERC4626.sol new file mode 100644 index 00000000..edc7d5f3 --- /dev/null +++ b/CVLByExample/UniswapHooks/lib/uniswap-hooks/lib/v4-core/lib/solmate/src/test/utils/mocks/MockERC4626.sol @@ -0,0 +1,28 @@ +// SPDX-License-Identifier: AGPL-3.0-only +pragma solidity >=0.8.0; + +import {ERC20} from "../../../tokens/ERC20.sol"; +import {ERC4626} from "../../../mixins/ERC4626.sol"; + +contract MockERC4626 is ERC4626 { + uint256 public beforeWithdrawHookCalledCounter = 0; + uint256 public afterDepositHookCalledCounter = 0; + + constructor( + ERC20 _underlying, + string memory _name, + string memory _symbol + ) ERC4626(_underlying, _name, _symbol) {} + + function totalAssets() public view override returns (uint256) { + return asset.balanceOf(address(this)); + } + + function beforeWithdraw(uint256, uint256) internal override { + beforeWithdrawHookCalledCounter++; + } + + function afterDeposit(uint256, uint256) internal override { + afterDepositHookCalledCounter++; + } +} diff --git a/CVLByExample/UniswapHooks/lib/uniswap-hooks/lib/v4-core/lib/solmate/src/test/utils/mocks/MockERC6909.sol b/CVLByExample/UniswapHooks/lib/uniswap-hooks/lib/v4-core/lib/solmate/src/test/utils/mocks/MockERC6909.sol new file mode 100644 index 00000000..5f7eabed --- /dev/null +++ b/CVLByExample/UniswapHooks/lib/uniswap-hooks/lib/v4-core/lib/solmate/src/test/utils/mocks/MockERC6909.sol @@ -0,0 +1,22 @@ +// SPDX-License-Identifier: MIT +pragma solidity >=0.8.0; + +import {ERC6909} from "../../../tokens/ERC6909.sol"; + +contract MockERC6909 is ERC6909 { + function mint( + address receiver, + uint256 id, + uint256 amount + ) public virtual { + _mint(receiver, id, amount); + } + + function burn( + address sender, + uint256 id, + uint256 amount + ) public virtual { + _burn(sender, id, amount); + } +} diff --git a/CVLByExample/UniswapHooks/lib/uniswap-hooks/lib/v4-core/lib/solmate/src/test/utils/mocks/MockERC721.sol b/CVLByExample/UniswapHooks/lib/uniswap-hooks/lib/v4-core/lib/solmate/src/test/utils/mocks/MockERC721.sol new file mode 100644 index 00000000..51227c0e --- /dev/null +++ b/CVLByExample/UniswapHooks/lib/uniswap-hooks/lib/v4-core/lib/solmate/src/test/utils/mocks/MockERC721.sol @@ -0,0 +1,30 @@ +// SPDX-License-Identifier: AGPL-3.0-only +pragma solidity >=0.8.0; + +import {ERC721} from "../../../tokens/ERC721.sol"; + +contract MockERC721 is ERC721 { + constructor(string memory _name, string memory _symbol) ERC721(_name, _symbol) {} + + function tokenURI(uint256) public pure virtual override returns (string memory) {} + + function mint(address to, uint256 tokenId) public virtual { + _mint(to, tokenId); + } + + function burn(uint256 tokenId) public virtual { + _burn(tokenId); + } + + function safeMint(address to, uint256 tokenId) public virtual { + _safeMint(to, tokenId); + } + + function safeMint( + address to, + uint256 tokenId, + bytes memory data + ) public virtual { + _safeMint(to, tokenId, data); + } +} diff --git a/CVLByExample/UniswapHooks/lib/uniswap-hooks/lib/v4-core/lib/solmate/src/test/utils/mocks/MockOwned.sol b/CVLByExample/UniswapHooks/lib/uniswap-hooks/lib/v4-core/lib/solmate/src/test/utils/mocks/MockOwned.sol new file mode 100644 index 00000000..52ef918e --- /dev/null +++ b/CVLByExample/UniswapHooks/lib/uniswap-hooks/lib/v4-core/lib/solmate/src/test/utils/mocks/MockOwned.sol @@ -0,0 +1,12 @@ +// SPDX-License-Identifier: AGPL-3.0-only +pragma solidity >=0.8.0; + +import {Owned} from "../../../auth/Owned.sol"; + +contract MockOwned is Owned(msg.sender) { + bool public flag; + + function updateFlag() public virtual onlyOwner { + flag = true; + } +} diff --git a/CVLByExample/UniswapHooks/lib/uniswap-hooks/lib/v4-core/lib/solmate/src/test/utils/weird-tokens/MissingReturnToken.sol b/CVLByExample/UniswapHooks/lib/uniswap-hooks/lib/v4-core/lib/solmate/src/test/utils/weird-tokens/MissingReturnToken.sol new file mode 100644 index 00000000..23f46336 --- /dev/null +++ b/CVLByExample/UniswapHooks/lib/uniswap-hooks/lib/v4-core/lib/solmate/src/test/utils/weird-tokens/MissingReturnToken.sol @@ -0,0 +1,83 @@ +// SPDX-License-Identifier: AGPL-3.0-only +pragma solidity >=0.8.0; + +contract MissingReturnToken { + /*/////////////////////////////////////////////////////////////// + EVENTS + //////////////////////////////////////////////////////////////*/ + + event Transfer(address indexed from, address indexed to, uint256 amount); + + event Approval(address indexed owner, address indexed spender, uint256 amount); + + /*/////////////////////////////////////////////////////////////// + METADATA STORAGE + //////////////////////////////////////////////////////////////*/ + + string public constant name = "MissingReturnToken"; + + string public constant symbol = "MRT"; + + uint8 public constant decimals = 18; + + /*/////////////////////////////////////////////////////////////// + ERC20 STORAGE + //////////////////////////////////////////////////////////////*/ + + uint256 public totalSupply; + + mapping(address => uint256) public balanceOf; + + mapping(address => mapping(address => uint256)) public allowance; + + /*/////////////////////////////////////////////////////////////// + CONSTRUCTOR + //////////////////////////////////////////////////////////////*/ + + constructor() { + totalSupply = type(uint256).max; + balanceOf[msg.sender] = type(uint256).max; + } + + /*/////////////////////////////////////////////////////////////// + ERC20 LOGIC + //////////////////////////////////////////////////////////////*/ + + function approve(address spender, uint256 amount) public virtual { + allowance[msg.sender][spender] = amount; + + emit Approval(msg.sender, spender, amount); + } + + function transfer(address to, uint256 amount) public virtual { + balanceOf[msg.sender] -= amount; + + // Cannot overflow because the sum of all user + // balances can't exceed the max uint256 value. + unchecked { + balanceOf[to] += amount; + } + + emit Transfer(msg.sender, to, amount); + } + + function transferFrom( + address from, + address to, + uint256 amount + ) public virtual { + uint256 allowed = allowance[from][msg.sender]; // Saves gas for limited approvals. + + if (allowed != type(uint256).max) allowance[from][msg.sender] = allowed - amount; + + balanceOf[from] -= amount; + + // Cannot overflow because the sum of all user + // balances can't exceed the max uint256 value. + unchecked { + balanceOf[to] += amount; + } + + emit Transfer(from, to, amount); + } +} diff --git a/CVLByExample/UniswapHooks/lib/uniswap-hooks/lib/v4-core/lib/solmate/src/test/utils/weird-tokens/ReturnsFalseToken.sol b/CVLByExample/UniswapHooks/lib/uniswap-hooks/lib/v4-core/lib/solmate/src/test/utils/weird-tokens/ReturnsFalseToken.sol new file mode 100644 index 00000000..8139efe7 --- /dev/null +++ b/CVLByExample/UniswapHooks/lib/uniswap-hooks/lib/v4-core/lib/solmate/src/test/utils/weird-tokens/ReturnsFalseToken.sol @@ -0,0 +1,61 @@ +// SPDX-License-Identifier: AGPL-3.0-only +pragma solidity >=0.8.0; + +contract ReturnsFalseToken { + /*/////////////////////////////////////////////////////////////// + EVENTS + //////////////////////////////////////////////////////////////*/ + + event Transfer(address indexed from, address indexed to, uint256 amount); + + event Approval(address indexed owner, address indexed spender, uint256 amount); + + /*/////////////////////////////////////////////////////////////// + METADATA STORAGE + //////////////////////////////////////////////////////////////*/ + + string public constant name = "ReturnsFalseToken"; + + string public constant symbol = "RFT"; + + uint8 public constant decimals = 18; + + /*/////////////////////////////////////////////////////////////// + ERC20 STORAGE + //////////////////////////////////////////////////////////////*/ + + uint256 public totalSupply; + + mapping(address => uint256) public balanceOf; + + mapping(address => mapping(address => uint256)) public allowance; + + /*/////////////////////////////////////////////////////////////// + CONSTRUCTOR + //////////////////////////////////////////////////////////////*/ + + constructor() { + totalSupply = type(uint256).max; + balanceOf[msg.sender] = type(uint256).max; + } + + /*/////////////////////////////////////////////////////////////// + ERC20 LOGIC + //////////////////////////////////////////////////////////////*/ + + function approve(address, uint256) public virtual returns (bool) { + return false; + } + + function transfer(address, uint256) public virtual returns (bool) { + return false; + } + + function transferFrom( + address, + address, + uint256 + ) public virtual returns (bool) { + return false; + } +} diff --git a/CVLByExample/UniswapHooks/lib/uniswap-hooks/lib/v4-core/lib/solmate/src/test/utils/weird-tokens/ReturnsGarbageToken.sol b/CVLByExample/UniswapHooks/lib/uniswap-hooks/lib/v4-core/lib/solmate/src/test/utils/weird-tokens/ReturnsGarbageToken.sol new file mode 100644 index 00000000..77c95751 --- /dev/null +++ b/CVLByExample/UniswapHooks/lib/uniswap-hooks/lib/v4-core/lib/solmate/src/test/utils/weird-tokens/ReturnsGarbageToken.sol @@ -0,0 +1,115 @@ +// SPDX-License-Identifier: AGPL-3.0-only +pragma solidity >=0.8.0; + +contract ReturnsGarbageToken { + /*/////////////////////////////////////////////////////////////// + EVENTS + //////////////////////////////////////////////////////////////*/ + + event Transfer(address indexed from, address indexed to, uint256 amount); + + event Approval(address indexed owner, address indexed spender, uint256 amount); + + /*/////////////////////////////////////////////////////////////// + METADATA STORAGE + //////////////////////////////////////////////////////////////*/ + + string public constant name = "ReturnsGarbageToken"; + + string public constant symbol = "RGT"; + + uint8 public constant decimals = 18; + + /*/////////////////////////////////////////////////////////////// + ERC20 STORAGE + //////////////////////////////////////////////////////////////*/ + + uint256 public totalSupply; + + mapping(address => uint256) public balanceOf; + + mapping(address => mapping(address => uint256)) public allowance; + + /*/////////////////////////////////////////////////////////////// + MOCK STORAGE + //////////////////////////////////////////////////////////////*/ + + bytes garbage; + + /*/////////////////////////////////////////////////////////////// + CONSTRUCTOR + //////////////////////////////////////////////////////////////*/ + + constructor() { + totalSupply = type(uint256).max; + balanceOf[msg.sender] = type(uint256).max; + } + + /*/////////////////////////////////////////////////////////////// + ERC20 LOGIC + //////////////////////////////////////////////////////////////*/ + + function approve(address spender, uint256 amount) public virtual { + allowance[msg.sender][spender] = amount; + + emit Approval(msg.sender, spender, amount); + + bytes memory _garbage = garbage; + + assembly { + return(add(_garbage, 32), mload(_garbage)) + } + } + + function transfer(address to, uint256 amount) public virtual { + balanceOf[msg.sender] -= amount; + + // Cannot overflow because the sum of all user + // balances can't exceed the max uint256 value. + unchecked { + balanceOf[to] += amount; + } + + emit Transfer(msg.sender, to, amount); + + bytes memory _garbage = garbage; + + assembly { + return(add(_garbage, 32), mload(_garbage)) + } + } + + function transferFrom( + address from, + address to, + uint256 amount + ) public virtual { + uint256 allowed = allowance[from][msg.sender]; // Saves gas for limited approvals. + + if (allowed != type(uint256).max) allowance[from][msg.sender] = allowed - amount; + + balanceOf[from] -= amount; + + // Cannot overflow because the sum of all user + // balances can't exceed the max uint256 value. + unchecked { + balanceOf[to] += amount; + } + + emit Transfer(from, to, amount); + + bytes memory _garbage = garbage; + + assembly { + return(add(_garbage, 32), mload(_garbage)) + } + } + + /*/////////////////////////////////////////////////////////////// + MOCK LOGIC + //////////////////////////////////////////////////////////////*/ + + function setGarbage(bytes memory _garbage) public virtual { + garbage = _garbage; + } +} diff --git a/CVLByExample/UniswapHooks/lib/uniswap-hooks/lib/v4-core/lib/solmate/src/test/utils/weird-tokens/ReturnsTooLittleToken.sol b/CVLByExample/UniswapHooks/lib/uniswap-hooks/lib/v4-core/lib/solmate/src/test/utils/weird-tokens/ReturnsTooLittleToken.sol new file mode 100644 index 00000000..69947c30 --- /dev/null +++ b/CVLByExample/UniswapHooks/lib/uniswap-hooks/lib/v4-core/lib/solmate/src/test/utils/weird-tokens/ReturnsTooLittleToken.sol @@ -0,0 +1,70 @@ +// SPDX-License-Identifier: AGPL-3.0-only +pragma solidity >=0.8.0; + +contract ReturnsTooLittleToken { + /*/////////////////////////////////////////////////////////////// + EVENTS + //////////////////////////////////////////////////////////////*/ + + event Transfer(address indexed from, address indexed to, uint256 amount); + + event Approval(address indexed owner, address indexed spender, uint256 amount); + + /*/////////////////////////////////////////////////////////////// + METADATA STORAGE + //////////////////////////////////////////////////////////////*/ + + string public constant name = "ReturnsTooLittleToken"; + + string public constant symbol = "RTLT"; + + uint8 public constant decimals = 18; + + /*/////////////////////////////////////////////////////////////// + ERC20 STORAGE + //////////////////////////////////////////////////////////////*/ + + uint256 public totalSupply; + + mapping(address => uint256) public balanceOf; + + mapping(address => mapping(address => uint256)) public allowance; + + /*/////////////////////////////////////////////////////////////// + CONSTRUCTOR + //////////////////////////////////////////////////////////////*/ + + constructor() { + totalSupply = type(uint256).max; + balanceOf[msg.sender] = type(uint256).max; + } + + /*/////////////////////////////////////////////////////////////// + ERC20 LOGIC + //////////////////////////////////////////////////////////////*/ + + function approve(address, uint256) public virtual { + assembly { + mstore(0, 0x0100000000000000000000000000000000000000000000000000000000000000) + return(0, 8) + } + } + + function transfer(address, uint256) public virtual { + assembly { + mstore(0, 0x0100000000000000000000000000000000000000000000000000000000000000) + return(0, 8) + } + } + + function transferFrom( + address, + address, + uint256 + ) public virtual { + assembly { + mstore(0, 0x0100000000000000000000000000000000000000000000000000000000000000) + return(0, 8) + } + } +} diff --git a/CVLByExample/UniswapHooks/lib/uniswap-hooks/lib/v4-core/lib/solmate/src/test/utils/weird-tokens/ReturnsTooMuchToken.sol b/CVLByExample/UniswapHooks/lib/uniswap-hooks/lib/v4-core/lib/solmate/src/test/utils/weird-tokens/ReturnsTooMuchToken.sol new file mode 100644 index 00000000..8774cbbd --- /dev/null +++ b/CVLByExample/UniswapHooks/lib/uniswap-hooks/lib/v4-core/lib/solmate/src/test/utils/weird-tokens/ReturnsTooMuchToken.sol @@ -0,0 +1,98 @@ +// SPDX-License-Identifier: AGPL-3.0-only +pragma solidity >=0.8.0; + +contract ReturnsTooMuchToken { + /*/////////////////////////////////////////////////////////////// + EVENTS + //////////////////////////////////////////////////////////////*/ + + event Transfer(address indexed from, address indexed to, uint256 amount); + + event Approval(address indexed owner, address indexed spender, uint256 amount); + + /*/////////////////////////////////////////////////////////////// + METADATA STORAGE + //////////////////////////////////////////////////////////////*/ + + string public constant name = "ReturnsTooMuchToken"; + + string public constant symbol = "RTMT"; + + uint8 public constant decimals = 18; + + /*/////////////////////////////////////////////////////////////// + ERC20 STORAGE + //////////////////////////////////////////////////////////////*/ + + uint256 public totalSupply; + + mapping(address => uint256) public balanceOf; + + mapping(address => mapping(address => uint256)) public allowance; + + /*/////////////////////////////////////////////////////////////// + CONSTRUCTOR + //////////////////////////////////////////////////////////////*/ + + constructor() { + totalSupply = type(uint256).max; + balanceOf[msg.sender] = type(uint256).max; + } + + /*/////////////////////////////////////////////////////////////// + ERC20 LOGIC + //////////////////////////////////////////////////////////////*/ + + function approve(address spender, uint256 amount) public virtual { + allowance[msg.sender][spender] = amount; + + emit Approval(msg.sender, spender, amount); + + assembly { + mstore(0, 1) + return(0, 4096) + } + } + + function transfer(address to, uint256 amount) public virtual { + balanceOf[msg.sender] -= amount; + + // Cannot overflow because the sum of all user + // balances can't exceed the max uint256 value. + unchecked { + balanceOf[to] += amount; + } + + emit Transfer(msg.sender, to, amount); + + assembly { + mstore(0, 1) + return(0, 4096) + } + } + + function transferFrom( + address from, + address to, + uint256 amount + ) public virtual { + uint256 allowed = allowance[from][msg.sender]; // Saves gas for limited approvals. + + if (allowed != type(uint256).max) allowance[from][msg.sender] = allowed - amount; + + balanceOf[from] -= amount; + + // Cannot overflow because the sum of all user + // balances can't exceed the max uint256 value. + unchecked { + balanceOf[to] += amount; + } + + emit Transfer(from, to, amount); + + assembly { + mstore(0, 1) + return(0, 4096) + } + } +} diff --git a/CVLByExample/UniswapHooks/lib/uniswap-hooks/lib/v4-core/lib/solmate/src/test/utils/weird-tokens/ReturnsTwoToken.sol b/CVLByExample/UniswapHooks/lib/uniswap-hooks/lib/v4-core/lib/solmate/src/test/utils/weird-tokens/ReturnsTwoToken.sol new file mode 100644 index 00000000..ac980f88 --- /dev/null +++ b/CVLByExample/UniswapHooks/lib/uniswap-hooks/lib/v4-core/lib/solmate/src/test/utils/weird-tokens/ReturnsTwoToken.sol @@ -0,0 +1,61 @@ +// SPDX-License-Identifier: AGPL-3.0-only +pragma solidity >=0.8.0; + +contract ReturnsTwoToken { + /*/////////////////////////////////////////////////////////////// + EVENTS + //////////////////////////////////////////////////////////////*/ + + event Transfer(address indexed from, address indexed to, uint256 amount); + + event Approval(address indexed owner, address indexed spender, uint256 amount); + + /*/////////////////////////////////////////////////////////////// + METADATA STORAGE + //////////////////////////////////////////////////////////////*/ + + string public constant name = "ReturnsFalseToken"; + + string public constant symbol = "RTT"; + + uint8 public constant decimals = 18; + + /*/////////////////////////////////////////////////////////////// + ERC20 STORAGE + //////////////////////////////////////////////////////////////*/ + + uint256 public totalSupply; + + mapping(address => uint256) public balanceOf; + + mapping(address => mapping(address => uint256)) public allowance; + + /*/////////////////////////////////////////////////////////////// + CONSTRUCTOR + //////////////////////////////////////////////////////////////*/ + + constructor() { + totalSupply = type(uint256).max; + balanceOf[msg.sender] = type(uint256).max; + } + + /*/////////////////////////////////////////////////////////////// + ERC20 LOGIC + //////////////////////////////////////////////////////////////*/ + + function approve(address, uint256) public virtual returns (uint256) { + return 2; + } + + function transfer(address, uint256) public virtual returns (uint256) { + return 2; + } + + function transferFrom( + address, + address, + uint256 + ) public virtual returns (uint256) { + return 2; + } +} diff --git a/CVLByExample/UniswapHooks/lib/uniswap-hooks/lib/v4-core/lib/solmate/src/test/utils/weird-tokens/RevertingToken.sol b/CVLByExample/UniswapHooks/lib/uniswap-hooks/lib/v4-core/lib/solmate/src/test/utils/weird-tokens/RevertingToken.sol new file mode 100644 index 00000000..48ac1fa1 --- /dev/null +++ b/CVLByExample/UniswapHooks/lib/uniswap-hooks/lib/v4-core/lib/solmate/src/test/utils/weird-tokens/RevertingToken.sol @@ -0,0 +1,61 @@ +// SPDX-License-Identifier: AGPL-3.0-only +pragma solidity >=0.8.0; + +contract RevertingToken { + /*/////////////////////////////////////////////////////////////// + EVENTS + //////////////////////////////////////////////////////////////*/ + + event Transfer(address indexed from, address indexed to, uint256 amount); + + event Approval(address indexed owner, address indexed spender, uint256 amount); + + /*/////////////////////////////////////////////////////////////// + METADATA STORAGE + //////////////////////////////////////////////////////////////*/ + + string public constant name = "RevertingToken"; + + string public constant symbol = "RT"; + + uint8 public constant decimals = 18; + + /*/////////////////////////////////////////////////////////////// + ERC20 STORAGE + //////////////////////////////////////////////////////////////*/ + + uint256 public totalSupply; + + mapping(address => uint256) public balanceOf; + + mapping(address => mapping(address => uint256)) public allowance; + + /*/////////////////////////////////////////////////////////////// + CONSTRUCTOR + //////////////////////////////////////////////////////////////*/ + + constructor() { + totalSupply = type(uint256).max; + balanceOf[msg.sender] = type(uint256).max; + } + + /*/////////////////////////////////////////////////////////////// + ERC20 LOGIC + //////////////////////////////////////////////////////////////*/ + + function approve(address, uint256) public virtual { + revert(); + } + + function transfer(address, uint256) public virtual { + revert(); + } + + function transferFrom( + address, + address, + uint256 + ) public virtual { + revert(); + } +} diff --git a/CVLByExample/UniswapHooks/lib/uniswap-hooks/lib/v4-core/lib/solmate/src/tokens/ERC1155.sol b/CVLByExample/UniswapHooks/lib/uniswap-hooks/lib/v4-core/lib/solmate/src/tokens/ERC1155.sol new file mode 100644 index 00000000..cff0f02d --- /dev/null +++ b/CVLByExample/UniswapHooks/lib/uniswap-hooks/lib/v4-core/lib/solmate/src/tokens/ERC1155.sol @@ -0,0 +1,257 @@ +// SPDX-License-Identifier: AGPL-3.0-only +pragma solidity >=0.8.0; + +/// @notice Minimalist and gas efficient standard ERC1155 implementation. +/// @author Solmate (https://github.com/transmissions11/solmate/blob/main/src/tokens/ERC1155.sol) +abstract contract ERC1155 { + /*////////////////////////////////////////////////////////////// + EVENTS + //////////////////////////////////////////////////////////////*/ + + event TransferSingle( + address indexed operator, + address indexed from, + address indexed to, + uint256 id, + uint256 amount + ); + + event TransferBatch( + address indexed operator, + address indexed from, + address indexed to, + uint256[] ids, + uint256[] amounts + ); + + event ApprovalForAll(address indexed owner, address indexed operator, bool approved); + + event URI(string value, uint256 indexed id); + + /*////////////////////////////////////////////////////////////// + ERC1155 STORAGE + //////////////////////////////////////////////////////////////*/ + + mapping(address => mapping(uint256 => uint256)) public balanceOf; + + mapping(address => mapping(address => bool)) public isApprovedForAll; + + /*////////////////////////////////////////////////////////////// + METADATA LOGIC + //////////////////////////////////////////////////////////////*/ + + function uri(uint256 id) public view virtual returns (string memory); + + /*////////////////////////////////////////////////////////////// + ERC1155 LOGIC + //////////////////////////////////////////////////////////////*/ + + function setApprovalForAll(address operator, bool approved) public virtual { + isApprovedForAll[msg.sender][operator] = approved; + + emit ApprovalForAll(msg.sender, operator, approved); + } + + function safeTransferFrom( + address from, + address to, + uint256 id, + uint256 amount, + bytes calldata data + ) public virtual { + require(msg.sender == from || isApprovedForAll[from][msg.sender], "NOT_AUTHORIZED"); + + balanceOf[from][id] -= amount; + balanceOf[to][id] += amount; + + emit TransferSingle(msg.sender, from, to, id, amount); + + require( + to.code.length == 0 + ? to != address(0) + : ERC1155TokenReceiver(to).onERC1155Received(msg.sender, from, id, amount, data) == + ERC1155TokenReceiver.onERC1155Received.selector, + "UNSAFE_RECIPIENT" + ); + } + + function safeBatchTransferFrom( + address from, + address to, + uint256[] calldata ids, + uint256[] calldata amounts, + bytes calldata data + ) public virtual { + require(ids.length == amounts.length, "LENGTH_MISMATCH"); + + require(msg.sender == from || isApprovedForAll[from][msg.sender], "NOT_AUTHORIZED"); + + // Storing these outside the loop saves ~15 gas per iteration. + uint256 id; + uint256 amount; + + for (uint256 i = 0; i < ids.length; ) { + id = ids[i]; + amount = amounts[i]; + + balanceOf[from][id] -= amount; + balanceOf[to][id] += amount; + + // An array can't have a total length + // larger than the max uint256 value. + unchecked { + ++i; + } + } + + emit TransferBatch(msg.sender, from, to, ids, amounts); + + require( + to.code.length == 0 + ? to != address(0) + : ERC1155TokenReceiver(to).onERC1155BatchReceived(msg.sender, from, ids, amounts, data) == + ERC1155TokenReceiver.onERC1155BatchReceived.selector, + "UNSAFE_RECIPIENT" + ); + } + + function balanceOfBatch(address[] calldata owners, uint256[] calldata ids) + public + view + virtual + returns (uint256[] memory balances) + { + require(owners.length == ids.length, "LENGTH_MISMATCH"); + + balances = new uint256[](owners.length); + + // Unchecked because the only math done is incrementing + // the array index counter which cannot possibly overflow. + unchecked { + for (uint256 i = 0; i < owners.length; ++i) { + balances[i] = balanceOf[owners[i]][ids[i]]; + } + } + } + + /*////////////////////////////////////////////////////////////// + ERC165 LOGIC + //////////////////////////////////////////////////////////////*/ + + function supportsInterface(bytes4 interfaceId) public view virtual returns (bool) { + return + interfaceId == 0x01ffc9a7 || // ERC165 Interface ID for ERC165 + interfaceId == 0xd9b67a26 || // ERC165 Interface ID for ERC1155 + interfaceId == 0x0e89341c; // ERC165 Interface ID for ERC1155MetadataURI + } + + /*////////////////////////////////////////////////////////////// + INTERNAL MINT/BURN LOGIC + //////////////////////////////////////////////////////////////*/ + + function _mint( + address to, + uint256 id, + uint256 amount, + bytes memory data + ) internal virtual { + balanceOf[to][id] += amount; + + emit TransferSingle(msg.sender, address(0), to, id, amount); + + require( + to.code.length == 0 + ? to != address(0) + : ERC1155TokenReceiver(to).onERC1155Received(msg.sender, address(0), id, amount, data) == + ERC1155TokenReceiver.onERC1155Received.selector, + "UNSAFE_RECIPIENT" + ); + } + + function _batchMint( + address to, + uint256[] memory ids, + uint256[] memory amounts, + bytes memory data + ) internal virtual { + uint256 idsLength = ids.length; // Saves MLOADs. + + require(idsLength == amounts.length, "LENGTH_MISMATCH"); + + for (uint256 i = 0; i < idsLength; ) { + balanceOf[to][ids[i]] += amounts[i]; + + // An array can't have a total length + // larger than the max uint256 value. + unchecked { + ++i; + } + } + + emit TransferBatch(msg.sender, address(0), to, ids, amounts); + + require( + to.code.length == 0 + ? to != address(0) + : ERC1155TokenReceiver(to).onERC1155BatchReceived(msg.sender, address(0), ids, amounts, data) == + ERC1155TokenReceiver.onERC1155BatchReceived.selector, + "UNSAFE_RECIPIENT" + ); + } + + function _batchBurn( + address from, + uint256[] memory ids, + uint256[] memory amounts + ) internal virtual { + uint256 idsLength = ids.length; // Saves MLOADs. + + require(idsLength == amounts.length, "LENGTH_MISMATCH"); + + for (uint256 i = 0; i < idsLength; ) { + balanceOf[from][ids[i]] -= amounts[i]; + + // An array can't have a total length + // larger than the max uint256 value. + unchecked { + ++i; + } + } + + emit TransferBatch(msg.sender, from, address(0), ids, amounts); + } + + function _burn( + address from, + uint256 id, + uint256 amount + ) internal virtual { + balanceOf[from][id] -= amount; + + emit TransferSingle(msg.sender, from, address(0), id, amount); + } +} + +/// @notice A generic interface for a contract which properly accepts ERC1155 tokens. +/// @author Solmate (https://github.com/transmissions11/solmate/blob/main/src/tokens/ERC1155.sol) +abstract contract ERC1155TokenReceiver { + function onERC1155Received( + address, + address, + uint256, + uint256, + bytes calldata + ) external virtual returns (bytes4) { + return ERC1155TokenReceiver.onERC1155Received.selector; + } + + function onERC1155BatchReceived( + address, + address, + uint256[] calldata, + uint256[] calldata, + bytes calldata + ) external virtual returns (bytes4) { + return ERC1155TokenReceiver.onERC1155BatchReceived.selector; + } +} diff --git a/CVLByExample/UniswapHooks/lib/uniswap-hooks/lib/v4-core/lib/solmate/src/tokens/ERC20.sol b/CVLByExample/UniswapHooks/lib/uniswap-hooks/lib/v4-core/lib/solmate/src/tokens/ERC20.sol new file mode 100644 index 00000000..96570446 --- /dev/null +++ b/CVLByExample/UniswapHooks/lib/uniswap-hooks/lib/v4-core/lib/solmate/src/tokens/ERC20.sol @@ -0,0 +1,206 @@ +// SPDX-License-Identifier: AGPL-3.0-only +pragma solidity >=0.8.0; + +/// @notice Modern and gas efficient ERC20 + EIP-2612 implementation. +/// @author Solmate (https://github.com/transmissions11/solmate/blob/main/src/tokens/ERC20.sol) +/// @author Modified from Uniswap (https://github.com/Uniswap/uniswap-v2-core/blob/master/contracts/UniswapV2ERC20.sol) +/// @dev Do not manually set balances without updating totalSupply, as the sum of all user balances must not exceed it. +abstract contract ERC20 { + /*////////////////////////////////////////////////////////////// + EVENTS + //////////////////////////////////////////////////////////////*/ + + event Transfer(address indexed from, address indexed to, uint256 amount); + + event Approval(address indexed owner, address indexed spender, uint256 amount); + + /*////////////////////////////////////////////////////////////// + METADATA STORAGE + //////////////////////////////////////////////////////////////*/ + + string public name; + + string public symbol; + + uint8 public immutable decimals; + + /*////////////////////////////////////////////////////////////// + ERC20 STORAGE + //////////////////////////////////////////////////////////////*/ + + uint256 public totalSupply; + + mapping(address => uint256) public balanceOf; + + mapping(address => mapping(address => uint256)) public allowance; + + /*////////////////////////////////////////////////////////////// + EIP-2612 STORAGE + //////////////////////////////////////////////////////////////*/ + + uint256 internal immutable INITIAL_CHAIN_ID; + + bytes32 internal immutable INITIAL_DOMAIN_SEPARATOR; + + mapping(address => uint256) public nonces; + + /*////////////////////////////////////////////////////////////// + CONSTRUCTOR + //////////////////////////////////////////////////////////////*/ + + constructor( + string memory _name, + string memory _symbol, + uint8 _decimals + ) { + name = _name; + symbol = _symbol; + decimals = _decimals; + + INITIAL_CHAIN_ID = block.chainid; + INITIAL_DOMAIN_SEPARATOR = computeDomainSeparator(); + } + + /*////////////////////////////////////////////////////////////// + ERC20 LOGIC + //////////////////////////////////////////////////////////////*/ + + function approve(address spender, uint256 amount) public virtual returns (bool) { + allowance[msg.sender][spender] = amount; + + emit Approval(msg.sender, spender, amount); + + return true; + } + + function transfer(address to, uint256 amount) public virtual returns (bool) { + balanceOf[msg.sender] -= amount; + + // Cannot overflow because the sum of all user + // balances can't exceed the max uint256 value. + unchecked { + balanceOf[to] += amount; + } + + emit Transfer(msg.sender, to, amount); + + return true; + } + + function transferFrom( + address from, + address to, + uint256 amount + ) public virtual returns (bool) { + uint256 allowed = allowance[from][msg.sender]; // Saves gas for limited approvals. + + if (allowed != type(uint256).max) allowance[from][msg.sender] = allowed - amount; + + balanceOf[from] -= amount; + + // Cannot overflow because the sum of all user + // balances can't exceed the max uint256 value. + unchecked { + balanceOf[to] += amount; + } + + emit Transfer(from, to, amount); + + return true; + } + + /*////////////////////////////////////////////////////////////// + EIP-2612 LOGIC + //////////////////////////////////////////////////////////////*/ + + function permit( + address owner, + address spender, + uint256 value, + uint256 deadline, + uint8 v, + bytes32 r, + bytes32 s + ) public virtual { + require(deadline >= block.timestamp, "PERMIT_DEADLINE_EXPIRED"); + + // Unchecked because the only math done is incrementing + // the owner's nonce which cannot realistically overflow. + unchecked { + address recoveredAddress = ecrecover( + keccak256( + abi.encodePacked( + "\x19\x01", + DOMAIN_SEPARATOR(), + keccak256( + abi.encode( + keccak256( + "Permit(address owner,address spender,uint256 value,uint256 nonce,uint256 deadline)" + ), + owner, + spender, + value, + nonces[owner]++, + deadline + ) + ) + ) + ), + v, + r, + s + ); + + require(recoveredAddress != address(0) && recoveredAddress == owner, "INVALID_SIGNER"); + + allowance[recoveredAddress][spender] = value; + } + + emit Approval(owner, spender, value); + } + + function DOMAIN_SEPARATOR() public view virtual returns (bytes32) { + return block.chainid == INITIAL_CHAIN_ID ? INITIAL_DOMAIN_SEPARATOR : computeDomainSeparator(); + } + + function computeDomainSeparator() internal view virtual returns (bytes32) { + return + keccak256( + abi.encode( + keccak256("EIP712Domain(string name,string version,uint256 chainId,address verifyingContract)"), + keccak256(bytes(name)), + keccak256("1"), + block.chainid, + address(this) + ) + ); + } + + /*////////////////////////////////////////////////////////////// + INTERNAL MINT/BURN LOGIC + //////////////////////////////////////////////////////////////*/ + + function _mint(address to, uint256 amount) internal virtual { + totalSupply += amount; + + // Cannot overflow because the sum of all user + // balances can't exceed the max uint256 value. + unchecked { + balanceOf[to] += amount; + } + + emit Transfer(address(0), to, amount); + } + + function _burn(address from, uint256 amount) internal virtual { + balanceOf[from] -= amount; + + // Cannot underflow because a user's balance + // will never be larger than the total supply. + unchecked { + totalSupply -= amount; + } + + emit Transfer(from, address(0), amount); + } +} diff --git a/CVLByExample/UniswapHooks/lib/uniswap-hooks/lib/v4-core/lib/solmate/src/tokens/ERC6909.sol b/CVLByExample/UniswapHooks/lib/uniswap-hooks/lib/v4-core/lib/solmate/src/tokens/ERC6909.sol new file mode 100644 index 00000000..15b4f307 --- /dev/null +++ b/CVLByExample/UniswapHooks/lib/uniswap-hooks/lib/v4-core/lib/solmate/src/tokens/ERC6909.sol @@ -0,0 +1,118 @@ +// SPDX-License-Identifier: MIT +pragma solidity >=0.8.0; + +/// @notice Minimalist and gas efficient standard ERC6909 implementation. +/// @author Solmate (https://github.com/transmissions11/solmate/blob/main/src/tokens/ERC6909.sol) +abstract contract ERC6909 { + /*////////////////////////////////////////////////////////////// + EVENTS + //////////////////////////////////////////////////////////////*/ + + event OperatorSet(address indexed owner, address indexed operator, bool approved); + + event Approval(address indexed owner, address indexed spender, uint256 indexed id, uint256 amount); + + event Transfer(address caller, address indexed from, address indexed to, uint256 indexed id, uint256 amount); + + /*////////////////////////////////////////////////////////////// + ERC6909 STORAGE + //////////////////////////////////////////////////////////////*/ + + mapping(address => mapping(address => bool)) public isOperator; + + mapping(address => mapping(uint256 => uint256)) public balanceOf; + + mapping(address => mapping(address => mapping(uint256 => uint256))) public allowance; + + /*////////////////////////////////////////////////////////////// + ERC6909 LOGIC + //////////////////////////////////////////////////////////////*/ + + function transfer( + address receiver, + uint256 id, + uint256 amount + ) public virtual returns (bool) { + balanceOf[msg.sender][id] -= amount; + + balanceOf[receiver][id] += amount; + + emit Transfer(msg.sender, msg.sender, receiver, id, amount); + + return true; + } + + function transferFrom( + address sender, + address receiver, + uint256 id, + uint256 amount + ) public virtual returns (bool) { + if (msg.sender != sender && !isOperator[sender][msg.sender]) { + uint256 allowed = allowance[sender][msg.sender][id]; + if (allowed != type(uint256).max) allowance[sender][msg.sender][id] = allowed - amount; + } + + balanceOf[sender][id] -= amount; + + balanceOf[receiver][id] += amount; + + emit Transfer(msg.sender, sender, receiver, id, amount); + + return true; + } + + function approve( + address spender, + uint256 id, + uint256 amount + ) public virtual returns (bool) { + allowance[msg.sender][spender][id] = amount; + + emit Approval(msg.sender, spender, id, amount); + + return true; + } + + function setOperator(address operator, bool approved) public virtual returns (bool) { + isOperator[msg.sender][operator] = approved; + + emit OperatorSet(msg.sender, operator, approved); + + return true; + } + + /*////////////////////////////////////////////////////////////// + ERC165 LOGIC + //////////////////////////////////////////////////////////////*/ + + function supportsInterface(bytes4 interfaceId) public view virtual returns (bool) { + return + interfaceId == 0x01ffc9a7 || // ERC165 Interface ID for ERC165 + interfaceId == 0x0f632fb3; // ERC165 Interface ID for ERC6909 + } + + /*////////////////////////////////////////////////////////////// + INTERNAL MINT/BURN LOGIC + //////////////////////////////////////////////////////////////*/ + + function _mint( + address receiver, + uint256 id, + uint256 amount + ) internal virtual { + balanceOf[receiver][id] += amount; + + emit Transfer(msg.sender, address(0), receiver, id, amount); + } + + function _burn( + address sender, + uint256 id, + uint256 amount + ) internal virtual { + balanceOf[sender][id] -= amount; + + emit Transfer(msg.sender, sender, address(0), id, amount); + } +} diff --git a/CVLByExample/UniswapHooks/lib/uniswap-hooks/lib/v4-core/lib/solmate/src/tokens/ERC721.sol b/CVLByExample/UniswapHooks/lib/uniswap-hooks/lib/v4-core/lib/solmate/src/tokens/ERC721.sol new file mode 100644 index 00000000..b47f2712 --- /dev/null +++ b/CVLByExample/UniswapHooks/lib/uniswap-hooks/lib/v4-core/lib/solmate/src/tokens/ERC721.sol @@ -0,0 +1,231 @@ +// SPDX-License-Identifier: AGPL-3.0-only +pragma solidity >=0.8.0; + +/// @notice Modern, minimalist, and gas efficient ERC-721 implementation. +/// @author Solmate (https://github.com/transmissions11/solmate/blob/main/src/tokens/ERC721.sol) +abstract contract ERC721 { + /*////////////////////////////////////////////////////////////// + EVENTS + //////////////////////////////////////////////////////////////*/ + + event Transfer(address indexed from, address indexed to, uint256 indexed id); + + event Approval(address indexed owner, address indexed spender, uint256 indexed id); + + event ApprovalForAll(address indexed owner, address indexed operator, bool approved); + + /*////////////////////////////////////////////////////////////// + METADATA STORAGE/LOGIC + //////////////////////////////////////////////////////////////*/ + + string public name; + + string public symbol; + + function tokenURI(uint256 id) public view virtual returns (string memory); + + /*////////////////////////////////////////////////////////////// + ERC721 BALANCE/OWNER STORAGE + //////////////////////////////////////////////////////////////*/ + + mapping(uint256 => address) internal _ownerOf; + + mapping(address => uint256) internal _balanceOf; + + function ownerOf(uint256 id) public view virtual returns (address owner) { + require((owner = _ownerOf[id]) != address(0), "NOT_MINTED"); + } + + function balanceOf(address owner) public view virtual returns (uint256) { + require(owner != address(0), "ZERO_ADDRESS"); + + return _balanceOf[owner]; + } + + /*////////////////////////////////////////////////////////////// + ERC721 APPROVAL STORAGE + //////////////////////////////////////////////////////////////*/ + + mapping(uint256 => address) public getApproved; + + mapping(address => mapping(address => bool)) public isApprovedForAll; + + /*////////////////////////////////////////////////////////////// + CONSTRUCTOR + //////////////////////////////////////////////////////////////*/ + + constructor(string memory _name, string memory _symbol) { + name = _name; + symbol = _symbol; + } + + /*////////////////////////////////////////////////////////////// + ERC721 LOGIC + //////////////////////////////////////////////////////////////*/ + + function approve(address spender, uint256 id) public virtual { + address owner = _ownerOf[id]; + + require(msg.sender == owner || isApprovedForAll[owner][msg.sender], "NOT_AUTHORIZED"); + + getApproved[id] = spender; + + emit Approval(owner, spender, id); + } + + function setApprovalForAll(address operator, bool approved) public virtual { + isApprovedForAll[msg.sender][operator] = approved; + + emit ApprovalForAll(msg.sender, operator, approved); + } + + function transferFrom( + address from, + address to, + uint256 id + ) public virtual { + require(from == _ownerOf[id], "WRONG_FROM"); + + require(to != address(0), "INVALID_RECIPIENT"); + + require( + msg.sender == from || isApprovedForAll[from][msg.sender] || msg.sender == getApproved[id], + "NOT_AUTHORIZED" + ); + + // Underflow of the sender's balance is impossible because we check for + // ownership above and the recipient's balance can't realistically overflow. + unchecked { + _balanceOf[from]--; + + _balanceOf[to]++; + } + + _ownerOf[id] = to; + + delete getApproved[id]; + + emit Transfer(from, to, id); + } + + function safeTransferFrom( + address from, + address to, + uint256 id + ) public virtual { + transferFrom(from, to, id); + + require( + to.code.length == 0 || + ERC721TokenReceiver(to).onERC721Received(msg.sender, from, id, "") == + ERC721TokenReceiver.onERC721Received.selector, + "UNSAFE_RECIPIENT" + ); + } + + function safeTransferFrom( + address from, + address to, + uint256 id, + bytes calldata data + ) public virtual { + transferFrom(from, to, id); + + require( + to.code.length == 0 || + ERC721TokenReceiver(to).onERC721Received(msg.sender, from, id, data) == + ERC721TokenReceiver.onERC721Received.selector, + "UNSAFE_RECIPIENT" + ); + } + + /*////////////////////////////////////////////////////////////// + ERC165 LOGIC + //////////////////////////////////////////////////////////////*/ + + function supportsInterface(bytes4 interfaceId) public view virtual returns (bool) { + return + interfaceId == 0x01ffc9a7 || // ERC165 Interface ID for ERC165 + interfaceId == 0x80ac58cd || // ERC165 Interface ID for ERC721 + interfaceId == 0x5b5e139f; // ERC165 Interface ID for ERC721Metadata + } + + /*////////////////////////////////////////////////////////////// + INTERNAL MINT/BURN LOGIC + //////////////////////////////////////////////////////////////*/ + + function _mint(address to, uint256 id) internal virtual { + require(to != address(0), "INVALID_RECIPIENT"); + + require(_ownerOf[id] == address(0), "ALREADY_MINTED"); + + // Counter overflow is incredibly unrealistic. + unchecked { + _balanceOf[to]++; + } + + _ownerOf[id] = to; + + emit Transfer(address(0), to, id); + } + + function _burn(uint256 id) internal virtual { + address owner = _ownerOf[id]; + + require(owner != address(0), "NOT_MINTED"); + + // Ownership check above ensures no underflow. + unchecked { + _balanceOf[owner]--; + } + + delete _ownerOf[id]; + + delete getApproved[id]; + + emit Transfer(owner, address(0), id); + } + + /*////////////////////////////////////////////////////////////// + INTERNAL SAFE MINT LOGIC + //////////////////////////////////////////////////////////////*/ + + function _safeMint(address to, uint256 id) internal virtual { + _mint(to, id); + + require( + to.code.length == 0 || + ERC721TokenReceiver(to).onERC721Received(msg.sender, address(0), id, "") == + ERC721TokenReceiver.onERC721Received.selector, + "UNSAFE_RECIPIENT" + ); + } + + function _safeMint( + address to, + uint256 id, + bytes memory data + ) internal virtual { + _mint(to, id); + + require( + to.code.length == 0 || + ERC721TokenReceiver(to).onERC721Received(msg.sender, address(0), id, data) == + ERC721TokenReceiver.onERC721Received.selector, + "UNSAFE_RECIPIENT" + ); + } +} + +/// @notice A generic interface for a contract which properly accepts ERC721 tokens. +/// @author Solmate (https://github.com/transmissions11/solmate/blob/main/src/tokens/ERC721.sol) +abstract contract ERC721TokenReceiver { + function onERC721Received( + address, + address, + uint256, + bytes calldata + ) external virtual returns (bytes4) { + return ERC721TokenReceiver.onERC721Received.selector; + } +} diff --git a/CVLByExample/UniswapHooks/lib/uniswap-hooks/lib/v4-core/lib/solmate/src/tokens/WETH.sol b/CVLByExample/UniswapHooks/lib/uniswap-hooks/lib/v4-core/lib/solmate/src/tokens/WETH.sol new file mode 100644 index 00000000..ddf9647d --- /dev/null +++ b/CVLByExample/UniswapHooks/lib/uniswap-hooks/lib/v4-core/lib/solmate/src/tokens/WETH.sol @@ -0,0 +1,35 @@ +// SPDX-License-Identifier: AGPL-3.0-only +pragma solidity >=0.8.0; + +import {ERC20} from "./ERC20.sol"; + +import {SafeTransferLib} from "../utils/SafeTransferLib.sol"; + +/// @notice Minimalist and modern Wrapped Ether implementation. +/// @author Solmate (https://github.com/transmissions11/solmate/blob/main/src/tokens/WETH.sol) +/// @author Inspired by WETH9 (https://github.com/dapphub/ds-weth/blob/master/src/weth9.sol) +contract WETH is ERC20("Wrapped Ether", "WETH", 18) { + using SafeTransferLib for address; + + event Deposit(address indexed from, uint256 amount); + + event Withdrawal(address indexed to, uint256 amount); + + function deposit() public payable virtual { + _mint(msg.sender, msg.value); + + emit Deposit(msg.sender, msg.value); + } + + function withdraw(uint256 amount) public virtual { + _burn(msg.sender, amount); + + emit Withdrawal(msg.sender, amount); + + msg.sender.safeTransferETH(amount); + } + + receive() external payable virtual { + deposit(); + } +} diff --git a/CVLByExample/UniswapHooks/lib/uniswap-hooks/lib/v4-core/lib/solmate/src/utils/Bytes32AddressLib.sol b/CVLByExample/UniswapHooks/lib/uniswap-hooks/lib/v4-core/lib/solmate/src/utils/Bytes32AddressLib.sol new file mode 100644 index 00000000..448fb759 --- /dev/null +++ b/CVLByExample/UniswapHooks/lib/uniswap-hooks/lib/v4-core/lib/solmate/src/utils/Bytes32AddressLib.sol @@ -0,0 +1,14 @@ +// SPDX-License-Identifier: AGPL-3.0-only +pragma solidity >=0.8.0; + +/// @notice Library for converting between addresses and bytes32 values. +/// @author Solmate (https://github.com/transmissions11/solmate/blob/main/src/utils/Bytes32AddressLib.sol) +library Bytes32AddressLib { + function fromLast20Bytes(bytes32 bytesValue) internal pure returns (address) { + return address(uint160(uint256(bytesValue))); + } + + function fillLast12Bytes(address addressValue) internal pure returns (bytes32) { + return bytes32(bytes20(addressValue)); + } +} diff --git a/CVLByExample/UniswapHooks/lib/uniswap-hooks/lib/v4-core/lib/solmate/src/utils/CREATE3.sol b/CVLByExample/UniswapHooks/lib/uniswap-hooks/lib/v4-core/lib/solmate/src/utils/CREATE3.sol new file mode 100644 index 00000000..8fe0511a --- /dev/null +++ b/CVLByExample/UniswapHooks/lib/uniswap-hooks/lib/v4-core/lib/solmate/src/utils/CREATE3.sol @@ -0,0 +1,87 @@ +// SPDX-License-Identifier: AGPL-3.0-only +pragma solidity >=0.8.0; + +import {Bytes32AddressLib} from "./Bytes32AddressLib.sol"; + +/// @notice Deploy to deterministic addresses without an initcode factor. +/// @author Solmate (https://github.com/transmissions11/solmate/blob/main/src/utils/CREATE3.sol) +/// @author Modified from 0xSequence (https://github.com/0xSequence/create3/blob/master/contracts/Create3.sol) +library CREATE3 { + using Bytes32AddressLib for bytes32; + + //--------------------------------------------------------------------------------// + // Opcode | Opcode + Arguments | Description | Stack View // + //--------------------------------------------------------------------------------// + // 0x36 | 0x36 | CALLDATASIZE | size // + // 0x3d | 0x3d | RETURNDATASIZE | 0 size // + // 0x3d | 0x3d | RETURNDATASIZE | 0 0 size // + // 0x37 | 0x37 | CALLDATACOPY | // + // 0x36 | 0x36 | CALLDATASIZE | size // + // 0x3d | 0x3d | RETURNDATASIZE | 0 size // + // 0x34 | 0x34 | CALLVALUE | value 0 size // + // 0xf0 | 0xf0 | CREATE | newContract // + //--------------------------------------------------------------------------------// + // Opcode | Opcode + Arguments | Description | Stack View // + //--------------------------------------------------------------------------------// + // 0x67 | 0x67XXXXXXXXXXXXXXXX | PUSH8 bytecode | bytecode // + // 0x3d | 0x3d | RETURNDATASIZE | 0 bytecode // + // 0x52 | 0x52 | MSTORE | // + // 0x60 | 0x6008 | PUSH1 08 | 8 // + // 0x60 | 0x6018 | PUSH1 18 | 24 8 // + // 0xf3 | 0xf3 | RETURN | // + //--------------------------------------------------------------------------------// + bytes internal constant PROXY_BYTECODE = hex"67_36_3d_3d_37_36_3d_34_f0_3d_52_60_08_60_18_f3"; + + bytes32 internal constant PROXY_BYTECODE_HASH = keccak256(PROXY_BYTECODE); + + function deploy( + bytes32 salt, + bytes memory creationCode, + uint256 value + ) internal returns (address deployed) { + bytes memory proxyChildBytecode = PROXY_BYTECODE; + + address proxy; + /// @solidity memory-safe-assembly + assembly { + // Deploy a new contract with our pre-made bytecode via CREATE2. + // We start 32 bytes into the code to avoid copying the byte length. + proxy := create2(0, add(proxyChildBytecode, 32), mload(proxyChildBytecode), salt) + } + require(proxy != address(0), "DEPLOYMENT_FAILED"); + + deployed = getDeployed(salt); + (bool success, ) = proxy.call{value: value}(creationCode); + require(success && deployed.code.length != 0, "INITIALIZATION_FAILED"); + } + + function getDeployed(bytes32 salt) internal view returns (address) { + return getDeployed(salt, address(this)); + } + + function getDeployed(bytes32 salt, address creator) internal pure returns (address) { + address proxy = keccak256( + abi.encodePacked( + // Prefix: + bytes1(0xFF), + // Creator: + creator, + // Salt: + salt, + // Bytecode hash: + PROXY_BYTECODE_HASH + ) + ).fromLast20Bytes(); + + return + keccak256( + abi.encodePacked( + // 0xd6 = 0xc0 (short RLP prefix) + 0x16 (length of: 0x94 ++ proxy ++ 0x01) + // 0x94 = 0x80 + 0x14 (0x14 = the length of an address, 20 bytes, in hex) + hex"d6_94", + proxy, + hex"01" // Nonce of the proxy contract (1) + ) + ).fromLast20Bytes(); + } +} diff --git a/CVLByExample/UniswapHooks/lib/uniswap-hooks/lib/v4-core/lib/solmate/src/utils/FixedPointMathLib.sol b/CVLByExample/UniswapHooks/lib/uniswap-hooks/lib/v4-core/lib/solmate/src/utils/FixedPointMathLib.sol new file mode 100644 index 00000000..68877227 --- /dev/null +++ b/CVLByExample/UniswapHooks/lib/uniswap-hooks/lib/v4-core/lib/solmate/src/utils/FixedPointMathLib.sol @@ -0,0 +1,255 @@ +// SPDX-License-Identifier: AGPL-3.0-only +pragma solidity >=0.8.0; + +/// @notice Arithmetic library with operations for fixed-point numbers. +/// @author Solmate (https://github.com/transmissions11/solmate/blob/main/src/utils/FixedPointMathLib.sol) +/// @author Inspired by USM (https://github.com/usmfum/USM/blob/master/contracts/WadMath.sol) +library FixedPointMathLib { + /*////////////////////////////////////////////////////////////// + SIMPLIFIED FIXED POINT OPERATIONS + //////////////////////////////////////////////////////////////*/ + + uint256 internal constant MAX_UINT256 = 2**256 - 1; + + uint256 internal constant WAD = 1e18; // The scalar of ETH and most ERC20s. + + function mulWadDown(uint256 x, uint256 y) internal pure returns (uint256) { + return mulDivDown(x, y, WAD); // Equivalent to (x * y) / WAD rounded down. + } + + function mulWadUp(uint256 x, uint256 y) internal pure returns (uint256) { + return mulDivUp(x, y, WAD); // Equivalent to (x * y) / WAD rounded up. + } + + function divWadDown(uint256 x, uint256 y) internal pure returns (uint256) { + return mulDivDown(x, WAD, y); // Equivalent to (x * WAD) / y rounded down. + } + + function divWadUp(uint256 x, uint256 y) internal pure returns (uint256) { + return mulDivUp(x, WAD, y); // Equivalent to (x * WAD) / y rounded up. + } + + /*////////////////////////////////////////////////////////////// + LOW LEVEL FIXED POINT OPERATIONS + //////////////////////////////////////////////////////////////*/ + + function mulDivDown( + uint256 x, + uint256 y, + uint256 denominator + ) internal pure returns (uint256 z) { + /// @solidity memory-safe-assembly + assembly { + // Equivalent to require(denominator != 0 && (y == 0 || x <= type(uint256).max / y)) + if iszero(mul(denominator, iszero(mul(y, gt(x, div(MAX_UINT256, y)))))) { + revert(0, 0) + } + + // Divide x * y by the denominator. + z := div(mul(x, y), denominator) + } + } + + function mulDivUp( + uint256 x, + uint256 y, + uint256 denominator + ) internal pure returns (uint256 z) { + /// @solidity memory-safe-assembly + assembly { + // Equivalent to require(denominator != 0 && (y == 0 || x <= type(uint256).max / y)) + if iszero(mul(denominator, iszero(mul(y, gt(x, div(MAX_UINT256, y)))))) { + revert(0, 0) + } + + // If x * y modulo the denominator is strictly greater than 0, + // 1 is added to round up the division of x * y by the denominator. + z := add(gt(mod(mul(x, y), denominator), 0), div(mul(x, y), denominator)) + } + } + + function rpow( + uint256 x, + uint256 n, + uint256 scalar + ) internal pure returns (uint256 z) { + /// @solidity memory-safe-assembly + assembly { + switch x + case 0 { + switch n + case 0 { + // 0 ** 0 = 1 + z := scalar + } + default { + // 0 ** n = 0 + z := 0 + } + } + default { + switch mod(n, 2) + case 0 { + // If n is even, store scalar in z for now. + z := scalar + } + default { + // If n is odd, store x in z for now. + z := x + } + + // Shifting right by 1 is like dividing by 2. + let half := shr(1, scalar) + + for { + // Shift n right by 1 before looping to halve it. + n := shr(1, n) + } n { + // Shift n right by 1 each iteration to halve it. + n := shr(1, n) + } { + // Revert immediately if x ** 2 would overflow. + // Equivalent to iszero(eq(div(xx, x), x)) here. + if shr(128, x) { + revert(0, 0) + } + + // Store x squared. + let xx := mul(x, x) + + // Round to the nearest number. + let xxRound := add(xx, half) + + // Revert if xx + half overflowed. + if lt(xxRound, xx) { + revert(0, 0) + } + + // Set x to scaled xxRound. + x := div(xxRound, scalar) + + // If n is even: + if mod(n, 2) { + // Compute z * x. + let zx := mul(z, x) + + // If z * x overflowed: + if iszero(eq(div(zx, x), z)) { + // Revert if x is non-zero. + if iszero(iszero(x)) { + revert(0, 0) + } + } + + // Round to the nearest number. + let zxRound := add(zx, half) + + // Revert if zx + half overflowed. + if lt(zxRound, zx) { + revert(0, 0) + } + + // Return properly scaled zxRound. + z := div(zxRound, scalar) + } + } + } + } + } + + /*////////////////////////////////////////////////////////////// + GENERAL NUMBER UTILITIES + //////////////////////////////////////////////////////////////*/ + + function sqrt(uint256 x) internal pure returns (uint256 z) { + /// @solidity memory-safe-assembly + assembly { + let y := x // We start y at x, which will help us make our initial estimate. + + z := 181 // The "correct" value is 1, but this saves a multiplication later. + + // This segment is to get a reasonable initial estimate for the Babylonian method. With a bad + // start, the correct # of bits increases ~linearly each iteration instead of ~quadratically. + + // We check y >= 2^(k + 8) but shift right by k bits + // each branch to ensure that if x >= 256, then y >= 256. + if iszero(lt(y, 0x10000000000000000000000000000000000)) { + y := shr(128, y) + z := shl(64, z) + } + if iszero(lt(y, 0x1000000000000000000)) { + y := shr(64, y) + z := shl(32, z) + } + if iszero(lt(y, 0x10000000000)) { + y := shr(32, y) + z := shl(16, z) + } + if iszero(lt(y, 0x1000000)) { + y := shr(16, y) + z := shl(8, z) + } + + // Goal was to get z*z*y within a small factor of x. More iterations could + // get y in a tighter range. Currently, we will have y in [256, 256*2^16). + // We ensured y >= 256 so that the relative difference between y and y+1 is small. + // That's not possible if x < 256 but we can just verify those cases exhaustively. + + // Now, z*z*y <= x < z*z*(y+1), and y <= 2^(16+8), and either y >= 256, or x < 256. + // Correctness can be checked exhaustively for x < 256, so we assume y >= 256. + // Then z*sqrt(y) is within sqrt(257)/sqrt(256) of sqrt(x), or about 20bps. + + // For s in the range [1/256, 256], the estimate f(s) = (181/1024) * (s+1) is in the range + // (1/2.84 * sqrt(s), 2.84 * sqrt(s)), with largest error when s = 1 and when s = 256 or 1/256. + + // Since y is in [256, 256*2^16), let a = y/65536, so that a is in [1/256, 256). Then we can estimate + // sqrt(y) using sqrt(65536) * 181/1024 * (a + 1) = 181/4 * (y + 65536)/65536 = 181 * (y + 65536)/2^18. + + // There is no overflow risk here since y < 2^136 after the first branch above. + z := shr(18, mul(z, add(y, 65536))) // A mul() is saved from starting z at 181. + + // Given the worst case multiplicative error of 2.84 above, 7 iterations should be enough. + z := shr(1, add(z, div(x, z))) + z := shr(1, add(z, div(x, z))) + z := shr(1, add(z, div(x, z))) + z := shr(1, add(z, div(x, z))) + z := shr(1, add(z, div(x, z))) + z := shr(1, add(z, div(x, z))) + z := shr(1, add(z, div(x, z))) + + // If x+1 is a perfect square, the Babylonian method cycles between + // floor(sqrt(x)) and ceil(sqrt(x)). This statement ensures we return floor. + // See: https://en.wikipedia.org/wiki/Integer_square_root#Using_only_integer_division + // Since the ceil is rare, we save gas on the assignment and repeat division in the rare case. + // If you don't care whether the floor or ceil square root is returned, you can remove this statement. + z := sub(z, lt(div(x, z), z)) + } + } + + function unsafeMod(uint256 x, uint256 y) internal pure returns (uint256 z) { + /// @solidity memory-safe-assembly + assembly { + // Mod x by y. Note this will return + // 0 instead of reverting if y is zero. + z := mod(x, y) + } + } + + function unsafeDiv(uint256 x, uint256 y) internal pure returns (uint256 r) { + /// @solidity memory-safe-assembly + assembly { + // Divide x by y. Note this will return + // 0 instead of reverting if y is zero. + r := div(x, y) + } + } + + function unsafeDivUp(uint256 x, uint256 y) internal pure returns (uint256 z) { + /// @solidity memory-safe-assembly + assembly { + // Add 1 to x * y if x % y > 0. Note this will + // return 0 instead of reverting if y is zero. + z := add(gt(mod(x, y), 0), div(x, y)) + } + } +} diff --git a/CVLByExample/UniswapHooks/lib/uniswap-hooks/lib/v4-core/lib/solmate/src/utils/LibString.sol b/CVLByExample/UniswapHooks/lib/uniswap-hooks/lib/v4-core/lib/solmate/src/utils/LibString.sol new file mode 100644 index 00000000..97c89e0b --- /dev/null +++ b/CVLByExample/UniswapHooks/lib/uniswap-hooks/lib/v4-core/lib/solmate/src/utils/LibString.sol @@ -0,0 +1,77 @@ +// SPDX-License-Identifier: MIT +pragma solidity >=0.8.0; + +/// @notice Efficient library for creating string representations of integers. +/// @author Solmate (https://github.com/transmissions11/solmate/blob/main/src/utils/LibString.sol) +/// @author Modified from Solady (https://github.com/Vectorized/solady/blob/main/src/utils/LibString.sol) +library LibString { + function toString(int256 value) internal pure returns (string memory str) { + if (value >= 0) return toString(uint256(value)); + + unchecked { + str = toString(uint256(-value)); + + /// @solidity memory-safe-assembly + assembly { + // Note: This is only safe because we over-allocate memory + // and write the string from right to left in toString(uint256), + // and thus can be sure that sub(str, 1) is an unused memory location. + + let length := mload(str) // Load the string length. + // Put the - character at the start of the string contents. + mstore(str, 45) // 45 is the ASCII code for the - character. + str := sub(str, 1) // Move back the string pointer by a byte. + mstore(str, add(length, 1)) // Update the string length. + } + } + } + + function toString(uint256 value) internal pure returns (string memory str) { + /// @solidity memory-safe-assembly + assembly { + // The maximum value of a uint256 contains 78 digits (1 byte per digit), but we allocate 160 bytes + // to keep the free memory pointer word aligned. We'll need 1 word for the length, 1 word for the + // trailing zeros padding, and 3 other words for a max of 78 digits. In total: 5 * 32 = 160 bytes. + let newFreeMemoryPointer := add(mload(0x40), 160) + + // Update the free memory pointer to avoid overriding our string. + mstore(0x40, newFreeMemoryPointer) + + // Assign str to the end of the zone of newly allocated memory. + str := sub(newFreeMemoryPointer, 32) + + // Clean the last word of memory it may not be overwritten. + mstore(str, 0) + + // Cache the end of the memory to calculate the length later. + let end := str + + // We write the string from rightmost digit to leftmost digit. + // The following is essentially a do-while loop that also handles the zero case. + // prettier-ignore + for { let temp := value } 1 {} { + // Move the pointer 1 byte to the left. + str := sub(str, 1) + + // Write the character to the pointer. + // The ASCII index of the '0' character is 48. + mstore8(str, add(48, mod(temp, 10))) + + // Keep dividing temp until zero. + temp := div(temp, 10) + + // prettier-ignore + if iszero(temp) { break } + } + + // Compute and cache the final total length of the string. + let length := sub(end, str) + + // Move the pointer 32 bytes leftwards to make room for the length. + str := sub(str, 32) + + // Store the string's length at the start of memory allocated for our string. + mstore(str, length) + } + } +} diff --git a/CVLByExample/UniswapHooks/lib/uniswap-hooks/lib/v4-core/lib/solmate/src/utils/MerkleProofLib.sol b/CVLByExample/UniswapHooks/lib/uniswap-hooks/lib/v4-core/lib/solmate/src/utils/MerkleProofLib.sol new file mode 100644 index 00000000..8fd7cbd7 --- /dev/null +++ b/CVLByExample/UniswapHooks/lib/uniswap-hooks/lib/v4-core/lib/solmate/src/utils/MerkleProofLib.sol @@ -0,0 +1,48 @@ +// SPDX-License-Identifier: MIT +pragma solidity >=0.8.0; + +/// @notice Gas optimized merkle proof verification library. +/// @author Solmate (https://github.com/transmissions11/solmate/blob/main/src/utils/MerkleProofLib.sol) +/// @author Modified from Solady (https://github.com/Vectorized/solady/blob/main/src/utils/MerkleProofLib.sol) +library MerkleProofLib { + function verify( + bytes32[] calldata proof, + bytes32 root, + bytes32 leaf + ) internal pure returns (bool isValid) { + /// @solidity memory-safe-assembly + assembly { + if proof.length { + // Left shifting by 5 is like multiplying by 32. + let end := add(proof.offset, shl(5, proof.length)) + + // Initialize offset to the offset of the proof in calldata. + let offset := proof.offset + + // Iterate over proof elements to compute root hash. + // prettier-ignore + for {} 1 {} { + // Slot where the leaf should be put in scratch space. If + // leaf > calldataload(offset): slot 32, otherwise: slot 0. + let leafSlot := shl(5, gt(leaf, calldataload(offset))) + + // Store elements to hash contiguously in scratch space. + // The xor puts calldataload(offset) in whichever slot leaf + // is not occupying, so 0 if leafSlot is 32, and 32 otherwise. + mstore(leafSlot, leaf) + mstore(xor(leafSlot, 32), calldataload(offset)) + + // Reuse leaf to store the hash to reduce stack operations. + leaf := keccak256(0, 64) // Hash both slots of scratch space. + + offset := add(offset, 32) // Shift 1 word per cycle. + + // prettier-ignore + if iszero(lt(offset, end)) { break } + } + } + + isValid := eq(leaf, root) // The proof is valid if the roots match. + } + } +} diff --git a/CVLByExample/UniswapHooks/lib/uniswap-hooks/lib/v4-core/lib/solmate/src/utils/ReentrancyGuard.sol b/CVLByExample/UniswapHooks/lib/uniswap-hooks/lib/v4-core/lib/solmate/src/utils/ReentrancyGuard.sol new file mode 100644 index 00000000..1453e24c --- /dev/null +++ b/CVLByExample/UniswapHooks/lib/uniswap-hooks/lib/v4-core/lib/solmate/src/utils/ReentrancyGuard.sol @@ -0,0 +1,19 @@ +// SPDX-License-Identifier: AGPL-3.0-only +pragma solidity >=0.8.0; + +/// @notice Gas optimized reentrancy protection for smart contracts. +/// @author Solmate (https://github.com/transmissions11/solmate/blob/main/src/utils/ReentrancyGuard.sol) +/// @author Modified from OpenZeppelin (https://github.com/OpenZeppelin/openzeppelin-contracts/blob/master/contracts/security/ReentrancyGuard.sol) +abstract contract ReentrancyGuard { + uint256 private locked = 1; + + modifier nonReentrant() virtual { + require(locked == 1, "REENTRANCY"); + + locked = 2; + + _; + + locked = 1; + } +} diff --git a/CVLByExample/UniswapHooks/lib/uniswap-hooks/lib/v4-core/lib/solmate/src/utils/SSTORE2.sol b/CVLByExample/UniswapHooks/lib/uniswap-hooks/lib/v4-core/lib/solmate/src/utils/SSTORE2.sol new file mode 100644 index 00000000..23d69803 --- /dev/null +++ b/CVLByExample/UniswapHooks/lib/uniswap-hooks/lib/v4-core/lib/solmate/src/utils/SSTORE2.sol @@ -0,0 +1,101 @@ +// SPDX-License-Identifier: AGPL-3.0-only +pragma solidity >=0.8.0; + +/// @notice Read and write to persistent storage at a fraction of the cost. +/// @author Solmate (https://github.com/transmissions11/solmate/blob/main/src/utils/SSTORE2.sol) +/// @author Modified from 0xSequence (https://github.com/0xSequence/sstore2/blob/master/contracts/SSTORE2.sol) +library SSTORE2 { + uint256 internal constant DATA_OFFSET = 1; // We skip the first byte as it's a STOP opcode to ensure the contract can't be called. + + /*////////////////////////////////////////////////////////////// + WRITE LOGIC + //////////////////////////////////////////////////////////////*/ + + function write(bytes memory data) internal returns (address pointer) { + // Prefix the bytecode with a STOP opcode to ensure it cannot be called. + bytes memory runtimeCode = abi.encodePacked(hex"00", data); + + bytes memory creationCode = abi.encodePacked( + //---------------------------------------------------------------------------------------------------------------// + // Opcode | Opcode + Arguments | Description | Stack View // + //---------------------------------------------------------------------------------------------------------------// + // 0x60 | 0x600B | PUSH1 11 | codeOffset // + // 0x59 | 0x59 | MSIZE | 0 codeOffset // + // 0x81 | 0x81 | DUP2 | codeOffset 0 codeOffset // + // 0x38 | 0x38 | CODESIZE | codeSize codeOffset 0 codeOffset // + // 0x03 | 0x03 | SUB | (codeSize - codeOffset) 0 codeOffset // + // 0x80 | 0x80 | DUP | (codeSize - codeOffset) (codeSize - codeOffset) 0 codeOffset // + // 0x92 | 0x92 | SWAP3 | codeOffset (codeSize - codeOffset) 0 (codeSize - codeOffset) // + // 0x59 | 0x59 | MSIZE | 0 codeOffset (codeSize - codeOffset) 0 (codeSize - codeOffset) // + // 0x39 | 0x39 | CODECOPY | 0 (codeSize - codeOffset) // + // 0xf3 | 0xf3 | RETURN | // + //---------------------------------------------------------------------------------------------------------------// + hex"60_0B_59_81_38_03_80_92_59_39_F3", // Returns all code in the contract except for the first 11 (0B in hex) bytes. + runtimeCode // The bytecode we want the contract to have after deployment. Capped at 1 byte less than the code size limit. + ); + + /// @solidity memory-safe-assembly + assembly { + // Deploy a new contract with the generated creation code. + // We start 32 bytes into the code to avoid copying the byte length. + pointer := create(0, add(creationCode, 32), mload(creationCode)) + } + + require(pointer != address(0), "DEPLOYMENT_FAILED"); + } + + /*////////////////////////////////////////////////////////////// + READ LOGIC + //////////////////////////////////////////////////////////////*/ + + function read(address pointer) internal view returns (bytes memory) { + return readBytecode(pointer, DATA_OFFSET, pointer.code.length - DATA_OFFSET); + } + + function read(address pointer, uint256 start) internal view returns (bytes memory) { + start += DATA_OFFSET; + + return readBytecode(pointer, start, pointer.code.length - start); + } + + function read( + address pointer, + uint256 start, + uint256 end + ) internal view returns (bytes memory) { + start += DATA_OFFSET; + end += DATA_OFFSET; + + require(pointer.code.length >= end, "OUT_OF_BOUNDS"); + + return readBytecode(pointer, start, end - start); + } + + /*////////////////////////////////////////////////////////////// + INTERNAL HELPER LOGIC + //////////////////////////////////////////////////////////////*/ + + function readBytecode( + address pointer, + uint256 start, + uint256 size + ) private view returns (bytes memory data) { + /// @solidity memory-safe-assembly + assembly { + // Get a pointer to some free memory. + data := mload(0x40) + + // Update the free memory pointer to prevent overriding our data. + // We use and(x, not(31)) as a cheaper equivalent to sub(x, mod(x, 32)). + // Adding 31 to size and running the result through the logic above ensures + // the memory pointer remains word-aligned, following the Solidity convention. + mstore(0x40, add(data, and(add(add(size, 32), 31), not(31)))) + + // Store the size of the data in the first 32 byte chunk of free memory. + mstore(data, size) + + // Copy the code into memory right after the 32 bytes we used to store the size. + extcodecopy(pointer, add(data, 32), start, size) + } + } +} diff --git a/CVLByExample/UniswapHooks/lib/uniswap-hooks/lib/v4-core/lib/solmate/src/utils/SafeCastLib.sol b/CVLByExample/UniswapHooks/lib/uniswap-hooks/lib/v4-core/lib/solmate/src/utils/SafeCastLib.sol new file mode 100644 index 00000000..9e8a2af0 --- /dev/null +++ b/CVLByExample/UniswapHooks/lib/uniswap-hooks/lib/v4-core/lib/solmate/src/utils/SafeCastLib.sol @@ -0,0 +1,193 @@ +// SPDX-License-Identifier: AGPL-3.0-only +pragma solidity >=0.8.0; + +/// @notice Safe unsigned integer casting library that reverts on overflow. +/// @author Solmate (https://github.com/transmissions11/solmate/blob/main/src/utils/SafeCastLib.sol) +/// @author Modified from OpenZeppelin (https://github.com/OpenZeppelin/openzeppelin-contracts/blob/master/contracts/utils/math/SafeCast.sol) +library SafeCastLib { + function safeCastTo248(uint256 x) internal pure returns (uint248 y) { + require(x < 1 << 248); + + y = uint248(x); + } + + function safeCastTo240(uint256 x) internal pure returns (uint240 y) { + require(x < 1 << 240); + + y = uint240(x); + } + + function safeCastTo232(uint256 x) internal pure returns (uint232 y) { + require(x < 1 << 232); + + y = uint232(x); + } + + function safeCastTo224(uint256 x) internal pure returns (uint224 y) { + require(x < 1 << 224); + + y = uint224(x); + } + + function safeCastTo216(uint256 x) internal pure returns (uint216 y) { + require(x < 1 << 216); + + y = uint216(x); + } + + function safeCastTo208(uint256 x) internal pure returns (uint208 y) { + require(x < 1 << 208); + + y = uint208(x); + } + + function safeCastTo200(uint256 x) internal pure returns (uint200 y) { + require(x < 1 << 200); + + y = uint200(x); + } + + function safeCastTo192(uint256 x) internal pure returns (uint192 y) { + require(x < 1 << 192); + + y = uint192(x); + } + + function safeCastTo184(uint256 x) internal pure returns (uint184 y) { + require(x < 1 << 184); + + y = uint184(x); + } + + function safeCastTo176(uint256 x) internal pure returns (uint176 y) { + require(x < 1 << 176); + + y = uint176(x); + } + + function safeCastTo168(uint256 x) internal pure returns (uint168 y) { + require(x < 1 << 168); + + y = uint168(x); + } + + function safeCastTo160(uint256 x) internal pure returns (uint160 y) { + require(x < 1 << 160); + + y = uint160(x); + } + + function safeCastTo152(uint256 x) internal pure returns (uint152 y) { + require(x < 1 << 152); + + y = uint152(x); + } + + function safeCastTo144(uint256 x) internal pure returns (uint144 y) { + require(x < 1 << 144); + + y = uint144(x); + } + + function safeCastTo136(uint256 x) internal pure returns (uint136 y) { + require(x < 1 << 136); + + y = uint136(x); + } + + function safeCastTo128(uint256 x) internal pure returns (uint128 y) { + require(x < 1 << 128); + + y = uint128(x); + } + + function safeCastTo120(uint256 x) internal pure returns (uint120 y) { + require(x < 1 << 120); + + y = uint120(x); + } + + function safeCastTo112(uint256 x) internal pure returns (uint112 y) { + require(x < 1 << 112); + + y = uint112(x); + } + + function safeCastTo104(uint256 x) internal pure returns (uint104 y) { + require(x < 1 << 104); + + y = uint104(x); + } + + function safeCastTo96(uint256 x) internal pure returns (uint96 y) { + require(x < 1 << 96); + + y = uint96(x); + } + + function safeCastTo88(uint256 x) internal pure returns (uint88 y) { + require(x < 1 << 88); + + y = uint88(x); + } + + function safeCastTo80(uint256 x) internal pure returns (uint80 y) { + require(x < 1 << 80); + + y = uint80(x); + } + + function safeCastTo72(uint256 x) internal pure returns (uint72 y) { + require(x < 1 << 72); + + y = uint72(x); + } + + function safeCastTo64(uint256 x) internal pure returns (uint64 y) { + require(x < 1 << 64); + + y = uint64(x); + } + + function safeCastTo56(uint256 x) internal pure returns (uint56 y) { + require(x < 1 << 56); + + y = uint56(x); + } + + function safeCastTo48(uint256 x) internal pure returns (uint48 y) { + require(x < 1 << 48); + + y = uint48(x); + } + + function safeCastTo40(uint256 x) internal pure returns (uint40 y) { + require(x < 1 << 40); + + y = uint40(x); + } + + function safeCastTo32(uint256 x) internal pure returns (uint32 y) { + require(x < 1 << 32); + + y = uint32(x); + } + + function safeCastTo24(uint256 x) internal pure returns (uint24 y) { + require(x < 1 << 24); + + y = uint24(x); + } + + function safeCastTo16(uint256 x) internal pure returns (uint16 y) { + require(x < 1 << 16); + + y = uint16(x); + } + + function safeCastTo8(uint256 x) internal pure returns (uint8 y) { + require(x < 1 << 8); + + y = uint8(x); + } +} diff --git a/CVLByExample/UniswapHooks/lib/uniswap-hooks/lib/v4-core/lib/solmate/src/utils/SafeTransferLib.sol b/CVLByExample/UniswapHooks/lib/uniswap-hooks/lib/v4-core/lib/solmate/src/utils/SafeTransferLib.sol new file mode 100644 index 00000000..93ef5a91 --- /dev/null +++ b/CVLByExample/UniswapHooks/lib/uniswap-hooks/lib/v4-core/lib/solmate/src/utils/SafeTransferLib.sol @@ -0,0 +1,128 @@ +// SPDX-License-Identifier: AGPL-3.0-only +pragma solidity >=0.8.0; + +import {ERC20} from "../tokens/ERC20.sol"; + +/// @notice Safe ETH and ERC20 transfer library that gracefully handles missing return values. +/// @author Solmate (https://github.com/transmissions11/solmate/blob/main/src/utils/SafeTransferLib.sol) +/// @dev Use with caution! Some functions in this library knowingly create dirty bits at the destination of the free memory pointer. +/// @dev Note that none of the functions in this library check that a token has code at all! That responsibility is delegated to the caller. +library SafeTransferLib { + /*////////////////////////////////////////////////////////////// + ETH OPERATIONS + //////////////////////////////////////////////////////////////*/ + + function safeTransferETH(address to, uint256 amount) internal { + bool success; + + /// @solidity memory-safe-assembly + assembly { + // Transfer the ETH and store if it succeeded or not. + success := call(gas(), to, amount, 0, 0, 0, 0) + } + + require(success, "ETH_TRANSFER_FAILED"); + } + + /*////////////////////////////////////////////////////////////// + ERC20 OPERATIONS + //////////////////////////////////////////////////////////////*/ + + function safeTransferFrom( + ERC20 token, + address from, + address to, + uint256 amount + ) internal { + bool success; + + /// @solidity memory-safe-assembly + assembly { + // Get a pointer to some free memory. + let freeMemoryPointer := mload(0x40) + + // Write the abi-encoded calldata into memory, beginning with the function selector. + mstore(freeMemoryPointer, 0x23b872dd00000000000000000000000000000000000000000000000000000000) + mstore(add(freeMemoryPointer, 4), and(from, 0xffffffffffffffffffffffffffffffffffffffff)) // Append and mask the "from" argument. + mstore(add(freeMemoryPointer, 36), and(to, 0xffffffffffffffffffffffffffffffffffffffff)) // Append and mask the "to" argument. + mstore(add(freeMemoryPointer, 68), amount) // Append the "amount" argument. Masking not required as it's a full 32 byte type. + + success := and( + // Set success to whether the call reverted, if not we check it either + // returned exactly 1 (can't just be non-zero data), or had no return data. + or(and(eq(mload(0), 1), gt(returndatasize(), 31)), iszero(returndatasize())), + // We use 100 because the length of our calldata totals up like so: 4 + 32 * 3. + // We use 0 and 32 to copy up to 32 bytes of return data into the scratch space. + // Counterintuitively, this call must be positioned second to the or() call in the + // surrounding and() call or else returndatasize() will be zero during the computation. + call(gas(), token, 0, freeMemoryPointer, 100, 0, 32) + ) + } + + require(success, "TRANSFER_FROM_FAILED"); + } + + function safeTransfer( + ERC20 token, + address to, + uint256 amount + ) internal { + bool success; + + /// @solidity memory-safe-assembly + assembly { + // Get a pointer to some free memory. + let freeMemoryPointer := mload(0x40) + + // Write the abi-encoded calldata into memory, beginning with the function selector. + mstore(freeMemoryPointer, 0xa9059cbb00000000000000000000000000000000000000000000000000000000) + mstore(add(freeMemoryPointer, 4), and(to, 0xffffffffffffffffffffffffffffffffffffffff)) // Append and mask the "to" argument. + mstore(add(freeMemoryPointer, 36), amount) // Append the "amount" argument. Masking not required as it's a full 32 byte type. + + success := and( + // Set success to whether the call reverted, if not we check it either + // returned exactly 1 (can't just be non-zero data), or had no return data. + or(and(eq(mload(0), 1), gt(returndatasize(), 31)), iszero(returndatasize())), + // We use 68 because the length of our calldata totals up like so: 4 + 32 * 2. + // We use 0 and 32 to copy up to 32 bytes of return data into the scratch space. + // Counterintuitively, this call must be positioned second to the or() call in the + // surrounding and() call or else returndatasize() will be zero during the computation. + call(gas(), token, 0, freeMemoryPointer, 68, 0, 32) + ) + } + + require(success, "TRANSFER_FAILED"); + } + + function safeApprove( + ERC20 token, + address to, + uint256 amount + ) internal { + bool success; + + /// @solidity memory-safe-assembly + assembly { + // Get a pointer to some free memory. + let freeMemoryPointer := mload(0x40) + + // Write the abi-encoded calldata into memory, beginning with the function selector. + mstore(freeMemoryPointer, 0x095ea7b300000000000000000000000000000000000000000000000000000000) + mstore(add(freeMemoryPointer, 4), and(to, 0xffffffffffffffffffffffffffffffffffffffff)) // Append and mask the "to" argument. + mstore(add(freeMemoryPointer, 36), amount) // Append the "amount" argument. Masking not required as it's a full 32 byte type. + + success := and( + // Set success to whether the call reverted, if not we check it either + // returned exactly 1 (can't just be non-zero data), or had no return data. + or(and(eq(mload(0), 1), gt(returndatasize(), 31)), iszero(returndatasize())), + // We use 68 because the length of our calldata totals up like so: 4 + 32 * 2. + // We use 0 and 32 to copy up to 32 bytes of return data into the scratch space. + // Counterintuitively, this call must be positioned second to the or() call in the + // surrounding and() call or else returndatasize() will be zero during the computation. + call(gas(), token, 0, freeMemoryPointer, 68, 0, 32) + ) + } + + require(success, "APPROVE_FAILED"); + } +} diff --git a/CVLByExample/UniswapHooks/lib/uniswap-hooks/lib/v4-core/lib/solmate/src/utils/SignedWadMath.sol b/CVLByExample/UniswapHooks/lib/uniswap-hooks/lib/v4-core/lib/solmate/src/utils/SignedWadMath.sol new file mode 100644 index 00000000..e7d30a43 --- /dev/null +++ b/CVLByExample/UniswapHooks/lib/uniswap-hooks/lib/v4-core/lib/solmate/src/utils/SignedWadMath.sol @@ -0,0 +1,245 @@ +// SPDX-License-Identifier: MIT +pragma solidity >=0.8.0; + +/// @notice Signed 18 decimal fixed point (wad) arithmetic library. +/// @author Solmate (https://github.com/transmissions11/solmate/blob/main/src/utils/SignedWadMath.sol) +/// @author Modified from Remco Bloemen (https://xn--2-umb.com/22/exp-ln/index.html) + +/// @dev Will not revert on overflow, only use where overflow is not possible. +function toWadUnsafe(uint256 x) pure returns (int256 r) { + /// @solidity memory-safe-assembly + assembly { + // Multiply x by 1e18. + r := mul(x, 1000000000000000000) + } +} + +/// @dev Takes an integer amount of seconds and converts it to a wad amount of days. +/// @dev Will not revert on overflow, only use where overflow is not possible. +/// @dev Not meant for negative second amounts, it assumes x is positive. +function toDaysWadUnsafe(uint256 x) pure returns (int256 r) { + /// @solidity memory-safe-assembly + assembly { + // Multiply x by 1e18 and then divide it by 86400. + r := div(mul(x, 1000000000000000000), 86400) + } +} + +/// @dev Takes a wad amount of days and converts it to an integer amount of seconds. +/// @dev Will not revert on overflow, only use where overflow is not possible. +/// @dev Not meant for negative day amounts, it assumes x is positive. +function fromDaysWadUnsafe(int256 x) pure returns (uint256 r) { + /// @solidity memory-safe-assembly + assembly { + // Multiply x by 86400 and then divide it by 1e18. + r := div(mul(x, 86400), 1000000000000000000) + } +} + +/// @dev Will not revert on overflow, only use where overflow is not possible. +function unsafeWadMul(int256 x, int256 y) pure returns (int256 r) { + /// @solidity memory-safe-assembly + assembly { + // Multiply x by y and divide by 1e18. + r := sdiv(mul(x, y), 1000000000000000000) + } +} + +/// @dev Will return 0 instead of reverting if y is zero and will +/// not revert on overflow, only use where overflow is not possible. +function unsafeWadDiv(int256 x, int256 y) pure returns (int256 r) { + /// @solidity memory-safe-assembly + assembly { + // Multiply x by 1e18 and divide it by y. + r := sdiv(mul(x, 1000000000000000000), y) + } +} + +function wadMul(int256 x, int256 y) pure returns (int256 r) { + /// @solidity memory-safe-assembly + assembly { + // Store x * y in r for now. + r := mul(x, y) + + // Combined overflow check (`x == 0 || (x * y) / x == y`) and edge case check + // where x == -1 and y == type(int256).min, for y == -1 and x == min int256, + // the second overflow check will catch this. + // See: https://secure-contracts.com/learn_evm/arithmetic-checks.html#arithmetic-checks-for-int256-multiplication + // Combining into 1 expression saves gas as resulting bytecode will only have 1 `JUMPI` + // rather than 2. + if iszero( + and( + or(iszero(x), eq(sdiv(r, x), y)), + or(lt(x, not(0)), sgt(y, 0x8000000000000000000000000000000000000000000000000000000000000000)) + ) + ) { + revert(0, 0) + } + + // Scale the result down by 1e18. + r := sdiv(r, 1000000000000000000) + } +} + +function wadDiv(int256 x, int256 y) pure returns (int256 r) { + /// @solidity memory-safe-assembly + assembly { + // Store x * 1e18 in r for now. + r := mul(x, 1000000000000000000) + + // Equivalent to require(y != 0 && ((x * 1e18) / 1e18 == x)) + if iszero(and(iszero(iszero(y)), eq(sdiv(r, 1000000000000000000), x))) { + revert(0, 0) + } + + // Divide r by y. + r := sdiv(r, y) + } +} + +/// @dev Will not work with negative bases, only use when x is positive. +function wadPow(int256 x, int256 y) pure returns (int256) { + // Equivalent to x to the power of y because x ** y = (e ** ln(x)) ** y = e ** (ln(x) * y) + return wadExp((wadLn(x) * y) / 1e18); // Using ln(x) means x must be greater than 0. +} + +function wadExp(int256 x) pure returns (int256 r) { + unchecked { + // When the result is < 0.5 we return zero. This happens when + // x <= floor(log(0.5e18) * 1e18) ~ -42e18 + if (x <= -42139678854452767551) return 0; + + // When the result is > (2**255 - 1) / 1e18 we can not represent it as an + // int. This happens when x >= floor(log((2**255 - 1) / 1e18) * 1e18) ~ 135. + if (x >= 135305999368893231589) revert("EXP_OVERFLOW"); + + // x is now in the range (-42, 136) * 1e18. Convert to (-42, 136) * 2**96 + // for more intermediate precision and a binary basis. This base conversion + // is a multiplication by 1e18 / 2**96 = 5**18 / 2**78. + x = (x << 78) / 5**18; + + // Reduce range of x to (-½ ln 2, ½ ln 2) * 2**96 by factoring out powers + // of two such that exp(x) = exp(x') * 2**k, where k is an integer. + // Solving this gives k = round(x / log(2)) and x' = x - k * log(2). + int256 k = ((x << 96) / 54916777467707473351141471128 + 2**95) >> 96; + x = x - k * 54916777467707473351141471128; + + // k is in the range [-61, 195]. + + // Evaluate using a (6, 7)-term rational approximation. + // p is made monic, we'll multiply by a scale factor later. + int256 y = x + 1346386616545796478920950773328; + y = ((y * x) >> 96) + 57155421227552351082224309758442; + int256 p = y + x - 94201549194550492254356042504812; + p = ((p * y) >> 96) + 28719021644029726153956944680412240; + p = p * x + (4385272521454847904659076985693276 << 96); + + // We leave p in 2**192 basis so we don't need to scale it back up for the division. + int256 q = x - 2855989394907223263936484059900; + q = ((q * x) >> 96) + 50020603652535783019961831881945; + q = ((q * x) >> 96) - 533845033583426703283633433725380; + q = ((q * x) >> 96) + 3604857256930695427073651918091429; + q = ((q * x) >> 96) - 14423608567350463180887372962807573; + q = ((q * x) >> 96) + 26449188498355588339934803723976023; + + /// @solidity memory-safe-assembly + assembly { + // Div in assembly because solidity adds a zero check despite the unchecked. + // The q polynomial won't have zeros in the domain as all its roots are complex. + // No scaling is necessary because p is already 2**96 too large. + r := sdiv(p, q) + } + + // r should be in the range (0.09, 0.25) * 2**96. + + // We now need to multiply r by: + // * the scale factor s = ~6.031367120. + // * the 2**k factor from the range reduction. + // * the 1e18 / 2**96 factor for base conversion. + // We do this all at once, with an intermediate result in 2**213 + // basis, so the final right shift is always by a positive amount. + r = int256((uint256(r) * 3822833074963236453042738258902158003155416615667) >> uint256(195 - k)); + } +} + +function wadLn(int256 x) pure returns (int256 r) { + unchecked { + require(x > 0, "UNDEFINED"); + + // We want to convert x from 10**18 fixed point to 2**96 fixed point. + // We do this by multiplying by 2**96 / 10**18. But since + // ln(x * C) = ln(x) + ln(C), we can simply do nothing here + // and add ln(2**96 / 10**18) at the end. + + /// @solidity memory-safe-assembly + assembly { + r := shl(7, lt(0xffffffffffffffffffffffffffffffff, x)) + r := or(r, shl(6, lt(0xffffffffffffffff, shr(r, x)))) + r := or(r, shl(5, lt(0xffffffff, shr(r, x)))) + r := or(r, shl(4, lt(0xffff, shr(r, x)))) + r := or(r, shl(3, lt(0xff, shr(r, x)))) + r := or(r, shl(2, lt(0xf, shr(r, x)))) + r := or(r, shl(1, lt(0x3, shr(r, x)))) + r := or(r, lt(0x1, shr(r, x))) + } + + // Reduce range of x to (1, 2) * 2**96 + // ln(2^k * x) = k * ln(2) + ln(x) + int256 k = r - 96; + x <<= uint256(159 - k); + x = int256(uint256(x) >> 159); + + // Evaluate using a (8, 8)-term rational approximation. + // p is made monic, we will multiply by a scale factor later. + int256 p = x + 3273285459638523848632254066296; + p = ((p * x) >> 96) + 24828157081833163892658089445524; + p = ((p * x) >> 96) + 43456485725739037958740375743393; + p = ((p * x) >> 96) - 11111509109440967052023855526967; + p = ((p * x) >> 96) - 45023709667254063763336534515857; + p = ((p * x) >> 96) - 14706773417378608786704636184526; + p = p * x - (795164235651350426258249787498 << 96); + + // We leave p in 2**192 basis so we don't need to scale it back up for the division. + // q is monic by convention. + int256 q = x + 5573035233440673466300451813936; + q = ((q * x) >> 96) + 71694874799317883764090561454958; + q = ((q * x) >> 96) + 283447036172924575727196451306956; + q = ((q * x) >> 96) + 401686690394027663651624208769553; + q = ((q * x) >> 96) + 204048457590392012362485061816622; + q = ((q * x) >> 96) + 31853899698501571402653359427138; + q = ((q * x) >> 96) + 909429971244387300277376558375; + /// @solidity memory-safe-assembly + assembly { + // Div in assembly because solidity adds a zero check despite the unchecked. + // The q polynomial is known not to have zeros in the domain. + // No scaling required because p is already 2**96 too large. + r := sdiv(p, q) + } + + // r is in the range (0, 0.125) * 2**96 + + // Finalization, we need to: + // * multiply by the scale factor s = 5.549… + // * add ln(2**96 / 10**18) + // * add k * ln(2) + // * multiply by 10**18 / 2**96 = 5**18 >> 78 + + // mul s * 5e18 * 2**96, base is now 5**18 * 2**192 + r *= 1677202110996718588342820967067443963516166; + // add ln(2) * k * 5e18 * 2**192 + r += 16597577552685614221487285958193947469193820559219878177908093499208371 * k; + // add ln(2**96 / 10**18) * 5e18 * 2**192 + r += 600920179829731861736702779321621459595472258049074101567377883020018308; + // base conversion: mul 2**18 / 2**192 + r >>= 174; + } +} + +/// @dev Will return 0 instead of reverting if y is zero. +function unsafeDiv(int256 x, int256 y) pure returns (int256 r) { + /// @solidity memory-safe-assembly + assembly { + // Divide x by y. + r := sdiv(x, y) + } +} From d1b7606600173a1d51a8d63b0a1a825d19afc031 Mon Sep 17 00:00:00 2001 From: Jochen Hoenicke Date: Thu, 21 Aug 2025 14:17:00 +0200 Subject: [PATCH 4/5] Added openzeppelin contracts --- .../contracts/access/AccessControl.sol | 209 +++ .../contracts/access/IAccessControl.sol | 98 ++ .../contracts/access/Ownable.sol | 100 ++ .../contracts/access/Ownable2Step.sol | 59 + .../contracts/access/README.adoc | 45 + .../AccessControlDefaultAdminRules.sol | 396 ++++++ .../extensions/AccessControlEnumerable.sol | 70 + .../IAccessControlDefaultAdminRules.sol | 192 +++ .../extensions/IAccessControlEnumerable.sol | 31 + .../access/manager/AccessManaged.sol | 113 ++ .../access/manager/AccessManager.sol | 730 +++++++++++ .../access/manager/AuthorityUtils.sol | 32 + .../access/manager/IAccessManaged.sol | 32 + .../access/manager/IAccessManager.sol | 392 ++++++ .../contracts/access/manager/IAuthority.sol | 14 + .../contracts/finance/README.adoc | 14 + .../contracts/finance/VestingWallet.sol | 154 +++ .../contracts/governance/Governor.sol | 850 ++++++++++++ .../contracts/governance/IGovernor.sol | 427 ++++++ .../contracts/governance/README.adoc | 171 +++ .../governance/TimelockController.sol | 472 +++++++ .../extensions/GovernorCountingSimple.sol | 100 ++ .../extensions/GovernorPreventLateQuorum.sol | 102 ++ .../extensions/GovernorSettings.sol | 112 ++ .../governance/extensions/GovernorStorage.sol | 115 ++ .../extensions/GovernorTimelockAccess.sol | 346 +++++ .../extensions/GovernorTimelockCompound.sol | 167 +++ .../extensions/GovernorTimelockControl.sol | 173 +++ .../governance/extensions/GovernorVotes.sol | 64 + .../GovernorVotesQuorumFraction.sol | 110 ++ .../contracts/governance/utils/IVotes.sol | 59 + .../contracts/governance/utils/Votes.sol | 251 ++++ .../contracts/interfaces/IERC1155.sol | 6 + .../interfaces/IERC1155MetadataURI.sol | 6 + .../contracts/interfaces/IERC1155Receiver.sol | 6 + .../contracts/interfaces/IERC1271.sol | 17 + .../contracts/interfaces/IERC1363.sol | 80 ++ .../contracts/interfaces/IERC1363Receiver.sol | 35 + .../contracts/interfaces/IERC1363Spender.sol | 29 + .../contracts/interfaces/IERC165.sol | 6 + .../interfaces/IERC1820Implementer.sol | 20 + .../contracts/interfaces/IERC1820Registry.sol | 112 ++ .../contracts/interfaces/IERC1967.sol | 24 + .../contracts/interfaces/IERC20.sol | 6 + .../contracts/interfaces/IERC20Metadata.sol | 6 + .../contracts/interfaces/IERC2309.sol | 19 + .../contracts/interfaces/IERC2612.sol | 8 + .../contracts/interfaces/IERC2981.sol | 23 + .../contracts/interfaces/IERC3156.sol | 7 + .../interfaces/IERC3156FlashBorrower.sol | 27 + .../interfaces/IERC3156FlashLender.sol | 41 + .../contracts/interfaces/IERC4626.sol | 230 ++++ .../contracts/interfaces/IERC4906.sol | 20 + .../contracts/interfaces/IERC5267.sol | 28 + .../contracts/interfaces/IERC5313.sol | 16 + .../contracts/interfaces/IERC5805.sol | 9 + .../contracts/interfaces/IERC6372.sol | 17 + .../contracts/interfaces/IERC721.sol | 6 + .../interfaces/IERC721Enumerable.sol | 6 + .../contracts/interfaces/IERC721Metadata.sol | 6 + .../contracts/interfaces/IERC721Receiver.sol | 6 + .../contracts/interfaces/IERC777.sol | 200 +++ .../contracts/interfaces/IERC777Recipient.sol | 35 + .../contracts/interfaces/IERC777Sender.sol | 35 + .../contracts/interfaces/README.adoc | 82 ++ .../contracts/interfaces/draft-IERC1822.sol | 20 + .../contracts/interfaces/draft-IERC6093.sol | 161 +++ .../contracts/metatx/ERC2771Context.sol | 86 ++ .../contracts/metatx/ERC2771Forwarder.sol | 370 ++++++ .../contracts/metatx/README.adoc | 17 + .../contracts/mocks/AccessManagedTarget.sol | 34 + .../contracts/mocks/ArraysMock.sol | 51 + .../contracts/mocks/AuthorityMock.sol | 69 + .../contracts/mocks/Base64Dirty.sol | 19 + .../contracts/mocks/CallReceiverMock.sol | 73 ++ .../contracts/mocks/ContextMock.sol | 35 + .../contracts/mocks/DummyImplementation.sol | 65 + .../contracts/mocks/EIP712Verifier.sol | 16 + .../contracts/mocks/ERC1271WalletMock.sol | 24 + .../ERC165/ERC165InterfacesSupported.sol | 58 + .../mocks/ERC165/ERC165MaliciousData.sol | 12 + .../mocks/ERC165/ERC165MissingData.sol | 7 + .../mocks/ERC165/ERC165NotSupported.sol | 5 + .../mocks/ERC165/ERC165ReturnBomb.sol | 18 + .../contracts/mocks/ERC2771ContextMock.sol | 28 + .../mocks/ERC3156FlashBorrowerMock.sol | 53 + .../contracts/mocks/EtherReceiverMock.sol | 17 + .../contracts/mocks/InitializableMock.sol | 130 ++ .../contracts/mocks/MulticallTest.sol | 23 + .../MultipleInheritanceInitializableMocks.sol | 131 ++ .../contracts/mocks/PausableMock.sol | 31 + .../contracts/mocks/ReentrancyAttack.sol | 12 + .../contracts/mocks/ReentrancyMock.sol | 50 + .../mocks/RegressionImplementation.sol | 61 + .../SingleInheritanceInitializableMocks.sol | 49 + .../contracts/mocks/Stateless.sol | 36 + .../contracts/mocks/StorageSlotMock.sol | 77 ++ .../contracts/mocks/TimelockReentrant.sol | 26 + .../contracts/mocks/UpgradeableBeaconMock.sol | 27 + .../contracts/mocks/VotesMock.sol | 42 + .../contracts/mocks/compound/CompTimelock.sol | 174 +++ .../mocks/governance/GovernorMock.sol | 14 + .../GovernorPreventLateQuorumMock.sol | 46 + .../mocks/governance/GovernorStorageMock.sol | 79 ++ .../governance/GovernorTimelockAccessMock.sol | 70 + .../GovernorTimelockCompoundMock.sol | 69 + .../GovernorTimelockControlMock.sol | 69 + .../mocks/governance/GovernorVoteMock.sol | 20 + .../governance/GovernorWithParamsMock.sol | 51 + .../contracts/mocks/proxy/BadBeacon.sol | 11 + .../mocks/proxy/ClashingImplementation.sol | 19 + .../mocks/proxy/UUPSUpgradeableMock.sol | 35 + .../mocks/token/ERC1155ReceiverMock.sol | 74 ++ .../mocks/token/ERC20ApprovalMock.sol | 10 + .../mocks/token/ERC20DecimalsMock.sol | 17 + .../mocks/token/ERC20ExcessDecimalsMock.sol | 9 + .../mocks/token/ERC20FlashMintMock.sol | 26 + .../mocks/token/ERC20ForceApproveMock.sol | 13 + .../contracts/mocks/token/ERC20Mock.sol | 16 + .../mocks/token/ERC20MulticallMock.sol | 8 + .../mocks/token/ERC20NoReturnMock.sol | 28 + .../contracts/mocks/token/ERC20Reentrant.sol | 39 + .../mocks/token/ERC20ReturnFalseMock.sol | 19 + .../mocks/token/ERC20VotesLegacyMock.sol | 253 ++++ .../mocks/token/ERC4626LimitsMock.sol | 23 + .../contracts/mocks/token/ERC4626Mock.sol | 17 + .../mocks/token/ERC4626OffsetMock.sol | 17 + .../contracts/mocks/token/ERC4646FeesMock.sol | 40 + .../token/ERC721ConsecutiveEnumerableMock.sol | 42 + .../mocks/token/ERC721ConsecutiveMock.sol | 61 + .../mocks/token/ERC721ReceiverMock.sol | 47 + .../mocks/token/ERC721URIStorageMock.sol | 17 + .../contracts/mocks/token/VotesTimestamp.sol | 29 + .../contracts/package.json | 32 + .../contracts/proxy/Clones.sol | 95 ++ .../contracts/proxy/ERC1967/ERC1967Proxy.sol | 40 + .../contracts/proxy/ERC1967/ERC1967Utils.sol | 193 +++ .../contracts/proxy/Proxy.sol | 69 + .../contracts/proxy/README.adoc | 87 ++ .../contracts/proxy/beacon/BeaconProxy.sol | 57 + .../contracts/proxy/beacon/IBeacon.sol | 16 + .../proxy/beacon/UpgradeableBeacon.sol | 70 + .../proxy/transparent/ProxyAdmin.sol | 45 + .../TransparentUpgradeableProxy.sol | 116 ++ .../contracts/proxy/utils/Initializable.sol | 228 ++++ .../contracts/proxy/utils/UUPSUpgradeable.sol | 147 +++ .../contracts/token/ERC1155/ERC1155.sol | 468 +++++++ .../contracts/token/ERC1155/IERC1155.sol | 127 ++ .../token/ERC1155/IERC1155Receiver.sol | 59 + .../contracts/token/ERC1155/README.adoc | 41 + .../ERC1155/extensions/ERC1155Burnable.sol | 28 + .../ERC1155/extensions/ERC1155Pausable.sol | 38 + .../ERC1155/extensions/ERC1155Supply.sol | 87 ++ .../ERC1155/extensions/ERC1155URIStorage.sol | 61 + .../extensions/IERC1155MetadataURI.sol | 20 + .../token/ERC1155/utils/ERC1155Holder.sol | 42 + .../contracts/token/ERC20/ERC20.sol | 316 +++++ .../contracts/token/ERC20/IERC20.sol | 79 ++ .../contracts/token/ERC20/README.adoc | 67 + .../token/ERC20/extensions/ERC20Burnable.sol | 39 + .../token/ERC20/extensions/ERC20Capped.sol | 56 + .../token/ERC20/extensions/ERC20FlashMint.sol | 134 ++ .../token/ERC20/extensions/ERC20Pausable.sol | 33 + .../token/ERC20/extensions/ERC20Permit.sol | 83 ++ .../token/ERC20/extensions/ERC20Votes.sol | 83 ++ .../token/ERC20/extensions/ERC20Wrapper.sol | 86 ++ .../token/ERC20/extensions/ERC4626.sol | 286 ++++ .../token/ERC20/extensions/IERC20Metadata.sol | 26 + .../token/ERC20/extensions/IERC20Permit.sol | 90 ++ .../contracts/token/ERC20/utils/SafeERC20.sol | 118 ++ .../contracts/token/ERC721/ERC721.sol | 483 +++++++ .../contracts/token/ERC721/IERC721.sol | 135 ++ .../token/ERC721/IERC721Receiver.sol | 28 + .../contracts/token/ERC721/README.adoc | 67 + .../ERC721/extensions/ERC721Burnable.sol | 26 + .../ERC721/extensions/ERC721Consecutive.sol | 176 +++ .../ERC721/extensions/ERC721Enumerable.sol | 172 +++ .../ERC721/extensions/ERC721Pausable.sol | 37 + .../token/ERC721/extensions/ERC721Royalty.sol | 27 + .../ERC721/extensions/ERC721URIStorage.sol | 61 + .../token/ERC721/extensions/ERC721Votes.sol | 47 + .../token/ERC721/extensions/ERC721Wrapper.sol | 102 ++ .../ERC721/extensions/IERC721Enumerable.sol | 29 + .../ERC721/extensions/IERC721Metadata.sol | 27 + .../token/ERC721/utils/ERC721Holder.sol | 24 + .../contracts/token/common/ERC2981.sol | 137 ++ .../contracts/token/common/README.adoc | 10 + .../contracts/utils/Address.sol | 159 +++ .../contracts/utils/Arrays.sol | 127 ++ .../contracts/utils/Base64.sol | 99 ++ .../contracts/utils/Context.sol | 28 + .../contracts/utils/Create2.sol | 96 ++ .../contracts/utils/Multicall.sol | 37 + .../contracts/utils/Nonces.sol | 46 + .../contracts/utils/Pausable.sol | 119 ++ .../contracts/utils/README.adoc | 108 ++ .../contracts/utils/ReentrancyGuard.sol | 84 ++ .../contracts/utils/ShortStrings.sol | 123 ++ .../contracts/utils/StorageSlot.sol | 135 ++ .../contracts/utils/Strings.sol | 94 ++ .../contracts/utils/cryptography/ECDSA.sol | 174 +++ .../contracts/utils/cryptography/EIP712.sol | 160 +++ .../utils/cryptography/MerkleProof.sol | 232 ++++ .../utils/cryptography/MessageHashUtils.sol | 86 ++ .../utils/cryptography/SignatureChecker.sol | 48 + .../contracts/utils/introspection/ERC165.sol | 27 + .../utils/introspection/ERC165Checker.sol | 124 ++ .../contracts/utils/introspection/IERC165.sol | 25 + .../contracts/utils/math/Math.sol | 415 ++++++ .../contracts/utils/math/SafeCast.sol | 1153 +++++++++++++++++ .../contracts/utils/math/SignedMath.sol | 43 + .../contracts/utils/structs/BitMaps.sol | 60 + .../contracts/utils/structs/Checkpoints.sol | 603 +++++++++ .../utils/structs/DoubleEndedQueue.sol | 169 +++ .../contracts/utils/structs/EnumerableMap.sol | 533 ++++++++ .../contracts/utils/structs/EnumerableSet.sol | 378 ++++++ .../contracts/utils/types/Time.sol | 130 ++ .../vendor/compound/ICompoundTimelock.sol | 86 ++ .../contracts/vendor/compound/LICENSE | 11 + 219 files changed, 21731 insertions(+) create mode 100644 CVLByExample/UniswapHooks/lib/uniswap-hooks/lib/v4-core/lib/openzeppelin-contracts/contracts/access/AccessControl.sol create mode 100644 CVLByExample/UniswapHooks/lib/uniswap-hooks/lib/v4-core/lib/openzeppelin-contracts/contracts/access/IAccessControl.sol create mode 100644 CVLByExample/UniswapHooks/lib/uniswap-hooks/lib/v4-core/lib/openzeppelin-contracts/contracts/access/Ownable.sol create mode 100644 CVLByExample/UniswapHooks/lib/uniswap-hooks/lib/v4-core/lib/openzeppelin-contracts/contracts/access/Ownable2Step.sol create mode 100644 CVLByExample/UniswapHooks/lib/uniswap-hooks/lib/v4-core/lib/openzeppelin-contracts/contracts/access/README.adoc create mode 100644 CVLByExample/UniswapHooks/lib/uniswap-hooks/lib/v4-core/lib/openzeppelin-contracts/contracts/access/extensions/AccessControlDefaultAdminRules.sol create mode 100644 CVLByExample/UniswapHooks/lib/uniswap-hooks/lib/v4-core/lib/openzeppelin-contracts/contracts/access/extensions/AccessControlEnumerable.sol create mode 100644 CVLByExample/UniswapHooks/lib/uniswap-hooks/lib/v4-core/lib/openzeppelin-contracts/contracts/access/extensions/IAccessControlDefaultAdminRules.sol create mode 100644 CVLByExample/UniswapHooks/lib/uniswap-hooks/lib/v4-core/lib/openzeppelin-contracts/contracts/access/extensions/IAccessControlEnumerable.sol create mode 100644 CVLByExample/UniswapHooks/lib/uniswap-hooks/lib/v4-core/lib/openzeppelin-contracts/contracts/access/manager/AccessManaged.sol create mode 100644 CVLByExample/UniswapHooks/lib/uniswap-hooks/lib/v4-core/lib/openzeppelin-contracts/contracts/access/manager/AccessManager.sol create mode 100644 CVLByExample/UniswapHooks/lib/uniswap-hooks/lib/v4-core/lib/openzeppelin-contracts/contracts/access/manager/AuthorityUtils.sol create mode 100644 CVLByExample/UniswapHooks/lib/uniswap-hooks/lib/v4-core/lib/openzeppelin-contracts/contracts/access/manager/IAccessManaged.sol create mode 100644 CVLByExample/UniswapHooks/lib/uniswap-hooks/lib/v4-core/lib/openzeppelin-contracts/contracts/access/manager/IAccessManager.sol create mode 100644 CVLByExample/UniswapHooks/lib/uniswap-hooks/lib/v4-core/lib/openzeppelin-contracts/contracts/access/manager/IAuthority.sol create mode 100644 CVLByExample/UniswapHooks/lib/uniswap-hooks/lib/v4-core/lib/openzeppelin-contracts/contracts/finance/README.adoc create mode 100644 CVLByExample/UniswapHooks/lib/uniswap-hooks/lib/v4-core/lib/openzeppelin-contracts/contracts/finance/VestingWallet.sol create mode 100644 CVLByExample/UniswapHooks/lib/uniswap-hooks/lib/v4-core/lib/openzeppelin-contracts/contracts/governance/Governor.sol create mode 100644 CVLByExample/UniswapHooks/lib/uniswap-hooks/lib/v4-core/lib/openzeppelin-contracts/contracts/governance/IGovernor.sol create mode 100644 CVLByExample/UniswapHooks/lib/uniswap-hooks/lib/v4-core/lib/openzeppelin-contracts/contracts/governance/README.adoc create mode 100644 CVLByExample/UniswapHooks/lib/uniswap-hooks/lib/v4-core/lib/openzeppelin-contracts/contracts/governance/TimelockController.sol create mode 100644 CVLByExample/UniswapHooks/lib/uniswap-hooks/lib/v4-core/lib/openzeppelin-contracts/contracts/governance/extensions/GovernorCountingSimple.sol create mode 100644 CVLByExample/UniswapHooks/lib/uniswap-hooks/lib/v4-core/lib/openzeppelin-contracts/contracts/governance/extensions/GovernorPreventLateQuorum.sol create mode 100644 CVLByExample/UniswapHooks/lib/uniswap-hooks/lib/v4-core/lib/openzeppelin-contracts/contracts/governance/extensions/GovernorSettings.sol create mode 100644 CVLByExample/UniswapHooks/lib/uniswap-hooks/lib/v4-core/lib/openzeppelin-contracts/contracts/governance/extensions/GovernorStorage.sol create mode 100644 CVLByExample/UniswapHooks/lib/uniswap-hooks/lib/v4-core/lib/openzeppelin-contracts/contracts/governance/extensions/GovernorTimelockAccess.sol create mode 100644 CVLByExample/UniswapHooks/lib/uniswap-hooks/lib/v4-core/lib/openzeppelin-contracts/contracts/governance/extensions/GovernorTimelockCompound.sol create mode 100644 CVLByExample/UniswapHooks/lib/uniswap-hooks/lib/v4-core/lib/openzeppelin-contracts/contracts/governance/extensions/GovernorTimelockControl.sol create mode 100644 CVLByExample/UniswapHooks/lib/uniswap-hooks/lib/v4-core/lib/openzeppelin-contracts/contracts/governance/extensions/GovernorVotes.sol create mode 100644 CVLByExample/UniswapHooks/lib/uniswap-hooks/lib/v4-core/lib/openzeppelin-contracts/contracts/governance/extensions/GovernorVotesQuorumFraction.sol create mode 100644 CVLByExample/UniswapHooks/lib/uniswap-hooks/lib/v4-core/lib/openzeppelin-contracts/contracts/governance/utils/IVotes.sol create mode 100644 CVLByExample/UniswapHooks/lib/uniswap-hooks/lib/v4-core/lib/openzeppelin-contracts/contracts/governance/utils/Votes.sol create mode 100644 CVLByExample/UniswapHooks/lib/uniswap-hooks/lib/v4-core/lib/openzeppelin-contracts/contracts/interfaces/IERC1155.sol create mode 100644 CVLByExample/UniswapHooks/lib/uniswap-hooks/lib/v4-core/lib/openzeppelin-contracts/contracts/interfaces/IERC1155MetadataURI.sol create mode 100644 CVLByExample/UniswapHooks/lib/uniswap-hooks/lib/v4-core/lib/openzeppelin-contracts/contracts/interfaces/IERC1155Receiver.sol create mode 100644 CVLByExample/UniswapHooks/lib/uniswap-hooks/lib/v4-core/lib/openzeppelin-contracts/contracts/interfaces/IERC1271.sol create mode 100644 CVLByExample/UniswapHooks/lib/uniswap-hooks/lib/v4-core/lib/openzeppelin-contracts/contracts/interfaces/IERC1363.sol create mode 100644 CVLByExample/UniswapHooks/lib/uniswap-hooks/lib/v4-core/lib/openzeppelin-contracts/contracts/interfaces/IERC1363Receiver.sol create mode 100644 CVLByExample/UniswapHooks/lib/uniswap-hooks/lib/v4-core/lib/openzeppelin-contracts/contracts/interfaces/IERC1363Spender.sol create mode 100644 CVLByExample/UniswapHooks/lib/uniswap-hooks/lib/v4-core/lib/openzeppelin-contracts/contracts/interfaces/IERC165.sol create mode 100644 CVLByExample/UniswapHooks/lib/uniswap-hooks/lib/v4-core/lib/openzeppelin-contracts/contracts/interfaces/IERC1820Implementer.sol create mode 100644 CVLByExample/UniswapHooks/lib/uniswap-hooks/lib/v4-core/lib/openzeppelin-contracts/contracts/interfaces/IERC1820Registry.sol create mode 100644 CVLByExample/UniswapHooks/lib/uniswap-hooks/lib/v4-core/lib/openzeppelin-contracts/contracts/interfaces/IERC1967.sol create mode 100644 CVLByExample/UniswapHooks/lib/uniswap-hooks/lib/v4-core/lib/openzeppelin-contracts/contracts/interfaces/IERC20.sol create mode 100644 CVLByExample/UniswapHooks/lib/uniswap-hooks/lib/v4-core/lib/openzeppelin-contracts/contracts/interfaces/IERC20Metadata.sol create mode 100644 CVLByExample/UniswapHooks/lib/uniswap-hooks/lib/v4-core/lib/openzeppelin-contracts/contracts/interfaces/IERC2309.sol create mode 100644 CVLByExample/UniswapHooks/lib/uniswap-hooks/lib/v4-core/lib/openzeppelin-contracts/contracts/interfaces/IERC2612.sol create mode 100644 CVLByExample/UniswapHooks/lib/uniswap-hooks/lib/v4-core/lib/openzeppelin-contracts/contracts/interfaces/IERC2981.sol create mode 100644 CVLByExample/UniswapHooks/lib/uniswap-hooks/lib/v4-core/lib/openzeppelin-contracts/contracts/interfaces/IERC3156.sol create mode 100644 CVLByExample/UniswapHooks/lib/uniswap-hooks/lib/v4-core/lib/openzeppelin-contracts/contracts/interfaces/IERC3156FlashBorrower.sol create mode 100644 CVLByExample/UniswapHooks/lib/uniswap-hooks/lib/v4-core/lib/openzeppelin-contracts/contracts/interfaces/IERC3156FlashLender.sol create mode 100644 CVLByExample/UniswapHooks/lib/uniswap-hooks/lib/v4-core/lib/openzeppelin-contracts/contracts/interfaces/IERC4626.sol create mode 100644 CVLByExample/UniswapHooks/lib/uniswap-hooks/lib/v4-core/lib/openzeppelin-contracts/contracts/interfaces/IERC4906.sol create mode 100644 CVLByExample/UniswapHooks/lib/uniswap-hooks/lib/v4-core/lib/openzeppelin-contracts/contracts/interfaces/IERC5267.sol create mode 100644 CVLByExample/UniswapHooks/lib/uniswap-hooks/lib/v4-core/lib/openzeppelin-contracts/contracts/interfaces/IERC5313.sol create mode 100644 CVLByExample/UniswapHooks/lib/uniswap-hooks/lib/v4-core/lib/openzeppelin-contracts/contracts/interfaces/IERC5805.sol create mode 100644 CVLByExample/UniswapHooks/lib/uniswap-hooks/lib/v4-core/lib/openzeppelin-contracts/contracts/interfaces/IERC6372.sol create mode 100644 CVLByExample/UniswapHooks/lib/uniswap-hooks/lib/v4-core/lib/openzeppelin-contracts/contracts/interfaces/IERC721.sol create mode 100644 CVLByExample/UniswapHooks/lib/uniswap-hooks/lib/v4-core/lib/openzeppelin-contracts/contracts/interfaces/IERC721Enumerable.sol create mode 100644 CVLByExample/UniswapHooks/lib/uniswap-hooks/lib/v4-core/lib/openzeppelin-contracts/contracts/interfaces/IERC721Metadata.sol create mode 100644 CVLByExample/UniswapHooks/lib/uniswap-hooks/lib/v4-core/lib/openzeppelin-contracts/contracts/interfaces/IERC721Receiver.sol create mode 100644 CVLByExample/UniswapHooks/lib/uniswap-hooks/lib/v4-core/lib/openzeppelin-contracts/contracts/interfaces/IERC777.sol create mode 100644 CVLByExample/UniswapHooks/lib/uniswap-hooks/lib/v4-core/lib/openzeppelin-contracts/contracts/interfaces/IERC777Recipient.sol create mode 100644 CVLByExample/UniswapHooks/lib/uniswap-hooks/lib/v4-core/lib/openzeppelin-contracts/contracts/interfaces/IERC777Sender.sol create mode 100644 CVLByExample/UniswapHooks/lib/uniswap-hooks/lib/v4-core/lib/openzeppelin-contracts/contracts/interfaces/README.adoc create mode 100644 CVLByExample/UniswapHooks/lib/uniswap-hooks/lib/v4-core/lib/openzeppelin-contracts/contracts/interfaces/draft-IERC1822.sol create mode 100644 CVLByExample/UniswapHooks/lib/uniswap-hooks/lib/v4-core/lib/openzeppelin-contracts/contracts/interfaces/draft-IERC6093.sol create mode 100644 CVLByExample/UniswapHooks/lib/uniswap-hooks/lib/v4-core/lib/openzeppelin-contracts/contracts/metatx/ERC2771Context.sol create mode 100644 CVLByExample/UniswapHooks/lib/uniswap-hooks/lib/v4-core/lib/openzeppelin-contracts/contracts/metatx/ERC2771Forwarder.sol create mode 100644 CVLByExample/UniswapHooks/lib/uniswap-hooks/lib/v4-core/lib/openzeppelin-contracts/contracts/metatx/README.adoc create mode 100644 CVLByExample/UniswapHooks/lib/uniswap-hooks/lib/v4-core/lib/openzeppelin-contracts/contracts/mocks/AccessManagedTarget.sol create mode 100644 CVLByExample/UniswapHooks/lib/uniswap-hooks/lib/v4-core/lib/openzeppelin-contracts/contracts/mocks/ArraysMock.sol create mode 100644 CVLByExample/UniswapHooks/lib/uniswap-hooks/lib/v4-core/lib/openzeppelin-contracts/contracts/mocks/AuthorityMock.sol create mode 100644 CVLByExample/UniswapHooks/lib/uniswap-hooks/lib/v4-core/lib/openzeppelin-contracts/contracts/mocks/Base64Dirty.sol create mode 100644 CVLByExample/UniswapHooks/lib/uniswap-hooks/lib/v4-core/lib/openzeppelin-contracts/contracts/mocks/CallReceiverMock.sol create mode 100644 CVLByExample/UniswapHooks/lib/uniswap-hooks/lib/v4-core/lib/openzeppelin-contracts/contracts/mocks/ContextMock.sol create mode 100644 CVLByExample/UniswapHooks/lib/uniswap-hooks/lib/v4-core/lib/openzeppelin-contracts/contracts/mocks/DummyImplementation.sol create mode 100644 CVLByExample/UniswapHooks/lib/uniswap-hooks/lib/v4-core/lib/openzeppelin-contracts/contracts/mocks/EIP712Verifier.sol create mode 100644 CVLByExample/UniswapHooks/lib/uniswap-hooks/lib/v4-core/lib/openzeppelin-contracts/contracts/mocks/ERC1271WalletMock.sol create mode 100644 CVLByExample/UniswapHooks/lib/uniswap-hooks/lib/v4-core/lib/openzeppelin-contracts/contracts/mocks/ERC165/ERC165InterfacesSupported.sol create mode 100644 CVLByExample/UniswapHooks/lib/uniswap-hooks/lib/v4-core/lib/openzeppelin-contracts/contracts/mocks/ERC165/ERC165MaliciousData.sol create mode 100644 CVLByExample/UniswapHooks/lib/uniswap-hooks/lib/v4-core/lib/openzeppelin-contracts/contracts/mocks/ERC165/ERC165MissingData.sol create mode 100644 CVLByExample/UniswapHooks/lib/uniswap-hooks/lib/v4-core/lib/openzeppelin-contracts/contracts/mocks/ERC165/ERC165NotSupported.sol create mode 100644 CVLByExample/UniswapHooks/lib/uniswap-hooks/lib/v4-core/lib/openzeppelin-contracts/contracts/mocks/ERC165/ERC165ReturnBomb.sol create mode 100644 CVLByExample/UniswapHooks/lib/uniswap-hooks/lib/v4-core/lib/openzeppelin-contracts/contracts/mocks/ERC2771ContextMock.sol create mode 100644 CVLByExample/UniswapHooks/lib/uniswap-hooks/lib/v4-core/lib/openzeppelin-contracts/contracts/mocks/ERC3156FlashBorrowerMock.sol create mode 100644 CVLByExample/UniswapHooks/lib/uniswap-hooks/lib/v4-core/lib/openzeppelin-contracts/contracts/mocks/EtherReceiverMock.sol create mode 100644 CVLByExample/UniswapHooks/lib/uniswap-hooks/lib/v4-core/lib/openzeppelin-contracts/contracts/mocks/InitializableMock.sol create mode 100644 CVLByExample/UniswapHooks/lib/uniswap-hooks/lib/v4-core/lib/openzeppelin-contracts/contracts/mocks/MulticallTest.sol create mode 100644 CVLByExample/UniswapHooks/lib/uniswap-hooks/lib/v4-core/lib/openzeppelin-contracts/contracts/mocks/MultipleInheritanceInitializableMocks.sol create mode 100644 CVLByExample/UniswapHooks/lib/uniswap-hooks/lib/v4-core/lib/openzeppelin-contracts/contracts/mocks/PausableMock.sol create mode 100644 CVLByExample/UniswapHooks/lib/uniswap-hooks/lib/v4-core/lib/openzeppelin-contracts/contracts/mocks/ReentrancyAttack.sol create mode 100644 CVLByExample/UniswapHooks/lib/uniswap-hooks/lib/v4-core/lib/openzeppelin-contracts/contracts/mocks/ReentrancyMock.sol create mode 100644 CVLByExample/UniswapHooks/lib/uniswap-hooks/lib/v4-core/lib/openzeppelin-contracts/contracts/mocks/RegressionImplementation.sol create mode 100644 CVLByExample/UniswapHooks/lib/uniswap-hooks/lib/v4-core/lib/openzeppelin-contracts/contracts/mocks/SingleInheritanceInitializableMocks.sol create mode 100644 CVLByExample/UniswapHooks/lib/uniswap-hooks/lib/v4-core/lib/openzeppelin-contracts/contracts/mocks/Stateless.sol create mode 100644 CVLByExample/UniswapHooks/lib/uniswap-hooks/lib/v4-core/lib/openzeppelin-contracts/contracts/mocks/StorageSlotMock.sol create mode 100644 CVLByExample/UniswapHooks/lib/uniswap-hooks/lib/v4-core/lib/openzeppelin-contracts/contracts/mocks/TimelockReentrant.sol create mode 100644 CVLByExample/UniswapHooks/lib/uniswap-hooks/lib/v4-core/lib/openzeppelin-contracts/contracts/mocks/UpgradeableBeaconMock.sol create mode 100644 CVLByExample/UniswapHooks/lib/uniswap-hooks/lib/v4-core/lib/openzeppelin-contracts/contracts/mocks/VotesMock.sol create mode 100644 CVLByExample/UniswapHooks/lib/uniswap-hooks/lib/v4-core/lib/openzeppelin-contracts/contracts/mocks/compound/CompTimelock.sol create mode 100644 CVLByExample/UniswapHooks/lib/uniswap-hooks/lib/v4-core/lib/openzeppelin-contracts/contracts/mocks/governance/GovernorMock.sol create mode 100644 CVLByExample/UniswapHooks/lib/uniswap-hooks/lib/v4-core/lib/openzeppelin-contracts/contracts/mocks/governance/GovernorPreventLateQuorumMock.sol create mode 100644 CVLByExample/UniswapHooks/lib/uniswap-hooks/lib/v4-core/lib/openzeppelin-contracts/contracts/mocks/governance/GovernorStorageMock.sol create mode 100644 CVLByExample/UniswapHooks/lib/uniswap-hooks/lib/v4-core/lib/openzeppelin-contracts/contracts/mocks/governance/GovernorTimelockAccessMock.sol create mode 100644 CVLByExample/UniswapHooks/lib/uniswap-hooks/lib/v4-core/lib/openzeppelin-contracts/contracts/mocks/governance/GovernorTimelockCompoundMock.sol create mode 100644 CVLByExample/UniswapHooks/lib/uniswap-hooks/lib/v4-core/lib/openzeppelin-contracts/contracts/mocks/governance/GovernorTimelockControlMock.sol create mode 100644 CVLByExample/UniswapHooks/lib/uniswap-hooks/lib/v4-core/lib/openzeppelin-contracts/contracts/mocks/governance/GovernorVoteMock.sol create mode 100644 CVLByExample/UniswapHooks/lib/uniswap-hooks/lib/v4-core/lib/openzeppelin-contracts/contracts/mocks/governance/GovernorWithParamsMock.sol create mode 100644 CVLByExample/UniswapHooks/lib/uniswap-hooks/lib/v4-core/lib/openzeppelin-contracts/contracts/mocks/proxy/BadBeacon.sol create mode 100644 CVLByExample/UniswapHooks/lib/uniswap-hooks/lib/v4-core/lib/openzeppelin-contracts/contracts/mocks/proxy/ClashingImplementation.sol create mode 100644 CVLByExample/UniswapHooks/lib/uniswap-hooks/lib/v4-core/lib/openzeppelin-contracts/contracts/mocks/proxy/UUPSUpgradeableMock.sol create mode 100644 CVLByExample/UniswapHooks/lib/uniswap-hooks/lib/v4-core/lib/openzeppelin-contracts/contracts/mocks/token/ERC1155ReceiverMock.sol create mode 100644 CVLByExample/UniswapHooks/lib/uniswap-hooks/lib/v4-core/lib/openzeppelin-contracts/contracts/mocks/token/ERC20ApprovalMock.sol create mode 100644 CVLByExample/UniswapHooks/lib/uniswap-hooks/lib/v4-core/lib/openzeppelin-contracts/contracts/mocks/token/ERC20DecimalsMock.sol create mode 100644 CVLByExample/UniswapHooks/lib/uniswap-hooks/lib/v4-core/lib/openzeppelin-contracts/contracts/mocks/token/ERC20ExcessDecimalsMock.sol create mode 100644 CVLByExample/UniswapHooks/lib/uniswap-hooks/lib/v4-core/lib/openzeppelin-contracts/contracts/mocks/token/ERC20FlashMintMock.sol create mode 100644 CVLByExample/UniswapHooks/lib/uniswap-hooks/lib/v4-core/lib/openzeppelin-contracts/contracts/mocks/token/ERC20ForceApproveMock.sol create mode 100644 CVLByExample/UniswapHooks/lib/uniswap-hooks/lib/v4-core/lib/openzeppelin-contracts/contracts/mocks/token/ERC20Mock.sol create mode 100644 CVLByExample/UniswapHooks/lib/uniswap-hooks/lib/v4-core/lib/openzeppelin-contracts/contracts/mocks/token/ERC20MulticallMock.sol create mode 100644 CVLByExample/UniswapHooks/lib/uniswap-hooks/lib/v4-core/lib/openzeppelin-contracts/contracts/mocks/token/ERC20NoReturnMock.sol create mode 100644 CVLByExample/UniswapHooks/lib/uniswap-hooks/lib/v4-core/lib/openzeppelin-contracts/contracts/mocks/token/ERC20Reentrant.sol create mode 100644 CVLByExample/UniswapHooks/lib/uniswap-hooks/lib/v4-core/lib/openzeppelin-contracts/contracts/mocks/token/ERC20ReturnFalseMock.sol create mode 100644 CVLByExample/UniswapHooks/lib/uniswap-hooks/lib/v4-core/lib/openzeppelin-contracts/contracts/mocks/token/ERC20VotesLegacyMock.sol create mode 100644 CVLByExample/UniswapHooks/lib/uniswap-hooks/lib/v4-core/lib/openzeppelin-contracts/contracts/mocks/token/ERC4626LimitsMock.sol create mode 100644 CVLByExample/UniswapHooks/lib/uniswap-hooks/lib/v4-core/lib/openzeppelin-contracts/contracts/mocks/token/ERC4626Mock.sol create mode 100644 CVLByExample/UniswapHooks/lib/uniswap-hooks/lib/v4-core/lib/openzeppelin-contracts/contracts/mocks/token/ERC4626OffsetMock.sol create mode 100644 CVLByExample/UniswapHooks/lib/uniswap-hooks/lib/v4-core/lib/openzeppelin-contracts/contracts/mocks/token/ERC4646FeesMock.sol create mode 100644 CVLByExample/UniswapHooks/lib/uniswap-hooks/lib/v4-core/lib/openzeppelin-contracts/contracts/mocks/token/ERC721ConsecutiveEnumerableMock.sol create mode 100644 CVLByExample/UniswapHooks/lib/uniswap-hooks/lib/v4-core/lib/openzeppelin-contracts/contracts/mocks/token/ERC721ConsecutiveMock.sol create mode 100644 CVLByExample/UniswapHooks/lib/uniswap-hooks/lib/v4-core/lib/openzeppelin-contracts/contracts/mocks/token/ERC721ReceiverMock.sol create mode 100644 CVLByExample/UniswapHooks/lib/uniswap-hooks/lib/v4-core/lib/openzeppelin-contracts/contracts/mocks/token/ERC721URIStorageMock.sol create mode 100644 CVLByExample/UniswapHooks/lib/uniswap-hooks/lib/v4-core/lib/openzeppelin-contracts/contracts/mocks/token/VotesTimestamp.sol create mode 100644 CVLByExample/UniswapHooks/lib/uniswap-hooks/lib/v4-core/lib/openzeppelin-contracts/contracts/package.json create mode 100644 CVLByExample/UniswapHooks/lib/uniswap-hooks/lib/v4-core/lib/openzeppelin-contracts/contracts/proxy/Clones.sol create mode 100644 CVLByExample/UniswapHooks/lib/uniswap-hooks/lib/v4-core/lib/openzeppelin-contracts/contracts/proxy/ERC1967/ERC1967Proxy.sol create mode 100644 CVLByExample/UniswapHooks/lib/uniswap-hooks/lib/v4-core/lib/openzeppelin-contracts/contracts/proxy/ERC1967/ERC1967Utils.sol create mode 100644 CVLByExample/UniswapHooks/lib/uniswap-hooks/lib/v4-core/lib/openzeppelin-contracts/contracts/proxy/Proxy.sol create mode 100644 CVLByExample/UniswapHooks/lib/uniswap-hooks/lib/v4-core/lib/openzeppelin-contracts/contracts/proxy/README.adoc create mode 100644 CVLByExample/UniswapHooks/lib/uniswap-hooks/lib/v4-core/lib/openzeppelin-contracts/contracts/proxy/beacon/BeaconProxy.sol create mode 100644 CVLByExample/UniswapHooks/lib/uniswap-hooks/lib/v4-core/lib/openzeppelin-contracts/contracts/proxy/beacon/IBeacon.sol create mode 100644 CVLByExample/UniswapHooks/lib/uniswap-hooks/lib/v4-core/lib/openzeppelin-contracts/contracts/proxy/beacon/UpgradeableBeacon.sol create mode 100644 CVLByExample/UniswapHooks/lib/uniswap-hooks/lib/v4-core/lib/openzeppelin-contracts/contracts/proxy/transparent/ProxyAdmin.sol create mode 100644 CVLByExample/UniswapHooks/lib/uniswap-hooks/lib/v4-core/lib/openzeppelin-contracts/contracts/proxy/transparent/TransparentUpgradeableProxy.sol create mode 100644 CVLByExample/UniswapHooks/lib/uniswap-hooks/lib/v4-core/lib/openzeppelin-contracts/contracts/proxy/utils/Initializable.sol create mode 100644 CVLByExample/UniswapHooks/lib/uniswap-hooks/lib/v4-core/lib/openzeppelin-contracts/contracts/proxy/utils/UUPSUpgradeable.sol create mode 100644 CVLByExample/UniswapHooks/lib/uniswap-hooks/lib/v4-core/lib/openzeppelin-contracts/contracts/token/ERC1155/ERC1155.sol create mode 100644 CVLByExample/UniswapHooks/lib/uniswap-hooks/lib/v4-core/lib/openzeppelin-contracts/contracts/token/ERC1155/IERC1155.sol create mode 100644 CVLByExample/UniswapHooks/lib/uniswap-hooks/lib/v4-core/lib/openzeppelin-contracts/contracts/token/ERC1155/IERC1155Receiver.sol create mode 100644 CVLByExample/UniswapHooks/lib/uniswap-hooks/lib/v4-core/lib/openzeppelin-contracts/contracts/token/ERC1155/README.adoc create mode 100644 CVLByExample/UniswapHooks/lib/uniswap-hooks/lib/v4-core/lib/openzeppelin-contracts/contracts/token/ERC1155/extensions/ERC1155Burnable.sol create mode 100644 CVLByExample/UniswapHooks/lib/uniswap-hooks/lib/v4-core/lib/openzeppelin-contracts/contracts/token/ERC1155/extensions/ERC1155Pausable.sol create mode 100644 CVLByExample/UniswapHooks/lib/uniswap-hooks/lib/v4-core/lib/openzeppelin-contracts/contracts/token/ERC1155/extensions/ERC1155Supply.sol create mode 100644 CVLByExample/UniswapHooks/lib/uniswap-hooks/lib/v4-core/lib/openzeppelin-contracts/contracts/token/ERC1155/extensions/ERC1155URIStorage.sol create mode 100644 CVLByExample/UniswapHooks/lib/uniswap-hooks/lib/v4-core/lib/openzeppelin-contracts/contracts/token/ERC1155/extensions/IERC1155MetadataURI.sol create mode 100644 CVLByExample/UniswapHooks/lib/uniswap-hooks/lib/v4-core/lib/openzeppelin-contracts/contracts/token/ERC1155/utils/ERC1155Holder.sol create mode 100644 CVLByExample/UniswapHooks/lib/uniswap-hooks/lib/v4-core/lib/openzeppelin-contracts/contracts/token/ERC20/ERC20.sol create mode 100644 CVLByExample/UniswapHooks/lib/uniswap-hooks/lib/v4-core/lib/openzeppelin-contracts/contracts/token/ERC20/IERC20.sol create mode 100644 CVLByExample/UniswapHooks/lib/uniswap-hooks/lib/v4-core/lib/openzeppelin-contracts/contracts/token/ERC20/README.adoc create mode 100644 CVLByExample/UniswapHooks/lib/uniswap-hooks/lib/v4-core/lib/openzeppelin-contracts/contracts/token/ERC20/extensions/ERC20Burnable.sol create mode 100644 CVLByExample/UniswapHooks/lib/uniswap-hooks/lib/v4-core/lib/openzeppelin-contracts/contracts/token/ERC20/extensions/ERC20Capped.sol create mode 100644 CVLByExample/UniswapHooks/lib/uniswap-hooks/lib/v4-core/lib/openzeppelin-contracts/contracts/token/ERC20/extensions/ERC20FlashMint.sol create mode 100644 CVLByExample/UniswapHooks/lib/uniswap-hooks/lib/v4-core/lib/openzeppelin-contracts/contracts/token/ERC20/extensions/ERC20Pausable.sol create mode 100644 CVLByExample/UniswapHooks/lib/uniswap-hooks/lib/v4-core/lib/openzeppelin-contracts/contracts/token/ERC20/extensions/ERC20Permit.sol create mode 100644 CVLByExample/UniswapHooks/lib/uniswap-hooks/lib/v4-core/lib/openzeppelin-contracts/contracts/token/ERC20/extensions/ERC20Votes.sol create mode 100644 CVLByExample/UniswapHooks/lib/uniswap-hooks/lib/v4-core/lib/openzeppelin-contracts/contracts/token/ERC20/extensions/ERC20Wrapper.sol create mode 100644 CVLByExample/UniswapHooks/lib/uniswap-hooks/lib/v4-core/lib/openzeppelin-contracts/contracts/token/ERC20/extensions/ERC4626.sol create mode 100644 CVLByExample/UniswapHooks/lib/uniswap-hooks/lib/v4-core/lib/openzeppelin-contracts/contracts/token/ERC20/extensions/IERC20Metadata.sol create mode 100644 CVLByExample/UniswapHooks/lib/uniswap-hooks/lib/v4-core/lib/openzeppelin-contracts/contracts/token/ERC20/extensions/IERC20Permit.sol create mode 100644 CVLByExample/UniswapHooks/lib/uniswap-hooks/lib/v4-core/lib/openzeppelin-contracts/contracts/token/ERC20/utils/SafeERC20.sol create mode 100644 CVLByExample/UniswapHooks/lib/uniswap-hooks/lib/v4-core/lib/openzeppelin-contracts/contracts/token/ERC721/ERC721.sol create mode 100644 CVLByExample/UniswapHooks/lib/uniswap-hooks/lib/v4-core/lib/openzeppelin-contracts/contracts/token/ERC721/IERC721.sol create mode 100644 CVLByExample/UniswapHooks/lib/uniswap-hooks/lib/v4-core/lib/openzeppelin-contracts/contracts/token/ERC721/IERC721Receiver.sol create mode 100644 CVLByExample/UniswapHooks/lib/uniswap-hooks/lib/v4-core/lib/openzeppelin-contracts/contracts/token/ERC721/README.adoc create mode 100644 CVLByExample/UniswapHooks/lib/uniswap-hooks/lib/v4-core/lib/openzeppelin-contracts/contracts/token/ERC721/extensions/ERC721Burnable.sol create mode 100644 CVLByExample/UniswapHooks/lib/uniswap-hooks/lib/v4-core/lib/openzeppelin-contracts/contracts/token/ERC721/extensions/ERC721Consecutive.sol create mode 100644 CVLByExample/UniswapHooks/lib/uniswap-hooks/lib/v4-core/lib/openzeppelin-contracts/contracts/token/ERC721/extensions/ERC721Enumerable.sol create mode 100644 CVLByExample/UniswapHooks/lib/uniswap-hooks/lib/v4-core/lib/openzeppelin-contracts/contracts/token/ERC721/extensions/ERC721Pausable.sol create mode 100644 CVLByExample/UniswapHooks/lib/uniswap-hooks/lib/v4-core/lib/openzeppelin-contracts/contracts/token/ERC721/extensions/ERC721Royalty.sol create mode 100644 CVLByExample/UniswapHooks/lib/uniswap-hooks/lib/v4-core/lib/openzeppelin-contracts/contracts/token/ERC721/extensions/ERC721URIStorage.sol create mode 100644 CVLByExample/UniswapHooks/lib/uniswap-hooks/lib/v4-core/lib/openzeppelin-contracts/contracts/token/ERC721/extensions/ERC721Votes.sol create mode 100644 CVLByExample/UniswapHooks/lib/uniswap-hooks/lib/v4-core/lib/openzeppelin-contracts/contracts/token/ERC721/extensions/ERC721Wrapper.sol create mode 100644 CVLByExample/UniswapHooks/lib/uniswap-hooks/lib/v4-core/lib/openzeppelin-contracts/contracts/token/ERC721/extensions/IERC721Enumerable.sol create mode 100644 CVLByExample/UniswapHooks/lib/uniswap-hooks/lib/v4-core/lib/openzeppelin-contracts/contracts/token/ERC721/extensions/IERC721Metadata.sol create mode 100644 CVLByExample/UniswapHooks/lib/uniswap-hooks/lib/v4-core/lib/openzeppelin-contracts/contracts/token/ERC721/utils/ERC721Holder.sol create mode 100644 CVLByExample/UniswapHooks/lib/uniswap-hooks/lib/v4-core/lib/openzeppelin-contracts/contracts/token/common/ERC2981.sol create mode 100644 CVLByExample/UniswapHooks/lib/uniswap-hooks/lib/v4-core/lib/openzeppelin-contracts/contracts/token/common/README.adoc create mode 100644 CVLByExample/UniswapHooks/lib/uniswap-hooks/lib/v4-core/lib/openzeppelin-contracts/contracts/utils/Address.sol create mode 100644 CVLByExample/UniswapHooks/lib/uniswap-hooks/lib/v4-core/lib/openzeppelin-contracts/contracts/utils/Arrays.sol create mode 100644 CVLByExample/UniswapHooks/lib/uniswap-hooks/lib/v4-core/lib/openzeppelin-contracts/contracts/utils/Base64.sol create mode 100644 CVLByExample/UniswapHooks/lib/uniswap-hooks/lib/v4-core/lib/openzeppelin-contracts/contracts/utils/Context.sol create mode 100644 CVLByExample/UniswapHooks/lib/uniswap-hooks/lib/v4-core/lib/openzeppelin-contracts/contracts/utils/Create2.sol create mode 100644 CVLByExample/UniswapHooks/lib/uniswap-hooks/lib/v4-core/lib/openzeppelin-contracts/contracts/utils/Multicall.sol create mode 100644 CVLByExample/UniswapHooks/lib/uniswap-hooks/lib/v4-core/lib/openzeppelin-contracts/contracts/utils/Nonces.sol create mode 100644 CVLByExample/UniswapHooks/lib/uniswap-hooks/lib/v4-core/lib/openzeppelin-contracts/contracts/utils/Pausable.sol create mode 100644 CVLByExample/UniswapHooks/lib/uniswap-hooks/lib/v4-core/lib/openzeppelin-contracts/contracts/utils/README.adoc create mode 100644 CVLByExample/UniswapHooks/lib/uniswap-hooks/lib/v4-core/lib/openzeppelin-contracts/contracts/utils/ReentrancyGuard.sol create mode 100644 CVLByExample/UniswapHooks/lib/uniswap-hooks/lib/v4-core/lib/openzeppelin-contracts/contracts/utils/ShortStrings.sol create mode 100644 CVLByExample/UniswapHooks/lib/uniswap-hooks/lib/v4-core/lib/openzeppelin-contracts/contracts/utils/StorageSlot.sol create mode 100644 CVLByExample/UniswapHooks/lib/uniswap-hooks/lib/v4-core/lib/openzeppelin-contracts/contracts/utils/Strings.sol create mode 100644 CVLByExample/UniswapHooks/lib/uniswap-hooks/lib/v4-core/lib/openzeppelin-contracts/contracts/utils/cryptography/ECDSA.sol create mode 100644 CVLByExample/UniswapHooks/lib/uniswap-hooks/lib/v4-core/lib/openzeppelin-contracts/contracts/utils/cryptography/EIP712.sol create mode 100644 CVLByExample/UniswapHooks/lib/uniswap-hooks/lib/v4-core/lib/openzeppelin-contracts/contracts/utils/cryptography/MerkleProof.sol create mode 100644 CVLByExample/UniswapHooks/lib/uniswap-hooks/lib/v4-core/lib/openzeppelin-contracts/contracts/utils/cryptography/MessageHashUtils.sol create mode 100644 CVLByExample/UniswapHooks/lib/uniswap-hooks/lib/v4-core/lib/openzeppelin-contracts/contracts/utils/cryptography/SignatureChecker.sol create mode 100644 CVLByExample/UniswapHooks/lib/uniswap-hooks/lib/v4-core/lib/openzeppelin-contracts/contracts/utils/introspection/ERC165.sol create mode 100644 CVLByExample/UniswapHooks/lib/uniswap-hooks/lib/v4-core/lib/openzeppelin-contracts/contracts/utils/introspection/ERC165Checker.sol create mode 100644 CVLByExample/UniswapHooks/lib/uniswap-hooks/lib/v4-core/lib/openzeppelin-contracts/contracts/utils/introspection/IERC165.sol create mode 100644 CVLByExample/UniswapHooks/lib/uniswap-hooks/lib/v4-core/lib/openzeppelin-contracts/contracts/utils/math/Math.sol create mode 100644 CVLByExample/UniswapHooks/lib/uniswap-hooks/lib/v4-core/lib/openzeppelin-contracts/contracts/utils/math/SafeCast.sol create mode 100644 CVLByExample/UniswapHooks/lib/uniswap-hooks/lib/v4-core/lib/openzeppelin-contracts/contracts/utils/math/SignedMath.sol create mode 100644 CVLByExample/UniswapHooks/lib/uniswap-hooks/lib/v4-core/lib/openzeppelin-contracts/contracts/utils/structs/BitMaps.sol create mode 100644 CVLByExample/UniswapHooks/lib/uniswap-hooks/lib/v4-core/lib/openzeppelin-contracts/contracts/utils/structs/Checkpoints.sol create mode 100644 CVLByExample/UniswapHooks/lib/uniswap-hooks/lib/v4-core/lib/openzeppelin-contracts/contracts/utils/structs/DoubleEndedQueue.sol create mode 100644 CVLByExample/UniswapHooks/lib/uniswap-hooks/lib/v4-core/lib/openzeppelin-contracts/contracts/utils/structs/EnumerableMap.sol create mode 100644 CVLByExample/UniswapHooks/lib/uniswap-hooks/lib/v4-core/lib/openzeppelin-contracts/contracts/utils/structs/EnumerableSet.sol create mode 100644 CVLByExample/UniswapHooks/lib/uniswap-hooks/lib/v4-core/lib/openzeppelin-contracts/contracts/utils/types/Time.sol create mode 100644 CVLByExample/UniswapHooks/lib/uniswap-hooks/lib/v4-core/lib/openzeppelin-contracts/contracts/vendor/compound/ICompoundTimelock.sol create mode 100644 CVLByExample/UniswapHooks/lib/uniswap-hooks/lib/v4-core/lib/openzeppelin-contracts/contracts/vendor/compound/LICENSE diff --git a/CVLByExample/UniswapHooks/lib/uniswap-hooks/lib/v4-core/lib/openzeppelin-contracts/contracts/access/AccessControl.sol b/CVLByExample/UniswapHooks/lib/uniswap-hooks/lib/v4-core/lib/openzeppelin-contracts/contracts/access/AccessControl.sol new file mode 100644 index 00000000..3e3341e9 --- /dev/null +++ b/CVLByExample/UniswapHooks/lib/uniswap-hooks/lib/v4-core/lib/openzeppelin-contracts/contracts/access/AccessControl.sol @@ -0,0 +1,209 @@ +// SPDX-License-Identifier: MIT +// OpenZeppelin Contracts (last updated v5.0.0) (access/AccessControl.sol) + +pragma solidity ^0.8.20; + +import {IAccessControl} from "./IAccessControl.sol"; +import {Context} from "../utils/Context.sol"; +import {ERC165} from "../utils/introspection/ERC165.sol"; + +/** + * @dev Contract module that allows children to implement role-based access + * control mechanisms. This is a lightweight version that doesn't allow enumerating role + * members except through off-chain means by accessing the contract event logs. Some + * applications may benefit from on-chain enumerability, for those cases see + * {AccessControlEnumerable}. + * + * Roles are referred to by their `bytes32` identifier. These should be exposed + * in the external API and be unique. The best way to achieve this is by + * using `public constant` hash digests: + * + * ```solidity + * bytes32 public constant MY_ROLE = keccak256("MY_ROLE"); + * ``` + * + * Roles can be used to represent a set of permissions. To restrict access to a + * function call, use {hasRole}: + * + * ```solidity + * function foo() public { + * require(hasRole(MY_ROLE, msg.sender)); + * ... + * } + * ``` + * + * Roles can be granted and revoked dynamically via the {grantRole} and + * {revokeRole} functions. Each role has an associated admin role, and only + * accounts that have a role's admin role can call {grantRole} and {revokeRole}. + * + * By default, the admin role for all roles is `DEFAULT_ADMIN_ROLE`, which means + * that only accounts with this role will be able to grant or revoke other + * roles. More complex role relationships can be created by using + * {_setRoleAdmin}. + * + * WARNING: The `DEFAULT_ADMIN_ROLE` is also its own admin: it has permission to + * grant and revoke this role. Extra precautions should be taken to secure + * accounts that have been granted it. We recommend using {AccessControlDefaultAdminRules} + * to enforce additional security measures for this role. + */ +abstract contract AccessControl is Context, IAccessControl, ERC165 { + struct RoleData { + mapping(address account => bool) hasRole; + bytes32 adminRole; + } + + mapping(bytes32 role => RoleData) private _roles; + + bytes32 public constant DEFAULT_ADMIN_ROLE = 0x00; + + /** + * @dev Modifier that checks that an account has a specific role. Reverts + * with an {AccessControlUnauthorizedAccount} error including the required role. + */ + modifier onlyRole(bytes32 role) { + _checkRole(role); + _; + } + + /** + * @dev See {IERC165-supportsInterface}. + */ + function supportsInterface(bytes4 interfaceId) public view virtual override returns (bool) { + return interfaceId == type(IAccessControl).interfaceId || super.supportsInterface(interfaceId); + } + + /** + * @dev Returns `true` if `account` has been granted `role`. + */ + function hasRole(bytes32 role, address account) public view virtual returns (bool) { + return _roles[role].hasRole[account]; + } + + /** + * @dev Reverts with an {AccessControlUnauthorizedAccount} error if `_msgSender()` + * is missing `role`. Overriding this function changes the behavior of the {onlyRole} modifier. + */ + function _checkRole(bytes32 role) internal view virtual { + _checkRole(role, _msgSender()); + } + + /** + * @dev Reverts with an {AccessControlUnauthorizedAccount} error if `account` + * is missing `role`. + */ + function _checkRole(bytes32 role, address account) internal view virtual { + if (!hasRole(role, account)) { + revert AccessControlUnauthorizedAccount(account, role); + } + } + + /** + * @dev Returns the admin role that controls `role`. See {grantRole} and + * {revokeRole}. + * + * To change a role's admin, use {_setRoleAdmin}. + */ + function getRoleAdmin(bytes32 role) public view virtual returns (bytes32) { + return _roles[role].adminRole; + } + + /** + * @dev Grants `role` to `account`. + * + * If `account` had not been already granted `role`, emits a {RoleGranted} + * event. + * + * Requirements: + * + * - the caller must have ``role``'s admin role. + * + * May emit a {RoleGranted} event. + */ + function grantRole(bytes32 role, address account) public virtual onlyRole(getRoleAdmin(role)) { + _grantRole(role, account); + } + + /** + * @dev Revokes `role` from `account`. + * + * If `account` had been granted `role`, emits a {RoleRevoked} event. + * + * Requirements: + * + * - the caller must have ``role``'s admin role. + * + * May emit a {RoleRevoked} event. + */ + function revokeRole(bytes32 role, address account) public virtual onlyRole(getRoleAdmin(role)) { + _revokeRole(role, account); + } + + /** + * @dev Revokes `role` from the calling account. + * + * Roles are often managed via {grantRole} and {revokeRole}: this function's + * purpose is to provide a mechanism for accounts to lose their privileges + * if they are compromised (such as when a trusted device is misplaced). + * + * If the calling account had been revoked `role`, emits a {RoleRevoked} + * event. + * + * Requirements: + * + * - the caller must be `callerConfirmation`. + * + * May emit a {RoleRevoked} event. + */ + function renounceRole(bytes32 role, address callerConfirmation) public virtual { + if (callerConfirmation != _msgSender()) { + revert AccessControlBadConfirmation(); + } + + _revokeRole(role, callerConfirmation); + } + + /** + * @dev Sets `adminRole` as ``role``'s admin role. + * + * Emits a {RoleAdminChanged} event. + */ + function _setRoleAdmin(bytes32 role, bytes32 adminRole) internal virtual { + bytes32 previousAdminRole = getRoleAdmin(role); + _roles[role].adminRole = adminRole; + emit RoleAdminChanged(role, previousAdminRole, adminRole); + } + + /** + * @dev Attempts to grant `role` to `account` and returns a boolean indicating if `role` was granted. + * + * Internal function without access restriction. + * + * May emit a {RoleGranted} event. + */ + function _grantRole(bytes32 role, address account) internal virtual returns (bool) { + if (!hasRole(role, account)) { + _roles[role].hasRole[account] = true; + emit RoleGranted(role, account, _msgSender()); + return true; + } else { + return false; + } + } + + /** + * @dev Attempts to revoke `role` to `account` and returns a boolean indicating if `role` was revoked. + * + * Internal function without access restriction. + * + * May emit a {RoleRevoked} event. + */ + function _revokeRole(bytes32 role, address account) internal virtual returns (bool) { + if (hasRole(role, account)) { + _roles[role].hasRole[account] = false; + emit RoleRevoked(role, account, _msgSender()); + return true; + } else { + return false; + } + } +} diff --git a/CVLByExample/UniswapHooks/lib/uniswap-hooks/lib/v4-core/lib/openzeppelin-contracts/contracts/access/IAccessControl.sol b/CVLByExample/UniswapHooks/lib/uniswap-hooks/lib/v4-core/lib/openzeppelin-contracts/contracts/access/IAccessControl.sol new file mode 100644 index 00000000..2ac89ca7 --- /dev/null +++ b/CVLByExample/UniswapHooks/lib/uniswap-hooks/lib/v4-core/lib/openzeppelin-contracts/contracts/access/IAccessControl.sol @@ -0,0 +1,98 @@ +// SPDX-License-Identifier: MIT +// OpenZeppelin Contracts (last updated v5.0.0) (access/IAccessControl.sol) + +pragma solidity ^0.8.20; + +/** + * @dev External interface of AccessControl declared to support ERC165 detection. + */ +interface IAccessControl { + /** + * @dev The `account` is missing a role. + */ + error AccessControlUnauthorizedAccount(address account, bytes32 neededRole); + + /** + * @dev The caller of a function is not the expected one. + * + * NOTE: Don't confuse with {AccessControlUnauthorizedAccount}. + */ + error AccessControlBadConfirmation(); + + /** + * @dev Emitted when `newAdminRole` is set as ``role``'s admin role, replacing `previousAdminRole` + * + * `DEFAULT_ADMIN_ROLE` is the starting admin for all roles, despite + * {RoleAdminChanged} not being emitted signaling this. + */ + event RoleAdminChanged(bytes32 indexed role, bytes32 indexed previousAdminRole, bytes32 indexed newAdminRole); + + /** + * @dev Emitted when `account` is granted `role`. + * + * `sender` is the account that originated the contract call, an admin role + * bearer except when using {AccessControl-_setupRole}. + */ + event RoleGranted(bytes32 indexed role, address indexed account, address indexed sender); + + /** + * @dev Emitted when `account` is revoked `role`. + * + * `sender` is the account that originated the contract call: + * - if using `revokeRole`, it is the admin role bearer + * - if using `renounceRole`, it is the role bearer (i.e. `account`) + */ + event RoleRevoked(bytes32 indexed role, address indexed account, address indexed sender); + + /** + * @dev Returns `true` if `account` has been granted `role`. + */ + function hasRole(bytes32 role, address account) external view returns (bool); + + /** + * @dev Returns the admin role that controls `role`. See {grantRole} and + * {revokeRole}. + * + * To change a role's admin, use {AccessControl-_setRoleAdmin}. + */ + function getRoleAdmin(bytes32 role) external view returns (bytes32); + + /** + * @dev Grants `role` to `account`. + * + * If `account` had not been already granted `role`, emits a {RoleGranted} + * event. + * + * Requirements: + * + * - the caller must have ``role``'s admin role. + */ + function grantRole(bytes32 role, address account) external; + + /** + * @dev Revokes `role` from `account`. + * + * If `account` had been granted `role`, emits a {RoleRevoked} event. + * + * Requirements: + * + * - the caller must have ``role``'s admin role. + */ + function revokeRole(bytes32 role, address account) external; + + /** + * @dev Revokes `role` from the calling account. + * + * Roles are often managed via {grantRole} and {revokeRole}: this function's + * purpose is to provide a mechanism for accounts to lose their privileges + * if they are compromised (such as when a trusted device is misplaced). + * + * If the calling account had been granted `role`, emits a {RoleRevoked} + * event. + * + * Requirements: + * + * - the caller must be `callerConfirmation`. + */ + function renounceRole(bytes32 role, address callerConfirmation) external; +} diff --git a/CVLByExample/UniswapHooks/lib/uniswap-hooks/lib/v4-core/lib/openzeppelin-contracts/contracts/access/Ownable.sol b/CVLByExample/UniswapHooks/lib/uniswap-hooks/lib/v4-core/lib/openzeppelin-contracts/contracts/access/Ownable.sol new file mode 100644 index 00000000..bd96f666 --- /dev/null +++ b/CVLByExample/UniswapHooks/lib/uniswap-hooks/lib/v4-core/lib/openzeppelin-contracts/contracts/access/Ownable.sol @@ -0,0 +1,100 @@ +// SPDX-License-Identifier: MIT +// OpenZeppelin Contracts (last updated v5.0.0) (access/Ownable.sol) + +pragma solidity ^0.8.20; + +import {Context} from "../utils/Context.sol"; + +/** + * @dev Contract module which provides a basic access control mechanism, where + * there is an account (an owner) that can be granted exclusive access to + * specific functions. + * + * The initial owner is set to the address provided by the deployer. This can + * later be changed with {transferOwnership}. + * + * This module is used through inheritance. It will make available the modifier + * `onlyOwner`, which can be applied to your functions to restrict their use to + * the owner. + */ +abstract contract Ownable is Context { + address private _owner; + + /** + * @dev The caller account is not authorized to perform an operation. + */ + error OwnableUnauthorizedAccount(address account); + + /** + * @dev The owner is not a valid owner account. (eg. `address(0)`) + */ + error OwnableInvalidOwner(address owner); + + event OwnershipTransferred(address indexed previousOwner, address indexed newOwner); + + /** + * @dev Initializes the contract setting the address provided by the deployer as the initial owner. + */ + constructor(address initialOwner) { + if (initialOwner == address(0)) { + revert OwnableInvalidOwner(address(0)); + } + _transferOwnership(initialOwner); + } + + /** + * @dev Throws if called by any account other than the owner. + */ + modifier onlyOwner() { + _checkOwner(); + _; + } + + /** + * @dev Returns the address of the current owner. + */ + function owner() public view virtual returns (address) { + return _owner; + } + + /** + * @dev Throws if the sender is not the owner. + */ + function _checkOwner() internal view virtual { + if (owner() != _msgSender()) { + revert OwnableUnauthorizedAccount(_msgSender()); + } + } + + /** + * @dev Leaves the contract without owner. It will not be possible to call + * `onlyOwner` functions. Can only be called by the current owner. + * + * NOTE: Renouncing ownership will leave the contract without an owner, + * thereby disabling any functionality that is only available to the owner. + */ + function renounceOwnership() public virtual onlyOwner { + _transferOwnership(address(0)); + } + + /** + * @dev Transfers ownership of the contract to a new account (`newOwner`). + * Can only be called by the current owner. + */ + function transferOwnership(address newOwner) public virtual onlyOwner { + if (newOwner == address(0)) { + revert OwnableInvalidOwner(address(0)); + } + _transferOwnership(newOwner); + } + + /** + * @dev Transfers ownership of the contract to a new account (`newOwner`). + * Internal function without access restriction. + */ + function _transferOwnership(address newOwner) internal virtual { + address oldOwner = _owner; + _owner = newOwner; + emit OwnershipTransferred(oldOwner, newOwner); + } +} diff --git a/CVLByExample/UniswapHooks/lib/uniswap-hooks/lib/v4-core/lib/openzeppelin-contracts/contracts/access/Ownable2Step.sol b/CVLByExample/UniswapHooks/lib/uniswap-hooks/lib/v4-core/lib/openzeppelin-contracts/contracts/access/Ownable2Step.sol new file mode 100644 index 00000000..f0427e2f --- /dev/null +++ b/CVLByExample/UniswapHooks/lib/uniswap-hooks/lib/v4-core/lib/openzeppelin-contracts/contracts/access/Ownable2Step.sol @@ -0,0 +1,59 @@ +// SPDX-License-Identifier: MIT +// OpenZeppelin Contracts (last updated v5.0.0) (access/Ownable2Step.sol) + +pragma solidity ^0.8.20; + +import {Ownable} from "./Ownable.sol"; + +/** + * @dev Contract module which provides access control mechanism, where + * there is an account (an owner) that can be granted exclusive access to + * specific functions. + * + * The initial owner is specified at deployment time in the constructor for `Ownable`. This + * can later be changed with {transferOwnership} and {acceptOwnership}. + * + * This module is used through inheritance. It will make available all functions + * from parent (Ownable). + */ +abstract contract Ownable2Step is Ownable { + address private _pendingOwner; + + event OwnershipTransferStarted(address indexed previousOwner, address indexed newOwner); + + /** + * @dev Returns the address of the pending owner. + */ + function pendingOwner() public view virtual returns (address) { + return _pendingOwner; + } + + /** + * @dev Starts the ownership transfer of the contract to a new account. Replaces the pending transfer if there is one. + * Can only be called by the current owner. + */ + function transferOwnership(address newOwner) public virtual override onlyOwner { + _pendingOwner = newOwner; + emit OwnershipTransferStarted(owner(), newOwner); + } + + /** + * @dev Transfers ownership of the contract to a new account (`newOwner`) and deletes any pending owner. + * Internal function without access restriction. + */ + function _transferOwnership(address newOwner) internal virtual override { + delete _pendingOwner; + super._transferOwnership(newOwner); + } + + /** + * @dev The new owner accepts the ownership transfer. + */ + function acceptOwnership() public virtual { + address sender = _msgSender(); + if (pendingOwner() != sender) { + revert OwnableUnauthorizedAccount(sender); + } + _transferOwnership(sender); + } +} diff --git a/CVLByExample/UniswapHooks/lib/uniswap-hooks/lib/v4-core/lib/openzeppelin-contracts/contracts/access/README.adoc b/CVLByExample/UniswapHooks/lib/uniswap-hooks/lib/v4-core/lib/openzeppelin-contracts/contracts/access/README.adoc new file mode 100644 index 00000000..b89865b2 --- /dev/null +++ b/CVLByExample/UniswapHooks/lib/uniswap-hooks/lib/v4-core/lib/openzeppelin-contracts/contracts/access/README.adoc @@ -0,0 +1,45 @@ += Access Control + +[.readme-notice] +NOTE: This document is better viewed at https://docs.openzeppelin.com/contracts/api/access + +This directory provides ways to restrict who can access the functions of a contract or when they can do it. + +- {AccessManager} is a full-fledged access control solution for smart contract systems. Allows creating and assigning multiple hierarchical roles with execution delays for each account across various contracts. +- {AccessManaged} delegates its access control to an authority that dictates the permissions of the managed contract. It's compatible with an AccessManager as an authority. +- {AccessControl} provides a per-contract role based access control mechanism. Multiple hierarchical roles can be created and assigned each to multiple accounts within the same instance. +- {Ownable} is a simpler mechanism with a single owner "role" that can be assigned to a single account. This simpler mechanism can be useful for quick tests but projects with production concerns are likely to outgrow it. + +== Core + +{{Ownable}} + +{{Ownable2Step}} + +{{IAccessControl}} + +{{AccessControl}} + +== Extensions + +{{IAccessControlEnumerable}} + +{{AccessControlEnumerable}} + +{{IAccessControlDefaultAdminRules}} + +{{AccessControlDefaultAdminRules}} + +== AccessManager + +{{IAuthority}} + +{{IAccessManager}} + +{{AccessManager}} + +{{IAccessManaged}} + +{{AccessManaged}} + +{{AuthorityUtils}} diff --git a/CVLByExample/UniswapHooks/lib/uniswap-hooks/lib/v4-core/lib/openzeppelin-contracts/contracts/access/extensions/AccessControlDefaultAdminRules.sol b/CVLByExample/UniswapHooks/lib/uniswap-hooks/lib/v4-core/lib/openzeppelin-contracts/contracts/access/extensions/AccessControlDefaultAdminRules.sol new file mode 100644 index 00000000..ef71a648 --- /dev/null +++ b/CVLByExample/UniswapHooks/lib/uniswap-hooks/lib/v4-core/lib/openzeppelin-contracts/contracts/access/extensions/AccessControlDefaultAdminRules.sol @@ -0,0 +1,396 @@ +// SPDX-License-Identifier: MIT +// OpenZeppelin Contracts (last updated v5.0.0) (access/extensions/AccessControlDefaultAdminRules.sol) + +pragma solidity ^0.8.20; + +import {IAccessControlDefaultAdminRules} from "./IAccessControlDefaultAdminRules.sol"; +import {AccessControl, IAccessControl} from "../AccessControl.sol"; +import {SafeCast} from "../../utils/math/SafeCast.sol"; +import {Math} from "../../utils/math/Math.sol"; +import {IERC5313} from "../../interfaces/IERC5313.sol"; + +/** + * @dev Extension of {AccessControl} that allows specifying special rules to manage + * the `DEFAULT_ADMIN_ROLE` holder, which is a sensitive role with special permissions + * over other roles that may potentially have privileged rights in the system. + * + * If a specific role doesn't have an admin role assigned, the holder of the + * `DEFAULT_ADMIN_ROLE` will have the ability to grant it and revoke it. + * + * This contract implements the following risk mitigations on top of {AccessControl}: + * + * * Only one account holds the `DEFAULT_ADMIN_ROLE` since deployment until it's potentially renounced. + * * Enforces a 2-step process to transfer the `DEFAULT_ADMIN_ROLE` to another account. + * * Enforces a configurable delay between the two steps, with the ability to cancel before the transfer is accepted. + * * The delay can be changed by scheduling, see {changeDefaultAdminDelay}. + * * It is not possible to use another role to manage the `DEFAULT_ADMIN_ROLE`. + * + * Example usage: + * + * ```solidity + * contract MyToken is AccessControlDefaultAdminRules { + * constructor() AccessControlDefaultAdminRules( + * 3 days, + * msg.sender // Explicit initial `DEFAULT_ADMIN_ROLE` holder + * ) {} + * } + * ``` + */ +abstract contract AccessControlDefaultAdminRules is IAccessControlDefaultAdminRules, IERC5313, AccessControl { + // pending admin pair read/written together frequently + address private _pendingDefaultAdmin; + uint48 private _pendingDefaultAdminSchedule; // 0 == unset + + uint48 private _currentDelay; + address private _currentDefaultAdmin; + + // pending delay pair read/written together frequently + uint48 private _pendingDelay; + uint48 private _pendingDelaySchedule; // 0 == unset + + /** + * @dev Sets the initial values for {defaultAdminDelay} and {defaultAdmin} address. + */ + constructor(uint48 initialDelay, address initialDefaultAdmin) { + if (initialDefaultAdmin == address(0)) { + revert AccessControlInvalidDefaultAdmin(address(0)); + } + _currentDelay = initialDelay; + _grantRole(DEFAULT_ADMIN_ROLE, initialDefaultAdmin); + } + + /** + * @dev See {IERC165-supportsInterface}. + */ + function supportsInterface(bytes4 interfaceId) public view virtual override returns (bool) { + return interfaceId == type(IAccessControlDefaultAdminRules).interfaceId || super.supportsInterface(interfaceId); + } + + /** + * @dev See {IERC5313-owner}. + */ + function owner() public view virtual returns (address) { + return defaultAdmin(); + } + + /// + /// Override AccessControl role management + /// + + /** + * @dev See {AccessControl-grantRole}. Reverts for `DEFAULT_ADMIN_ROLE`. + */ + function grantRole(bytes32 role, address account) public virtual override(AccessControl, IAccessControl) { + if (role == DEFAULT_ADMIN_ROLE) { + revert AccessControlEnforcedDefaultAdminRules(); + } + super.grantRole(role, account); + } + + /** + * @dev See {AccessControl-revokeRole}. Reverts for `DEFAULT_ADMIN_ROLE`. + */ + function revokeRole(bytes32 role, address account) public virtual override(AccessControl, IAccessControl) { + if (role == DEFAULT_ADMIN_ROLE) { + revert AccessControlEnforcedDefaultAdminRules(); + } + super.revokeRole(role, account); + } + + /** + * @dev See {AccessControl-renounceRole}. + * + * For the `DEFAULT_ADMIN_ROLE`, it only allows renouncing in two steps by first calling + * {beginDefaultAdminTransfer} to the `address(0)`, so it's required that the {pendingDefaultAdmin} schedule + * has also passed when calling this function. + * + * After its execution, it will not be possible to call `onlyRole(DEFAULT_ADMIN_ROLE)` functions. + * + * NOTE: Renouncing `DEFAULT_ADMIN_ROLE` will leave the contract without a {defaultAdmin}, + * thereby disabling any functionality that is only available for it, and the possibility of reassigning a + * non-administrated role. + */ + function renounceRole(bytes32 role, address account) public virtual override(AccessControl, IAccessControl) { + if (role == DEFAULT_ADMIN_ROLE && account == defaultAdmin()) { + (address newDefaultAdmin, uint48 schedule) = pendingDefaultAdmin(); + if (newDefaultAdmin != address(0) || !_isScheduleSet(schedule) || !_hasSchedulePassed(schedule)) { + revert AccessControlEnforcedDefaultAdminDelay(schedule); + } + delete _pendingDefaultAdminSchedule; + } + super.renounceRole(role, account); + } + + /** + * @dev See {AccessControl-_grantRole}. + * + * For `DEFAULT_ADMIN_ROLE`, it only allows granting if there isn't already a {defaultAdmin} or if the + * role has been previously renounced. + * + * NOTE: Exposing this function through another mechanism may make the `DEFAULT_ADMIN_ROLE` + * assignable again. Make sure to guarantee this is the expected behavior in your implementation. + */ + function _grantRole(bytes32 role, address account) internal virtual override returns (bool) { + if (role == DEFAULT_ADMIN_ROLE) { + if (defaultAdmin() != address(0)) { + revert AccessControlEnforcedDefaultAdminRules(); + } + _currentDefaultAdmin = account; + } + return super._grantRole(role, account); + } + + /** + * @dev See {AccessControl-_revokeRole}. + */ + function _revokeRole(bytes32 role, address account) internal virtual override returns (bool) { + if (role == DEFAULT_ADMIN_ROLE && account == defaultAdmin()) { + delete _currentDefaultAdmin; + } + return super._revokeRole(role, account); + } + + /** + * @dev See {AccessControl-_setRoleAdmin}. Reverts for `DEFAULT_ADMIN_ROLE`. + */ + function _setRoleAdmin(bytes32 role, bytes32 adminRole) internal virtual override { + if (role == DEFAULT_ADMIN_ROLE) { + revert AccessControlEnforcedDefaultAdminRules(); + } + super._setRoleAdmin(role, adminRole); + } + + /// + /// AccessControlDefaultAdminRules accessors + /// + + /** + * @inheritdoc IAccessControlDefaultAdminRules + */ + function defaultAdmin() public view virtual returns (address) { + return _currentDefaultAdmin; + } + + /** + * @inheritdoc IAccessControlDefaultAdminRules + */ + function pendingDefaultAdmin() public view virtual returns (address newAdmin, uint48 schedule) { + return (_pendingDefaultAdmin, _pendingDefaultAdminSchedule); + } + + /** + * @inheritdoc IAccessControlDefaultAdminRules + */ + function defaultAdminDelay() public view virtual returns (uint48) { + uint48 schedule = _pendingDelaySchedule; + return (_isScheduleSet(schedule) && _hasSchedulePassed(schedule)) ? _pendingDelay : _currentDelay; + } + + /** + * @inheritdoc IAccessControlDefaultAdminRules + */ + function pendingDefaultAdminDelay() public view virtual returns (uint48 newDelay, uint48 schedule) { + schedule = _pendingDelaySchedule; + return (_isScheduleSet(schedule) && !_hasSchedulePassed(schedule)) ? (_pendingDelay, schedule) : (0, 0); + } + + /** + * @inheritdoc IAccessControlDefaultAdminRules + */ + function defaultAdminDelayIncreaseWait() public view virtual returns (uint48) { + return 5 days; + } + + /// + /// AccessControlDefaultAdminRules public and internal setters for defaultAdmin/pendingDefaultAdmin + /// + + /** + * @inheritdoc IAccessControlDefaultAdminRules + */ + function beginDefaultAdminTransfer(address newAdmin) public virtual onlyRole(DEFAULT_ADMIN_ROLE) { + _beginDefaultAdminTransfer(newAdmin); + } + + /** + * @dev See {beginDefaultAdminTransfer}. + * + * Internal function without access restriction. + */ + function _beginDefaultAdminTransfer(address newAdmin) internal virtual { + uint48 newSchedule = SafeCast.toUint48(block.timestamp) + defaultAdminDelay(); + _setPendingDefaultAdmin(newAdmin, newSchedule); + emit DefaultAdminTransferScheduled(newAdmin, newSchedule); + } + + /** + * @inheritdoc IAccessControlDefaultAdminRules + */ + function cancelDefaultAdminTransfer() public virtual onlyRole(DEFAULT_ADMIN_ROLE) { + _cancelDefaultAdminTransfer(); + } + + /** + * @dev See {cancelDefaultAdminTransfer}. + * + * Internal function without access restriction. + */ + function _cancelDefaultAdminTransfer() internal virtual { + _setPendingDefaultAdmin(address(0), 0); + } + + /** + * @inheritdoc IAccessControlDefaultAdminRules + */ + function acceptDefaultAdminTransfer() public virtual { + (address newDefaultAdmin, ) = pendingDefaultAdmin(); + if (_msgSender() != newDefaultAdmin) { + // Enforce newDefaultAdmin explicit acceptance. + revert AccessControlInvalidDefaultAdmin(_msgSender()); + } + _acceptDefaultAdminTransfer(); + } + + /** + * @dev See {acceptDefaultAdminTransfer}. + * + * Internal function without access restriction. + */ + function _acceptDefaultAdminTransfer() internal virtual { + (address newAdmin, uint48 schedule) = pendingDefaultAdmin(); + if (!_isScheduleSet(schedule) || !_hasSchedulePassed(schedule)) { + revert AccessControlEnforcedDefaultAdminDelay(schedule); + } + _revokeRole(DEFAULT_ADMIN_ROLE, defaultAdmin()); + _grantRole(DEFAULT_ADMIN_ROLE, newAdmin); + delete _pendingDefaultAdmin; + delete _pendingDefaultAdminSchedule; + } + + /// + /// AccessControlDefaultAdminRules public and internal setters for defaultAdminDelay/pendingDefaultAdminDelay + /// + + /** + * @inheritdoc IAccessControlDefaultAdminRules + */ + function changeDefaultAdminDelay(uint48 newDelay) public virtual onlyRole(DEFAULT_ADMIN_ROLE) { + _changeDefaultAdminDelay(newDelay); + } + + /** + * @dev See {changeDefaultAdminDelay}. + * + * Internal function without access restriction. + */ + function _changeDefaultAdminDelay(uint48 newDelay) internal virtual { + uint48 newSchedule = SafeCast.toUint48(block.timestamp) + _delayChangeWait(newDelay); + _setPendingDelay(newDelay, newSchedule); + emit DefaultAdminDelayChangeScheduled(newDelay, newSchedule); + } + + /** + * @inheritdoc IAccessControlDefaultAdminRules + */ + function rollbackDefaultAdminDelay() public virtual onlyRole(DEFAULT_ADMIN_ROLE) { + _rollbackDefaultAdminDelay(); + } + + /** + * @dev See {rollbackDefaultAdminDelay}. + * + * Internal function without access restriction. + */ + function _rollbackDefaultAdminDelay() internal virtual { + _setPendingDelay(0, 0); + } + + /** + * @dev Returns the amount of seconds to wait after the `newDelay` will + * become the new {defaultAdminDelay}. + * + * The value returned guarantees that if the delay is reduced, it will go into effect + * after a wait that honors the previously set delay. + * + * See {defaultAdminDelayIncreaseWait}. + */ + function _delayChangeWait(uint48 newDelay) internal view virtual returns (uint48) { + uint48 currentDelay = defaultAdminDelay(); + + // When increasing the delay, we schedule the delay change to occur after a period of "new delay" has passed, up + // to a maximum given by defaultAdminDelayIncreaseWait, by default 5 days. For example, if increasing from 1 day + // to 3 days, the new delay will come into effect after 3 days. If increasing from 1 day to 10 days, the new + // delay will come into effect after 5 days. The 5 day wait period is intended to be able to fix an error like + // using milliseconds instead of seconds. + // + // When decreasing the delay, we wait the difference between "current delay" and "new delay". This guarantees + // that an admin transfer cannot be made faster than "current delay" at the time the delay change is scheduled. + // For example, if decreasing from 10 days to 3 days, the new delay will come into effect after 7 days. + return + newDelay > currentDelay + ? uint48(Math.min(newDelay, defaultAdminDelayIncreaseWait())) // no need to safecast, both inputs are uint48 + : currentDelay - newDelay; + } + + /// + /// Private setters + /// + + /** + * @dev Setter of the tuple for pending admin and its schedule. + * + * May emit a DefaultAdminTransferCanceled event. + */ + function _setPendingDefaultAdmin(address newAdmin, uint48 newSchedule) private { + (, uint48 oldSchedule) = pendingDefaultAdmin(); + + _pendingDefaultAdmin = newAdmin; + _pendingDefaultAdminSchedule = newSchedule; + + // An `oldSchedule` from `pendingDefaultAdmin()` is only set if it hasn't been accepted. + if (_isScheduleSet(oldSchedule)) { + // Emit for implicit cancellations when another default admin was scheduled. + emit DefaultAdminTransferCanceled(); + } + } + + /** + * @dev Setter of the tuple for pending delay and its schedule. + * + * May emit a DefaultAdminDelayChangeCanceled event. + */ + function _setPendingDelay(uint48 newDelay, uint48 newSchedule) private { + uint48 oldSchedule = _pendingDelaySchedule; + + if (_isScheduleSet(oldSchedule)) { + if (_hasSchedulePassed(oldSchedule)) { + // Materialize a virtual delay + _currentDelay = _pendingDelay; + } else { + // Emit for implicit cancellations when another delay was scheduled. + emit DefaultAdminDelayChangeCanceled(); + } + } + + _pendingDelay = newDelay; + _pendingDelaySchedule = newSchedule; + } + + /// + /// Private helpers + /// + + /** + * @dev Defines if an `schedule` is considered set. For consistency purposes. + */ + function _isScheduleSet(uint48 schedule) private pure returns (bool) { + return schedule != 0; + } + + /** + * @dev Defines if an `schedule` is considered passed. For consistency purposes. + */ + function _hasSchedulePassed(uint48 schedule) private view returns (bool) { + return schedule < block.timestamp; + } +} diff --git a/CVLByExample/UniswapHooks/lib/uniswap-hooks/lib/v4-core/lib/openzeppelin-contracts/contracts/access/extensions/AccessControlEnumerable.sol b/CVLByExample/UniswapHooks/lib/uniswap-hooks/lib/v4-core/lib/openzeppelin-contracts/contracts/access/extensions/AccessControlEnumerable.sol new file mode 100644 index 00000000..151de05c --- /dev/null +++ b/CVLByExample/UniswapHooks/lib/uniswap-hooks/lib/v4-core/lib/openzeppelin-contracts/contracts/access/extensions/AccessControlEnumerable.sol @@ -0,0 +1,70 @@ +// SPDX-License-Identifier: MIT +// OpenZeppelin Contracts (last updated v5.0.0) (access/extensions/AccessControlEnumerable.sol) + +pragma solidity ^0.8.20; + +import {IAccessControlEnumerable} from "./IAccessControlEnumerable.sol"; +import {AccessControl} from "../AccessControl.sol"; +import {EnumerableSet} from "../../utils/structs/EnumerableSet.sol"; + +/** + * @dev Extension of {AccessControl} that allows enumerating the members of each role. + */ +abstract contract AccessControlEnumerable is IAccessControlEnumerable, AccessControl { + using EnumerableSet for EnumerableSet.AddressSet; + + mapping(bytes32 role => EnumerableSet.AddressSet) private _roleMembers; + + /** + * @dev See {IERC165-supportsInterface}. + */ + function supportsInterface(bytes4 interfaceId) public view virtual override returns (bool) { + return interfaceId == type(IAccessControlEnumerable).interfaceId || super.supportsInterface(interfaceId); + } + + /** + * @dev Returns one of the accounts that have `role`. `index` must be a + * value between 0 and {getRoleMemberCount}, non-inclusive. + * + * Role bearers are not sorted in any particular way, and their ordering may + * change at any point. + * + * WARNING: When using {getRoleMember} and {getRoleMemberCount}, make sure + * you perform all queries on the same block. See the following + * https://forum.openzeppelin.com/t/iterating-over-elements-on-enumerableset-in-openzeppelin-contracts/2296[forum post] + * for more information. + */ + function getRoleMember(bytes32 role, uint256 index) public view virtual returns (address) { + return _roleMembers[role].at(index); + } + + /** + * @dev Returns the number of accounts that have `role`. Can be used + * together with {getRoleMember} to enumerate all bearers of a role. + */ + function getRoleMemberCount(bytes32 role) public view virtual returns (uint256) { + return _roleMembers[role].length(); + } + + /** + * @dev Overload {AccessControl-_grantRole} to track enumerable memberships + */ + function _grantRole(bytes32 role, address account) internal virtual override returns (bool) { + bool granted = super._grantRole(role, account); + if (granted) { + _roleMembers[role].add(account); + } + return granted; + } + + /** + * @dev Overload {AccessControl-_revokeRole} to track enumerable memberships + */ + function _revokeRole(bytes32 role, address account) internal virtual override returns (bool) { + bool revoked = super._revokeRole(role, account); + if (revoked) { + _roleMembers[role].remove(account); + } + return revoked; + } +} diff --git a/CVLByExample/UniswapHooks/lib/uniswap-hooks/lib/v4-core/lib/openzeppelin-contracts/contracts/access/extensions/IAccessControlDefaultAdminRules.sol b/CVLByExample/UniswapHooks/lib/uniswap-hooks/lib/v4-core/lib/openzeppelin-contracts/contracts/access/extensions/IAccessControlDefaultAdminRules.sol new file mode 100644 index 00000000..73531faf --- /dev/null +++ b/CVLByExample/UniswapHooks/lib/uniswap-hooks/lib/v4-core/lib/openzeppelin-contracts/contracts/access/extensions/IAccessControlDefaultAdminRules.sol @@ -0,0 +1,192 @@ +// SPDX-License-Identifier: MIT +// OpenZeppelin Contracts (last updated v5.0.0) (access/extensions/IAccessControlDefaultAdminRules.sol) + +pragma solidity ^0.8.20; + +import {IAccessControl} from "../IAccessControl.sol"; + +/** + * @dev External interface of AccessControlDefaultAdminRules declared to support ERC165 detection. + */ +interface IAccessControlDefaultAdminRules is IAccessControl { + /** + * @dev The new default admin is not a valid default admin. + */ + error AccessControlInvalidDefaultAdmin(address defaultAdmin); + + /** + * @dev At least one of the following rules was violated: + * + * - The `DEFAULT_ADMIN_ROLE` must only be managed by itself. + * - The `DEFAULT_ADMIN_ROLE` must only be held by one account at the time. + * - Any `DEFAULT_ADMIN_ROLE` transfer must be in two delayed steps. + */ + error AccessControlEnforcedDefaultAdminRules(); + + /** + * @dev The delay for transferring the default admin delay is enforced and + * the operation must wait until `schedule`. + * + * NOTE: `schedule` can be 0 indicating there's no transfer scheduled. + */ + error AccessControlEnforcedDefaultAdminDelay(uint48 schedule); + + /** + * @dev Emitted when a {defaultAdmin} transfer is started, setting `newAdmin` as the next + * address to become the {defaultAdmin} by calling {acceptDefaultAdminTransfer} only after `acceptSchedule` + * passes. + */ + event DefaultAdminTransferScheduled(address indexed newAdmin, uint48 acceptSchedule); + + /** + * @dev Emitted when a {pendingDefaultAdmin} is reset if it was never accepted, regardless of its schedule. + */ + event DefaultAdminTransferCanceled(); + + /** + * @dev Emitted when a {defaultAdminDelay} change is started, setting `newDelay` as the next + * delay to be applied between default admin transfer after `effectSchedule` has passed. + */ + event DefaultAdminDelayChangeScheduled(uint48 newDelay, uint48 effectSchedule); + + /** + * @dev Emitted when a {pendingDefaultAdminDelay} is reset if its schedule didn't pass. + */ + event DefaultAdminDelayChangeCanceled(); + + /** + * @dev Returns the address of the current `DEFAULT_ADMIN_ROLE` holder. + */ + function defaultAdmin() external view returns (address); + + /** + * @dev Returns a tuple of a `newAdmin` and an accept schedule. + * + * After the `schedule` passes, the `newAdmin` will be able to accept the {defaultAdmin} role + * by calling {acceptDefaultAdminTransfer}, completing the role transfer. + * + * A zero value only in `acceptSchedule` indicates no pending admin transfer. + * + * NOTE: A zero address `newAdmin` means that {defaultAdmin} is being renounced. + */ + function pendingDefaultAdmin() external view returns (address newAdmin, uint48 acceptSchedule); + + /** + * @dev Returns the delay required to schedule the acceptance of a {defaultAdmin} transfer started. + * + * This delay will be added to the current timestamp when calling {beginDefaultAdminTransfer} to set + * the acceptance schedule. + * + * NOTE: If a delay change has been scheduled, it will take effect as soon as the schedule passes, making this + * function returns the new delay. See {changeDefaultAdminDelay}. + */ + function defaultAdminDelay() external view returns (uint48); + + /** + * @dev Returns a tuple of `newDelay` and an effect schedule. + * + * After the `schedule` passes, the `newDelay` will get into effect immediately for every + * new {defaultAdmin} transfer started with {beginDefaultAdminTransfer}. + * + * A zero value only in `effectSchedule` indicates no pending delay change. + * + * NOTE: A zero value only for `newDelay` means that the next {defaultAdminDelay} + * will be zero after the effect schedule. + */ + function pendingDefaultAdminDelay() external view returns (uint48 newDelay, uint48 effectSchedule); + + /** + * @dev Starts a {defaultAdmin} transfer by setting a {pendingDefaultAdmin} scheduled for acceptance + * after the current timestamp plus a {defaultAdminDelay}. + * + * Requirements: + * + * - Only can be called by the current {defaultAdmin}. + * + * Emits a DefaultAdminRoleChangeStarted event. + */ + function beginDefaultAdminTransfer(address newAdmin) external; + + /** + * @dev Cancels a {defaultAdmin} transfer previously started with {beginDefaultAdminTransfer}. + * + * A {pendingDefaultAdmin} not yet accepted can also be cancelled with this function. + * + * Requirements: + * + * - Only can be called by the current {defaultAdmin}. + * + * May emit a DefaultAdminTransferCanceled event. + */ + function cancelDefaultAdminTransfer() external; + + /** + * @dev Completes a {defaultAdmin} transfer previously started with {beginDefaultAdminTransfer}. + * + * After calling the function: + * + * - `DEFAULT_ADMIN_ROLE` should be granted to the caller. + * - `DEFAULT_ADMIN_ROLE` should be revoked from the previous holder. + * - {pendingDefaultAdmin} should be reset to zero values. + * + * Requirements: + * + * - Only can be called by the {pendingDefaultAdmin}'s `newAdmin`. + * - The {pendingDefaultAdmin}'s `acceptSchedule` should've passed. + */ + function acceptDefaultAdminTransfer() external; + + /** + * @dev Initiates a {defaultAdminDelay} update by setting a {pendingDefaultAdminDelay} scheduled for getting + * into effect after the current timestamp plus a {defaultAdminDelay}. + * + * This function guarantees that any call to {beginDefaultAdminTransfer} done between the timestamp this + * method is called and the {pendingDefaultAdminDelay} effect schedule will use the current {defaultAdminDelay} + * set before calling. + * + * The {pendingDefaultAdminDelay}'s effect schedule is defined in a way that waiting until the schedule and then + * calling {beginDefaultAdminTransfer} with the new delay will take at least the same as another {defaultAdmin} + * complete transfer (including acceptance). + * + * The schedule is designed for two scenarios: + * + * - When the delay is changed for a larger one the schedule is `block.timestamp + newDelay` capped by + * {defaultAdminDelayIncreaseWait}. + * - When the delay is changed for a shorter one, the schedule is `block.timestamp + (current delay - new delay)`. + * + * A {pendingDefaultAdminDelay} that never got into effect will be canceled in favor of a new scheduled change. + * + * Requirements: + * + * - Only can be called by the current {defaultAdmin}. + * + * Emits a DefaultAdminDelayChangeScheduled event and may emit a DefaultAdminDelayChangeCanceled event. + */ + function changeDefaultAdminDelay(uint48 newDelay) external; + + /** + * @dev Cancels a scheduled {defaultAdminDelay} change. + * + * Requirements: + * + * - Only can be called by the current {defaultAdmin}. + * + * May emit a DefaultAdminDelayChangeCanceled event. + */ + function rollbackDefaultAdminDelay() external; + + /** + * @dev Maximum time in seconds for an increase to {defaultAdminDelay} (that is scheduled using {changeDefaultAdminDelay}) + * to take effect. Default to 5 days. + * + * When the {defaultAdminDelay} is scheduled to be increased, it goes into effect after the new delay has passed with + * the purpose of giving enough time for reverting any accidental change (i.e. using milliseconds instead of seconds) + * that may lock the contract. However, to avoid excessive schedules, the wait is capped by this function and it can + * be overrode for a custom {defaultAdminDelay} increase scheduling. + * + * IMPORTANT: Make sure to add a reasonable amount of time while overriding this value, otherwise, + * there's a risk of setting a high new delay that goes into effect almost immediately without the + * possibility of human intervention in the case of an input error (eg. set milliseconds instead of seconds). + */ + function defaultAdminDelayIncreaseWait() external view returns (uint48); +} diff --git a/CVLByExample/UniswapHooks/lib/uniswap-hooks/lib/v4-core/lib/openzeppelin-contracts/contracts/access/extensions/IAccessControlEnumerable.sol b/CVLByExample/UniswapHooks/lib/uniswap-hooks/lib/v4-core/lib/openzeppelin-contracts/contracts/access/extensions/IAccessControlEnumerable.sol new file mode 100644 index 00000000..a39d0516 --- /dev/null +++ b/CVLByExample/UniswapHooks/lib/uniswap-hooks/lib/v4-core/lib/openzeppelin-contracts/contracts/access/extensions/IAccessControlEnumerable.sol @@ -0,0 +1,31 @@ +// SPDX-License-Identifier: MIT +// OpenZeppelin Contracts (last updated v5.0.0) (access/extensions/IAccessControlEnumerable.sol) + +pragma solidity ^0.8.20; + +import {IAccessControl} from "../IAccessControl.sol"; + +/** + * @dev External interface of AccessControlEnumerable declared to support ERC165 detection. + */ +interface IAccessControlEnumerable is IAccessControl { + /** + * @dev Returns one of the accounts that have `role`. `index` must be a + * value between 0 and {getRoleMemberCount}, non-inclusive. + * + * Role bearers are not sorted in any particular way, and their ordering may + * change at any point. + * + * WARNING: When using {getRoleMember} and {getRoleMemberCount}, make sure + * you perform all queries on the same block. See the following + * https://forum.openzeppelin.com/t/iterating-over-elements-on-enumerableset-in-openzeppelin-contracts/2296[forum post] + * for more information. + */ + function getRoleMember(bytes32 role, uint256 index) external view returns (address); + + /** + * @dev Returns the number of accounts that have `role`. Can be used + * together with {getRoleMember} to enumerate all bearers of a role. + */ + function getRoleMemberCount(bytes32 role) external view returns (uint256); +} diff --git a/CVLByExample/UniswapHooks/lib/uniswap-hooks/lib/v4-core/lib/openzeppelin-contracts/contracts/access/manager/AccessManaged.sol b/CVLByExample/UniswapHooks/lib/uniswap-hooks/lib/v4-core/lib/openzeppelin-contracts/contracts/access/manager/AccessManaged.sol new file mode 100644 index 00000000..b5f45240 --- /dev/null +++ b/CVLByExample/UniswapHooks/lib/uniswap-hooks/lib/v4-core/lib/openzeppelin-contracts/contracts/access/manager/AccessManaged.sol @@ -0,0 +1,113 @@ +// SPDX-License-Identifier: MIT +// OpenZeppelin Contracts (last updated v5.0.0) (access/manager/AccessManaged.sol) + +pragma solidity ^0.8.20; + +import {IAuthority} from "./IAuthority.sol"; +import {AuthorityUtils} from "./AuthorityUtils.sol"; +import {IAccessManager} from "./IAccessManager.sol"; +import {IAccessManaged} from "./IAccessManaged.sol"; +import {Context} from "../../utils/Context.sol"; + +/** + * @dev This contract module makes available a {restricted} modifier. Functions decorated with this modifier will be + * permissioned according to an "authority": a contract like {AccessManager} that follows the {IAuthority} interface, + * implementing a policy that allows certain callers to access certain functions. + * + * IMPORTANT: The `restricted` modifier should never be used on `internal` functions, judiciously used in `public` + * functions, and ideally only used in `external` functions. See {restricted}. + */ +abstract contract AccessManaged is Context, IAccessManaged { + address private _authority; + + bool private _consumingSchedule; + + /** + * @dev Initializes the contract connected to an initial authority. + */ + constructor(address initialAuthority) { + _setAuthority(initialAuthority); + } + + /** + * @dev Restricts access to a function as defined by the connected Authority for this contract and the + * caller and selector of the function that entered the contract. + * + * [IMPORTANT] + * ==== + * In general, this modifier should only be used on `external` functions. It is okay to use it on `public` + * functions that are used as external entry points and are not called internally. Unless you know what you're + * doing, it should never be used on `internal` functions. Failure to follow these rules can have critical security + * implications! This is because the permissions are determined by the function that entered the contract, i.e. the + * function at the bottom of the call stack, and not the function where the modifier is visible in the source code. + * ==== + * + * [WARNING] + * ==== + * Avoid adding this modifier to the https://docs.soliditylang.org/en/v0.8.20/contracts.html#receive-ether-function[`receive()`] + * function or the https://docs.soliditylang.org/en/v0.8.20/contracts.html#fallback-function[`fallback()`]. These + * functions are the only execution paths where a function selector cannot be unambiguosly determined from the calldata + * since the selector defaults to `0x00000000` in the `receive()` function and similarly in the `fallback()` function + * if no calldata is provided. (See {_checkCanCall}). + * + * The `receive()` function will always panic whereas the `fallback()` may panic depending on the calldata length. + * ==== + */ + modifier restricted() { + _checkCanCall(_msgSender(), _msgData()); + _; + } + + /// @inheritdoc IAccessManaged + function authority() public view virtual returns (address) { + return _authority; + } + + /// @inheritdoc IAccessManaged + function setAuthority(address newAuthority) public virtual { + address caller = _msgSender(); + if (caller != authority()) { + revert AccessManagedUnauthorized(caller); + } + if (newAuthority.code.length == 0) { + revert AccessManagedInvalidAuthority(newAuthority); + } + _setAuthority(newAuthority); + } + + /// @inheritdoc IAccessManaged + function isConsumingScheduledOp() public view returns (bytes4) { + return _consumingSchedule ? this.isConsumingScheduledOp.selector : bytes4(0); + } + + /** + * @dev Transfers control to a new authority. Internal function with no access restriction. Allows bypassing the + * permissions set by the current authority. + */ + function _setAuthority(address newAuthority) internal virtual { + _authority = newAuthority; + emit AuthorityUpdated(newAuthority); + } + + /** + * @dev Reverts if the caller is not allowed to call the function identified by a selector. Panics if the calldata + * is less than 4 bytes long. + */ + function _checkCanCall(address caller, bytes calldata data) internal virtual { + (bool immediate, uint32 delay) = AuthorityUtils.canCallWithDelay( + authority(), + caller, + address(this), + bytes4(data[0:4]) + ); + if (!immediate) { + if (delay > 0) { + _consumingSchedule = true; + IAccessManager(authority()).consumeScheduledOp(caller, data); + _consumingSchedule = false; + } else { + revert AccessManagedUnauthorized(caller); + } + } + } +} diff --git a/CVLByExample/UniswapHooks/lib/uniswap-hooks/lib/v4-core/lib/openzeppelin-contracts/contracts/access/manager/AccessManager.sol b/CVLByExample/UniswapHooks/lib/uniswap-hooks/lib/v4-core/lib/openzeppelin-contracts/contracts/access/manager/AccessManager.sol new file mode 100644 index 00000000..1e4afa49 --- /dev/null +++ b/CVLByExample/UniswapHooks/lib/uniswap-hooks/lib/v4-core/lib/openzeppelin-contracts/contracts/access/manager/AccessManager.sol @@ -0,0 +1,730 @@ +// SPDX-License-Identifier: MIT +// OpenZeppelin Contracts (last updated v5.0.0) (access/manager/AccessManager.sol) + +pragma solidity ^0.8.20; + +import {IAccessManager} from "./IAccessManager.sol"; +import {IAccessManaged} from "./IAccessManaged.sol"; +import {Address} from "../../utils/Address.sol"; +import {Context} from "../../utils/Context.sol"; +import {Multicall} from "../../utils/Multicall.sol"; +import {Math} from "../../utils/math/Math.sol"; +import {Time} from "../../utils/types/Time.sol"; + +/** + * @dev AccessManager is a central contract to store the permissions of a system. + * + * A smart contract under the control of an AccessManager instance is known as a target, and will inherit from the + * {AccessManaged} contract, be connected to this contract as its manager and implement the {AccessManaged-restricted} + * modifier on a set of functions selected to be permissioned. Note that any function without this setup won't be + * effectively restricted. + * + * The restriction rules for such functions are defined in terms of "roles" identified by an `uint64` and scoped + * by target (`address`) and function selectors (`bytes4`). These roles are stored in this contract and can be + * configured by admins (`ADMIN_ROLE` members) after a delay (see {getTargetAdminDelay}). + * + * For each target contract, admins can configure the following without any delay: + * + * * The target's {AccessManaged-authority} via {updateAuthority}. + * * Close or open a target via {setTargetClosed} keeping the permissions intact. + * * The roles that are allowed (or disallowed) to call a given function (identified by its selector) through {setTargetFunctionRole}. + * + * By default every address is member of the `PUBLIC_ROLE` and every target function is restricted to the `ADMIN_ROLE` until configured otherwise. + * Additionally, each role has the following configuration options restricted to this manager's admins: + * + * * A role's admin role via {setRoleAdmin} who can grant or revoke roles. + * * A role's guardian role via {setRoleGuardian} who's allowed to cancel operations. + * * A delay in which a role takes effect after being granted through {setGrantDelay}. + * * A delay of any target's admin action via {setTargetAdminDelay}. + * * A role label for discoverability purposes with {labelRole}. + * + * Any account can be added and removed into any number of these roles by using the {grantRole} and {revokeRole} functions + * restricted to each role's admin (see {getRoleAdmin}). + * + * Since all the permissions of the managed system can be modified by the admins of this instance, it is expected that + * they will be highly secured (e.g., a multisig or a well-configured DAO). + * + * NOTE: This contract implements a form of the {IAuthority} interface, but {canCall} has additional return data so it + * doesn't inherit `IAuthority`. It is however compatible with the `IAuthority` interface since the first 32 bytes of + * the return data are a boolean as expected by that interface. + * + * NOTE: Systems that implement other access control mechanisms (for example using {Ownable}) can be paired with an + * {AccessManager} by transferring permissions (ownership in the case of {Ownable}) directly to the {AccessManager}. + * Users will be able to interact with these contracts through the {execute} function, following the access rules + * registered in the {AccessManager}. Keep in mind that in that context, the msg.sender seen by restricted functions + * will be {AccessManager} itself. + * + * WARNING: When granting permissions over an {Ownable} or {AccessControl} contract to an {AccessManager}, be very + * mindful of the danger associated with functions such as {{Ownable-renounceOwnership}} or + * {{AccessControl-renounceRole}}. + */ +contract AccessManager is Context, Multicall, IAccessManager { + using Time for *; + + // Structure that stores the details for a target contract. + struct TargetConfig { + mapping(bytes4 selector => uint64 roleId) allowedRoles; + Time.Delay adminDelay; + bool closed; + } + + // Structure that stores the details for a role/account pair. This structures fit into a single slot. + struct Access { + // Timepoint at which the user gets the permission. + // If this is either 0 or in the future, then the role permission is not available. + uint48 since; + // Delay for execution. Only applies to restricted() / execute() calls. + Time.Delay delay; + } + + // Structure that stores the details of a role. + struct Role { + // Members of the role. + mapping(address user => Access access) members; + // Admin who can grant or revoke permissions. + uint64 admin; + // Guardian who can cancel operations targeting functions that need this role. + uint64 guardian; + // Delay in which the role takes effect after being granted. + Time.Delay grantDelay; + } + + // Structure that stores the details for a scheduled operation. This structure fits into a single slot. + struct Schedule { + // Moment at which the operation can be executed. + uint48 timepoint; + // Operation nonce to allow third-party contracts to identify the operation. + uint32 nonce; + } + + uint64 public constant ADMIN_ROLE = type(uint64).min; // 0 + uint64 public constant PUBLIC_ROLE = type(uint64).max; // 2**64-1 + + mapping(address target => TargetConfig mode) private _targets; + mapping(uint64 roleId => Role) private _roles; + mapping(bytes32 operationId => Schedule) private _schedules; + + // Used to identify operations that are currently being executed via {execute}. + // This should be transient storage when supported by the EVM. + bytes32 private _executionId; + + /** + * @dev Check that the caller is authorized to perform the operation, following the restrictions encoded in + * {_getAdminRestrictions}. + */ + modifier onlyAuthorized() { + _checkAuthorized(); + _; + } + + constructor(address initialAdmin) { + if (initialAdmin == address(0)) { + revert AccessManagerInvalidInitialAdmin(address(0)); + } + + // admin is active immediately and without any execution delay. + _grantRole(ADMIN_ROLE, initialAdmin, 0, 0); + } + + // =================================================== GETTERS ==================================================== + /// @inheritdoc IAccessManager + function canCall( + address caller, + address target, + bytes4 selector + ) public view virtual returns (bool immediate, uint32 delay) { + if (isTargetClosed(target)) { + return (false, 0); + } else if (caller == address(this)) { + // Caller is AccessManager, this means the call was sent through {execute} and it already checked + // permissions. We verify that the call "identifier", which is set during {execute}, is correct. + return (_isExecuting(target, selector), 0); + } else { + uint64 roleId = getTargetFunctionRole(target, selector); + (bool isMember, uint32 currentDelay) = hasRole(roleId, caller); + return isMember ? (currentDelay == 0, currentDelay) : (false, 0); + } + } + + /// @inheritdoc IAccessManager + function expiration() public view virtual returns (uint32) { + return 1 weeks; + } + + /// @inheritdoc IAccessManager + function minSetback() public view virtual returns (uint32) { + return 5 days; + } + + /// @inheritdoc IAccessManager + function isTargetClosed(address target) public view virtual returns (bool) { + return _targets[target].closed; + } + + /// @inheritdoc IAccessManager + function getTargetFunctionRole(address target, bytes4 selector) public view virtual returns (uint64) { + return _targets[target].allowedRoles[selector]; + } + + /// @inheritdoc IAccessManager + function getTargetAdminDelay(address target) public view virtual returns (uint32) { + return _targets[target].adminDelay.get(); + } + + /// @inheritdoc IAccessManager + function getRoleAdmin(uint64 roleId) public view virtual returns (uint64) { + return _roles[roleId].admin; + } + + /// @inheritdoc IAccessManager + function getRoleGuardian(uint64 roleId) public view virtual returns (uint64) { + return _roles[roleId].guardian; + } + + /// @inheritdoc IAccessManager + function getRoleGrantDelay(uint64 roleId) public view virtual returns (uint32) { + return _roles[roleId].grantDelay.get(); + } + + /// @inheritdoc IAccessManager + function getAccess( + uint64 roleId, + address account + ) public view virtual returns (uint48 since, uint32 currentDelay, uint32 pendingDelay, uint48 effect) { + Access storage access = _roles[roleId].members[account]; + + since = access.since; + (currentDelay, pendingDelay, effect) = access.delay.getFull(); + + return (since, currentDelay, pendingDelay, effect); + } + + /// @inheritdoc IAccessManager + function hasRole( + uint64 roleId, + address account + ) public view virtual returns (bool isMember, uint32 executionDelay) { + if (roleId == PUBLIC_ROLE) { + return (true, 0); + } else { + (uint48 hasRoleSince, uint32 currentDelay, , ) = getAccess(roleId, account); + return (hasRoleSince != 0 && hasRoleSince <= Time.timestamp(), currentDelay); + } + } + + // =============================================== ROLE MANAGEMENT =============================================== + /// @inheritdoc IAccessManager + function labelRole(uint64 roleId, string calldata label) public virtual onlyAuthorized { + if (roleId == ADMIN_ROLE || roleId == PUBLIC_ROLE) { + revert AccessManagerLockedRole(roleId); + } + emit RoleLabel(roleId, label); + } + + /// @inheritdoc IAccessManager + function grantRole(uint64 roleId, address account, uint32 executionDelay) public virtual onlyAuthorized { + _grantRole(roleId, account, getRoleGrantDelay(roleId), executionDelay); + } + + /// @inheritdoc IAccessManager + function revokeRole(uint64 roleId, address account) public virtual onlyAuthorized { + _revokeRole(roleId, account); + } + + /// @inheritdoc IAccessManager + function renounceRole(uint64 roleId, address callerConfirmation) public virtual { + if (callerConfirmation != _msgSender()) { + revert AccessManagerBadConfirmation(); + } + _revokeRole(roleId, callerConfirmation); + } + + /// @inheritdoc IAccessManager + function setRoleAdmin(uint64 roleId, uint64 admin) public virtual onlyAuthorized { + _setRoleAdmin(roleId, admin); + } + + /// @inheritdoc IAccessManager + function setRoleGuardian(uint64 roleId, uint64 guardian) public virtual onlyAuthorized { + _setRoleGuardian(roleId, guardian); + } + + /// @inheritdoc IAccessManager + function setGrantDelay(uint64 roleId, uint32 newDelay) public virtual onlyAuthorized { + _setGrantDelay(roleId, newDelay); + } + + /** + * @dev Internal version of {grantRole} without access control. Returns true if the role was newly granted. + * + * Emits a {RoleGranted} event. + */ + function _grantRole( + uint64 roleId, + address account, + uint32 grantDelay, + uint32 executionDelay + ) internal virtual returns (bool) { + if (roleId == PUBLIC_ROLE) { + revert AccessManagerLockedRole(roleId); + } + + bool newMember = _roles[roleId].members[account].since == 0; + uint48 since; + + if (newMember) { + since = Time.timestamp() + grantDelay; + _roles[roleId].members[account] = Access({since: since, delay: executionDelay.toDelay()}); + } else { + // No setback here. Value can be reset by doing revoke + grant, effectively allowing the admin to perform + // any change to the execution delay within the duration of the role admin delay. + (_roles[roleId].members[account].delay, since) = _roles[roleId].members[account].delay.withUpdate( + executionDelay, + 0 + ); + } + + emit RoleGranted(roleId, account, executionDelay, since, newMember); + return newMember; + } + + /** + * @dev Internal version of {revokeRole} without access control. This logic is also used by {renounceRole}. + * Returns true if the role was previously granted. + * + * Emits a {RoleRevoked} event if the account had the role. + */ + function _revokeRole(uint64 roleId, address account) internal virtual returns (bool) { + if (roleId == PUBLIC_ROLE) { + revert AccessManagerLockedRole(roleId); + } + + if (_roles[roleId].members[account].since == 0) { + return false; + } + + delete _roles[roleId].members[account]; + + emit RoleRevoked(roleId, account); + return true; + } + + /** + * @dev Internal version of {setRoleAdmin} without access control. + * + * Emits a {RoleAdminChanged} event. + * + * NOTE: Setting the admin role as the `PUBLIC_ROLE` is allowed, but it will effectively allow + * anyone to set grant or revoke such role. + */ + function _setRoleAdmin(uint64 roleId, uint64 admin) internal virtual { + if (roleId == ADMIN_ROLE || roleId == PUBLIC_ROLE) { + revert AccessManagerLockedRole(roleId); + } + + _roles[roleId].admin = admin; + + emit RoleAdminChanged(roleId, admin); + } + + /** + * @dev Internal version of {setRoleGuardian} without access control. + * + * Emits a {RoleGuardianChanged} event. + * + * NOTE: Setting the guardian role as the `PUBLIC_ROLE` is allowed, but it will effectively allow + * anyone to cancel any scheduled operation for such role. + */ + function _setRoleGuardian(uint64 roleId, uint64 guardian) internal virtual { + if (roleId == ADMIN_ROLE || roleId == PUBLIC_ROLE) { + revert AccessManagerLockedRole(roleId); + } + + _roles[roleId].guardian = guardian; + + emit RoleGuardianChanged(roleId, guardian); + } + + /** + * @dev Internal version of {setGrantDelay} without access control. + * + * Emits a {RoleGrantDelayChanged} event. + */ + function _setGrantDelay(uint64 roleId, uint32 newDelay) internal virtual { + if (roleId == PUBLIC_ROLE) { + revert AccessManagerLockedRole(roleId); + } + + uint48 effect; + (_roles[roleId].grantDelay, effect) = _roles[roleId].grantDelay.withUpdate(newDelay, minSetback()); + + emit RoleGrantDelayChanged(roleId, newDelay, effect); + } + + // ============================================= FUNCTION MANAGEMENT ============================================== + /// @inheritdoc IAccessManager + function setTargetFunctionRole( + address target, + bytes4[] calldata selectors, + uint64 roleId + ) public virtual onlyAuthorized { + for (uint256 i = 0; i < selectors.length; ++i) { + _setTargetFunctionRole(target, selectors[i], roleId); + } + } + + /** + * @dev Internal version of {setTargetFunctionRole} without access control. + * + * Emits a {TargetFunctionRoleUpdated} event. + */ + function _setTargetFunctionRole(address target, bytes4 selector, uint64 roleId) internal virtual { + _targets[target].allowedRoles[selector] = roleId; + emit TargetFunctionRoleUpdated(target, selector, roleId); + } + + /// @inheritdoc IAccessManager + function setTargetAdminDelay(address target, uint32 newDelay) public virtual onlyAuthorized { + _setTargetAdminDelay(target, newDelay); + } + + /** + * @dev Internal version of {setTargetAdminDelay} without access control. + * + * Emits a {TargetAdminDelayUpdated} event. + */ + function _setTargetAdminDelay(address target, uint32 newDelay) internal virtual { + uint48 effect; + (_targets[target].adminDelay, effect) = _targets[target].adminDelay.withUpdate(newDelay, minSetback()); + + emit TargetAdminDelayUpdated(target, newDelay, effect); + } + + // =============================================== MODE MANAGEMENT ================================================ + /// @inheritdoc IAccessManager + function setTargetClosed(address target, bool closed) public virtual onlyAuthorized { + _setTargetClosed(target, closed); + } + + /** + * @dev Set the closed flag for a contract. This is an internal setter with no access restrictions. + * + * Emits a {TargetClosed} event. + */ + function _setTargetClosed(address target, bool closed) internal virtual { + if (target == address(this)) { + revert AccessManagerLockedAccount(target); + } + _targets[target].closed = closed; + emit TargetClosed(target, closed); + } + + // ============================================== DELAYED OPERATIONS ============================================== + /// @inheritdoc IAccessManager + function getSchedule(bytes32 id) public view virtual returns (uint48) { + uint48 timepoint = _schedules[id].timepoint; + return _isExpired(timepoint) ? 0 : timepoint; + } + + /// @inheritdoc IAccessManager + function getNonce(bytes32 id) public view virtual returns (uint32) { + return _schedules[id].nonce; + } + + /// @inheritdoc IAccessManager + function schedule( + address target, + bytes calldata data, + uint48 when + ) public virtual returns (bytes32 operationId, uint32 nonce) { + address caller = _msgSender(); + + // Fetch restrictions that apply to the caller on the targeted function + (, uint32 setback) = _canCallExtended(caller, target, data); + + uint48 minWhen = Time.timestamp() + setback; + + // if call with delay is not authorized, or if requested timing is too soon + if (setback == 0 || (when > 0 && when < minWhen)) { + revert AccessManagerUnauthorizedCall(caller, target, _checkSelector(data)); + } + + // Reuse variable due to stack too deep + when = uint48(Math.max(when, minWhen)); // cast is safe: both inputs are uint48 + + // If caller is authorised, schedule operation + operationId = hashOperation(caller, target, data); + + _checkNotScheduled(operationId); + + unchecked { + // It's not feasible to overflow the nonce in less than 1000 years + nonce = _schedules[operationId].nonce + 1; + } + _schedules[operationId].timepoint = when; + _schedules[operationId].nonce = nonce; + emit OperationScheduled(operationId, nonce, when, caller, target, data); + + // Using named return values because otherwise we get stack too deep + } + + /** + * @dev Reverts if the operation is currently scheduled and has not expired. + * (Note: This function was introduced due to stack too deep errors in schedule.) + */ + function _checkNotScheduled(bytes32 operationId) private view { + uint48 prevTimepoint = _schedules[operationId].timepoint; + if (prevTimepoint != 0 && !_isExpired(prevTimepoint)) { + revert AccessManagerAlreadyScheduled(operationId); + } + } + + /// @inheritdoc IAccessManager + // Reentrancy is not an issue because permissions are checked on msg.sender. Additionally, + // _consumeScheduledOp guarantees a scheduled operation is only executed once. + // slither-disable-next-line reentrancy-no-eth + function execute(address target, bytes calldata data) public payable virtual returns (uint32) { + address caller = _msgSender(); + + // Fetch restrictions that apply to the caller on the targeted function + (bool immediate, uint32 setback) = _canCallExtended(caller, target, data); + + // If caller is not authorised, revert + if (!immediate && setback == 0) { + revert AccessManagerUnauthorizedCall(caller, target, _checkSelector(data)); + } + + bytes32 operationId = hashOperation(caller, target, data); + uint32 nonce; + + // If caller is authorised, check operation was scheduled early enough + // Consume an available schedule even if there is no currently enforced delay + if (setback != 0 || getSchedule(operationId) != 0) { + nonce = _consumeScheduledOp(operationId); + } + + // Mark the target and selector as authorised + bytes32 executionIdBefore = _executionId; + _executionId = _hashExecutionId(target, _checkSelector(data)); + + // Perform call + Address.functionCallWithValue(target, data, msg.value); + + // Reset execute identifier + _executionId = executionIdBefore; + + return nonce; + } + + /// @inheritdoc IAccessManager + function cancel(address caller, address target, bytes calldata data) public virtual returns (uint32) { + address msgsender = _msgSender(); + bytes4 selector = _checkSelector(data); + + bytes32 operationId = hashOperation(caller, target, data); + if (_schedules[operationId].timepoint == 0) { + revert AccessManagerNotScheduled(operationId); + } else if (caller != msgsender) { + // calls can only be canceled by the account that scheduled them, a global admin, or by a guardian of the required role. + (bool isAdmin, ) = hasRole(ADMIN_ROLE, msgsender); + (bool isGuardian, ) = hasRole(getRoleGuardian(getTargetFunctionRole(target, selector)), msgsender); + if (!isAdmin && !isGuardian) { + revert AccessManagerUnauthorizedCancel(msgsender, caller, target, selector); + } + } + + delete _schedules[operationId].timepoint; // reset the timepoint, keep the nonce + uint32 nonce = _schedules[operationId].nonce; + emit OperationCanceled(operationId, nonce); + + return nonce; + } + + /// @inheritdoc IAccessManager + function consumeScheduledOp(address caller, bytes calldata data) public virtual { + address target = _msgSender(); + if (IAccessManaged(target).isConsumingScheduledOp() != IAccessManaged.isConsumingScheduledOp.selector) { + revert AccessManagerUnauthorizedConsume(target); + } + _consumeScheduledOp(hashOperation(caller, target, data)); + } + + /** + * @dev Internal variant of {consumeScheduledOp} that operates on bytes32 operationId. + * + * Returns the nonce of the scheduled operation that is consumed. + */ + function _consumeScheduledOp(bytes32 operationId) internal virtual returns (uint32) { + uint48 timepoint = _schedules[operationId].timepoint; + uint32 nonce = _schedules[operationId].nonce; + + if (timepoint == 0) { + revert AccessManagerNotScheduled(operationId); + } else if (timepoint > Time.timestamp()) { + revert AccessManagerNotReady(operationId); + } else if (_isExpired(timepoint)) { + revert AccessManagerExpired(operationId); + } + + delete _schedules[operationId].timepoint; // reset the timepoint, keep the nonce + emit OperationExecuted(operationId, nonce); + + return nonce; + } + + /// @inheritdoc IAccessManager + function hashOperation(address caller, address target, bytes calldata data) public view virtual returns (bytes32) { + return keccak256(abi.encode(caller, target, data)); + } + + // ==================================================== OTHERS ==================================================== + /// @inheritdoc IAccessManager + function updateAuthority(address target, address newAuthority) public virtual onlyAuthorized { + IAccessManaged(target).setAuthority(newAuthority); + } + + // ================================================= ADMIN LOGIC ================================================== + /** + * @dev Check if the current call is authorized according to admin logic. + */ + function _checkAuthorized() private { + address caller = _msgSender(); + (bool immediate, uint32 delay) = _canCallSelf(caller, _msgData()); + if (!immediate) { + if (delay == 0) { + (, uint64 requiredRole, ) = _getAdminRestrictions(_msgData()); + revert AccessManagerUnauthorizedAccount(caller, requiredRole); + } else { + _consumeScheduledOp(hashOperation(caller, address(this), _msgData())); + } + } + } + + /** + * @dev Get the admin restrictions of a given function call based on the function and arguments involved. + * + * Returns: + * - bool restricted: does this data match a restricted operation + * - uint64: which role is this operation restricted to + * - uint32: minimum delay to enforce for that operation (max between operation's delay and admin's execution delay) + */ + function _getAdminRestrictions( + bytes calldata data + ) private view returns (bool restricted, uint64 roleAdminId, uint32 executionDelay) { + if (data.length < 4) { + return (false, 0, 0); + } + + bytes4 selector = _checkSelector(data); + + // Restricted to ADMIN with no delay beside any execution delay the caller may have + if ( + selector == this.labelRole.selector || + selector == this.setRoleAdmin.selector || + selector == this.setRoleGuardian.selector || + selector == this.setGrantDelay.selector || + selector == this.setTargetAdminDelay.selector + ) { + return (true, ADMIN_ROLE, 0); + } + + // Restricted to ADMIN with the admin delay corresponding to the target + if ( + selector == this.updateAuthority.selector || + selector == this.setTargetClosed.selector || + selector == this.setTargetFunctionRole.selector + ) { + // First argument is a target. + address target = abi.decode(data[0x04:0x24], (address)); + uint32 delay = getTargetAdminDelay(target); + return (true, ADMIN_ROLE, delay); + } + + // Restricted to that role's admin with no delay beside any execution delay the caller may have. + if (selector == this.grantRole.selector || selector == this.revokeRole.selector) { + // First argument is a roleId. + uint64 roleId = abi.decode(data[0x04:0x24], (uint64)); + return (true, getRoleAdmin(roleId), 0); + } + + return (false, 0, 0); + } + + // =================================================== HELPERS ==================================================== + /** + * @dev An extended version of {canCall} for internal usage that checks {_canCallSelf} + * when the target is this contract. + * + * Returns: + * - bool immediate: whether the operation can be executed immediately (with no delay) + * - uint32 delay: the execution delay + */ + function _canCallExtended( + address caller, + address target, + bytes calldata data + ) private view returns (bool immediate, uint32 delay) { + if (target == address(this)) { + return _canCallSelf(caller, data); + } else { + return data.length < 4 ? (false, 0) : canCall(caller, target, _checkSelector(data)); + } + } + + /** + * @dev A version of {canCall} that checks for admin restrictions in this contract. + */ + function _canCallSelf(address caller, bytes calldata data) private view returns (bool immediate, uint32 delay) { + if (data.length < 4) { + return (false, 0); + } + + if (caller == address(this)) { + // Caller is AccessManager, this means the call was sent through {execute} and it already checked + // permissions. We verify that the call "identifier", which is set during {execute}, is correct. + return (_isExecuting(address(this), _checkSelector(data)), 0); + } + + (bool enabled, uint64 roleId, uint32 operationDelay) = _getAdminRestrictions(data); + if (!enabled) { + return (false, 0); + } + + (bool inRole, uint32 executionDelay) = hasRole(roleId, caller); + if (!inRole) { + return (false, 0); + } + + // downcast is safe because both options are uint32 + delay = uint32(Math.max(operationDelay, executionDelay)); + return (delay == 0, delay); + } + + /** + * @dev Returns true if a call with `target` and `selector` is being executed via {executed}. + */ + function _isExecuting(address target, bytes4 selector) private view returns (bool) { + return _executionId == _hashExecutionId(target, selector); + } + + /** + * @dev Returns true if a schedule timepoint is past its expiration deadline. + */ + function _isExpired(uint48 timepoint) private view returns (bool) { + return timepoint + expiration() <= Time.timestamp(); + } + + /** + * @dev Extracts the selector from calldata. Panics if data is not at least 4 bytes + */ + function _checkSelector(bytes calldata data) private pure returns (bytes4) { + return bytes4(data[0:4]); + } + + /** + * @dev Hashing function for execute protection + */ + function _hashExecutionId(address target, bytes4 selector) private pure returns (bytes32) { + return keccak256(abi.encode(target, selector)); + } +} diff --git a/CVLByExample/UniswapHooks/lib/uniswap-hooks/lib/v4-core/lib/openzeppelin-contracts/contracts/access/manager/AuthorityUtils.sol b/CVLByExample/UniswapHooks/lib/uniswap-hooks/lib/v4-core/lib/openzeppelin-contracts/contracts/access/manager/AuthorityUtils.sol new file mode 100644 index 00000000..fb3018ca --- /dev/null +++ b/CVLByExample/UniswapHooks/lib/uniswap-hooks/lib/v4-core/lib/openzeppelin-contracts/contracts/access/manager/AuthorityUtils.sol @@ -0,0 +1,32 @@ +// SPDX-License-Identifier: MIT +// OpenZeppelin Contracts (last updated v5.0.0) (access/manager/AuthorityUtils.sol) + +pragma solidity ^0.8.20; + +import {IAuthority} from "./IAuthority.sol"; + +library AuthorityUtils { + /** + * @dev Since `AccessManager` implements an extended IAuthority interface, invoking `canCall` with backwards compatibility + * for the preexisting `IAuthority` interface requires special care to avoid reverting on insufficient return data. + * This helper function takes care of invoking `canCall` in a backwards compatible way without reverting. + */ + function canCallWithDelay( + address authority, + address caller, + address target, + bytes4 selector + ) internal view returns (bool immediate, uint32 delay) { + (bool success, bytes memory data) = authority.staticcall( + abi.encodeCall(IAuthority.canCall, (caller, target, selector)) + ); + if (success) { + if (data.length >= 0x40) { + (immediate, delay) = abi.decode(data, (bool, uint32)); + } else if (data.length >= 0x20) { + immediate = abi.decode(data, (bool)); + } + } + return (immediate, delay); + } +} diff --git a/CVLByExample/UniswapHooks/lib/uniswap-hooks/lib/v4-core/lib/openzeppelin-contracts/contracts/access/manager/IAccessManaged.sol b/CVLByExample/UniswapHooks/lib/uniswap-hooks/lib/v4-core/lib/openzeppelin-contracts/contracts/access/manager/IAccessManaged.sol new file mode 100644 index 00000000..95206bde --- /dev/null +++ b/CVLByExample/UniswapHooks/lib/uniswap-hooks/lib/v4-core/lib/openzeppelin-contracts/contracts/access/manager/IAccessManaged.sol @@ -0,0 +1,32 @@ +// SPDX-License-Identifier: MIT +// OpenZeppelin Contracts (last updated v5.0.0) (access/manager/IAccessManaged.sol) + +pragma solidity ^0.8.20; + +interface IAccessManaged { + /** + * @dev Authority that manages this contract was updated. + */ + event AuthorityUpdated(address authority); + + error AccessManagedUnauthorized(address caller); + error AccessManagedRequiredDelay(address caller, uint32 delay); + error AccessManagedInvalidAuthority(address authority); + + /** + * @dev Returns the current authority. + */ + function authority() external view returns (address); + + /** + * @dev Transfers control to a new authority. The caller must be the current authority. + */ + function setAuthority(address) external; + + /** + * @dev Returns true only in the context of a delayed restricted call, at the moment that the scheduled operation is + * being consumed. Prevents denial of service for delayed restricted calls in the case that the contract performs + * attacker controlled calls. + */ + function isConsumingScheduledOp() external view returns (bytes4); +} diff --git a/CVLByExample/UniswapHooks/lib/uniswap-hooks/lib/v4-core/lib/openzeppelin-contracts/contracts/access/manager/IAccessManager.sol b/CVLByExample/UniswapHooks/lib/uniswap-hooks/lib/v4-core/lib/openzeppelin-contracts/contracts/access/manager/IAccessManager.sol new file mode 100644 index 00000000..3a6dc731 --- /dev/null +++ b/CVLByExample/UniswapHooks/lib/uniswap-hooks/lib/v4-core/lib/openzeppelin-contracts/contracts/access/manager/IAccessManager.sol @@ -0,0 +1,392 @@ +// SPDX-License-Identifier: MIT +// OpenZeppelin Contracts (last updated v5.0.0) (access/manager/IAccessManager.sol) + +pragma solidity ^0.8.20; + +import {IAccessManaged} from "./IAccessManaged.sol"; +import {Time} from "../../utils/types/Time.sol"; + +interface IAccessManager { + /** + * @dev A delayed operation was scheduled. + */ + event OperationScheduled( + bytes32 indexed operationId, + uint32 indexed nonce, + uint48 schedule, + address caller, + address target, + bytes data + ); + + /** + * @dev A scheduled operation was executed. + */ + event OperationExecuted(bytes32 indexed operationId, uint32 indexed nonce); + + /** + * @dev A scheduled operation was canceled. + */ + event OperationCanceled(bytes32 indexed operationId, uint32 indexed nonce); + + /** + * @dev Informational labelling for a roleId. + */ + event RoleLabel(uint64 indexed roleId, string label); + + /** + * @dev Emitted when `account` is granted `roleId`. + * + * NOTE: The meaning of the `since` argument depends on the `newMember` argument. + * If the role is granted to a new member, the `since` argument indicates when the account becomes a member of the role, + * otherwise it indicates the execution delay for this account and roleId is updated. + */ + event RoleGranted(uint64 indexed roleId, address indexed account, uint32 delay, uint48 since, bool newMember); + + /** + * @dev Emitted when `account` membership or `roleId` is revoked. Unlike granting, revoking is instantaneous. + */ + event RoleRevoked(uint64 indexed roleId, address indexed account); + + /** + * @dev Role acting as admin over a given `roleId` is updated. + */ + event RoleAdminChanged(uint64 indexed roleId, uint64 indexed admin); + + /** + * @dev Role acting as guardian over a given `roleId` is updated. + */ + event RoleGuardianChanged(uint64 indexed roleId, uint64 indexed guardian); + + /** + * @dev Grant delay for a given `roleId` will be updated to `delay` when `since` is reached. + */ + event RoleGrantDelayChanged(uint64 indexed roleId, uint32 delay, uint48 since); + + /** + * @dev Target mode is updated (true = closed, false = open). + */ + event TargetClosed(address indexed target, bool closed); + + /** + * @dev Role required to invoke `selector` on `target` is updated to `roleId`. + */ + event TargetFunctionRoleUpdated(address indexed target, bytes4 selector, uint64 indexed roleId); + + /** + * @dev Admin delay for a given `target` will be updated to `delay` when `since` is reached. + */ + event TargetAdminDelayUpdated(address indexed target, uint32 delay, uint48 since); + + error AccessManagerAlreadyScheduled(bytes32 operationId); + error AccessManagerNotScheduled(bytes32 operationId); + error AccessManagerNotReady(bytes32 operationId); + error AccessManagerExpired(bytes32 operationId); + error AccessManagerLockedAccount(address account); + error AccessManagerLockedRole(uint64 roleId); + error AccessManagerBadConfirmation(); + error AccessManagerUnauthorizedAccount(address msgsender, uint64 roleId); + error AccessManagerUnauthorizedCall(address caller, address target, bytes4 selector); + error AccessManagerUnauthorizedConsume(address target); + error AccessManagerUnauthorizedCancel(address msgsender, address caller, address target, bytes4 selector); + error AccessManagerInvalidInitialAdmin(address initialAdmin); + + /** + * @dev Check if an address (`caller`) is authorised to call a given function on a given contract directly (with + * no restriction). Additionally, it returns the delay needed to perform the call indirectly through the {schedule} + * & {execute} workflow. + * + * This function is usually called by the targeted contract to control immediate execution of restricted functions. + * Therefore we only return true if the call can be performed without any delay. If the call is subject to a + * previously set delay (not zero), then the function should return false and the caller should schedule the operation + * for future execution. + * + * If `immediate` is true, the delay can be disregarded and the operation can be immediately executed, otherwise + * the operation can be executed if and only if delay is greater than 0. + * + * NOTE: The IAuthority interface does not include the `uint32` delay. This is an extension of that interface that + * is backward compatible. Some contracts may thus ignore the second return argument. In that case they will fail + * to identify the indirect workflow, and will consider calls that require a delay to be forbidden. + * + * NOTE: This function does not report the permissions of this manager itself. These are defined by the + * {_canCallSelf} function instead. + */ + function canCall( + address caller, + address target, + bytes4 selector + ) external view returns (bool allowed, uint32 delay); + + /** + * @dev Expiration delay for scheduled proposals. Defaults to 1 week. + * + * IMPORTANT: Avoid overriding the expiration with 0. Otherwise every contract proposal will be expired immediately, + * disabling any scheduling usage. + */ + function expiration() external view returns (uint32); + + /** + * @dev Minimum setback for all delay updates, with the exception of execution delays. It + * can be increased without setback (and reset via {revokeRole} in the case event of an + * accidental increase). Defaults to 5 days. + */ + function minSetback() external view returns (uint32); + + /** + * @dev Get whether the contract is closed disabling any access. Otherwise role permissions are applied. + */ + function isTargetClosed(address target) external view returns (bool); + + /** + * @dev Get the role required to call a function. + */ + function getTargetFunctionRole(address target, bytes4 selector) external view returns (uint64); + + /** + * @dev Get the admin delay for a target contract. Changes to contract configuration are subject to this delay. + */ + function getTargetAdminDelay(address target) external view returns (uint32); + + /** + * @dev Get the id of the role that acts as an admin for the given role. + * + * The admin permission is required to grant the role, revoke the role and update the execution delay to execute + * an operation that is restricted to this role. + */ + function getRoleAdmin(uint64 roleId) external view returns (uint64); + + /** + * @dev Get the role that acts as a guardian for a given role. + * + * The guardian permission allows canceling operations that have been scheduled under the role. + */ + function getRoleGuardian(uint64 roleId) external view returns (uint64); + + /** + * @dev Get the role current grant delay. + * + * Its value may change at any point without an event emitted following a call to {setGrantDelay}. + * Changes to this value, including effect timepoint are notified in advance by the {RoleGrantDelayChanged} event. + */ + function getRoleGrantDelay(uint64 roleId) external view returns (uint32); + + /** + * @dev Get the access details for a given account for a given role. These details include the timepoint at which + * membership becomes active, and the delay applied to all operation by this user that requires this permission + * level. + * + * Returns: + * [0] Timestamp at which the account membership becomes valid. 0 means role is not granted. + * [1] Current execution delay for the account. + * [2] Pending execution delay for the account. + * [3] Timestamp at which the pending execution delay will become active. 0 means no delay update is scheduled. + */ + function getAccess(uint64 roleId, address account) external view returns (uint48, uint32, uint32, uint48); + + /** + * @dev Check if a given account currently has the permission level corresponding to a given role. Note that this + * permission might be associated with an execution delay. {getAccess} can provide more details. + */ + function hasRole(uint64 roleId, address account) external view returns (bool, uint32); + + /** + * @dev Give a label to a role, for improved role discoverability by UIs. + * + * Requirements: + * + * - the caller must be a global admin + * + * Emits a {RoleLabel} event. + */ + function labelRole(uint64 roleId, string calldata label) external; + + /** + * @dev Add `account` to `roleId`, or change its execution delay. + * + * This gives the account the authorization to call any function that is restricted to this role. An optional + * execution delay (in seconds) can be set. If that delay is non 0, the user is required to schedule any operation + * that is restricted to members of this role. The user will only be able to execute the operation after the delay has + * passed, before it has expired. During this period, admin and guardians can cancel the operation (see {cancel}). + * + * If the account has already been granted this role, the execution delay will be updated. This update is not + * immediate and follows the delay rules. For example, if a user currently has a delay of 3 hours, and this is + * called to reduce that delay to 1 hour, the new delay will take some time to take effect, enforcing that any + * operation executed in the 3 hours that follows this update was indeed scheduled before this update. + * + * Requirements: + * + * - the caller must be an admin for the role (see {getRoleAdmin}) + * - granted role must not be the `PUBLIC_ROLE` + * + * Emits a {RoleGranted} event. + */ + function grantRole(uint64 roleId, address account, uint32 executionDelay) external; + + /** + * @dev Remove an account from a role, with immediate effect. If the account does not have the role, this call has + * no effect. + * + * Requirements: + * + * - the caller must be an admin for the role (see {getRoleAdmin}) + * - revoked role must not be the `PUBLIC_ROLE` + * + * Emits a {RoleRevoked} event if the account had the role. + */ + function revokeRole(uint64 roleId, address account) external; + + /** + * @dev Renounce role permissions for the calling account with immediate effect. If the sender is not in + * the role this call has no effect. + * + * Requirements: + * + * - the caller must be `callerConfirmation`. + * + * Emits a {RoleRevoked} event if the account had the role. + */ + function renounceRole(uint64 roleId, address callerConfirmation) external; + + /** + * @dev Change admin role for a given role. + * + * Requirements: + * + * - the caller must be a global admin + * + * Emits a {RoleAdminChanged} event + */ + function setRoleAdmin(uint64 roleId, uint64 admin) external; + + /** + * @dev Change guardian role for a given role. + * + * Requirements: + * + * - the caller must be a global admin + * + * Emits a {RoleGuardianChanged} event + */ + function setRoleGuardian(uint64 roleId, uint64 guardian) external; + + /** + * @dev Update the delay for granting a `roleId`. + * + * Requirements: + * + * - the caller must be a global admin + * + * Emits a {RoleGrantDelayChanged} event. + */ + function setGrantDelay(uint64 roleId, uint32 newDelay) external; + + /** + * @dev Set the role required to call functions identified by the `selectors` in the `target` contract. + * + * Requirements: + * + * - the caller must be a global admin + * + * Emits a {TargetFunctionRoleUpdated} event per selector. + */ + function setTargetFunctionRole(address target, bytes4[] calldata selectors, uint64 roleId) external; + + /** + * @dev Set the delay for changing the configuration of a given target contract. + * + * Requirements: + * + * - the caller must be a global admin + * + * Emits a {TargetAdminDelayUpdated} event. + */ + function setTargetAdminDelay(address target, uint32 newDelay) external; + + /** + * @dev Set the closed flag for a contract. + * + * Requirements: + * + * - the caller must be a global admin + * + * Emits a {TargetClosed} event. + */ + function setTargetClosed(address target, bool closed) external; + + /** + * @dev Return the timepoint at which a scheduled operation will be ready for execution. This returns 0 if the + * operation is not yet scheduled, has expired, was executed, or was canceled. + */ + function getSchedule(bytes32 id) external view returns (uint48); + + /** + * @dev Return the nonce for the latest scheduled operation with a given id. Returns 0 if the operation has never + * been scheduled. + */ + function getNonce(bytes32 id) external view returns (uint32); + + /** + * @dev Schedule a delayed operation for future execution, and return the operation identifier. It is possible to + * choose the timestamp at which the operation becomes executable as long as it satisfies the execution delays + * required for the caller. The special value zero will automatically set the earliest possible time. + * + * Returns the `operationId` that was scheduled. Since this value is a hash of the parameters, it can reoccur when + * the same parameters are used; if this is relevant, the returned `nonce` can be used to uniquely identify this + * scheduled operation from other occurrences of the same `operationId` in invocations of {execute} and {cancel}. + * + * Emits a {OperationScheduled} event. + * + * NOTE: It is not possible to concurrently schedule more than one operation with the same `target` and `data`. If + * this is necessary, a random byte can be appended to `data` to act as a salt that will be ignored by the target + * contract if it is using standard Solidity ABI encoding. + */ + function schedule(address target, bytes calldata data, uint48 when) external returns (bytes32, uint32); + + /** + * @dev Execute a function that is delay restricted, provided it was properly scheduled beforehand, or the + * execution delay is 0. + * + * Returns the nonce that identifies the previously scheduled operation that is executed, or 0 if the + * operation wasn't previously scheduled (if the caller doesn't have an execution delay). + * + * Emits an {OperationExecuted} event only if the call was scheduled and delayed. + */ + function execute(address target, bytes calldata data) external payable returns (uint32); + + /** + * @dev Cancel a scheduled (delayed) operation. Returns the nonce that identifies the previously scheduled + * operation that is cancelled. + * + * Requirements: + * + * - the caller must be the proposer, a guardian of the targeted function, or a global admin + * + * Emits a {OperationCanceled} event. + */ + function cancel(address caller, address target, bytes calldata data) external returns (uint32); + + /** + * @dev Consume a scheduled operation targeting the caller. If such an operation exists, mark it as consumed + * (emit an {OperationExecuted} event and clean the state). Otherwise, throw an error. + * + * This is useful for contract that want to enforce that calls targeting them were scheduled on the manager, + * with all the verifications that it implies. + * + * Emit a {OperationExecuted} event. + */ + function consumeScheduledOp(address caller, bytes calldata data) external; + + /** + * @dev Hashing function for delayed operations. + */ + function hashOperation(address caller, address target, bytes calldata data) external view returns (bytes32); + + /** + * @dev Changes the authority of a target managed by this manager instance. + * + * Requirements: + * + * - the caller must be a global admin + */ + function updateAuthority(address target, address newAuthority) external; +} diff --git a/CVLByExample/UniswapHooks/lib/uniswap-hooks/lib/v4-core/lib/openzeppelin-contracts/contracts/access/manager/IAuthority.sol b/CVLByExample/UniswapHooks/lib/uniswap-hooks/lib/v4-core/lib/openzeppelin-contracts/contracts/access/manager/IAuthority.sol new file mode 100644 index 00000000..e2d3898f --- /dev/null +++ b/CVLByExample/UniswapHooks/lib/uniswap-hooks/lib/v4-core/lib/openzeppelin-contracts/contracts/access/manager/IAuthority.sol @@ -0,0 +1,14 @@ +// SPDX-License-Identifier: MIT +// OpenZeppelin Contracts (last updated v5.0.0) (access/manager/IAuthority.sol) + +pragma solidity ^0.8.20; + +/** + * @dev Standard interface for permissioning originally defined in Dappsys. + */ +interface IAuthority { + /** + * @dev Returns true if the caller can invoke on a target the function identified by a function selector. + */ + function canCall(address caller, address target, bytes4 selector) external view returns (bool allowed); +} diff --git a/CVLByExample/UniswapHooks/lib/uniswap-hooks/lib/v4-core/lib/openzeppelin-contracts/contracts/finance/README.adoc b/CVLByExample/UniswapHooks/lib/uniswap-hooks/lib/v4-core/lib/openzeppelin-contracts/contracts/finance/README.adoc new file mode 100644 index 00000000..ac7e4f01 --- /dev/null +++ b/CVLByExample/UniswapHooks/lib/uniswap-hooks/lib/v4-core/lib/openzeppelin-contracts/contracts/finance/README.adoc @@ -0,0 +1,14 @@ += Finance + +[.readme-notice] +NOTE: This document is better viewed at https://docs.openzeppelin.com/contracts/api/finance + +This directory includes primitives for financial systems: + +- {VestingWallet} handles the vesting of Ether and ERC20 tokens for a given beneficiary. Custody of multiple tokens can + be given to this contract, which will release the token to the beneficiary following a given, customizable, vesting + schedule. + +== Contracts + +{{VestingWallet}} diff --git a/CVLByExample/UniswapHooks/lib/uniswap-hooks/lib/v4-core/lib/openzeppelin-contracts/contracts/finance/VestingWallet.sol b/CVLByExample/UniswapHooks/lib/uniswap-hooks/lib/v4-core/lib/openzeppelin-contracts/contracts/finance/VestingWallet.sol new file mode 100644 index 00000000..5abb7cda --- /dev/null +++ b/CVLByExample/UniswapHooks/lib/uniswap-hooks/lib/v4-core/lib/openzeppelin-contracts/contracts/finance/VestingWallet.sol @@ -0,0 +1,154 @@ +// SPDX-License-Identifier: MIT +// OpenZeppelin Contracts (last updated v5.0.0) (finance/VestingWallet.sol) +pragma solidity ^0.8.20; + +import {IERC20} from "../token/ERC20/IERC20.sol"; +import {SafeERC20} from "../token/ERC20/utils/SafeERC20.sol"; +import {Address} from "../utils/Address.sol"; +import {Context} from "../utils/Context.sol"; +import {Ownable} from "../access/Ownable.sol"; + +/** + * @dev A vesting wallet is an ownable contract that can receive native currency and ERC20 tokens, and release these + * assets to the wallet owner, also referred to as "beneficiary", according to a vesting schedule. + * + * Any assets transferred to this contract will follow the vesting schedule as if they were locked from the beginning. + * Consequently, if the vesting has already started, any amount of tokens sent to this contract will (at least partly) + * be immediately releasable. + * + * By setting the duration to 0, one can configure this contract to behave like an asset timelock that hold tokens for + * a beneficiary until a specified time. + * + * NOTE: Since the wallet is {Ownable}, and ownership can be transferred, it is possible to sell unvested tokens. + * Preventing this in a smart contract is difficult, considering that: 1) a beneficiary address could be a + * counterfactually deployed contract, 2) there is likely to be a migration path for EOAs to become contracts in the + * near future. + * + * NOTE: When using this contract with any token whose balance is adjusted automatically (i.e. a rebase token), make + * sure to account the supply/balance adjustment in the vesting schedule to ensure the vested amount is as intended. + */ +contract VestingWallet is Context, Ownable { + event EtherReleased(uint256 amount); + event ERC20Released(address indexed token, uint256 amount); + + uint256 private _released; + mapping(address token => uint256) private _erc20Released; + uint64 private immutable _start; + uint64 private immutable _duration; + + /** + * @dev Sets the sender as the initial owner, the beneficiary as the pending owner, the start timestamp and the + * vesting duration of the vesting wallet. + */ + constructor(address beneficiary, uint64 startTimestamp, uint64 durationSeconds) payable Ownable(beneficiary) { + _start = startTimestamp; + _duration = durationSeconds; + } + + /** + * @dev The contract should be able to receive Eth. + */ + receive() external payable virtual {} + + /** + * @dev Getter for the start timestamp. + */ + function start() public view virtual returns (uint256) { + return _start; + } + + /** + * @dev Getter for the vesting duration. + */ + function duration() public view virtual returns (uint256) { + return _duration; + } + + /** + * @dev Getter for the end timestamp. + */ + function end() public view virtual returns (uint256) { + return start() + duration(); + } + + /** + * @dev Amount of eth already released + */ + function released() public view virtual returns (uint256) { + return _released; + } + + /** + * @dev Amount of token already released + */ + function released(address token) public view virtual returns (uint256) { + return _erc20Released[token]; + } + + /** + * @dev Getter for the amount of releasable eth. + */ + function releasable() public view virtual returns (uint256) { + return vestedAmount(uint64(block.timestamp)) - released(); + } + + /** + * @dev Getter for the amount of releasable `token` tokens. `token` should be the address of an + * IERC20 contract. + */ + function releasable(address token) public view virtual returns (uint256) { + return vestedAmount(token, uint64(block.timestamp)) - released(token); + } + + /** + * @dev Release the native token (ether) that have already vested. + * + * Emits a {EtherReleased} event. + */ + function release() public virtual { + uint256 amount = releasable(); + _released += amount; + emit EtherReleased(amount); + Address.sendValue(payable(owner()), amount); + } + + /** + * @dev Release the tokens that have already vested. + * + * Emits a {ERC20Released} event. + */ + function release(address token) public virtual { + uint256 amount = releasable(token); + _erc20Released[token] += amount; + emit ERC20Released(token, amount); + SafeERC20.safeTransfer(IERC20(token), owner(), amount); + } + + /** + * @dev Calculates the amount of ether that has already vested. Default implementation is a linear vesting curve. + */ + function vestedAmount(uint64 timestamp) public view virtual returns (uint256) { + return _vestingSchedule(address(this).balance + released(), timestamp); + } + + /** + * @dev Calculates the amount of tokens that has already vested. Default implementation is a linear vesting curve. + */ + function vestedAmount(address token, uint64 timestamp) public view virtual returns (uint256) { + return _vestingSchedule(IERC20(token).balanceOf(address(this)) + released(token), timestamp); + } + + /** + * @dev Virtual implementation of the vesting formula. This returns the amount vested, as a function of time, for + * an asset given its total historical allocation. + */ + function _vestingSchedule(uint256 totalAllocation, uint64 timestamp) internal view virtual returns (uint256) { + if (timestamp < start()) { + return 0; + } else if (timestamp >= end()) { + return totalAllocation; + } else { + return (totalAllocation * (timestamp - start())) / duration(); + } + } +} diff --git a/CVLByExample/UniswapHooks/lib/uniswap-hooks/lib/v4-core/lib/openzeppelin-contracts/contracts/governance/Governor.sol b/CVLByExample/UniswapHooks/lib/uniswap-hooks/lib/v4-core/lib/openzeppelin-contracts/contracts/governance/Governor.sol new file mode 100644 index 00000000..830c9d83 --- /dev/null +++ b/CVLByExample/UniswapHooks/lib/uniswap-hooks/lib/v4-core/lib/openzeppelin-contracts/contracts/governance/Governor.sol @@ -0,0 +1,850 @@ +// SPDX-License-Identifier: MIT +// OpenZeppelin Contracts (last updated v5.0.0) (governance/Governor.sol) + +pragma solidity ^0.8.20; + +import {IERC721Receiver} from "../token/ERC721/IERC721Receiver.sol"; +import {IERC1155Receiver} from "../token/ERC1155/IERC1155Receiver.sol"; +import {EIP712} from "../utils/cryptography/EIP712.sol"; +import {SignatureChecker} from "../utils/cryptography/SignatureChecker.sol"; +import {IERC165, ERC165} from "../utils/introspection/ERC165.sol"; +import {SafeCast} from "../utils/math/SafeCast.sol"; +import {DoubleEndedQueue} from "../utils/structs/DoubleEndedQueue.sol"; +import {Address} from "../utils/Address.sol"; +import {Context} from "../utils/Context.sol"; +import {Nonces} from "../utils/Nonces.sol"; +import {IGovernor, IERC6372} from "./IGovernor.sol"; + +/** + * @dev Core of the governance system, designed to be extended through various modules. + * + * This contract is abstract and requires several functions to be implemented in various modules: + * + * - A counting module must implement {quorum}, {_quorumReached}, {_voteSucceeded} and {_countVote} + * - A voting module must implement {_getVotes} + * - Additionally, {votingPeriod} must also be implemented + */ +abstract contract Governor is Context, ERC165, EIP712, Nonces, IGovernor, IERC721Receiver, IERC1155Receiver { + using DoubleEndedQueue for DoubleEndedQueue.Bytes32Deque; + + bytes32 public constant BALLOT_TYPEHASH = + keccak256("Ballot(uint256 proposalId,uint8 support,address voter,uint256 nonce)"); + bytes32 public constant EXTENDED_BALLOT_TYPEHASH = + keccak256( + "ExtendedBallot(uint256 proposalId,uint8 support,address voter,uint256 nonce,string reason,bytes params)" + ); + + struct ProposalCore { + address proposer; + uint48 voteStart; + uint32 voteDuration; + bool executed; + bool canceled; + uint48 etaSeconds; + } + + bytes32 private constant ALL_PROPOSAL_STATES_BITMAP = bytes32((2 ** (uint8(type(ProposalState).max) + 1)) - 1); + string private _name; + + mapping(uint256 proposalId => ProposalCore) private _proposals; + + // This queue keeps track of the governor operating on itself. Calls to functions protected by the {onlyGovernance} + // modifier needs to be whitelisted in this queue. Whitelisting is set in {execute}, consumed by the + // {onlyGovernance} modifier and eventually reset after {_executeOperations} completes. This ensures that the + // execution of {onlyGovernance} protected calls can only be achieved through successful proposals. + DoubleEndedQueue.Bytes32Deque private _governanceCall; + + /** + * @dev Restricts a function so it can only be executed through governance proposals. For example, governance + * parameter setters in {GovernorSettings} are protected using this modifier. + * + * The governance executing address may be different from the Governor's own address, for example it could be a + * timelock. This can be customized by modules by overriding {_executor}. The executor is only able to invoke these + * functions during the execution of the governor's {execute} function, and not under any other circumstances. Thus, + * for example, additional timelock proposers are not able to change governance parameters without going through the + * governance protocol (since v4.6). + */ + modifier onlyGovernance() { + _checkGovernance(); + _; + } + + /** + * @dev Sets the value for {name} and {version} + */ + constructor(string memory name_) EIP712(name_, version()) { + _name = name_; + } + + /** + * @dev Function to receive ETH that will be handled by the governor (disabled if executor is a third party contract) + */ + receive() external payable virtual { + if (_executor() != address(this)) { + revert GovernorDisabledDeposit(); + } + } + + /** + * @dev See {IERC165-supportsInterface}. + */ + function supportsInterface(bytes4 interfaceId) public view virtual override(IERC165, ERC165) returns (bool) { + return + interfaceId == type(IGovernor).interfaceId || + interfaceId == type(IERC1155Receiver).interfaceId || + super.supportsInterface(interfaceId); + } + + /** + * @dev See {IGovernor-name}. + */ + function name() public view virtual returns (string memory) { + return _name; + } + + /** + * @dev See {IGovernor-version}. + */ + function version() public view virtual returns (string memory) { + return "1"; + } + + /** + * @dev See {IGovernor-hashProposal}. + * + * The proposal id is produced by hashing the ABI encoded `targets` array, the `values` array, the `calldatas` array + * and the descriptionHash (bytes32 which itself is the keccak256 hash of the description string). This proposal id + * can be produced from the proposal data which is part of the {ProposalCreated} event. It can even be computed in + * advance, before the proposal is submitted. + * + * Note that the chainId and the governor address are not part of the proposal id computation. Consequently, the + * same proposal (with same operation and same description) will have the same id if submitted on multiple governors + * across multiple networks. This also means that in order to execute the same operation twice (on the same + * governor) the proposer will have to change the description in order to avoid proposal id conflicts. + */ + function hashProposal( + address[] memory targets, + uint256[] memory values, + bytes[] memory calldatas, + bytes32 descriptionHash + ) public pure virtual returns (uint256) { + return uint256(keccak256(abi.encode(targets, values, calldatas, descriptionHash))); + } + + /** + * @dev See {IGovernor-state}. + */ + function state(uint256 proposalId) public view virtual returns (ProposalState) { + // We read the struct fields into the stack at once so Solidity emits a single SLOAD + ProposalCore storage proposal = _proposals[proposalId]; + bool proposalExecuted = proposal.executed; + bool proposalCanceled = proposal.canceled; + + if (proposalExecuted) { + return ProposalState.Executed; + } + + if (proposalCanceled) { + return ProposalState.Canceled; + } + + uint256 snapshot = proposalSnapshot(proposalId); + + if (snapshot == 0) { + revert GovernorNonexistentProposal(proposalId); + } + + uint256 currentTimepoint = clock(); + + if (snapshot >= currentTimepoint) { + return ProposalState.Pending; + } + + uint256 deadline = proposalDeadline(proposalId); + + if (deadline >= currentTimepoint) { + return ProposalState.Active; + } else if (!_quorumReached(proposalId) || !_voteSucceeded(proposalId)) { + return ProposalState.Defeated; + } else if (proposalEta(proposalId) == 0) { + return ProposalState.Succeeded; + } else { + return ProposalState.Queued; + } + } + + /** + * @dev See {IGovernor-proposalThreshold}. + */ + function proposalThreshold() public view virtual returns (uint256) { + return 0; + } + + /** + * @dev See {IGovernor-proposalSnapshot}. + */ + function proposalSnapshot(uint256 proposalId) public view virtual returns (uint256) { + return _proposals[proposalId].voteStart; + } + + /** + * @dev See {IGovernor-proposalDeadline}. + */ + function proposalDeadline(uint256 proposalId) public view virtual returns (uint256) { + return _proposals[proposalId].voteStart + _proposals[proposalId].voteDuration; + } + + /** + * @dev See {IGovernor-proposalProposer}. + */ + function proposalProposer(uint256 proposalId) public view virtual returns (address) { + return _proposals[proposalId].proposer; + } + + /** + * @dev See {IGovernor-proposalEta}. + */ + function proposalEta(uint256 proposalId) public view virtual returns (uint256) { + return _proposals[proposalId].etaSeconds; + } + + /** + * @dev See {IGovernor-proposalNeedsQueuing}. + */ + function proposalNeedsQueuing(uint256) public view virtual returns (bool) { + return false; + } + + /** + * @dev Reverts if the `msg.sender` is not the executor. In case the executor is not this contract + * itself, the function reverts if `msg.data` is not whitelisted as a result of an {execute} + * operation. See {onlyGovernance}. + */ + function _checkGovernance() internal virtual { + if (_executor() != _msgSender()) { + revert GovernorOnlyExecutor(_msgSender()); + } + if (_executor() != address(this)) { + bytes32 msgDataHash = keccak256(_msgData()); + // loop until popping the expected operation - throw if deque is empty (operation not authorized) + while (_governanceCall.popFront() != msgDataHash) {} + } + } + + /** + * @dev Amount of votes already cast passes the threshold limit. + */ + function _quorumReached(uint256 proposalId) internal view virtual returns (bool); + + /** + * @dev Is the proposal successful or not. + */ + function _voteSucceeded(uint256 proposalId) internal view virtual returns (bool); + + /** + * @dev Get the voting weight of `account` at a specific `timepoint`, for a vote as described by `params`. + */ + function _getVotes(address account, uint256 timepoint, bytes memory params) internal view virtual returns (uint256); + + /** + * @dev Register a vote for `proposalId` by `account` with a given `support`, voting `weight` and voting `params`. + * + * Note: Support is generic and can represent various things depending on the voting system used. + */ + function _countVote( + uint256 proposalId, + address account, + uint8 support, + uint256 weight, + bytes memory params + ) internal virtual; + + /** + * @dev Default additional encoded parameters used by castVote methods that don't include them + * + * Note: Should be overridden by specific implementations to use an appropriate value, the + * meaning of the additional params, in the context of that implementation + */ + function _defaultParams() internal view virtual returns (bytes memory) { + return ""; + } + + /** + * @dev See {IGovernor-propose}. This function has opt-in frontrunning protection, described in {_isValidDescriptionForProposer}. + */ + function propose( + address[] memory targets, + uint256[] memory values, + bytes[] memory calldatas, + string memory description + ) public virtual returns (uint256) { + address proposer = _msgSender(); + + // check description restriction + if (!_isValidDescriptionForProposer(proposer, description)) { + revert GovernorRestrictedProposer(proposer); + } + + // check proposal threshold + uint256 proposerVotes = getVotes(proposer, clock() - 1); + uint256 votesThreshold = proposalThreshold(); + if (proposerVotes < votesThreshold) { + revert GovernorInsufficientProposerVotes(proposer, proposerVotes, votesThreshold); + } + + return _propose(targets, values, calldatas, description, proposer); + } + + /** + * @dev Internal propose mechanism. Can be overridden to add more logic on proposal creation. + * + * Emits a {IGovernor-ProposalCreated} event. + */ + function _propose( + address[] memory targets, + uint256[] memory values, + bytes[] memory calldatas, + string memory description, + address proposer + ) internal virtual returns (uint256 proposalId) { + proposalId = hashProposal(targets, values, calldatas, keccak256(bytes(description))); + + if (targets.length != values.length || targets.length != calldatas.length || targets.length == 0) { + revert GovernorInvalidProposalLength(targets.length, calldatas.length, values.length); + } + if (_proposals[proposalId].voteStart != 0) { + revert GovernorUnexpectedProposalState(proposalId, state(proposalId), bytes32(0)); + } + + uint256 snapshot = clock() + votingDelay(); + uint256 duration = votingPeriod(); + + ProposalCore storage proposal = _proposals[proposalId]; + proposal.proposer = proposer; + proposal.voteStart = SafeCast.toUint48(snapshot); + proposal.voteDuration = SafeCast.toUint32(duration); + + emit ProposalCreated( + proposalId, + proposer, + targets, + values, + new string[](targets.length), + calldatas, + snapshot, + snapshot + duration, + description + ); + + // Using a named return variable to avoid stack too deep errors + } + + /** + * @dev See {IGovernor-queue}. + */ + function queue( + address[] memory targets, + uint256[] memory values, + bytes[] memory calldatas, + bytes32 descriptionHash + ) public virtual returns (uint256) { + uint256 proposalId = hashProposal(targets, values, calldatas, descriptionHash); + + _validateStateBitmap(proposalId, _encodeStateBitmap(ProposalState.Succeeded)); + + uint48 etaSeconds = _queueOperations(proposalId, targets, values, calldatas, descriptionHash); + + if (etaSeconds != 0) { + _proposals[proposalId].etaSeconds = etaSeconds; + emit ProposalQueued(proposalId, etaSeconds); + } else { + revert GovernorQueueNotImplemented(); + } + + return proposalId; + } + + /** + * @dev Internal queuing mechanism. Can be overridden (without a super call) to modify the way queuing is + * performed (for example adding a vault/timelock). + * + * This is empty by default, and must be overridden to implement queuing. + * + * This function returns a timestamp that describes the expected ETA for execution. If the returned value is 0 + * (which is the default value), the core will consider queueing did not succeed, and the public {queue} function + * will revert. + * + * NOTE: Calling this function directly will NOT check the current state of the proposal, or emit the + * `ProposalQueued` event. Queuing a proposal should be done using {queue}. + */ + function _queueOperations( + uint256 /*proposalId*/, + address[] memory /*targets*/, + uint256[] memory /*values*/, + bytes[] memory /*calldatas*/, + bytes32 /*descriptionHash*/ + ) internal virtual returns (uint48) { + return 0; + } + + /** + * @dev See {IGovernor-execute}. + */ + function execute( + address[] memory targets, + uint256[] memory values, + bytes[] memory calldatas, + bytes32 descriptionHash + ) public payable virtual returns (uint256) { + uint256 proposalId = hashProposal(targets, values, calldatas, descriptionHash); + + _validateStateBitmap( + proposalId, + _encodeStateBitmap(ProposalState.Succeeded) | _encodeStateBitmap(ProposalState.Queued) + ); + + // mark as executed before calls to avoid reentrancy + _proposals[proposalId].executed = true; + + // before execute: register governance call in queue. + if (_executor() != address(this)) { + for (uint256 i = 0; i < targets.length; ++i) { + if (targets[i] == address(this)) { + _governanceCall.pushBack(keccak256(calldatas[i])); + } + } + } + + _executeOperations(proposalId, targets, values, calldatas, descriptionHash); + + // after execute: cleanup governance call queue. + if (_executor() != address(this) && !_governanceCall.empty()) { + _governanceCall.clear(); + } + + emit ProposalExecuted(proposalId); + + return proposalId; + } + + /** + * @dev Internal execution mechanism. Can be overridden (without a super call) to modify the way execution is + * performed (for example adding a vault/timelock). + * + * NOTE: Calling this function directly will NOT check the current state of the proposal, set the executed flag to + * true or emit the `ProposalExecuted` event. Executing a proposal should be done using {execute} or {_execute}. + */ + function _executeOperations( + uint256 /* proposalId */, + address[] memory targets, + uint256[] memory values, + bytes[] memory calldatas, + bytes32 /*descriptionHash*/ + ) internal virtual { + for (uint256 i = 0; i < targets.length; ++i) { + (bool success, bytes memory returndata) = targets[i].call{value: values[i]}(calldatas[i]); + Address.verifyCallResult(success, returndata); + } + } + + /** + * @dev See {IGovernor-cancel}. + */ + function cancel( + address[] memory targets, + uint256[] memory values, + bytes[] memory calldatas, + bytes32 descriptionHash + ) public virtual returns (uint256) { + // The proposalId will be recomputed in the `_cancel` call further down. However we need the value before we + // do the internal call, because we need to check the proposal state BEFORE the internal `_cancel` call + // changes it. The `hashProposal` duplication has a cost that is limited, and that we accept. + uint256 proposalId = hashProposal(targets, values, calldatas, descriptionHash); + + // public cancel restrictions (on top of existing _cancel restrictions). + _validateStateBitmap(proposalId, _encodeStateBitmap(ProposalState.Pending)); + if (_msgSender() != proposalProposer(proposalId)) { + revert GovernorOnlyProposer(_msgSender()); + } + + return _cancel(targets, values, calldatas, descriptionHash); + } + + /** + * @dev Internal cancel mechanism with minimal restrictions. A proposal can be cancelled in any state other than + * Canceled, Expired, or Executed. Once cancelled a proposal can't be re-submitted. + * + * Emits a {IGovernor-ProposalCanceled} event. + */ + function _cancel( + address[] memory targets, + uint256[] memory values, + bytes[] memory calldatas, + bytes32 descriptionHash + ) internal virtual returns (uint256) { + uint256 proposalId = hashProposal(targets, values, calldatas, descriptionHash); + + _validateStateBitmap( + proposalId, + ALL_PROPOSAL_STATES_BITMAP ^ + _encodeStateBitmap(ProposalState.Canceled) ^ + _encodeStateBitmap(ProposalState.Expired) ^ + _encodeStateBitmap(ProposalState.Executed) + ); + + _proposals[proposalId].canceled = true; + emit ProposalCanceled(proposalId); + + return proposalId; + } + + /** + * @dev See {IGovernor-getVotes}. + */ + function getVotes(address account, uint256 timepoint) public view virtual returns (uint256) { + return _getVotes(account, timepoint, _defaultParams()); + } + + /** + * @dev See {IGovernor-getVotesWithParams}. + */ + function getVotesWithParams( + address account, + uint256 timepoint, + bytes memory params + ) public view virtual returns (uint256) { + return _getVotes(account, timepoint, params); + } + + /** + * @dev See {IGovernor-castVote}. + */ + function castVote(uint256 proposalId, uint8 support) public virtual returns (uint256) { + address voter = _msgSender(); + return _castVote(proposalId, voter, support, ""); + } + + /** + * @dev See {IGovernor-castVoteWithReason}. + */ + function castVoteWithReason( + uint256 proposalId, + uint8 support, + string calldata reason + ) public virtual returns (uint256) { + address voter = _msgSender(); + return _castVote(proposalId, voter, support, reason); + } + + /** + * @dev See {IGovernor-castVoteWithReasonAndParams}. + */ + function castVoteWithReasonAndParams( + uint256 proposalId, + uint8 support, + string calldata reason, + bytes memory params + ) public virtual returns (uint256) { + address voter = _msgSender(); + return _castVote(proposalId, voter, support, reason, params); + } + + /** + * @dev See {IGovernor-castVoteBySig}. + */ + function castVoteBySig( + uint256 proposalId, + uint8 support, + address voter, + bytes memory signature + ) public virtual returns (uint256) { + bool valid = SignatureChecker.isValidSignatureNow( + voter, + _hashTypedDataV4(keccak256(abi.encode(BALLOT_TYPEHASH, proposalId, support, voter, _useNonce(voter)))), + signature + ); + + if (!valid) { + revert GovernorInvalidSignature(voter); + } + + return _castVote(proposalId, voter, support, ""); + } + + /** + * @dev See {IGovernor-castVoteWithReasonAndParamsBySig}. + */ + function castVoteWithReasonAndParamsBySig( + uint256 proposalId, + uint8 support, + address voter, + string calldata reason, + bytes memory params, + bytes memory signature + ) public virtual returns (uint256) { + bool valid = SignatureChecker.isValidSignatureNow( + voter, + _hashTypedDataV4( + keccak256( + abi.encode( + EXTENDED_BALLOT_TYPEHASH, + proposalId, + support, + voter, + _useNonce(voter), + keccak256(bytes(reason)), + keccak256(params) + ) + ) + ), + signature + ); + + if (!valid) { + revert GovernorInvalidSignature(voter); + } + + return _castVote(proposalId, voter, support, reason, params); + } + + /** + * @dev Internal vote casting mechanism: Check that the vote is pending, that it has not been cast yet, retrieve + * voting weight using {IGovernor-getVotes} and call the {_countVote} internal function. Uses the _defaultParams(). + * + * Emits a {IGovernor-VoteCast} event. + */ + function _castVote( + uint256 proposalId, + address account, + uint8 support, + string memory reason + ) internal virtual returns (uint256) { + return _castVote(proposalId, account, support, reason, _defaultParams()); + } + + /** + * @dev Internal vote casting mechanism: Check that the vote is pending, that it has not been cast yet, retrieve + * voting weight using {IGovernor-getVotes} and call the {_countVote} internal function. + * + * Emits a {IGovernor-VoteCast} event. + */ + function _castVote( + uint256 proposalId, + address account, + uint8 support, + string memory reason, + bytes memory params + ) internal virtual returns (uint256) { + _validateStateBitmap(proposalId, _encodeStateBitmap(ProposalState.Active)); + + uint256 weight = _getVotes(account, proposalSnapshot(proposalId), params); + _countVote(proposalId, account, support, weight, params); + + if (params.length == 0) { + emit VoteCast(account, proposalId, support, weight, reason); + } else { + emit VoteCastWithParams(account, proposalId, support, weight, reason, params); + } + + return weight; + } + + /** + * @dev Relays a transaction or function call to an arbitrary target. In cases where the governance executor + * is some contract other than the governor itself, like when using a timelock, this function can be invoked + * in a governance proposal to recover tokens or Ether that was sent to the governor contract by mistake. + * Note that if the executor is simply the governor itself, use of `relay` is redundant. + */ + function relay(address target, uint256 value, bytes calldata data) external payable virtual onlyGovernance { + (bool success, bytes memory returndata) = target.call{value: value}(data); + Address.verifyCallResult(success, returndata); + } + + /** + * @dev Address through which the governor executes action. Will be overloaded by module that execute actions + * through another contract such as a timelock. + */ + function _executor() internal view virtual returns (address) { + return address(this); + } + + /** + * @dev See {IERC721Receiver-onERC721Received}. + * Receiving tokens is disabled if the governance executor is other than the governor itself (eg. when using with a timelock). + */ + function onERC721Received(address, address, uint256, bytes memory) public virtual returns (bytes4) { + if (_executor() != address(this)) { + revert GovernorDisabledDeposit(); + } + return this.onERC721Received.selector; + } + + /** + * @dev See {IERC1155Receiver-onERC1155Received}. + * Receiving tokens is disabled if the governance executor is other than the governor itself (eg. when using with a timelock). + */ + function onERC1155Received(address, address, uint256, uint256, bytes memory) public virtual returns (bytes4) { + if (_executor() != address(this)) { + revert GovernorDisabledDeposit(); + } + return this.onERC1155Received.selector; + } + + /** + * @dev See {IERC1155Receiver-onERC1155BatchReceived}. + * Receiving tokens is disabled if the governance executor is other than the governor itself (eg. when using with a timelock). + */ + function onERC1155BatchReceived( + address, + address, + uint256[] memory, + uint256[] memory, + bytes memory + ) public virtual returns (bytes4) { + if (_executor() != address(this)) { + revert GovernorDisabledDeposit(); + } + return this.onERC1155BatchReceived.selector; + } + + /** + * @dev Encodes a `ProposalState` into a `bytes32` representation where each bit enabled corresponds to + * the underlying position in the `ProposalState` enum. For example: + * + * 0x000...10000 + * ^^^^^^------ ... + * ^----- Succeeded + * ^---- Defeated + * ^--- Canceled + * ^-- Active + * ^- Pending + */ + function _encodeStateBitmap(ProposalState proposalState) internal pure returns (bytes32) { + return bytes32(1 << uint8(proposalState)); + } + + /** + * @dev Check that the current state of a proposal matches the requirements described by the `allowedStates` bitmap. + * This bitmap should be built using `_encodeStateBitmap`. + * + * If requirements are not met, reverts with a {GovernorUnexpectedProposalState} error. + */ + function _validateStateBitmap(uint256 proposalId, bytes32 allowedStates) private view returns (ProposalState) { + ProposalState currentState = state(proposalId); + if (_encodeStateBitmap(currentState) & allowedStates == bytes32(0)) { + revert GovernorUnexpectedProposalState(proposalId, currentState, allowedStates); + } + return currentState; + } + + /* + * @dev Check if the proposer is authorized to submit a proposal with the given description. + * + * If the proposal description ends with `#proposer=0x???`, where `0x???` is an address written as a hex string + * (case insensitive), then the submission of this proposal will only be authorized to said address. + * + * This is used for frontrunning protection. By adding this pattern at the end of their proposal, one can ensure + * that no other address can submit the same proposal. An attacker would have to either remove or change that part, + * which would result in a different proposal id. + * + * If the description does not match this pattern, it is unrestricted and anyone can submit it. This includes: + * - If the `0x???` part is not a valid hex string. + * - If the `0x???` part is a valid hex string, but does not contain exactly 40 hex digits. + * - If it ends with the expected suffix followed by newlines or other whitespace. + * - If it ends with some other similar suffix, e.g. `#other=abc`. + * - If it does not end with any such suffix. + */ + function _isValidDescriptionForProposer( + address proposer, + string memory description + ) internal view virtual returns (bool) { + uint256 len = bytes(description).length; + + // Length is too short to contain a valid proposer suffix + if (len < 52) { + return true; + } + + // Extract what would be the `#proposer=0x` marker beginning the suffix + bytes12 marker; + assembly { + // - Start of the string contents in memory = description + 32 + // - First character of the marker = len - 52 + // - Length of "#proposer=0x0000000000000000000000000000000000000000" = 52 + // - We read the memory word starting at the first character of the marker: + // - (description + 32) + (len - 52) = description + (len - 20) + // - Note: Solidity will ignore anything past the first 12 bytes + marker := mload(add(description, sub(len, 20))) + } + + // If the marker is not found, there is no proposer suffix to check + if (marker != bytes12("#proposer=0x")) { + return true; + } + + // Parse the 40 characters following the marker as uint160 + uint160 recovered = 0; + for (uint256 i = len - 40; i < len; ++i) { + (bool isHex, uint8 value) = _tryHexToUint(bytes(description)[i]); + // If any of the characters is not a hex digit, ignore the suffix entirely + if (!isHex) { + return true; + } + recovered = (recovered << 4) | value; + } + + return recovered == uint160(proposer); + } + + /** + * @dev Try to parse a character from a string as a hex value. Returns `(true, value)` if the char is in + * `[0-9a-fA-F]` and `(false, 0)` otherwise. Value is guaranteed to be in the range `0 <= value < 16` + */ + function _tryHexToUint(bytes1 char) private pure returns (bool, uint8) { + uint8 c = uint8(char); + unchecked { + // Case 0-9 + if (47 < c && c < 58) { + return (true, c - 48); + } + // Case A-F + else if (64 < c && c < 71) { + return (true, c - 55); + } + // Case a-f + else if (96 < c && c < 103) { + return (true, c - 87); + } + // Else: not a hex char + else { + return (false, 0); + } + } + } + + /** + * @inheritdoc IERC6372 + */ + function clock() public view virtual returns (uint48); + + /** + * @inheritdoc IERC6372 + */ + // solhint-disable-next-line func-name-mixedcase + function CLOCK_MODE() public view virtual returns (string memory); + + /** + * @inheritdoc IGovernor + */ + function votingDelay() public view virtual returns (uint256); + + /** + * @inheritdoc IGovernor + */ + function votingPeriod() public view virtual returns (uint256); + + /** + * @inheritdoc IGovernor + */ + function quorum(uint256 timepoint) public view virtual returns (uint256); +} diff --git a/CVLByExample/UniswapHooks/lib/uniswap-hooks/lib/v4-core/lib/openzeppelin-contracts/contracts/governance/IGovernor.sol b/CVLByExample/UniswapHooks/lib/uniswap-hooks/lib/v4-core/lib/openzeppelin-contracts/contracts/governance/IGovernor.sol new file mode 100644 index 00000000..6cde0e86 --- /dev/null +++ b/CVLByExample/UniswapHooks/lib/uniswap-hooks/lib/v4-core/lib/openzeppelin-contracts/contracts/governance/IGovernor.sol @@ -0,0 +1,427 @@ +// SPDX-License-Identifier: MIT +// OpenZeppelin Contracts (last updated v5.0.0) (governance/IGovernor.sol) + +pragma solidity ^0.8.20; + +import {IERC165} from "../interfaces/IERC165.sol"; +import {IERC6372} from "../interfaces/IERC6372.sol"; + +/** + * @dev Interface of the {Governor} core. + */ +interface IGovernor is IERC165, IERC6372 { + enum ProposalState { + Pending, + Active, + Canceled, + Defeated, + Succeeded, + Queued, + Expired, + Executed + } + + /** + * @dev Empty proposal or a mismatch between the parameters length for a proposal call. + */ + error GovernorInvalidProposalLength(uint256 targets, uint256 calldatas, uint256 values); + + /** + * @dev The vote was already cast. + */ + error GovernorAlreadyCastVote(address voter); + + /** + * @dev Token deposits are disabled in this contract. + */ + error GovernorDisabledDeposit(); + + /** + * @dev The `account` is not a proposer. + */ + error GovernorOnlyProposer(address account); + + /** + * @dev The `account` is not the governance executor. + */ + error GovernorOnlyExecutor(address account); + + /** + * @dev The `proposalId` doesn't exist. + */ + error GovernorNonexistentProposal(uint256 proposalId); + + /** + * @dev The current state of a proposal is not the required for performing an operation. + * The `expectedStates` is a bitmap with the bits enabled for each ProposalState enum position + * counting from right to left. + * + * NOTE: If `expectedState` is `bytes32(0)`, the proposal is expected to not be in any state (i.e. not exist). + * This is the case when a proposal that is expected to be unset is already initiated (the proposal is duplicated). + * + * See {Governor-_encodeStateBitmap}. + */ + error GovernorUnexpectedProposalState(uint256 proposalId, ProposalState current, bytes32 expectedStates); + + /** + * @dev The voting period set is not a valid period. + */ + error GovernorInvalidVotingPeriod(uint256 votingPeriod); + + /** + * @dev The `proposer` does not have the required votes to create a proposal. + */ + error GovernorInsufficientProposerVotes(address proposer, uint256 votes, uint256 threshold); + + /** + * @dev The `proposer` is not allowed to create a proposal. + */ + error GovernorRestrictedProposer(address proposer); + + /** + * @dev The vote type used is not valid for the corresponding counting module. + */ + error GovernorInvalidVoteType(); + + /** + * @dev Queue operation is not implemented for this governor. Execute should be called directly. + */ + error GovernorQueueNotImplemented(); + + /** + * @dev The proposal hasn't been queued yet. + */ + error GovernorNotQueuedProposal(uint256 proposalId); + + /** + * @dev The proposal has already been queued. + */ + error GovernorAlreadyQueuedProposal(uint256 proposalId); + + /** + * @dev The provided signature is not valid for the expected `voter`. + * If the `voter` is a contract, the signature is not valid using {IERC1271-isValidSignature}. + */ + error GovernorInvalidSignature(address voter); + + /** + * @dev Emitted when a proposal is created. + */ + event ProposalCreated( + uint256 proposalId, + address proposer, + address[] targets, + uint256[] values, + string[] signatures, + bytes[] calldatas, + uint256 voteStart, + uint256 voteEnd, + string description + ); + + /** + * @dev Emitted when a proposal is queued. + */ + event ProposalQueued(uint256 proposalId, uint256 etaSeconds); + + /** + * @dev Emitted when a proposal is executed. + */ + event ProposalExecuted(uint256 proposalId); + + /** + * @dev Emitted when a proposal is canceled. + */ + event ProposalCanceled(uint256 proposalId); + + /** + * @dev Emitted when a vote is cast without params. + * + * Note: `support` values should be seen as buckets. Their interpretation depends on the voting module used. + */ + event VoteCast(address indexed voter, uint256 proposalId, uint8 support, uint256 weight, string reason); + + /** + * @dev Emitted when a vote is cast with params. + * + * Note: `support` values should be seen as buckets. Their interpretation depends on the voting module used. + * `params` are additional encoded parameters. Their interpepretation also depends on the voting module used. + */ + event VoteCastWithParams( + address indexed voter, + uint256 proposalId, + uint8 support, + uint256 weight, + string reason, + bytes params + ); + + /** + * @notice module:core + * @dev Name of the governor instance (used in building the ERC712 domain separator). + */ + function name() external view returns (string memory); + + /** + * @notice module:core + * @dev Version of the governor instance (used in building the ERC712 domain separator). Default: "1" + */ + function version() external view returns (string memory); + + /** + * @notice module:voting + * @dev A description of the possible `support` values for {castVote} and the way these votes are counted, meant to + * be consumed by UIs to show correct vote options and interpret the results. The string is a URL-encoded sequence of + * key-value pairs that each describe one aspect, for example `support=bravo&quorum=for,abstain`. + * + * There are 2 standard keys: `support` and `quorum`. + * + * - `support=bravo` refers to the vote options 0 = Against, 1 = For, 2 = Abstain, as in `GovernorBravo`. + * - `quorum=bravo` means that only For votes are counted towards quorum. + * - `quorum=for,abstain` means that both For and Abstain votes are counted towards quorum. + * + * If a counting module makes use of encoded `params`, it should include this under a `params` key with a unique + * name that describes the behavior. For example: + * + * - `params=fractional` might refer to a scheme where votes are divided fractionally between for/against/abstain. + * - `params=erc721` might refer to a scheme where specific NFTs are delegated to vote. + * + * NOTE: The string can be decoded by the standard + * https://developer.mozilla.org/en-US/docs/Web/API/URLSearchParams[`URLSearchParams`] + * JavaScript class. + */ + // solhint-disable-next-line func-name-mixedcase + function COUNTING_MODE() external view returns (string memory); + + /** + * @notice module:core + * @dev Hashing function used to (re)build the proposal id from the proposal details.. + */ + function hashProposal( + address[] memory targets, + uint256[] memory values, + bytes[] memory calldatas, + bytes32 descriptionHash + ) external pure returns (uint256); + + /** + * @notice module:core + * @dev Current state of a proposal, following Compound's convention + */ + function state(uint256 proposalId) external view returns (ProposalState); + + /** + * @notice module:core + * @dev The number of votes required in order for a voter to become a proposer. + */ + function proposalThreshold() external view returns (uint256); + + /** + * @notice module:core + * @dev Timepoint used to retrieve user's votes and quorum. If using block number (as per Compound's Comp), the + * snapshot is performed at the end of this block. Hence, voting for this proposal starts at the beginning of the + * following block. + */ + function proposalSnapshot(uint256 proposalId) external view returns (uint256); + + /** + * @notice module:core + * @dev Timepoint at which votes close. If using block number, votes close at the end of this block, so it is + * possible to cast a vote during this block. + */ + function proposalDeadline(uint256 proposalId) external view returns (uint256); + + /** + * @notice module:core + * @dev The account that created a proposal. + */ + function proposalProposer(uint256 proposalId) external view returns (address); + + /** + * @notice module:core + * @dev The time when a queued proposal becomes executable ("ETA"). Unlike {proposalSnapshot} and + * {proposalDeadline}, this doesn't use the governor clock, and instead relies on the executor's clock which may be + * different. In most cases this will be a timestamp. + */ + function proposalEta(uint256 proposalId) external view returns (uint256); + + /** + * @notice module:core + * @dev Whether a proposal needs to be queued before execution. + */ + function proposalNeedsQueuing(uint256 proposalId) external view returns (bool); + + /** + * @notice module:user-config + * @dev Delay, between the proposal is created and the vote starts. The unit this duration is expressed in depends + * on the clock (see EIP-6372) this contract uses. + * + * This can be increased to leave time for users to buy voting power, or delegate it, before the voting of a + * proposal starts. + * + * NOTE: While this interface returns a uint256, timepoints are stored as uint48 following the ERC-6372 clock type. + * Consequently this value must fit in a uint48 (when added to the current clock). See {IERC6372-clock}. + */ + function votingDelay() external view returns (uint256); + + /** + * @notice module:user-config + * @dev Delay between the vote start and vote end. The unit this duration is expressed in depends on the clock + * (see EIP-6372) this contract uses. + * + * NOTE: The {votingDelay} can delay the start of the vote. This must be considered when setting the voting + * duration compared to the voting delay. + * + * NOTE: This value is stored when the proposal is submitted so that possible changes to the value do not affect + * proposals that have already been submitted. The type used to save it is a uint32. Consequently, while this + * interface returns a uint256, the value it returns should fit in a uint32. + */ + function votingPeriod() external view returns (uint256); + + /** + * @notice module:user-config + * @dev Minimum number of cast voted required for a proposal to be successful. + * + * NOTE: The `timepoint` parameter corresponds to the snapshot used for counting vote. This allows to scale the + * quorum depending on values such as the totalSupply of a token at this timepoint (see {ERC20Votes}). + */ + function quorum(uint256 timepoint) external view returns (uint256); + + /** + * @notice module:reputation + * @dev Voting power of an `account` at a specific `timepoint`. + * + * Note: this can be implemented in a number of ways, for example by reading the delegated balance from one (or + * multiple), {ERC20Votes} tokens. + */ + function getVotes(address account, uint256 timepoint) external view returns (uint256); + + /** + * @notice module:reputation + * @dev Voting power of an `account` at a specific `timepoint` given additional encoded parameters. + */ + function getVotesWithParams( + address account, + uint256 timepoint, + bytes memory params + ) external view returns (uint256); + + /** + * @notice module:voting + * @dev Returns whether `account` has cast a vote on `proposalId`. + */ + function hasVoted(uint256 proposalId, address account) external view returns (bool); + + /** + * @dev Create a new proposal. Vote start after a delay specified by {IGovernor-votingDelay} and lasts for a + * duration specified by {IGovernor-votingPeriod}. + * + * Emits a {ProposalCreated} event. + */ + function propose( + address[] memory targets, + uint256[] memory values, + bytes[] memory calldatas, + string memory description + ) external returns (uint256 proposalId); + + /** + * @dev Queue a proposal. Some governors require this step to be performed before execution can happen. If queuing + * is not necessary, this function may revert. + * Queuing a proposal requires the quorum to be reached, the vote to be successful, and the deadline to be reached. + * + * Emits a {ProposalQueued} event. + */ + function queue( + address[] memory targets, + uint256[] memory values, + bytes[] memory calldatas, + bytes32 descriptionHash + ) external returns (uint256 proposalId); + + /** + * @dev Execute a successful proposal. This requires the quorum to be reached, the vote to be successful, and the + * deadline to be reached. Depending on the governor it might also be required that the proposal was queued and + * that some delay passed. + * + * Emits a {ProposalExecuted} event. + * + * NOTE: Some modules can modify the requirements for execution, for example by adding an additional timelock. + */ + function execute( + address[] memory targets, + uint256[] memory values, + bytes[] memory calldatas, + bytes32 descriptionHash + ) external payable returns (uint256 proposalId); + + /** + * @dev Cancel a proposal. A proposal is cancellable by the proposer, but only while it is Pending state, i.e. + * before the vote starts. + * + * Emits a {ProposalCanceled} event. + */ + function cancel( + address[] memory targets, + uint256[] memory values, + bytes[] memory calldatas, + bytes32 descriptionHash + ) external returns (uint256 proposalId); + + /** + * @dev Cast a vote + * + * Emits a {VoteCast} event. + */ + function castVote(uint256 proposalId, uint8 support) external returns (uint256 balance); + + /** + * @dev Cast a vote with a reason + * + * Emits a {VoteCast} event. + */ + function castVoteWithReason( + uint256 proposalId, + uint8 support, + string calldata reason + ) external returns (uint256 balance); + + /** + * @dev Cast a vote with a reason and additional encoded parameters + * + * Emits a {VoteCast} or {VoteCastWithParams} event depending on the length of params. + */ + function castVoteWithReasonAndParams( + uint256 proposalId, + uint8 support, + string calldata reason, + bytes memory params + ) external returns (uint256 balance); + + /** + * @dev Cast a vote using the voter's signature, including ERC-1271 signature support. + * + * Emits a {VoteCast} event. + */ + function castVoteBySig( + uint256 proposalId, + uint8 support, + address voter, + bytes memory signature + ) external returns (uint256 balance); + + /** + * @dev Cast a vote with a reason and additional encoded parameters using the voter's signature, + * including ERC-1271 signature support. + * + * Emits a {VoteCast} or {VoteCastWithParams} event depending on the length of params. + */ + function castVoteWithReasonAndParamsBySig( + uint256 proposalId, + uint8 support, + address voter, + string calldata reason, + bytes memory params, + bytes memory signature + ) external returns (uint256 balance); +} diff --git a/CVLByExample/UniswapHooks/lib/uniswap-hooks/lib/v4-core/lib/openzeppelin-contracts/contracts/governance/README.adoc b/CVLByExample/UniswapHooks/lib/uniswap-hooks/lib/v4-core/lib/openzeppelin-contracts/contracts/governance/README.adoc new file mode 100644 index 00000000..8c368aca --- /dev/null +++ b/CVLByExample/UniswapHooks/lib/uniswap-hooks/lib/v4-core/lib/openzeppelin-contracts/contracts/governance/README.adoc @@ -0,0 +1,171 @@ += Governance + +[.readme-notice] +NOTE: This document is better viewed at https://docs.openzeppelin.com/contracts/api/governance + +This directory includes primitives for on-chain governance. + +== Governor + +This modular system of Governor contracts allows the deployment on-chain voting protocols similar to https://compound.finance/docs/governance[Compound's Governor Alpha & Bravo] and beyond, through the ability to easily customize multiple aspects of the protocol. + +[TIP] +==== +For a guided experience, set up your Governor contract using https://wizard.openzeppelin.com/#governor[Contracts Wizard]. + +For a written walkthrough, check out our guide on xref:ROOT:governance.adoc[How to set up on-chain governance]. +==== + +* {Governor}: The core contract that contains all the logic and primitives. It is abstract and requires choosing one of each of the modules below, or custom ones. + +Votes modules determine the source of voting power, and sometimes quorum number. + +* {GovernorVotes}: Extracts voting weight from an {ERC20Votes}, or since v4.5 an {ERC721Votes} token. + +* {GovernorVotesQuorumFraction}: Combines with `GovernorVotes` to set the quorum as a fraction of the total token supply. + +Counting modules determine valid voting options. + +* {GovernorCountingSimple}: Simple voting mechanism with 3 voting options: Against, For and Abstain. + +Timelock extensions add a delay for governance decisions to be executed. The workflow is extended to require a `queue` step before execution. With these modules, proposals are executed by the external timelock contract, thus it is the timelock that has to hold the assets that are being governed. + +* {GovernorTimelockAccess}: Connects with an instance of an {AccessManager}. This allows restrictions (and delays) enforced by the manager to be considered by the Governor and integrated into the AccessManager's "schedule + execute" workflow. + +* {GovernorTimelockControl}: Connects with an instance of {TimelockController}. Allows multiple proposers and executors, in addition to the Governor itself. + +* {GovernorTimelockCompound}: Connects with an instance of Compound's https://github.com/compound-finance/compound-protocol/blob/master/contracts/Timelock.sol[`Timelock`] contract. + +Other extensions can customize the behavior or interface in multiple ways. + +* {GovernorStorage}: Stores the proposal details onchain and provides enumerability of the proposals. This can be useful for some L2 chains where storage is cheap compared to calldata. + +* {GovernorSettings}: Manages some of the settings (voting delay, voting period duration, and proposal threshold) in a way that can be updated through a governance proposal, without requiring an upgrade. + +* {GovernorPreventLateQuorum}: Ensures there is a minimum voting period after quorum is reached as a security protection against large voters. + +In addition to modules and extensions, the core contract requires a few virtual functions to be implemented to your particular specifications: + +* <>: Delay (in EIP-6372 clock) since the proposal is submitted until voting power is fixed and voting starts. This can be used to enforce a delay after a proposal is published for users to buy tokens, or delegate their votes. +* <>: Delay (in EIP-6372 clock) since the proposal starts until voting ends. +* <>: Quorum required for a proposal to be successful. This function includes a `timepoint` argument (see EIP-6372) so the quorum can adapt through time, for example, to follow a token's `totalSupply`. + +NOTE: Functions of the `Governor` contract do not include access control. If you want to restrict access, you should add these checks by overloading the particular functions. Among these, {Governor-_cancel} is internal by default, and you will have to expose it (with the right access control mechanism) yourself if this function is needed. + +=== Core + +{{IGovernor}} + +{{Governor}} + +=== Modules + +{{GovernorCountingSimple}} + +{{GovernorVotes}} + +{{GovernorVotesQuorumFraction}} + +=== Extensions + +{{GovernorTimelockAccess}} + +{{GovernorTimelockControl}} + +{{GovernorTimelockCompound}} + +{{GovernorSettings}} + +{{GovernorPreventLateQuorum}} + +{{GovernorStorage}} + +== Utils + +{{Votes}} + +== Timelock + +In a governance system, the {TimelockController} contract is in charge of introducing a delay between a proposal and its execution. It can be used with or without a {Governor}. + +{{TimelockController}} + +[[timelock-terminology]] +==== Terminology + +* *Operation:* A transaction (or a set of transactions) that is the subject of the timelock. It has to be scheduled by a proposer and executed by an executor. The timelock enforces a minimum delay between the proposition and the execution (see xref:access-control.adoc#operation_lifecycle[operation lifecycle]). If the operation contains multiple transactions (batch mode), they are executed atomically. Operations are identified by the hash of their content. +* *Operation status:* +** *Unset:* An operation that is not part of the timelock mechanism. +** *Waiting:* An operation that has been scheduled, before the timer expires. +** *Ready:* An operation that has been scheduled, after the timer expires. +** *Pending:* An operation that is either waiting or ready. +** *Done:* An operation that has been executed. +* *Predecessor*: An (optional) dependency between operations. An operation can depend on another operation (its predecessor), forcing the execution order of these two operations. +* *Role*: +** *Admin:* An address (smart contract or EOA) that is in charge of granting the roles of Proposer and Executor. +** *Proposer:* An address (smart contract or EOA) that is in charge of scheduling (and cancelling) operations. +** *Executor:* An address (smart contract or EOA) that is in charge of executing operations once the timelock has expired. This role can be given to the zero address to allow anyone to execute operations. + +[[timelock-operation]] +==== Operation structure + +Operation executed by the xref:api:governance.adoc#TimelockController[`TimelockController`] can contain one or multiple subsequent calls. Depending on whether you need to multiple calls to be executed atomically, you can either use simple or batched operations. + +Both operations contain: + +* *Target*, the address of the smart contract that the timelock should operate on. +* *Value*, in wei, that should be sent with the transaction. Most of the time this will be 0. Ether can be deposited before-end or passed along when executing the transaction. +* *Data*, containing the encoded function selector and parameters of the call. This can be produced using a number of tools. For example, a maintenance operation granting role `ROLE` to `ACCOUNT` can be encoded using web3js as follows: + +```javascript +const data = timelock.contract.methods.grantRole(ROLE, ACCOUNT).encodeABI() +``` + +* *Predecessor*, that specifies a dependency between operations. This dependency is optional. Use `bytes32(0)` if the operation does not have any dependency. +* *Salt*, used to disambiguate two otherwise identical operations. This can be any random value. + +In the case of batched operations, `target`, `value` and `data` are specified as arrays, which must be of the same length. + +[[timelock-operation-lifecycle]] +==== Operation lifecycle + +Timelocked operations are identified by a unique id (their hash) and follow a specific lifecycle: + +`Unset` -> `Pending` -> `Pending` + `Ready` -> `Done` + +* By calling xref:api:governance.adoc#TimelockController-schedule-address-uint256-bytes-bytes32-bytes32-uint256-[`schedule`] (or xref:api:governance.adoc#TimelockController-scheduleBatch-address---uint256---bytes---bytes32-bytes32-uint256-[`scheduleBatch`]), a proposer moves the operation from the `Unset` to the `Pending` state. This starts a timer that must be longer than the minimum delay. The timer expires at a timestamp accessible through the xref:api:governance.adoc#TimelockController-getTimestamp-bytes32-[`getTimestamp`] method. +* Once the timer expires, the operation automatically gets the `Ready` state. At this point, it can be executed. +* By calling xref:api:governance.adoc#TimelockController-TimelockController-execute-address-uint256-bytes-bytes32-bytes32-[`execute`] (or xref:api:governance.adoc#TimelockController-executeBatch-address---uint256---bytes---bytes32-bytes32-[`executeBatch`]), an executor triggers the operation's underlying transactions and moves it to the `Done` state. If the operation has a predecessor, it has to be in the `Done` state for this transition to succeed. +* xref:api:governance.adoc#TimelockController-TimelockController-cancel-bytes32-[`cancel`] allows proposers to cancel any `Pending` operation. This resets the operation to the `Unset` state. It is thus possible for a proposer to re-schedule an operation that has been cancelled. In this case, the timer restarts when the operation is re-scheduled. + +Operations status can be queried using the functions: + +* xref:api:governance.adoc#TimelockController-isOperationPending-bytes32-[`isOperationPending(bytes32)`] +* xref:api:governance.adoc#TimelockController-isOperationReady-bytes32-[`isOperationReady(bytes32)`] +* xref:api:governance.adoc#TimelockController-isOperationDone-bytes32-[`isOperationDone(bytes32)`] + +[[timelock-roles]] +==== Roles + +[[timelock-admin]] +===== Admin + +The admins are in charge of managing proposers and executors. For the timelock to be self-governed, this role should only be given to the timelock itself. Upon deployment, the admin role can be granted to any address (in addition to the timelock itself). After further configuration and testing, this optional admin should renounce its role such that all further maintenance operations have to go through the timelock process. + +[[timelock-proposer]] +===== Proposer + +The proposers are in charge of scheduling (and cancelling) operations. This is a critical role, that should be given to governing entities. This could be an EOA, a multisig, or a DAO. + +WARNING: *Proposer fight:* Having multiple proposers, while providing redundancy in case one becomes unavailable, can be dangerous. As proposer have their say on all operations, they could cancel operations they disagree with, including operations to remove them for the proposers. + +This role is identified by the *PROPOSER_ROLE* value: `0xb09aa5aeb3702cfd50b6b62bc4532604938f21248a27a1d5ca736082b6819cc1` + +[[timelock-executor]] +===== Executor + +The executors are in charge of executing the operations scheduled by the proposers once the timelock expires. Logic dictates that multisig or DAO that are proposers should also be executors in order to guarantee operations that have been scheduled will eventually be executed. However, having additional executors can reduce the cost (the executing transaction does not require validation by the multisig or DAO that proposed it), while ensuring whoever is in charge of execution cannot trigger actions that have not been scheduled by the proposers. Alternatively, it is possible to allow _any_ address to execute a proposal once the timelock has expired by granting the executor role to the zero address. + +This role is identified by the *EXECUTOR_ROLE* value: `0xd8aa0f3194971a2a116679f7c2090f6939c8d4e01a2a8d7e41d55e5351469e63` + +WARNING: A live contract without at least one proposer and one executor is locked. Make sure these roles are filled by reliable entities before the deployer renounces its administrative rights in favour of the timelock contract itself. See the {AccessControl} documentation to learn more about role management. diff --git a/CVLByExample/UniswapHooks/lib/uniswap-hooks/lib/v4-core/lib/openzeppelin-contracts/contracts/governance/TimelockController.sol b/CVLByExample/UniswapHooks/lib/uniswap-hooks/lib/v4-core/lib/openzeppelin-contracts/contracts/governance/TimelockController.sol new file mode 100644 index 00000000..349d940f --- /dev/null +++ b/CVLByExample/UniswapHooks/lib/uniswap-hooks/lib/v4-core/lib/openzeppelin-contracts/contracts/governance/TimelockController.sol @@ -0,0 +1,472 @@ +// SPDX-License-Identifier: MIT +// OpenZeppelin Contracts (last updated v5.0.0) (governance/TimelockController.sol) + +pragma solidity ^0.8.20; + +import {AccessControl} from "../access/AccessControl.sol"; +import {ERC721Holder} from "../token/ERC721/utils/ERC721Holder.sol"; +import {ERC1155Holder} from "../token/ERC1155/utils/ERC1155Holder.sol"; +import {Address} from "../utils/Address.sol"; + +/** + * @dev Contract module which acts as a timelocked controller. When set as the + * owner of an `Ownable` smart contract, it enforces a timelock on all + * `onlyOwner` maintenance operations. This gives time for users of the + * controlled contract to exit before a potentially dangerous maintenance + * operation is applied. + * + * By default, this contract is self administered, meaning administration tasks + * have to go through the timelock process. The proposer (resp executor) role + * is in charge of proposing (resp executing) operations. A common use case is + * to position this {TimelockController} as the owner of a smart contract, with + * a multisig or a DAO as the sole proposer. + */ +contract TimelockController is AccessControl, ERC721Holder, ERC1155Holder { + bytes32 public constant PROPOSER_ROLE = keccak256("PROPOSER_ROLE"); + bytes32 public constant EXECUTOR_ROLE = keccak256("EXECUTOR_ROLE"); + bytes32 public constant CANCELLER_ROLE = keccak256("CANCELLER_ROLE"); + uint256 internal constant _DONE_TIMESTAMP = uint256(1); + + mapping(bytes32 id => uint256) private _timestamps; + uint256 private _minDelay; + + enum OperationState { + Unset, + Waiting, + Ready, + Done + } + + /** + * @dev Mismatch between the parameters length for an operation call. + */ + error TimelockInvalidOperationLength(uint256 targets, uint256 payloads, uint256 values); + + /** + * @dev The schedule operation doesn't meet the minimum delay. + */ + error TimelockInsufficientDelay(uint256 delay, uint256 minDelay); + + /** + * @dev The current state of an operation is not as required. + * The `expectedStates` is a bitmap with the bits enabled for each OperationState enum position + * counting from right to left. + * + * See {_encodeStateBitmap}. + */ + error TimelockUnexpectedOperationState(bytes32 operationId, bytes32 expectedStates); + + /** + * @dev The predecessor to an operation not yet done. + */ + error TimelockUnexecutedPredecessor(bytes32 predecessorId); + + /** + * @dev The caller account is not authorized. + */ + error TimelockUnauthorizedCaller(address caller); + + /** + * @dev Emitted when a call is scheduled as part of operation `id`. + */ + event CallScheduled( + bytes32 indexed id, + uint256 indexed index, + address target, + uint256 value, + bytes data, + bytes32 predecessor, + uint256 delay + ); + + /** + * @dev Emitted when a call is performed as part of operation `id`. + */ + event CallExecuted(bytes32 indexed id, uint256 indexed index, address target, uint256 value, bytes data); + + /** + * @dev Emitted when new proposal is scheduled with non-zero salt. + */ + event CallSalt(bytes32 indexed id, bytes32 salt); + + /** + * @dev Emitted when operation `id` is cancelled. + */ + event Cancelled(bytes32 indexed id); + + /** + * @dev Emitted when the minimum delay for future operations is modified. + */ + event MinDelayChange(uint256 oldDuration, uint256 newDuration); + + /** + * @dev Initializes the contract with the following parameters: + * + * - `minDelay`: initial minimum delay in seconds for operations + * - `proposers`: accounts to be granted proposer and canceller roles + * - `executors`: accounts to be granted executor role + * - `admin`: optional account to be granted admin role; disable with zero address + * + * IMPORTANT: The optional admin can aid with initial configuration of roles after deployment + * without being subject to delay, but this role should be subsequently renounced in favor of + * administration through timelocked proposals. Previous versions of this contract would assign + * this admin to the deployer automatically and should be renounced as well. + */ + constructor(uint256 minDelay, address[] memory proposers, address[] memory executors, address admin) { + // self administration + _grantRole(DEFAULT_ADMIN_ROLE, address(this)); + + // optional admin + if (admin != address(0)) { + _grantRole(DEFAULT_ADMIN_ROLE, admin); + } + + // register proposers and cancellers + for (uint256 i = 0; i < proposers.length; ++i) { + _grantRole(PROPOSER_ROLE, proposers[i]); + _grantRole(CANCELLER_ROLE, proposers[i]); + } + + // register executors + for (uint256 i = 0; i < executors.length; ++i) { + _grantRole(EXECUTOR_ROLE, executors[i]); + } + + _minDelay = minDelay; + emit MinDelayChange(0, minDelay); + } + + /** + * @dev Modifier to make a function callable only by a certain role. In + * addition to checking the sender's role, `address(0)` 's role is also + * considered. Granting a role to `address(0)` is equivalent to enabling + * this role for everyone. + */ + modifier onlyRoleOrOpenRole(bytes32 role) { + if (!hasRole(role, address(0))) { + _checkRole(role, _msgSender()); + } + _; + } + + /** + * @dev Contract might receive/hold ETH as part of the maintenance process. + */ + receive() external payable {} + + /** + * @dev See {IERC165-supportsInterface}. + */ + function supportsInterface( + bytes4 interfaceId + ) public view virtual override(AccessControl, ERC1155Holder) returns (bool) { + return super.supportsInterface(interfaceId); + } + + /** + * @dev Returns whether an id corresponds to a registered operation. This + * includes both Waiting, Ready, and Done operations. + */ + function isOperation(bytes32 id) public view returns (bool) { + return getOperationState(id) != OperationState.Unset; + } + + /** + * @dev Returns whether an operation is pending or not. Note that a "pending" operation may also be "ready". + */ + function isOperationPending(bytes32 id) public view returns (bool) { + OperationState state = getOperationState(id); + return state == OperationState.Waiting || state == OperationState.Ready; + } + + /** + * @dev Returns whether an operation is ready for execution. Note that a "ready" operation is also "pending". + */ + function isOperationReady(bytes32 id) public view returns (bool) { + return getOperationState(id) == OperationState.Ready; + } + + /** + * @dev Returns whether an operation is done or not. + */ + function isOperationDone(bytes32 id) public view returns (bool) { + return getOperationState(id) == OperationState.Done; + } + + /** + * @dev Returns the timestamp at which an operation becomes ready (0 for + * unset operations, 1 for done operations). + */ + function getTimestamp(bytes32 id) public view virtual returns (uint256) { + return _timestamps[id]; + } + + /** + * @dev Returns operation state. + */ + function getOperationState(bytes32 id) public view virtual returns (OperationState) { + uint256 timestamp = getTimestamp(id); + if (timestamp == 0) { + return OperationState.Unset; + } else if (timestamp == _DONE_TIMESTAMP) { + return OperationState.Done; + } else if (timestamp > block.timestamp) { + return OperationState.Waiting; + } else { + return OperationState.Ready; + } + } + + /** + * @dev Returns the minimum delay in seconds for an operation to become valid. + * + * This value can be changed by executing an operation that calls `updateDelay`. + */ + function getMinDelay() public view virtual returns (uint256) { + return _minDelay; + } + + /** + * @dev Returns the identifier of an operation containing a single + * transaction. + */ + function hashOperation( + address target, + uint256 value, + bytes calldata data, + bytes32 predecessor, + bytes32 salt + ) public pure virtual returns (bytes32) { + return keccak256(abi.encode(target, value, data, predecessor, salt)); + } + + /** + * @dev Returns the identifier of an operation containing a batch of + * transactions. + */ + function hashOperationBatch( + address[] calldata targets, + uint256[] calldata values, + bytes[] calldata payloads, + bytes32 predecessor, + bytes32 salt + ) public pure virtual returns (bytes32) { + return keccak256(abi.encode(targets, values, payloads, predecessor, salt)); + } + + /** + * @dev Schedule an operation containing a single transaction. + * + * Emits {CallSalt} if salt is nonzero, and {CallScheduled}. + * + * Requirements: + * + * - the caller must have the 'proposer' role. + */ + function schedule( + address target, + uint256 value, + bytes calldata data, + bytes32 predecessor, + bytes32 salt, + uint256 delay + ) public virtual onlyRole(PROPOSER_ROLE) { + bytes32 id = hashOperation(target, value, data, predecessor, salt); + _schedule(id, delay); + emit CallScheduled(id, 0, target, value, data, predecessor, delay); + if (salt != bytes32(0)) { + emit CallSalt(id, salt); + } + } + + /** + * @dev Schedule an operation containing a batch of transactions. + * + * Emits {CallSalt} if salt is nonzero, and one {CallScheduled} event per transaction in the batch. + * + * Requirements: + * + * - the caller must have the 'proposer' role. + */ + function scheduleBatch( + address[] calldata targets, + uint256[] calldata values, + bytes[] calldata payloads, + bytes32 predecessor, + bytes32 salt, + uint256 delay + ) public virtual onlyRole(PROPOSER_ROLE) { + if (targets.length != values.length || targets.length != payloads.length) { + revert TimelockInvalidOperationLength(targets.length, payloads.length, values.length); + } + + bytes32 id = hashOperationBatch(targets, values, payloads, predecessor, salt); + _schedule(id, delay); + for (uint256 i = 0; i < targets.length; ++i) { + emit CallScheduled(id, i, targets[i], values[i], payloads[i], predecessor, delay); + } + if (salt != bytes32(0)) { + emit CallSalt(id, salt); + } + } + + /** + * @dev Schedule an operation that is to become valid after a given delay. + */ + function _schedule(bytes32 id, uint256 delay) private { + if (isOperation(id)) { + revert TimelockUnexpectedOperationState(id, _encodeStateBitmap(OperationState.Unset)); + } + uint256 minDelay = getMinDelay(); + if (delay < minDelay) { + revert TimelockInsufficientDelay(delay, minDelay); + } + _timestamps[id] = block.timestamp + delay; + } + + /** + * @dev Cancel an operation. + * + * Requirements: + * + * - the caller must have the 'canceller' role. + */ + function cancel(bytes32 id) public virtual onlyRole(CANCELLER_ROLE) { + if (!isOperationPending(id)) { + revert TimelockUnexpectedOperationState( + id, + _encodeStateBitmap(OperationState.Waiting) | _encodeStateBitmap(OperationState.Ready) + ); + } + delete _timestamps[id]; + + emit Cancelled(id); + } + + /** + * @dev Execute an (ready) operation containing a single transaction. + * + * Emits a {CallExecuted} event. + * + * Requirements: + * + * - the caller must have the 'executor' role. + */ + // This function can reenter, but it doesn't pose a risk because _afterCall checks that the proposal is pending, + // thus any modifications to the operation during reentrancy should be caught. + // slither-disable-next-line reentrancy-eth + function execute( + address target, + uint256 value, + bytes calldata payload, + bytes32 predecessor, + bytes32 salt + ) public payable virtual onlyRoleOrOpenRole(EXECUTOR_ROLE) { + bytes32 id = hashOperation(target, value, payload, predecessor, salt); + + _beforeCall(id, predecessor); + _execute(target, value, payload); + emit CallExecuted(id, 0, target, value, payload); + _afterCall(id); + } + + /** + * @dev Execute an (ready) operation containing a batch of transactions. + * + * Emits one {CallExecuted} event per transaction in the batch. + * + * Requirements: + * + * - the caller must have the 'executor' role. + */ + // This function can reenter, but it doesn't pose a risk because _afterCall checks that the proposal is pending, + // thus any modifications to the operation during reentrancy should be caught. + // slither-disable-next-line reentrancy-eth + function executeBatch( + address[] calldata targets, + uint256[] calldata values, + bytes[] calldata payloads, + bytes32 predecessor, + bytes32 salt + ) public payable virtual onlyRoleOrOpenRole(EXECUTOR_ROLE) { + if (targets.length != values.length || targets.length != payloads.length) { + revert TimelockInvalidOperationLength(targets.length, payloads.length, values.length); + } + + bytes32 id = hashOperationBatch(targets, values, payloads, predecessor, salt); + + _beforeCall(id, predecessor); + for (uint256 i = 0; i < targets.length; ++i) { + address target = targets[i]; + uint256 value = values[i]; + bytes calldata payload = payloads[i]; + _execute(target, value, payload); + emit CallExecuted(id, i, target, value, payload); + } + _afterCall(id); + } + + /** + * @dev Execute an operation's call. + */ + function _execute(address target, uint256 value, bytes calldata data) internal virtual { + (bool success, bytes memory returndata) = target.call{value: value}(data); + Address.verifyCallResult(success, returndata); + } + + /** + * @dev Checks before execution of an operation's calls. + */ + function _beforeCall(bytes32 id, bytes32 predecessor) private view { + if (!isOperationReady(id)) { + revert TimelockUnexpectedOperationState(id, _encodeStateBitmap(OperationState.Ready)); + } + if (predecessor != bytes32(0) && !isOperationDone(predecessor)) { + revert TimelockUnexecutedPredecessor(predecessor); + } + } + + /** + * @dev Checks after execution of an operation's calls. + */ + function _afterCall(bytes32 id) private { + if (!isOperationReady(id)) { + revert TimelockUnexpectedOperationState(id, _encodeStateBitmap(OperationState.Ready)); + } + _timestamps[id] = _DONE_TIMESTAMP; + } + + /** + * @dev Changes the minimum timelock duration for future operations. + * + * Emits a {MinDelayChange} event. + * + * Requirements: + * + * - the caller must be the timelock itself. This can only be achieved by scheduling and later executing + * an operation where the timelock is the target and the data is the ABI-encoded call to this function. + */ + function updateDelay(uint256 newDelay) external virtual { + address sender = _msgSender(); + if (sender != address(this)) { + revert TimelockUnauthorizedCaller(sender); + } + emit MinDelayChange(_minDelay, newDelay); + _minDelay = newDelay; + } + + /** + * @dev Encodes a `OperationState` into a `bytes32` representation where each bit enabled corresponds to + * the underlying position in the `OperationState` enum. For example: + * + * 0x000...1000 + * ^^^^^^----- ... + * ^---- Done + * ^--- Ready + * ^-- Waiting + * ^- Unset + */ + function _encodeStateBitmap(OperationState operationState) internal pure returns (bytes32) { + return bytes32(1 << uint8(operationState)); + } +} diff --git a/CVLByExample/UniswapHooks/lib/uniswap-hooks/lib/v4-core/lib/openzeppelin-contracts/contracts/governance/extensions/GovernorCountingSimple.sol b/CVLByExample/UniswapHooks/lib/uniswap-hooks/lib/v4-core/lib/openzeppelin-contracts/contracts/governance/extensions/GovernorCountingSimple.sol new file mode 100644 index 00000000..ac9c22aa --- /dev/null +++ b/CVLByExample/UniswapHooks/lib/uniswap-hooks/lib/v4-core/lib/openzeppelin-contracts/contracts/governance/extensions/GovernorCountingSimple.sol @@ -0,0 +1,100 @@ +// SPDX-License-Identifier: MIT +// OpenZeppelin Contracts (last updated v5.0.0) (governance/extensions/GovernorCountingSimple.sol) + +pragma solidity ^0.8.20; + +import {Governor} from "../Governor.sol"; + +/** + * @dev Extension of {Governor} for simple, 3 options, vote counting. + */ +abstract contract GovernorCountingSimple is Governor { + /** + * @dev Supported vote types. Matches Governor Bravo ordering. + */ + enum VoteType { + Against, + For, + Abstain + } + + struct ProposalVote { + uint256 againstVotes; + uint256 forVotes; + uint256 abstainVotes; + mapping(address voter => bool) hasVoted; + } + + mapping(uint256 proposalId => ProposalVote) private _proposalVotes; + + /** + * @dev See {IGovernor-COUNTING_MODE}. + */ + // solhint-disable-next-line func-name-mixedcase + function COUNTING_MODE() public pure virtual override returns (string memory) { + return "support=bravo&quorum=for,abstain"; + } + + /** + * @dev See {IGovernor-hasVoted}. + */ + function hasVoted(uint256 proposalId, address account) public view virtual override returns (bool) { + return _proposalVotes[proposalId].hasVoted[account]; + } + + /** + * @dev Accessor to the internal vote counts. + */ + function proposalVotes( + uint256 proposalId + ) public view virtual returns (uint256 againstVotes, uint256 forVotes, uint256 abstainVotes) { + ProposalVote storage proposalVote = _proposalVotes[proposalId]; + return (proposalVote.againstVotes, proposalVote.forVotes, proposalVote.abstainVotes); + } + + /** + * @dev See {Governor-_quorumReached}. + */ + function _quorumReached(uint256 proposalId) internal view virtual override returns (bool) { + ProposalVote storage proposalVote = _proposalVotes[proposalId]; + + return quorum(proposalSnapshot(proposalId)) <= proposalVote.forVotes + proposalVote.abstainVotes; + } + + /** + * @dev See {Governor-_voteSucceeded}. In this module, the forVotes must be strictly over the againstVotes. + */ + function _voteSucceeded(uint256 proposalId) internal view virtual override returns (bool) { + ProposalVote storage proposalVote = _proposalVotes[proposalId]; + + return proposalVote.forVotes > proposalVote.againstVotes; + } + + /** + * @dev See {Governor-_countVote}. In this module, the support follows the `VoteType` enum (from Governor Bravo). + */ + function _countVote( + uint256 proposalId, + address account, + uint8 support, + uint256 weight, + bytes memory // params + ) internal virtual override { + ProposalVote storage proposalVote = _proposalVotes[proposalId]; + + if (proposalVote.hasVoted[account]) { + revert GovernorAlreadyCastVote(account); + } + proposalVote.hasVoted[account] = true; + + if (support == uint8(VoteType.Against)) { + proposalVote.againstVotes += weight; + } else if (support == uint8(VoteType.For)) { + proposalVote.forVotes += weight; + } else if (support == uint8(VoteType.Abstain)) { + proposalVote.abstainVotes += weight; + } else { + revert GovernorInvalidVoteType(); + } + } +} diff --git a/CVLByExample/UniswapHooks/lib/uniswap-hooks/lib/v4-core/lib/openzeppelin-contracts/contracts/governance/extensions/GovernorPreventLateQuorum.sol b/CVLByExample/UniswapHooks/lib/uniswap-hooks/lib/v4-core/lib/openzeppelin-contracts/contracts/governance/extensions/GovernorPreventLateQuorum.sol new file mode 100644 index 00000000..ff80af64 --- /dev/null +++ b/CVLByExample/UniswapHooks/lib/uniswap-hooks/lib/v4-core/lib/openzeppelin-contracts/contracts/governance/extensions/GovernorPreventLateQuorum.sol @@ -0,0 +1,102 @@ +// SPDX-License-Identifier: MIT +// OpenZeppelin Contracts (last updated v5.0.0) (governance/extensions/GovernorPreventLateQuorum.sol) + +pragma solidity ^0.8.20; + +import {Governor} from "../Governor.sol"; +import {Math} from "../../utils/math/Math.sol"; + +/** + * @dev A module that ensures there is a minimum voting period after quorum is reached. This prevents a large voter from + * swaying a vote and triggering quorum at the last minute, by ensuring there is always time for other voters to react + * and try to oppose the decision. + * + * If a vote causes quorum to be reached, the proposal's voting period may be extended so that it does not end before at + * least a specified time has passed (the "vote extension" parameter). This parameter can be set through a governance + * proposal. + */ +abstract contract GovernorPreventLateQuorum is Governor { + uint48 private _voteExtension; + + mapping(uint256 proposalId => uint48) private _extendedDeadlines; + + /// @dev Emitted when a proposal deadline is pushed back due to reaching quorum late in its voting period. + event ProposalExtended(uint256 indexed proposalId, uint64 extendedDeadline); + + /// @dev Emitted when the {lateQuorumVoteExtension} parameter is changed. + event LateQuorumVoteExtensionSet(uint64 oldVoteExtension, uint64 newVoteExtension); + + /** + * @dev Initializes the vote extension parameter: the time in either number of blocks or seconds (depending on the + * governor clock mode) that is required to pass since the moment a proposal reaches quorum until its voting period + * ends. If necessary the voting period will be extended beyond the one set during proposal creation. + */ + constructor(uint48 initialVoteExtension) { + _setLateQuorumVoteExtension(initialVoteExtension); + } + + /** + * @dev Returns the proposal deadline, which may have been extended beyond that set at proposal creation, if the + * proposal reached quorum late in the voting period. See {Governor-proposalDeadline}. + */ + function proposalDeadline(uint256 proposalId) public view virtual override returns (uint256) { + return Math.max(super.proposalDeadline(proposalId), _extendedDeadlines[proposalId]); + } + + /** + * @dev Casts a vote and detects if it caused quorum to be reached, potentially extending the voting period. See + * {Governor-_castVote}. + * + * May emit a {ProposalExtended} event. + */ + function _castVote( + uint256 proposalId, + address account, + uint8 support, + string memory reason, + bytes memory params + ) internal virtual override returns (uint256) { + uint256 result = super._castVote(proposalId, account, support, reason, params); + + if (_extendedDeadlines[proposalId] == 0 && _quorumReached(proposalId)) { + uint48 extendedDeadline = clock() + lateQuorumVoteExtension(); + + if (extendedDeadline > proposalDeadline(proposalId)) { + emit ProposalExtended(proposalId, extendedDeadline); + } + + _extendedDeadlines[proposalId] = extendedDeadline; + } + + return result; + } + + /** + * @dev Returns the current value of the vote extension parameter: the number of blocks that are required to pass + * from the time a proposal reaches quorum until its voting period ends. + */ + function lateQuorumVoteExtension() public view virtual returns (uint48) { + return _voteExtension; + } + + /** + * @dev Changes the {lateQuorumVoteExtension}. This operation can only be performed by the governance executor, + * generally through a governance proposal. + * + * Emits a {LateQuorumVoteExtensionSet} event. + */ + function setLateQuorumVoteExtension(uint48 newVoteExtension) public virtual onlyGovernance { + _setLateQuorumVoteExtension(newVoteExtension); + } + + /** + * @dev Changes the {lateQuorumVoteExtension}. This is an internal function that can be exposed in a public function + * like {setLateQuorumVoteExtension} if another access control mechanism is needed. + * + * Emits a {LateQuorumVoteExtensionSet} event. + */ + function _setLateQuorumVoteExtension(uint48 newVoteExtension) internal virtual { + emit LateQuorumVoteExtensionSet(_voteExtension, newVoteExtension); + _voteExtension = newVoteExtension; + } +} diff --git a/CVLByExample/UniswapHooks/lib/uniswap-hooks/lib/v4-core/lib/openzeppelin-contracts/contracts/governance/extensions/GovernorSettings.sol b/CVLByExample/UniswapHooks/lib/uniswap-hooks/lib/v4-core/lib/openzeppelin-contracts/contracts/governance/extensions/GovernorSettings.sol new file mode 100644 index 00000000..7347ee29 --- /dev/null +++ b/CVLByExample/UniswapHooks/lib/uniswap-hooks/lib/v4-core/lib/openzeppelin-contracts/contracts/governance/extensions/GovernorSettings.sol @@ -0,0 +1,112 @@ +// SPDX-License-Identifier: MIT +// OpenZeppelin Contracts (last updated v5.0.0) (governance/extensions/GovernorSettings.sol) + +pragma solidity ^0.8.20; + +import {Governor} from "../Governor.sol"; + +/** + * @dev Extension of {Governor} for settings updatable through governance. + */ +abstract contract GovernorSettings is Governor { + // amount of token + uint256 private _proposalThreshold; + // timepoint: limited to uint48 in core (same as clock() type) + uint48 private _votingDelay; + // duration: limited to uint32 in core + uint32 private _votingPeriod; + + event VotingDelaySet(uint256 oldVotingDelay, uint256 newVotingDelay); + event VotingPeriodSet(uint256 oldVotingPeriod, uint256 newVotingPeriod); + event ProposalThresholdSet(uint256 oldProposalThreshold, uint256 newProposalThreshold); + + /** + * @dev Initialize the governance parameters. + */ + constructor(uint48 initialVotingDelay, uint32 initialVotingPeriod, uint256 initialProposalThreshold) { + _setVotingDelay(initialVotingDelay); + _setVotingPeriod(initialVotingPeriod); + _setProposalThreshold(initialProposalThreshold); + } + + /** + * @dev See {IGovernor-votingDelay}. + */ + function votingDelay() public view virtual override returns (uint256) { + return _votingDelay; + } + + /** + * @dev See {IGovernor-votingPeriod}. + */ + function votingPeriod() public view virtual override returns (uint256) { + return _votingPeriod; + } + + /** + * @dev See {Governor-proposalThreshold}. + */ + function proposalThreshold() public view virtual override returns (uint256) { + return _proposalThreshold; + } + + /** + * @dev Update the voting delay. This operation can only be performed through a governance proposal. + * + * Emits a {VotingDelaySet} event. + */ + function setVotingDelay(uint48 newVotingDelay) public virtual onlyGovernance { + _setVotingDelay(newVotingDelay); + } + + /** + * @dev Update the voting period. This operation can only be performed through a governance proposal. + * + * Emits a {VotingPeriodSet} event. + */ + function setVotingPeriod(uint32 newVotingPeriod) public virtual onlyGovernance { + _setVotingPeriod(newVotingPeriod); + } + + /** + * @dev Update the proposal threshold. This operation can only be performed through a governance proposal. + * + * Emits a {ProposalThresholdSet} event. + */ + function setProposalThreshold(uint256 newProposalThreshold) public virtual onlyGovernance { + _setProposalThreshold(newProposalThreshold); + } + + /** + * @dev Internal setter for the voting delay. + * + * Emits a {VotingDelaySet} event. + */ + function _setVotingDelay(uint48 newVotingDelay) internal virtual { + emit VotingDelaySet(_votingDelay, newVotingDelay); + _votingDelay = newVotingDelay; + } + + /** + * @dev Internal setter for the voting period. + * + * Emits a {VotingPeriodSet} event. + */ + function _setVotingPeriod(uint32 newVotingPeriod) internal virtual { + if (newVotingPeriod == 0) { + revert GovernorInvalidVotingPeriod(0); + } + emit VotingPeriodSet(_votingPeriod, newVotingPeriod); + _votingPeriod = newVotingPeriod; + } + + /** + * @dev Internal setter for the proposal threshold. + * + * Emits a {ProposalThresholdSet} event. + */ + function _setProposalThreshold(uint256 newProposalThreshold) internal virtual { + emit ProposalThresholdSet(_proposalThreshold, newProposalThreshold); + _proposalThreshold = newProposalThreshold; + } +} diff --git a/CVLByExample/UniswapHooks/lib/uniswap-hooks/lib/v4-core/lib/openzeppelin-contracts/contracts/governance/extensions/GovernorStorage.sol b/CVLByExample/UniswapHooks/lib/uniswap-hooks/lib/v4-core/lib/openzeppelin-contracts/contracts/governance/extensions/GovernorStorage.sol new file mode 100644 index 00000000..2547b553 --- /dev/null +++ b/CVLByExample/UniswapHooks/lib/uniswap-hooks/lib/v4-core/lib/openzeppelin-contracts/contracts/governance/extensions/GovernorStorage.sol @@ -0,0 +1,115 @@ +// SPDX-License-Identifier: MIT +// OpenZeppelin Contracts (last updated v5.0.0) (governance/extensions/GovernorStorage.sol) + +pragma solidity ^0.8.20; + +import {Governor} from "../Governor.sol"; + +/** + * @dev Extension of {Governor} that implements storage of proposal details. This modules also provides primitives for + * the enumerability of proposals. + * + * Use cases for this module include: + * - UIs that explore the proposal state without relying on event indexing. + * - Using only the proposalId as an argument in the {Governor-queue} and {Governor-execute} functions for L2 chains + * where storage is cheap compared to calldata. + */ +abstract contract GovernorStorage is Governor { + struct ProposalDetails { + address[] targets; + uint256[] values; + bytes[] calldatas; + bytes32 descriptionHash; + } + + uint256[] private _proposalIds; + mapping(uint256 proposalId => ProposalDetails) private _proposalDetails; + + /** + * @dev Hook into the proposing mechanism + */ + function _propose( + address[] memory targets, + uint256[] memory values, + bytes[] memory calldatas, + string memory description, + address proposer + ) internal virtual override returns (uint256) { + uint256 proposalId = super._propose(targets, values, calldatas, description, proposer); + + // store + _proposalIds.push(proposalId); + _proposalDetails[proposalId] = ProposalDetails({ + targets: targets, + values: values, + calldatas: calldatas, + descriptionHash: keccak256(bytes(description)) + }); + + return proposalId; + } + + /** + * @dev Version of {IGovernorTimelock-queue} with only `proposalId` as an argument. + */ + function queue(uint256 proposalId) public virtual { + // here, using storage is more efficient than memory + ProposalDetails storage details = _proposalDetails[proposalId]; + queue(details.targets, details.values, details.calldatas, details.descriptionHash); + } + + /** + * @dev Version of {IGovernor-execute} with only `proposalId` as an argument. + */ + function execute(uint256 proposalId) public payable virtual { + // here, using storage is more efficient than memory + ProposalDetails storage details = _proposalDetails[proposalId]; + execute(details.targets, details.values, details.calldatas, details.descriptionHash); + } + + /** + * @dev ProposalId version of {IGovernor-cancel}. + */ + function cancel(uint256 proposalId) public virtual { + // here, using storage is more efficient than memory + ProposalDetails storage details = _proposalDetails[proposalId]; + cancel(details.targets, details.values, details.calldatas, details.descriptionHash); + } + + /** + * @dev Returns the number of stored proposals. + */ + function proposalCount() public view virtual returns (uint256) { + return _proposalIds.length; + } + + /** + * @dev Returns the details of a proposalId. Reverts if `proposalId` is not a known proposal. + */ + function proposalDetails( + uint256 proposalId + ) public view virtual returns (address[] memory, uint256[] memory, bytes[] memory, bytes32) { + // here, using memory is more efficient than storage + ProposalDetails memory details = _proposalDetails[proposalId]; + if (details.descriptionHash == 0) { + revert GovernorNonexistentProposal(proposalId); + } + return (details.targets, details.values, details.calldatas, details.descriptionHash); + } + + /** + * @dev Returns the details (including the proposalId) of a proposal given its sequential index. + */ + function proposalDetailsAt( + uint256 index + ) public view virtual returns (uint256, address[] memory, uint256[] memory, bytes[] memory, bytes32) { + uint256 proposalId = _proposalIds[index]; + ( + address[] memory targets, + uint256[] memory values, + bytes[] memory calldatas, + bytes32 descriptionHash + ) = proposalDetails(proposalId); + return (proposalId, targets, values, calldatas, descriptionHash); + } +} diff --git a/CVLByExample/UniswapHooks/lib/uniswap-hooks/lib/v4-core/lib/openzeppelin-contracts/contracts/governance/extensions/GovernorTimelockAccess.sol b/CVLByExample/UniswapHooks/lib/uniswap-hooks/lib/v4-core/lib/openzeppelin-contracts/contracts/governance/extensions/GovernorTimelockAccess.sol new file mode 100644 index 00000000..a2373a4f --- /dev/null +++ b/CVLByExample/UniswapHooks/lib/uniswap-hooks/lib/v4-core/lib/openzeppelin-contracts/contracts/governance/extensions/GovernorTimelockAccess.sol @@ -0,0 +1,346 @@ +// SPDX-License-Identifier: MIT +// OpenZeppelin Contracts (last updated v5.0.0) (governance/extensions/GovernorTimelockAccess.sol) + +pragma solidity ^0.8.20; + +import {Governor} from "../Governor.sol"; +import {AuthorityUtils} from "../../access/manager/AuthorityUtils.sol"; +import {IAccessManager} from "../../access/manager/IAccessManager.sol"; +import {Address} from "../../utils/Address.sol"; +import {Math} from "../../utils/math/Math.sol"; +import {SafeCast} from "../../utils/math/SafeCast.sol"; +import {Time} from "../../utils/types/Time.sol"; + +/** + * @dev This module connects a {Governor} instance to an {AccessManager} instance, allowing the governor to make calls + * that are delay-restricted by the manager using the normal {queue} workflow. An optional base delay is applied to + * operations that are not delayed externally by the manager. Execution of a proposal will be delayed as much as + * necessary to meet the required delays of all of its operations. + * + * This extension allows the governor to hold and use its own assets and permissions, unlike {GovernorTimelockControl} + * and {GovernorTimelockCompound}, where the timelock is a separate contract that must be the one to hold assets and + * permissions. Operations that are delay-restricted by the manager, however, will be executed through the + * {AccessManager-execute} function. + * + * ==== Security Considerations + * + * Some operations may be cancelable in the `AccessManager` by the admin or a set of guardians, depending on the + * restricted function being invoked. Since proposals are atomic, the cancellation by a guardian of a single operation + * in a proposal will cause all of the proposal to become unable to execute. Consider proposing cancellable operations + * separately. + * + * By default, function calls will be routed through the associated `AccessManager` whenever it claims the target + * function to be restricted by it. However, admins may configure the manager to make that claim for functions that a + * governor would want to call directly (e.g., token transfers) in an attempt to deny it access to those functions. To + * mitigate this attack vector, the governor is able to ignore the restrictions claimed by the `AccessManager` using + * {setAccessManagerIgnored}. While permanent denial of service is mitigated, temporary DoS may still be technically + * possible. All of the governor's own functions (e.g., {setBaseDelaySeconds}) ignore the `AccessManager` by default. + */ +abstract contract GovernorTimelockAccess is Governor { + // An execution plan is produced at the moment a proposal is created, in order to fix at that point the exact + // execution semantics of the proposal, namely whether a call will go through {AccessManager-execute}. + struct ExecutionPlan { + uint16 length; + uint32 delay; + // We use mappings instead of arrays because it allows us to pack values in storage more tightly without + // storing the length redundantly. + // We pack 8 operations' data in each bucket. Each uint32 value is set to 1 upon proposal creation if it has + // to be scheduled and executed through the manager. Upon queuing, the value is set to nonce + 2, where the + // nonce is received from the manager when scheduling the operation. + mapping(uint256 operationBucket => uint32[8]) managerData; + } + + // The meaning of the "toggle" set to true depends on the target contract. + // If target == address(this), the manager is ignored by default, and a true toggle means it won't be ignored. + // For all other target contracts, the manager is used by default, and a true toggle means it will be ignored. + mapping(address target => mapping(bytes4 selector => bool)) private _ignoreToggle; + + mapping(uint256 proposalId => ExecutionPlan) private _executionPlan; + + uint32 private _baseDelay; + + IAccessManager private immutable _manager; + + error GovernorUnmetDelay(uint256 proposalId, uint256 neededTimestamp); + error GovernorMismatchedNonce(uint256 proposalId, uint256 expectedNonce, uint256 actualNonce); + error GovernorLockedIgnore(); + + event BaseDelaySet(uint32 oldBaseDelaySeconds, uint32 newBaseDelaySeconds); + event AccessManagerIgnoredSet(address target, bytes4 selector, bool ignored); + + /** + * @dev Initialize the governor with an {AccessManager} and initial base delay. + */ + constructor(address manager, uint32 initialBaseDelay) { + _manager = IAccessManager(manager); + _setBaseDelaySeconds(initialBaseDelay); + } + + /** + * @dev Returns the {AccessManager} instance associated to this governor. + */ + function accessManager() public view virtual returns (IAccessManager) { + return _manager; + } + + /** + * @dev Base delay that will be applied to all function calls. Some may be further delayed by their associated + * `AccessManager` authority; in this case the final delay will be the maximum of the base delay and the one + * demanded by the authority. + * + * NOTE: Execution delays are processed by the `AccessManager` contracts, and according to that contract are + * expressed in seconds. Therefore, the base delay is also in seconds, regardless of the governor's clock mode. + */ + function baseDelaySeconds() public view virtual returns (uint32) { + return _baseDelay; + } + + /** + * @dev Change the value of {baseDelaySeconds}. This operation can only be invoked through a governance proposal. + */ + function setBaseDelaySeconds(uint32 newBaseDelay) public virtual onlyGovernance { + _setBaseDelaySeconds(newBaseDelay); + } + + /** + * @dev Change the value of {baseDelaySeconds}. Internal function without access control. + */ + function _setBaseDelaySeconds(uint32 newBaseDelay) internal virtual { + emit BaseDelaySet(_baseDelay, newBaseDelay); + _baseDelay = newBaseDelay; + } + + /** + * @dev Check if restrictions from the associated {AccessManager} are ignored for a target function. Returns true + * when the target function will be invoked directly regardless of `AccessManager` settings for the function. + * See {setAccessManagerIgnored} and Security Considerations above. + */ + function isAccessManagerIgnored(address target, bytes4 selector) public view virtual returns (bool) { + bool isGovernor = target == address(this); + return _ignoreToggle[target][selector] != isGovernor; // equivalent to: isGovernor ? !toggle : toggle + } + + /** + * @dev Configure whether restrictions from the associated {AccessManager} are ignored for a target function. + * See Security Considerations above. + */ + function setAccessManagerIgnored( + address target, + bytes4[] calldata selectors, + bool ignored + ) public virtual onlyGovernance { + for (uint256 i = 0; i < selectors.length; ++i) { + _setAccessManagerIgnored(target, selectors[i], ignored); + } + } + + /** + * @dev Internal version of {setAccessManagerIgnored} without access restriction. + */ + function _setAccessManagerIgnored(address target, bytes4 selector, bool ignored) internal virtual { + bool isGovernor = target == address(this); + if (isGovernor && selector == this.setAccessManagerIgnored.selector) { + revert GovernorLockedIgnore(); + } + _ignoreToggle[target][selector] = ignored != isGovernor; // equivalent to: isGovernor ? !ignored : ignored + emit AccessManagerIgnoredSet(target, selector, ignored); + } + + /** + * @dev Public accessor to check the execution plan, including the number of seconds that the proposal will be + * delayed since queuing, an array indicating which of the proposal actions will be executed indirectly through + * the associated {AccessManager}, and another indicating which will be scheduled in {queue}. Note that + * those that must be scheduled are cancellable by `AccessManager` guardians. + */ + function proposalExecutionPlan( + uint256 proposalId + ) public view returns (uint32 delay, bool[] memory indirect, bool[] memory withDelay) { + ExecutionPlan storage plan = _executionPlan[proposalId]; + + uint32 length = plan.length; + delay = plan.delay; + indirect = new bool[](length); + withDelay = new bool[](length); + for (uint256 i = 0; i < length; ++i) { + (indirect[i], withDelay[i], ) = _getManagerData(plan, i); + } + + return (delay, indirect, withDelay); + } + + /** + * @dev See {IGovernor-proposalNeedsQueuing}. + */ + function proposalNeedsQueuing(uint256 proposalId) public view virtual override returns (bool) { + return _executionPlan[proposalId].delay > 0; + } + + /** + * @dev See {IGovernor-propose} + */ + function propose( + address[] memory targets, + uint256[] memory values, + bytes[] memory calldatas, + string memory description + ) public virtual override returns (uint256) { + uint256 proposalId = super.propose(targets, values, calldatas, description); + + uint32 neededDelay = baseDelaySeconds(); + + ExecutionPlan storage plan = _executionPlan[proposalId]; + plan.length = SafeCast.toUint16(targets.length); + + for (uint256 i = 0; i < targets.length; ++i) { + if (calldatas[i].length < 4) { + continue; + } + address target = targets[i]; + bytes4 selector = bytes4(calldatas[i]); + (bool immediate, uint32 delay) = AuthorityUtils.canCallWithDelay( + address(_manager), + address(this), + target, + selector + ); + if ((immediate || delay > 0) && !isAccessManagerIgnored(target, selector)) { + _setManagerData(plan, i, !immediate, 0); + // downcast is safe because both arguments are uint32 + neededDelay = uint32(Math.max(delay, neededDelay)); + } + } + + plan.delay = neededDelay; + + return proposalId; + } + + /** + * @dev Mechanism to queue a proposal, potentially scheduling some of its operations in the AccessManager. + * + * NOTE: The execution delay is chosen based on the delay information retrieved in {propose}. This value may be + * off if the delay was updated since proposal creation. In this case, the proposal needs to be recreated. + */ + function _queueOperations( + uint256 proposalId, + address[] memory targets, + uint256[] memory /* values */, + bytes[] memory calldatas, + bytes32 /* descriptionHash */ + ) internal virtual override returns (uint48) { + ExecutionPlan storage plan = _executionPlan[proposalId]; + uint48 etaSeconds = Time.timestamp() + plan.delay; + + for (uint256 i = 0; i < targets.length; ++i) { + (, bool withDelay, ) = _getManagerData(plan, i); + if (withDelay) { + (, uint32 nonce) = _manager.schedule(targets[i], calldatas[i], etaSeconds); + _setManagerData(plan, i, true, nonce); + } + } + + return etaSeconds; + } + + /** + * @dev Mechanism to execute a proposal, potentially going through {AccessManager-execute} for delayed operations. + */ + function _executeOperations( + uint256 proposalId, + address[] memory targets, + uint256[] memory values, + bytes[] memory calldatas, + bytes32 /* descriptionHash */ + ) internal virtual override { + uint48 etaSeconds = SafeCast.toUint48(proposalEta(proposalId)); + if (block.timestamp < etaSeconds) { + revert GovernorUnmetDelay(proposalId, etaSeconds); + } + + ExecutionPlan storage plan = _executionPlan[proposalId]; + + for (uint256 i = 0; i < targets.length; ++i) { + (bool controlled, bool withDelay, uint32 nonce) = _getManagerData(plan, i); + if (controlled) { + uint32 executedNonce = _manager.execute{value: values[i]}(targets[i], calldatas[i]); + if (withDelay && executedNonce != nonce) { + revert GovernorMismatchedNonce(proposalId, nonce, executedNonce); + } + } else { + (bool success, bytes memory returndata) = targets[i].call{value: values[i]}(calldatas[i]); + Address.verifyCallResult(success, returndata); + } + } + } + + /** + * @dev See {IGovernor-_cancel} + */ + function _cancel( + address[] memory targets, + uint256[] memory values, + bytes[] memory calldatas, + bytes32 descriptionHash + ) internal virtual override returns (uint256) { + uint256 proposalId = super._cancel(targets, values, calldatas, descriptionHash); + + uint48 etaSeconds = SafeCast.toUint48(proposalEta(proposalId)); + + ExecutionPlan storage plan = _executionPlan[proposalId]; + + // If the proposal has been scheduled it will have an ETA and we may have to externally cancel + if (etaSeconds != 0) { + for (uint256 i = 0; i < targets.length; ++i) { + (, bool withDelay, uint32 nonce) = _getManagerData(plan, i); + // Only attempt to cancel if the execution plan included a delay + if (withDelay) { + bytes32 operationId = _manager.hashOperation(address(this), targets[i], calldatas[i]); + // Check first if the current operation nonce is the one that we observed previously. It could + // already have been cancelled and rescheduled. We don't want to cancel unless it is exactly the + // instance that we previously scheduled. + if (nonce == _manager.getNonce(operationId)) { + // It is important that all calls have an opportunity to be cancelled. We chose to ignore + // potential failures of some of the cancel operations to give the other operations a chance to + // be properly cancelled. In particular cancel might fail if the operation was already cancelled + // by guardians previously. We don't match on the revert reason to avoid encoding assumptions + // about specific errors. + try _manager.cancel(address(this), targets[i], calldatas[i]) {} catch {} + } + } + } + } + + return proposalId; + } + + /** + * @dev Returns whether the operation at an index is delayed by the manager, and its scheduling nonce once queued. + */ + function _getManagerData( + ExecutionPlan storage plan, + uint256 index + ) private view returns (bool controlled, bool withDelay, uint32 nonce) { + (uint256 bucket, uint256 subindex) = _getManagerDataIndices(index); + uint32 value = plan.managerData[bucket][subindex]; + unchecked { + return (value > 0, value > 1, value > 1 ? value - 2 : 0); + } + } + + /** + * @dev Marks an operation at an index as permissioned by the manager, potentially delayed, and + * when delayed sets its scheduling nonce. + */ + function _setManagerData(ExecutionPlan storage plan, uint256 index, bool withDelay, uint32 nonce) private { + (uint256 bucket, uint256 subindex) = _getManagerDataIndices(index); + plan.managerData[bucket][subindex] = withDelay ? nonce + 2 : 1; + } + + /** + * @dev Returns bucket and subindex for reading manager data from the packed array mapping. + */ + function _getManagerDataIndices(uint256 index) private pure returns (uint256 bucket, uint256 subindex) { + bucket = index >> 3; // index / 8 + subindex = index & 7; // index % 8 + } +} diff --git a/CVLByExample/UniswapHooks/lib/uniswap-hooks/lib/v4-core/lib/openzeppelin-contracts/contracts/governance/extensions/GovernorTimelockCompound.sol b/CVLByExample/UniswapHooks/lib/uniswap-hooks/lib/v4-core/lib/openzeppelin-contracts/contracts/governance/extensions/GovernorTimelockCompound.sol new file mode 100644 index 00000000..117df01a --- /dev/null +++ b/CVLByExample/UniswapHooks/lib/uniswap-hooks/lib/v4-core/lib/openzeppelin-contracts/contracts/governance/extensions/GovernorTimelockCompound.sol @@ -0,0 +1,167 @@ +// SPDX-License-Identifier: MIT +// OpenZeppelin Contracts (last updated v5.0.0) (governance/extensions/GovernorTimelockCompound.sol) + +pragma solidity ^0.8.20; + +import {IGovernor, Governor} from "../Governor.sol"; +import {ICompoundTimelock} from "../../vendor/compound/ICompoundTimelock.sol"; +import {Address} from "../../utils/Address.sol"; +import {SafeCast} from "../../utils/math/SafeCast.sol"; + +/** + * @dev Extension of {Governor} that binds the execution process to a Compound Timelock. This adds a delay, enforced by + * the external timelock to all successful proposal (in addition to the voting duration). The {Governor} needs to be + * the admin of the timelock for any operation to be performed. A public, unrestricted, + * {GovernorTimelockCompound-__acceptAdmin} is available to accept ownership of the timelock. + * + * Using this model means the proposal will be operated by the {TimelockController} and not by the {Governor}. Thus, + * the assets and permissions must be attached to the {TimelockController}. Any asset sent to the {Governor} will be + * inaccessible. + */ +abstract contract GovernorTimelockCompound is Governor { + ICompoundTimelock private _timelock; + + /** + * @dev Emitted when the timelock controller used for proposal execution is modified. + */ + event TimelockChange(address oldTimelock, address newTimelock); + + /** + * @dev Set the timelock. + */ + constructor(ICompoundTimelock timelockAddress) { + _updateTimelock(timelockAddress); + } + + /** + * @dev Overridden version of the {Governor-state} function with added support for the `Expired` state. + */ + function state(uint256 proposalId) public view virtual override returns (ProposalState) { + ProposalState currentState = super.state(proposalId); + + return + (currentState == ProposalState.Queued && + block.timestamp >= proposalEta(proposalId) + _timelock.GRACE_PERIOD()) + ? ProposalState.Expired + : currentState; + } + + /** + * @dev Public accessor to check the address of the timelock + */ + function timelock() public view virtual returns (address) { + return address(_timelock); + } + + /** + * @dev See {IGovernor-proposalNeedsQueuing}. + */ + function proposalNeedsQueuing(uint256) public view virtual override returns (bool) { + return true; + } + + /** + * @dev Function to queue a proposal to the timelock. + */ + function _queueOperations( + uint256 proposalId, + address[] memory targets, + uint256[] memory values, + bytes[] memory calldatas, + bytes32 /*descriptionHash*/ + ) internal virtual override returns (uint48) { + uint48 etaSeconds = SafeCast.toUint48(block.timestamp + _timelock.delay()); + + for (uint256 i = 0; i < targets.length; ++i) { + if ( + _timelock.queuedTransactions(keccak256(abi.encode(targets[i], values[i], "", calldatas[i], etaSeconds))) + ) { + revert GovernorAlreadyQueuedProposal(proposalId); + } + _timelock.queueTransaction(targets[i], values[i], "", calldatas[i], etaSeconds); + } + + return etaSeconds; + } + + /** + * @dev Overridden version of the {Governor-_executeOperations} function that run the already queued proposal + * through the timelock. + */ + function _executeOperations( + uint256 proposalId, + address[] memory targets, + uint256[] memory values, + bytes[] memory calldatas, + bytes32 /*descriptionHash*/ + ) internal virtual override { + uint256 etaSeconds = proposalEta(proposalId); + if (etaSeconds == 0) { + revert GovernorNotQueuedProposal(proposalId); + } + Address.sendValue(payable(_timelock), msg.value); + for (uint256 i = 0; i < targets.length; ++i) { + _timelock.executeTransaction(targets[i], values[i], "", calldatas[i], etaSeconds); + } + } + + /** + * @dev Overridden version of the {Governor-_cancel} function to cancel the timelocked proposal if it has already + * been queued. + */ + function _cancel( + address[] memory targets, + uint256[] memory values, + bytes[] memory calldatas, + bytes32 descriptionHash + ) internal virtual override returns (uint256) { + uint256 proposalId = super._cancel(targets, values, calldatas, descriptionHash); + + uint256 etaSeconds = proposalEta(proposalId); + if (etaSeconds > 0) { + // do external call later + for (uint256 i = 0; i < targets.length; ++i) { + _timelock.cancelTransaction(targets[i], values[i], "", calldatas[i], etaSeconds); + } + } + + return proposalId; + } + + /** + * @dev Address through which the governor executes action. In this case, the timelock. + */ + function _executor() internal view virtual override returns (address) { + return address(_timelock); + } + + /** + * @dev Accept admin right over the timelock. + */ + // solhint-disable-next-line private-vars-leading-underscore + function __acceptAdmin() public { + _timelock.acceptAdmin(); + } + + /** + * @dev Public endpoint to update the underlying timelock instance. Restricted to the timelock itself, so updates + * must be proposed, scheduled, and executed through governance proposals. + * + * For security reasons, the timelock must be handed over to another admin before setting up a new one. The two + * operations (hand over the timelock) and do the update can be batched in a single proposal. + * + * Note that if the timelock admin has been handed over in a previous operation, we refuse updates made through the + * timelock if admin of the timelock has already been accepted and the operation is executed outside the scope of + * governance. + + * CAUTION: It is not recommended to change the timelock while there are other queued governance proposals. + */ + function updateTimelock(ICompoundTimelock newTimelock) external virtual onlyGovernance { + _updateTimelock(newTimelock); + } + + function _updateTimelock(ICompoundTimelock newTimelock) private { + emit TimelockChange(address(_timelock), address(newTimelock)); + _timelock = newTimelock; + } +} diff --git a/CVLByExample/UniswapHooks/lib/uniswap-hooks/lib/v4-core/lib/openzeppelin-contracts/contracts/governance/extensions/GovernorTimelockControl.sol b/CVLByExample/UniswapHooks/lib/uniswap-hooks/lib/v4-core/lib/openzeppelin-contracts/contracts/governance/extensions/GovernorTimelockControl.sol new file mode 100644 index 00000000..53503cc6 --- /dev/null +++ b/CVLByExample/UniswapHooks/lib/uniswap-hooks/lib/v4-core/lib/openzeppelin-contracts/contracts/governance/extensions/GovernorTimelockControl.sol @@ -0,0 +1,173 @@ +// SPDX-License-Identifier: MIT +// OpenZeppelin Contracts (last updated v5.0.0) (governance/extensions/GovernorTimelockControl.sol) + +pragma solidity ^0.8.20; + +import {IGovernor, Governor} from "../Governor.sol"; +import {TimelockController} from "../TimelockController.sol"; +import {IERC165} from "../../interfaces/IERC165.sol"; +import {SafeCast} from "../../utils/math/SafeCast.sol"; + +/** + * @dev Extension of {Governor} that binds the execution process to an instance of {TimelockController}. This adds a + * delay, enforced by the {TimelockController} to all successful proposal (in addition to the voting duration). The + * {Governor} needs the proposer (and ideally the executor) roles for the {Governor} to work properly. + * + * Using this model means the proposal will be operated by the {TimelockController} and not by the {Governor}. Thus, + * the assets and permissions must be attached to the {TimelockController}. Any asset sent to the {Governor} will be + * inaccessible from a proposal, unless executed via {Governor-relay}. + * + * WARNING: Setting up the TimelockController to have additional proposers or cancellers besides the governor is very + * risky, as it grants them the ability to: 1) execute operations as the timelock, and thus possibly performing + * operations or accessing funds that are expected to only be accessible through a vote, and 2) block governance + * proposals that have been approved by the voters, effectively executing a Denial of Service attack. + * + * NOTE: `AccessManager` does not support scheduling more than one operation with the same target and calldata at + * the same time. See {AccessManager-schedule} for a workaround. + */ +abstract contract GovernorTimelockControl is Governor { + TimelockController private _timelock; + mapping(uint256 proposalId => bytes32) private _timelockIds; + + /** + * @dev Emitted when the timelock controller used for proposal execution is modified. + */ + event TimelockChange(address oldTimelock, address newTimelock); + + /** + * @dev Set the timelock. + */ + constructor(TimelockController timelockAddress) { + _updateTimelock(timelockAddress); + } + + /** + * @dev Overridden version of the {Governor-state} function that considers the status reported by the timelock. + */ + function state(uint256 proposalId) public view virtual override returns (ProposalState) { + ProposalState currentState = super.state(proposalId); + + if (currentState != ProposalState.Queued) { + return currentState; + } + + bytes32 queueid = _timelockIds[proposalId]; + if (_timelock.isOperationPending(queueid)) { + return ProposalState.Queued; + } else if (_timelock.isOperationDone(queueid)) { + // This can happen if the proposal is executed directly on the timelock. + return ProposalState.Executed; + } else { + // This can happen if the proposal is canceled directly on the timelock. + return ProposalState.Canceled; + } + } + + /** + * @dev Public accessor to check the address of the timelock + */ + function timelock() public view virtual returns (address) { + return address(_timelock); + } + + /** + * @dev See {IGovernor-proposalNeedsQueuing}. + */ + function proposalNeedsQueuing(uint256) public view virtual override returns (bool) { + return true; + } + + /** + * @dev Function to queue a proposal to the timelock. + */ + function _queueOperations( + uint256 proposalId, + address[] memory targets, + uint256[] memory values, + bytes[] memory calldatas, + bytes32 descriptionHash + ) internal virtual override returns (uint48) { + uint256 delay = _timelock.getMinDelay(); + + bytes32 salt = _timelockSalt(descriptionHash); + _timelockIds[proposalId] = _timelock.hashOperationBatch(targets, values, calldatas, 0, salt); + _timelock.scheduleBatch(targets, values, calldatas, 0, salt, delay); + + return SafeCast.toUint48(block.timestamp + delay); + } + + /** + * @dev Overridden version of the {Governor-_executeOperations} function that runs the already queued proposal + * through the timelock. + */ + function _executeOperations( + uint256 proposalId, + address[] memory targets, + uint256[] memory values, + bytes[] memory calldatas, + bytes32 descriptionHash + ) internal virtual override { + // execute + _timelock.executeBatch{value: msg.value}(targets, values, calldatas, 0, _timelockSalt(descriptionHash)); + // cleanup for refund + delete _timelockIds[proposalId]; + } + + /** + * @dev Overridden version of the {Governor-_cancel} function to cancel the timelocked proposal if it has already + * been queued. + */ + // This function can reenter through the external call to the timelock, but we assume the timelock is trusted and + // well behaved (according to TimelockController) and this will not happen. + // slither-disable-next-line reentrancy-no-eth + function _cancel( + address[] memory targets, + uint256[] memory values, + bytes[] memory calldatas, + bytes32 descriptionHash + ) internal virtual override returns (uint256) { + uint256 proposalId = super._cancel(targets, values, calldatas, descriptionHash); + + bytes32 timelockId = _timelockIds[proposalId]; + if (timelockId != 0) { + // cancel + _timelock.cancel(timelockId); + // cleanup + delete _timelockIds[proposalId]; + } + + return proposalId; + } + + /** + * @dev Address through which the governor executes action. In this case, the timelock. + */ + function _executor() internal view virtual override returns (address) { + return address(_timelock); + } + + /** + * @dev Public endpoint to update the underlying timelock instance. Restricted to the timelock itself, so updates + * must be proposed, scheduled, and executed through governance proposals. + * + * CAUTION: It is not recommended to change the timelock while there are other queued governance proposals. + */ + function updateTimelock(TimelockController newTimelock) external virtual onlyGovernance { + _updateTimelock(newTimelock); + } + + function _updateTimelock(TimelockController newTimelock) private { + emit TimelockChange(address(_timelock), address(newTimelock)); + _timelock = newTimelock; + } + + /** + * @dev Computes the {TimelockController} operation salt. + * + * It is computed with the governor address itself to avoid collisions across governor instances using the + * same timelock. + */ + function _timelockSalt(bytes32 descriptionHash) private view returns (bytes32) { + return bytes20(address(this)) ^ descriptionHash; + } +} diff --git a/CVLByExample/UniswapHooks/lib/uniswap-hooks/lib/v4-core/lib/openzeppelin-contracts/contracts/governance/extensions/GovernorVotes.sol b/CVLByExample/UniswapHooks/lib/uniswap-hooks/lib/v4-core/lib/openzeppelin-contracts/contracts/governance/extensions/GovernorVotes.sol new file mode 100644 index 00000000..ec32ba47 --- /dev/null +++ b/CVLByExample/UniswapHooks/lib/uniswap-hooks/lib/v4-core/lib/openzeppelin-contracts/contracts/governance/extensions/GovernorVotes.sol @@ -0,0 +1,64 @@ +// SPDX-License-Identifier: MIT +// OpenZeppelin Contracts (last updated v5.0.0) (governance/extensions/GovernorVotes.sol) + +pragma solidity ^0.8.20; + +import {Governor} from "../Governor.sol"; +import {IVotes} from "../utils/IVotes.sol"; +import {IERC5805} from "../../interfaces/IERC5805.sol"; +import {SafeCast} from "../../utils/math/SafeCast.sol"; +import {Time} from "../../utils/types/Time.sol"; + +/** + * @dev Extension of {Governor} for voting weight extraction from an {ERC20Votes} token, or since v4.5 an {ERC721Votes} + * token. + */ +abstract contract GovernorVotes is Governor { + IERC5805 private immutable _token; + + constructor(IVotes tokenAddress) { + _token = IERC5805(address(tokenAddress)); + } + + /** + * @dev The token that voting power is sourced from. + */ + function token() public view virtual returns (IERC5805) { + return _token; + } + + /** + * @dev Clock (as specified in EIP-6372) is set to match the token's clock. Fallback to block numbers if the token + * does not implement EIP-6372. + */ + function clock() public view virtual override returns (uint48) { + try token().clock() returns (uint48 timepoint) { + return timepoint; + } catch { + return Time.blockNumber(); + } + } + + /** + * @dev Machine-readable description of the clock as specified in EIP-6372. + */ + // solhint-disable-next-line func-name-mixedcase + function CLOCK_MODE() public view virtual override returns (string memory) { + try token().CLOCK_MODE() returns (string memory clockmode) { + return clockmode; + } catch { + return "mode=blocknumber&from=default"; + } + } + + /** + * Read the voting weight from the token's built in snapshot mechanism (see {Governor-_getVotes}). + */ + function _getVotes( + address account, + uint256 timepoint, + bytes memory /*params*/ + ) internal view virtual override returns (uint256) { + return token().getPastVotes(account, timepoint); + } +} diff --git a/CVLByExample/UniswapHooks/lib/uniswap-hooks/lib/v4-core/lib/openzeppelin-contracts/contracts/governance/extensions/GovernorVotesQuorumFraction.sol b/CVLByExample/UniswapHooks/lib/uniswap-hooks/lib/v4-core/lib/openzeppelin-contracts/contracts/governance/extensions/GovernorVotesQuorumFraction.sol new file mode 100644 index 00000000..85a1f982 --- /dev/null +++ b/CVLByExample/UniswapHooks/lib/uniswap-hooks/lib/v4-core/lib/openzeppelin-contracts/contracts/governance/extensions/GovernorVotesQuorumFraction.sol @@ -0,0 +1,110 @@ +// SPDX-License-Identifier: MIT +// OpenZeppelin Contracts (last updated v5.0.0) (governance/extensions/GovernorVotesQuorumFraction.sol) + +pragma solidity ^0.8.20; + +import {GovernorVotes} from "./GovernorVotes.sol"; +import {SafeCast} from "../../utils/math/SafeCast.sol"; +import {Checkpoints} from "../../utils/structs/Checkpoints.sol"; + +/** + * @dev Extension of {Governor} for voting weight extraction from an {ERC20Votes} token and a quorum expressed as a + * fraction of the total supply. + */ +abstract contract GovernorVotesQuorumFraction is GovernorVotes { + using Checkpoints for Checkpoints.Trace208; + + Checkpoints.Trace208 private _quorumNumeratorHistory; + + event QuorumNumeratorUpdated(uint256 oldQuorumNumerator, uint256 newQuorumNumerator); + + /** + * @dev The quorum set is not a valid fraction. + */ + error GovernorInvalidQuorumFraction(uint256 quorumNumerator, uint256 quorumDenominator); + + /** + * @dev Initialize quorum as a fraction of the token's total supply. + * + * The fraction is specified as `numerator / denominator`. By default the denominator is 100, so quorum is + * specified as a percent: a numerator of 10 corresponds to quorum being 10% of total supply. The denominator can be + * customized by overriding {quorumDenominator}. + */ + constructor(uint256 quorumNumeratorValue) { + _updateQuorumNumerator(quorumNumeratorValue); + } + + /** + * @dev Returns the current quorum numerator. See {quorumDenominator}. + */ + function quorumNumerator() public view virtual returns (uint256) { + return _quorumNumeratorHistory.latest(); + } + + /** + * @dev Returns the quorum numerator at a specific timepoint. See {quorumDenominator}. + */ + function quorumNumerator(uint256 timepoint) public view virtual returns (uint256) { + uint256 length = _quorumNumeratorHistory._checkpoints.length; + + // Optimistic search, check the latest checkpoint + Checkpoints.Checkpoint208 storage latest = _quorumNumeratorHistory._checkpoints[length - 1]; + uint48 latestKey = latest._key; + uint208 latestValue = latest._value; + if (latestKey <= timepoint) { + return latestValue; + } + + // Otherwise, do the binary search + return _quorumNumeratorHistory.upperLookupRecent(SafeCast.toUint48(timepoint)); + } + + /** + * @dev Returns the quorum denominator. Defaults to 100, but may be overridden. + */ + function quorumDenominator() public view virtual returns (uint256) { + return 100; + } + + /** + * @dev Returns the quorum for a timepoint, in terms of number of votes: `supply * numerator / denominator`. + */ + function quorum(uint256 timepoint) public view virtual override returns (uint256) { + return (token().getPastTotalSupply(timepoint) * quorumNumerator(timepoint)) / quorumDenominator(); + } + + /** + * @dev Changes the quorum numerator. + * + * Emits a {QuorumNumeratorUpdated} event. + * + * Requirements: + * + * - Must be called through a governance proposal. + * - New numerator must be smaller or equal to the denominator. + */ + function updateQuorumNumerator(uint256 newQuorumNumerator) external virtual onlyGovernance { + _updateQuorumNumerator(newQuorumNumerator); + } + + /** + * @dev Changes the quorum numerator. + * + * Emits a {QuorumNumeratorUpdated} event. + * + * Requirements: + * + * - New numerator must be smaller or equal to the denominator. + */ + function _updateQuorumNumerator(uint256 newQuorumNumerator) internal virtual { + uint256 denominator = quorumDenominator(); + if (newQuorumNumerator > denominator) { + revert GovernorInvalidQuorumFraction(newQuorumNumerator, denominator); + } + + uint256 oldQuorumNumerator = quorumNumerator(); + _quorumNumeratorHistory.push(clock(), SafeCast.toUint208(newQuorumNumerator)); + + emit QuorumNumeratorUpdated(oldQuorumNumerator, newQuorumNumerator); + } +} diff --git a/CVLByExample/UniswapHooks/lib/uniswap-hooks/lib/v4-core/lib/openzeppelin-contracts/contracts/governance/utils/IVotes.sol b/CVLByExample/UniswapHooks/lib/uniswap-hooks/lib/v4-core/lib/openzeppelin-contracts/contracts/governance/utils/IVotes.sol new file mode 100644 index 00000000..7ba012e6 --- /dev/null +++ b/CVLByExample/UniswapHooks/lib/uniswap-hooks/lib/v4-core/lib/openzeppelin-contracts/contracts/governance/utils/IVotes.sol @@ -0,0 +1,59 @@ +// SPDX-License-Identifier: MIT +// OpenZeppelin Contracts (last updated v5.0.0) (governance/utils/IVotes.sol) +pragma solidity ^0.8.20; + +/** + * @dev Common interface for {ERC20Votes}, {ERC721Votes}, and other {Votes}-enabled contracts. + */ +interface IVotes { + /** + * @dev The signature used has expired. + */ + error VotesExpiredSignature(uint256 expiry); + + /** + * @dev Emitted when an account changes their delegate. + */ + event DelegateChanged(address indexed delegator, address indexed fromDelegate, address indexed toDelegate); + + /** + * @dev Emitted when a token transfer or delegate change results in changes to a delegate's number of voting units. + */ + event DelegateVotesChanged(address indexed delegate, uint256 previousVotes, uint256 newVotes); + + /** + * @dev Returns the current amount of votes that `account` has. + */ + function getVotes(address account) external view returns (uint256); + + /** + * @dev Returns the amount of votes that `account` had at a specific moment in the past. If the `clock()` is + * configured to use block numbers, this will return the value at the end of the corresponding block. + */ + function getPastVotes(address account, uint256 timepoint) external view returns (uint256); + + /** + * @dev Returns the total supply of votes available at a specific moment in the past. If the `clock()` is + * configured to use block numbers, this will return the value at the end of the corresponding block. + * + * NOTE: This value is the sum of all available votes, which is not necessarily the sum of all delegated votes. + * Votes that have not been delegated are still part of total supply, even though they would not participate in a + * vote. + */ + function getPastTotalSupply(uint256 timepoint) external view returns (uint256); + + /** + * @dev Returns the delegate that `account` has chosen. + */ + function delegates(address account) external view returns (address); + + /** + * @dev Delegates votes from the sender to `delegatee`. + */ + function delegate(address delegatee) external; + + /** + * @dev Delegates votes from signer to `delegatee`. + */ + function delegateBySig(address delegatee, uint256 nonce, uint256 expiry, uint8 v, bytes32 r, bytes32 s) external; +} diff --git a/CVLByExample/UniswapHooks/lib/uniswap-hooks/lib/v4-core/lib/openzeppelin-contracts/contracts/governance/utils/Votes.sol b/CVLByExample/UniswapHooks/lib/uniswap-hooks/lib/v4-core/lib/openzeppelin-contracts/contracts/governance/utils/Votes.sol new file mode 100644 index 00000000..9f966765 --- /dev/null +++ b/CVLByExample/UniswapHooks/lib/uniswap-hooks/lib/v4-core/lib/openzeppelin-contracts/contracts/governance/utils/Votes.sol @@ -0,0 +1,251 @@ +// SPDX-License-Identifier: MIT +// OpenZeppelin Contracts (last updated v5.0.0) (governance/utils/Votes.sol) +pragma solidity ^0.8.20; + +import {IERC5805} from "../../interfaces/IERC5805.sol"; +import {Context} from "../../utils/Context.sol"; +import {Nonces} from "../../utils/Nonces.sol"; +import {EIP712} from "../../utils/cryptography/EIP712.sol"; +import {Checkpoints} from "../../utils/structs/Checkpoints.sol"; +import {SafeCast} from "../../utils/math/SafeCast.sol"; +import {ECDSA} from "../../utils/cryptography/ECDSA.sol"; +import {Time} from "../../utils/types/Time.sol"; + +/** + * @dev This is a base abstract contract that tracks voting units, which are a measure of voting power that can be + * transferred, and provides a system of vote delegation, where an account can delegate its voting units to a sort of + * "representative" that will pool delegated voting units from different accounts and can then use it to vote in + * decisions. In fact, voting units _must_ be delegated in order to count as actual votes, and an account has to + * delegate those votes to itself if it wishes to participate in decisions and does not have a trusted representative. + * + * This contract is often combined with a token contract such that voting units correspond to token units. For an + * example, see {ERC721Votes}. + * + * The full history of delegate votes is tracked on-chain so that governance protocols can consider votes as distributed + * at a particular block number to protect against flash loans and double voting. The opt-in delegate system makes the + * cost of this history tracking optional. + * + * When using this module the derived contract must implement {_getVotingUnits} (for example, make it return + * {ERC721-balanceOf}), and can use {_transferVotingUnits} to track a change in the distribution of those units (in the + * previous example, it would be included in {ERC721-_update}). + */ +abstract contract Votes is Context, EIP712, Nonces, IERC5805 { + using Checkpoints for Checkpoints.Trace208; + + bytes32 private constant DELEGATION_TYPEHASH = + keccak256("Delegation(address delegatee,uint256 nonce,uint256 expiry)"); + + mapping(address account => address) private _delegatee; + + mapping(address delegatee => Checkpoints.Trace208) private _delegateCheckpoints; + + Checkpoints.Trace208 private _totalCheckpoints; + + /** + * @dev The clock was incorrectly modified. + */ + error ERC6372InconsistentClock(); + + /** + * @dev Lookup to future votes is not available. + */ + error ERC5805FutureLookup(uint256 timepoint, uint48 clock); + + /** + * @dev Clock used for flagging checkpoints. Can be overridden to implement timestamp based + * checkpoints (and voting), in which case {CLOCK_MODE} should be overridden as well to match. + */ + function clock() public view virtual returns (uint48) { + return Time.blockNumber(); + } + + /** + * @dev Machine-readable description of the clock as specified in EIP-6372. + */ + // solhint-disable-next-line func-name-mixedcase + function CLOCK_MODE() public view virtual returns (string memory) { + // Check that the clock was not modified + if (clock() != Time.blockNumber()) { + revert ERC6372InconsistentClock(); + } + return "mode=blocknumber&from=default"; + } + + /** + * @dev Returns the current amount of votes that `account` has. + */ + function getVotes(address account) public view virtual returns (uint256) { + return _delegateCheckpoints[account].latest(); + } + + /** + * @dev Returns the amount of votes that `account` had at a specific moment in the past. If the `clock()` is + * configured to use block numbers, this will return the value at the end of the corresponding block. + * + * Requirements: + * + * - `timepoint` must be in the past. If operating using block numbers, the block must be already mined. + */ + function getPastVotes(address account, uint256 timepoint) public view virtual returns (uint256) { + uint48 currentTimepoint = clock(); + if (timepoint >= currentTimepoint) { + revert ERC5805FutureLookup(timepoint, currentTimepoint); + } + return _delegateCheckpoints[account].upperLookupRecent(SafeCast.toUint48(timepoint)); + } + + /** + * @dev Returns the total supply of votes available at a specific moment in the past. If the `clock()` is + * configured to use block numbers, this will return the value at the end of the corresponding block. + * + * NOTE: This value is the sum of all available votes, which is not necessarily the sum of all delegated votes. + * Votes that have not been delegated are still part of total supply, even though they would not participate in a + * vote. + * + * Requirements: + * + * - `timepoint` must be in the past. If operating using block numbers, the block must be already mined. + */ + function getPastTotalSupply(uint256 timepoint) public view virtual returns (uint256) { + uint48 currentTimepoint = clock(); + if (timepoint >= currentTimepoint) { + revert ERC5805FutureLookup(timepoint, currentTimepoint); + } + return _totalCheckpoints.upperLookupRecent(SafeCast.toUint48(timepoint)); + } + + /** + * @dev Returns the current total supply of votes. + */ + function _getTotalSupply() internal view virtual returns (uint256) { + return _totalCheckpoints.latest(); + } + + /** + * @dev Returns the delegate that `account` has chosen. + */ + function delegates(address account) public view virtual returns (address) { + return _delegatee[account]; + } + + /** + * @dev Delegates votes from the sender to `delegatee`. + */ + function delegate(address delegatee) public virtual { + address account = _msgSender(); + _delegate(account, delegatee); + } + + /** + * @dev Delegates votes from signer to `delegatee`. + */ + function delegateBySig( + address delegatee, + uint256 nonce, + uint256 expiry, + uint8 v, + bytes32 r, + bytes32 s + ) public virtual { + if (block.timestamp > expiry) { + revert VotesExpiredSignature(expiry); + } + address signer = ECDSA.recover( + _hashTypedDataV4(keccak256(abi.encode(DELEGATION_TYPEHASH, delegatee, nonce, expiry))), + v, + r, + s + ); + _useCheckedNonce(signer, nonce); + _delegate(signer, delegatee); + } + + /** + * @dev Delegate all of `account`'s voting units to `delegatee`. + * + * Emits events {IVotes-DelegateChanged} and {IVotes-DelegateVotesChanged}. + */ + function _delegate(address account, address delegatee) internal virtual { + address oldDelegate = delegates(account); + _delegatee[account] = delegatee; + + emit DelegateChanged(account, oldDelegate, delegatee); + _moveDelegateVotes(oldDelegate, delegatee, _getVotingUnits(account)); + } + + /** + * @dev Transfers, mints, or burns voting units. To register a mint, `from` should be zero. To register a burn, `to` + * should be zero. Total supply of voting units will be adjusted with mints and burns. + */ + function _transferVotingUnits(address from, address to, uint256 amount) internal virtual { + if (from == address(0)) { + _push(_totalCheckpoints, _add, SafeCast.toUint208(amount)); + } + if (to == address(0)) { + _push(_totalCheckpoints, _subtract, SafeCast.toUint208(amount)); + } + _moveDelegateVotes(delegates(from), delegates(to), amount); + } + + /** + * @dev Moves delegated votes from one delegate to another. + */ + function _moveDelegateVotes(address from, address to, uint256 amount) private { + if (from != to && amount > 0) { + if (from != address(0)) { + (uint256 oldValue, uint256 newValue) = _push( + _delegateCheckpoints[from], + _subtract, + SafeCast.toUint208(amount) + ); + emit DelegateVotesChanged(from, oldValue, newValue); + } + if (to != address(0)) { + (uint256 oldValue, uint256 newValue) = _push( + _delegateCheckpoints[to], + _add, + SafeCast.toUint208(amount) + ); + emit DelegateVotesChanged(to, oldValue, newValue); + } + } + } + + /** + * @dev Get number of checkpoints for `account`. + */ + function _numCheckpoints(address account) internal view virtual returns (uint32) { + return SafeCast.toUint32(_delegateCheckpoints[account].length()); + } + + /** + * @dev Get the `pos`-th checkpoint for `account`. + */ + function _checkpoints( + address account, + uint32 pos + ) internal view virtual returns (Checkpoints.Checkpoint208 memory) { + return _delegateCheckpoints[account].at(pos); + } + + function _push( + Checkpoints.Trace208 storage store, + function(uint208, uint208) view returns (uint208) op, + uint208 delta + ) private returns (uint208, uint208) { + return store.push(clock(), op(store.latest(), delta)); + } + + function _add(uint208 a, uint208 b) private pure returns (uint208) { + return a + b; + } + + function _subtract(uint208 a, uint208 b) private pure returns (uint208) { + return a - b; + } + + /** + * @dev Must return the voting units held by an account. + */ + function _getVotingUnits(address) internal view virtual returns (uint256); +} diff --git a/CVLByExample/UniswapHooks/lib/uniswap-hooks/lib/v4-core/lib/openzeppelin-contracts/contracts/interfaces/IERC1155.sol b/CVLByExample/UniswapHooks/lib/uniswap-hooks/lib/v4-core/lib/openzeppelin-contracts/contracts/interfaces/IERC1155.sol new file mode 100644 index 00000000..bb502b1d --- /dev/null +++ b/CVLByExample/UniswapHooks/lib/uniswap-hooks/lib/v4-core/lib/openzeppelin-contracts/contracts/interfaces/IERC1155.sol @@ -0,0 +1,6 @@ +// SPDX-License-Identifier: MIT +// OpenZeppelin Contracts (last updated v5.0.0) (interfaces/IERC1155.sol) + +pragma solidity ^0.8.20; + +import {IERC1155} from "../token/ERC1155/IERC1155.sol"; diff --git a/CVLByExample/UniswapHooks/lib/uniswap-hooks/lib/v4-core/lib/openzeppelin-contracts/contracts/interfaces/IERC1155MetadataURI.sol b/CVLByExample/UniswapHooks/lib/uniswap-hooks/lib/v4-core/lib/openzeppelin-contracts/contracts/interfaces/IERC1155MetadataURI.sol new file mode 100644 index 00000000..dac0bab5 --- /dev/null +++ b/CVLByExample/UniswapHooks/lib/uniswap-hooks/lib/v4-core/lib/openzeppelin-contracts/contracts/interfaces/IERC1155MetadataURI.sol @@ -0,0 +1,6 @@ +// SPDX-License-Identifier: MIT +// OpenZeppelin Contracts (last updated v5.0.0) (interfaces/IERC1155MetadataURI.sol) + +pragma solidity ^0.8.20; + +import {IERC1155MetadataURI} from "../token/ERC1155/extensions/IERC1155MetadataURI.sol"; diff --git a/CVLByExample/UniswapHooks/lib/uniswap-hooks/lib/v4-core/lib/openzeppelin-contracts/contracts/interfaces/IERC1155Receiver.sol b/CVLByExample/UniswapHooks/lib/uniswap-hooks/lib/v4-core/lib/openzeppelin-contracts/contracts/interfaces/IERC1155Receiver.sol new file mode 100644 index 00000000..6bb7c968 --- /dev/null +++ b/CVLByExample/UniswapHooks/lib/uniswap-hooks/lib/v4-core/lib/openzeppelin-contracts/contracts/interfaces/IERC1155Receiver.sol @@ -0,0 +1,6 @@ +// SPDX-License-Identifier: MIT +// OpenZeppelin Contracts (last updated v5.0.0) (interfaces/IERC1155Receiver.sol) + +pragma solidity ^0.8.20; + +import {IERC1155Receiver} from "../token/ERC1155/IERC1155Receiver.sol"; diff --git a/CVLByExample/UniswapHooks/lib/uniswap-hooks/lib/v4-core/lib/openzeppelin-contracts/contracts/interfaces/IERC1271.sol b/CVLByExample/UniswapHooks/lib/uniswap-hooks/lib/v4-core/lib/openzeppelin-contracts/contracts/interfaces/IERC1271.sol new file mode 100644 index 00000000..a56057ba --- /dev/null +++ b/CVLByExample/UniswapHooks/lib/uniswap-hooks/lib/v4-core/lib/openzeppelin-contracts/contracts/interfaces/IERC1271.sol @@ -0,0 +1,17 @@ +// SPDX-License-Identifier: MIT +// OpenZeppelin Contracts (last updated v5.0.0) (interfaces/IERC1271.sol) + +pragma solidity ^0.8.20; + +/** + * @dev Interface of the ERC1271 standard signature validation method for + * contracts as defined in https://eips.ethereum.org/EIPS/eip-1271[ERC-1271]. + */ +interface IERC1271 { + /** + * @dev Should return whether the signature provided is valid for the provided data + * @param hash Hash of the data to be signed + * @param signature Signature byte array associated with _data + */ + function isValidSignature(bytes32 hash, bytes memory signature) external view returns (bytes4 magicValue); +} diff --git a/CVLByExample/UniswapHooks/lib/uniswap-hooks/lib/v4-core/lib/openzeppelin-contracts/contracts/interfaces/IERC1363.sol b/CVLByExample/UniswapHooks/lib/uniswap-hooks/lib/v4-core/lib/openzeppelin-contracts/contracts/interfaces/IERC1363.sol new file mode 100644 index 00000000..8b02aba5 --- /dev/null +++ b/CVLByExample/UniswapHooks/lib/uniswap-hooks/lib/v4-core/lib/openzeppelin-contracts/contracts/interfaces/IERC1363.sol @@ -0,0 +1,80 @@ +// SPDX-License-Identifier: MIT +// OpenZeppelin Contracts (last updated v5.0.0) (interfaces/IERC1363.sol) + +pragma solidity ^0.8.20; + +import {IERC20} from "./IERC20.sol"; +import {IERC165} from "./IERC165.sol"; + +/** + * @dev Interface of an ERC1363 compliant contract, as defined in the + * https://eips.ethereum.org/EIPS/eip-1363[EIP]. + * + * Defines a interface for ERC20 tokens that supports executing recipient + * code after `transfer` or `transferFrom`, or spender code after `approve`. + */ +interface IERC1363 is IERC165, IERC20 { + /* + * Note: the ERC-165 identifier for this interface is 0xb0202a11. + * 0xb0202a11 === + * bytes4(keccak256('transferAndCall(address,uint256)')) ^ + * bytes4(keccak256('transferAndCall(address,uint256,bytes)')) ^ + * bytes4(keccak256('transferFromAndCall(address,address,uint256)')) ^ + * bytes4(keccak256('transferFromAndCall(address,address,uint256,bytes)')) ^ + * bytes4(keccak256('approveAndCall(address,uint256)')) ^ + * bytes4(keccak256('approveAndCall(address,uint256,bytes)')) + */ + + /** + * @dev Transfer tokens from `msg.sender` to another address and then call `onTransferReceived` on receiver + * @param to address The address which you want to transfer to + * @param amount uint256 The amount of tokens to be transferred + * @return true unless throwing + */ + function transferAndCall(address to, uint256 amount) external returns (bool); + + /** + * @dev Transfer tokens from `msg.sender` to another address and then call `onTransferReceived` on receiver + * @param to address The address which you want to transfer to + * @param amount uint256 The amount of tokens to be transferred + * @param data bytes Additional data with no specified format, sent in call to `to` + * @return true unless throwing + */ + function transferAndCall(address to, uint256 amount, bytes memory data) external returns (bool); + + /** + * @dev Transfer tokens from one address to another and then call `onTransferReceived` on receiver + * @param from address The address which you want to send tokens from + * @param to address The address which you want to transfer to + * @param amount uint256 The amount of tokens to be transferred + * @return true unless throwing + */ + function transferFromAndCall(address from, address to, uint256 amount) external returns (bool); + + /** + * @dev Transfer tokens from one address to another and then call `onTransferReceived` on receiver + * @param from address The address which you want to send tokens from + * @param to address The address which you want to transfer to + * @param amount uint256 The amount of tokens to be transferred + * @param data bytes Additional data with no specified format, sent in call to `to` + * @return true unless throwing + */ + function transferFromAndCall(address from, address to, uint256 amount, bytes memory data) external returns (bool); + + /** + * @dev Approve the passed address to spend the specified amount of tokens on behalf of msg.sender + * and then call `onApprovalReceived` on spender. + * @param spender address The address which will spend the funds + * @param amount uint256 The amount of tokens to be spent + */ + function approveAndCall(address spender, uint256 amount) external returns (bool); + + /** + * @dev Approve the passed address to spend the specified amount of tokens on behalf of msg.sender + * and then call `onApprovalReceived` on spender. + * @param spender address The address which will spend the funds + * @param amount uint256 The amount of tokens to be spent + * @param data bytes Additional data with no specified format, sent in call to `spender` + */ + function approveAndCall(address spender, uint256 amount, bytes memory data) external returns (bool); +} diff --git a/CVLByExample/UniswapHooks/lib/uniswap-hooks/lib/v4-core/lib/openzeppelin-contracts/contracts/interfaces/IERC1363Receiver.sol b/CVLByExample/UniswapHooks/lib/uniswap-hooks/lib/v4-core/lib/openzeppelin-contracts/contracts/interfaces/IERC1363Receiver.sol new file mode 100644 index 00000000..64d669d4 --- /dev/null +++ b/CVLByExample/UniswapHooks/lib/uniswap-hooks/lib/v4-core/lib/openzeppelin-contracts/contracts/interfaces/IERC1363Receiver.sol @@ -0,0 +1,35 @@ +// SPDX-License-Identifier: MIT +// OpenZeppelin Contracts (last updated v5.0.0) (interfaces/IERC1363Receiver.sol) + +pragma solidity ^0.8.20; + +/** + * @dev Interface for any contract that wants to support {IERC1363-transferAndCall} + * or {IERC1363-transferFromAndCall} from {ERC1363} token contracts. + */ +interface IERC1363Receiver { + /* + * Note: the ERC-165 identifier for this interface is 0x88a7ca5c. + * 0x88a7ca5c === bytes4(keccak256("onTransferReceived(address,address,uint256,bytes)")) + */ + + /** + * @notice Handle the receipt of ERC1363 tokens + * @dev Any ERC1363 smart contract calls this function on the recipient + * after a `transfer` or a `transferFrom`. This function MAY throw to revert and reject the + * transfer. Return of other than the magic value MUST result in the + * transaction being reverted. + * Note: the token contract address is always the message sender. + * @param operator address The address which called `transferAndCall` or `transferFromAndCall` function + * @param from address The address which are token transferred from + * @param amount uint256 The amount of tokens transferred + * @param data bytes Additional data with no specified format + * @return `bytes4(keccak256("onTransferReceived(address,address,uint256,bytes)"))` unless throwing + */ + function onTransferReceived( + address operator, + address from, + uint256 amount, + bytes memory data + ) external returns (bytes4); +} diff --git a/CVLByExample/UniswapHooks/lib/uniswap-hooks/lib/v4-core/lib/openzeppelin-contracts/contracts/interfaces/IERC1363Spender.sol b/CVLByExample/UniswapHooks/lib/uniswap-hooks/lib/v4-core/lib/openzeppelin-contracts/contracts/interfaces/IERC1363Spender.sol new file mode 100644 index 00000000..f2215418 --- /dev/null +++ b/CVLByExample/UniswapHooks/lib/uniswap-hooks/lib/v4-core/lib/openzeppelin-contracts/contracts/interfaces/IERC1363Spender.sol @@ -0,0 +1,29 @@ +// SPDX-License-Identifier: MIT +// OpenZeppelin Contracts (last updated v5.0.0) (interfaces/IERC1363Spender.sol) + +pragma solidity ^0.8.20; + +/** + * @dev Interface for any contract that wants to support {IERC1363-approveAndCall} + * from {ERC1363} token contracts. + */ +interface IERC1363Spender { + /* + * Note: the ERC-165 identifier for this interface is 0x7b04a2d0. + * 0x7b04a2d0 === bytes4(keccak256("onApprovalReceived(address,uint256,bytes)")) + */ + + /** + * @notice Handle the approval of ERC1363 tokens + * @dev Any ERC1363 smart contract calls this function on the recipient + * after an `approve`. This function MAY throw to revert and reject the + * approval. Return of other than the magic value MUST result in the + * transaction being reverted. + * Note: the token contract address is always the message sender. + * @param owner address The address which called `approveAndCall` function + * @param amount uint256 The amount of tokens to be spent + * @param data bytes Additional data with no specified format + * @return `bytes4(keccak256("onApprovalReceived(address,uint256,bytes)"))`unless throwing + */ + function onApprovalReceived(address owner, uint256 amount, bytes memory data) external returns (bytes4); +} diff --git a/CVLByExample/UniswapHooks/lib/uniswap-hooks/lib/v4-core/lib/openzeppelin-contracts/contracts/interfaces/IERC165.sol b/CVLByExample/UniswapHooks/lib/uniswap-hooks/lib/v4-core/lib/openzeppelin-contracts/contracts/interfaces/IERC165.sol new file mode 100644 index 00000000..944dd0d5 --- /dev/null +++ b/CVLByExample/UniswapHooks/lib/uniswap-hooks/lib/v4-core/lib/openzeppelin-contracts/contracts/interfaces/IERC165.sol @@ -0,0 +1,6 @@ +// SPDX-License-Identifier: MIT +// OpenZeppelin Contracts (last updated v5.0.0) (interfaces/IERC165.sol) + +pragma solidity ^0.8.20; + +import {IERC165} from "../utils/introspection/IERC165.sol"; diff --git a/CVLByExample/UniswapHooks/lib/uniswap-hooks/lib/v4-core/lib/openzeppelin-contracts/contracts/interfaces/IERC1820Implementer.sol b/CVLByExample/UniswapHooks/lib/uniswap-hooks/lib/v4-core/lib/openzeppelin-contracts/contracts/interfaces/IERC1820Implementer.sol new file mode 100644 index 00000000..38e8a4e9 --- /dev/null +++ b/CVLByExample/UniswapHooks/lib/uniswap-hooks/lib/v4-core/lib/openzeppelin-contracts/contracts/interfaces/IERC1820Implementer.sol @@ -0,0 +1,20 @@ +// SPDX-License-Identifier: MIT +// OpenZeppelin Contracts (last updated v5.0.0) (interfaces/IERC1820Implementer.sol) + +pragma solidity ^0.8.20; + +/** + * @dev Interface for an ERC1820 implementer, as defined in the + * https://eips.ethereum.org/EIPS/eip-1820#interface-implementation-erc1820implementerinterface[EIP]. + * Used by contracts that will be registered as implementers in the + * {IERC1820Registry}. + */ +interface IERC1820Implementer { + /** + * @dev Returns a special value (`ERC1820_ACCEPT_MAGIC`) if this contract + * implements `interfaceHash` for `account`. + * + * See {IERC1820Registry-setInterfaceImplementer}. + */ + function canImplementInterfaceForAddress(bytes32 interfaceHash, address account) external view returns (bytes32); +} diff --git a/CVLByExample/UniswapHooks/lib/uniswap-hooks/lib/v4-core/lib/openzeppelin-contracts/contracts/interfaces/IERC1820Registry.sol b/CVLByExample/UniswapHooks/lib/uniswap-hooks/lib/v4-core/lib/openzeppelin-contracts/contracts/interfaces/IERC1820Registry.sol new file mode 100644 index 00000000..bf0140a1 --- /dev/null +++ b/CVLByExample/UniswapHooks/lib/uniswap-hooks/lib/v4-core/lib/openzeppelin-contracts/contracts/interfaces/IERC1820Registry.sol @@ -0,0 +1,112 @@ +// SPDX-License-Identifier: MIT +// OpenZeppelin Contracts (last updated v5.0.0) (interfaces/IERC1820Registry.sol) + +pragma solidity ^0.8.20; + +/** + * @dev Interface of the global ERC1820 Registry, as defined in the + * https://eips.ethereum.org/EIPS/eip-1820[EIP]. Accounts may register + * implementers for interfaces in this registry, as well as query support. + * + * Implementers may be shared by multiple accounts, and can also implement more + * than a single interface for each account. Contracts can implement interfaces + * for themselves, but externally-owned accounts (EOA) must delegate this to a + * contract. + * + * {IERC165} interfaces can also be queried via the registry. + * + * For an in-depth explanation and source code analysis, see the EIP text. + */ +interface IERC1820Registry { + event InterfaceImplementerSet(address indexed account, bytes32 indexed interfaceHash, address indexed implementer); + + event ManagerChanged(address indexed account, address indexed newManager); + + /** + * @dev Sets `newManager` as the manager for `account`. A manager of an + * account is able to set interface implementers for it. + * + * By default, each account is its own manager. Passing a value of `0x0` in + * `newManager` will reset the manager to this initial state. + * + * Emits a {ManagerChanged} event. + * + * Requirements: + * + * - the caller must be the current manager for `account`. + */ + function setManager(address account, address newManager) external; + + /** + * @dev Returns the manager for `account`. + * + * See {setManager}. + */ + function getManager(address account) external view returns (address); + + /** + * @dev Sets the `implementer` contract as ``account``'s implementer for + * `interfaceHash`. + * + * `account` being the zero address is an alias for the caller's address. + * The zero address can also be used in `implementer` to remove an old one. + * + * See {interfaceHash} to learn how these are created. + * + * Emits an {InterfaceImplementerSet} event. + * + * Requirements: + * + * - the caller must be the current manager for `account`. + * - `interfaceHash` must not be an {IERC165} interface id (i.e. it must not + * end in 28 zeroes). + * - `implementer` must implement {IERC1820Implementer} and return true when + * queried for support, unless `implementer` is the caller. See + * {IERC1820Implementer-canImplementInterfaceForAddress}. + */ + function setInterfaceImplementer(address account, bytes32 _interfaceHash, address implementer) external; + + /** + * @dev Returns the implementer of `interfaceHash` for `account`. If no such + * implementer is registered, returns the zero address. + * + * If `interfaceHash` is an {IERC165} interface id (i.e. it ends with 28 + * zeroes), `account` will be queried for support of it. + * + * `account` being the zero address is an alias for the caller's address. + */ + function getInterfaceImplementer(address account, bytes32 _interfaceHash) external view returns (address); + + /** + * @dev Returns the interface hash for an `interfaceName`, as defined in the + * corresponding + * https://eips.ethereum.org/EIPS/eip-1820#interface-name[section of the EIP]. + */ + function interfaceHash(string calldata interfaceName) external pure returns (bytes32); + + /** + * @notice Updates the cache with whether the contract implements an ERC165 interface or not. + * @param account Address of the contract for which to update the cache. + * @param interfaceId ERC165 interface for which to update the cache. + */ + function updateERC165Cache(address account, bytes4 interfaceId) external; + + /** + * @notice Checks whether a contract implements an ERC165 interface or not. + * If the result is not cached a direct lookup on the contract address is performed. + * If the result is not cached or the cached value is out-of-date, the cache MUST be updated manually by calling + * {updateERC165Cache} with the contract address. + * @param account Address of the contract to check. + * @param interfaceId ERC165 interface to check. + * @return True if `account` implements `interfaceId`, false otherwise. + */ + function implementsERC165Interface(address account, bytes4 interfaceId) external view returns (bool); + + /** + * @notice Checks whether a contract implements an ERC165 interface or not without using or updating the cache. + * @param account Address of the contract to check. + * @param interfaceId ERC165 interface to check. + * @return True if `account` implements `interfaceId`, false otherwise. + */ + function implementsERC165InterfaceNoCache(address account, bytes4 interfaceId) external view returns (bool); +} diff --git a/CVLByExample/UniswapHooks/lib/uniswap-hooks/lib/v4-core/lib/openzeppelin-contracts/contracts/interfaces/IERC1967.sol b/CVLByExample/UniswapHooks/lib/uniswap-hooks/lib/v4-core/lib/openzeppelin-contracts/contracts/interfaces/IERC1967.sol new file mode 100644 index 00000000..d285ec88 --- /dev/null +++ b/CVLByExample/UniswapHooks/lib/uniswap-hooks/lib/v4-core/lib/openzeppelin-contracts/contracts/interfaces/IERC1967.sol @@ -0,0 +1,24 @@ +// SPDX-License-Identifier: MIT +// OpenZeppelin Contracts (last updated v5.0.0) (interfaces/IERC1967.sol) + +pragma solidity ^0.8.20; + +/** + * @dev ERC-1967: Proxy Storage Slots. This interface contains the events defined in the ERC. + */ +interface IERC1967 { + /** + * @dev Emitted when the implementation is upgraded. + */ + event Upgraded(address indexed implementation); + + /** + * @dev Emitted when the admin account has changed. + */ + event AdminChanged(address previousAdmin, address newAdmin); + + /** + * @dev Emitted when the beacon is changed. + */ + event BeaconUpgraded(address indexed beacon); +} diff --git a/CVLByExample/UniswapHooks/lib/uniswap-hooks/lib/v4-core/lib/openzeppelin-contracts/contracts/interfaces/IERC20.sol b/CVLByExample/UniswapHooks/lib/uniswap-hooks/lib/v4-core/lib/openzeppelin-contracts/contracts/interfaces/IERC20.sol new file mode 100644 index 00000000..21d5a413 --- /dev/null +++ b/CVLByExample/UniswapHooks/lib/uniswap-hooks/lib/v4-core/lib/openzeppelin-contracts/contracts/interfaces/IERC20.sol @@ -0,0 +1,6 @@ +// SPDX-License-Identifier: MIT +// OpenZeppelin Contracts (last updated v5.0.0) (interfaces/IERC20.sol) + +pragma solidity ^0.8.20; + +import {IERC20} from "../token/ERC20/IERC20.sol"; diff --git a/CVLByExample/UniswapHooks/lib/uniswap-hooks/lib/v4-core/lib/openzeppelin-contracts/contracts/interfaces/IERC20Metadata.sol b/CVLByExample/UniswapHooks/lib/uniswap-hooks/lib/v4-core/lib/openzeppelin-contracts/contracts/interfaces/IERC20Metadata.sol new file mode 100644 index 00000000..b7bc6916 --- /dev/null +++ b/CVLByExample/UniswapHooks/lib/uniswap-hooks/lib/v4-core/lib/openzeppelin-contracts/contracts/interfaces/IERC20Metadata.sol @@ -0,0 +1,6 @@ +// SPDX-License-Identifier: MIT +// OpenZeppelin Contracts (last updated v5.0.0) (interfaces/IERC20Metadata.sol) + +pragma solidity ^0.8.20; + +import {IERC20Metadata} from "../token/ERC20/extensions/IERC20Metadata.sol"; diff --git a/CVLByExample/UniswapHooks/lib/uniswap-hooks/lib/v4-core/lib/openzeppelin-contracts/contracts/interfaces/IERC2309.sol b/CVLByExample/UniswapHooks/lib/uniswap-hooks/lib/v4-core/lib/openzeppelin-contracts/contracts/interfaces/IERC2309.sol new file mode 100644 index 00000000..aa00f341 --- /dev/null +++ b/CVLByExample/UniswapHooks/lib/uniswap-hooks/lib/v4-core/lib/openzeppelin-contracts/contracts/interfaces/IERC2309.sol @@ -0,0 +1,19 @@ +// SPDX-License-Identifier: MIT +// OpenZeppelin Contracts (last updated v5.0.0) (interfaces/IERC2309.sol) + +pragma solidity ^0.8.20; + +/** + * @dev ERC-2309: ERC-721 Consecutive Transfer Extension. + */ +interface IERC2309 { + /** + * @dev Emitted when the tokens from `fromTokenId` to `toTokenId` are transferred from `fromAddress` to `toAddress`. + */ + event ConsecutiveTransfer( + uint256 indexed fromTokenId, + uint256 toTokenId, + address indexed fromAddress, + address indexed toAddress + ); +} diff --git a/CVLByExample/UniswapHooks/lib/uniswap-hooks/lib/v4-core/lib/openzeppelin-contracts/contracts/interfaces/IERC2612.sol b/CVLByExample/UniswapHooks/lib/uniswap-hooks/lib/v4-core/lib/openzeppelin-contracts/contracts/interfaces/IERC2612.sol new file mode 100644 index 00000000..c0427bbf --- /dev/null +++ b/CVLByExample/UniswapHooks/lib/uniswap-hooks/lib/v4-core/lib/openzeppelin-contracts/contracts/interfaces/IERC2612.sol @@ -0,0 +1,8 @@ +// SPDX-License-Identifier: MIT +// OpenZeppelin Contracts (last updated v5.0.0) (interfaces/IERC2612.sol) + +pragma solidity ^0.8.20; + +import {IERC20Permit} from "../token/ERC20/extensions/IERC20Permit.sol"; + +interface IERC2612 is IERC20Permit {} diff --git a/CVLByExample/UniswapHooks/lib/uniswap-hooks/lib/v4-core/lib/openzeppelin-contracts/contracts/interfaces/IERC2981.sol b/CVLByExample/UniswapHooks/lib/uniswap-hooks/lib/v4-core/lib/openzeppelin-contracts/contracts/interfaces/IERC2981.sol new file mode 100644 index 00000000..9e7871df --- /dev/null +++ b/CVLByExample/UniswapHooks/lib/uniswap-hooks/lib/v4-core/lib/openzeppelin-contracts/contracts/interfaces/IERC2981.sol @@ -0,0 +1,23 @@ +// SPDX-License-Identifier: MIT +// OpenZeppelin Contracts (last updated v5.0.0) (interfaces/IERC2981.sol) + +pragma solidity ^0.8.20; + +import {IERC165} from "../utils/introspection/IERC165.sol"; + +/** + * @dev Interface for the NFT Royalty Standard. + * + * A standardized way to retrieve royalty payment information for non-fungible tokens (NFTs) to enable universal + * support for royalty payments across all NFT marketplaces and ecosystem participants. + */ +interface IERC2981 is IERC165 { + /** + * @dev Returns how much royalty is owed and to whom, based on a sale price that may be denominated in any unit of + * exchange. The royalty amount is denominated and should be paid in that same unit of exchange. + */ + function royaltyInfo( + uint256 tokenId, + uint256 salePrice + ) external view returns (address receiver, uint256 royaltyAmount); +} diff --git a/CVLByExample/UniswapHooks/lib/uniswap-hooks/lib/v4-core/lib/openzeppelin-contracts/contracts/interfaces/IERC3156.sol b/CVLByExample/UniswapHooks/lib/uniswap-hooks/lib/v4-core/lib/openzeppelin-contracts/contracts/interfaces/IERC3156.sol new file mode 100644 index 00000000..0f48bf38 --- /dev/null +++ b/CVLByExample/UniswapHooks/lib/uniswap-hooks/lib/v4-core/lib/openzeppelin-contracts/contracts/interfaces/IERC3156.sol @@ -0,0 +1,7 @@ +// SPDX-License-Identifier: MIT +// OpenZeppelin Contracts (last updated v5.0.0) (interfaces/IERC3156.sol) + +pragma solidity ^0.8.20; + +import {IERC3156FlashBorrower} from "./IERC3156FlashBorrower.sol"; +import {IERC3156FlashLender} from "./IERC3156FlashLender.sol"; diff --git a/CVLByExample/UniswapHooks/lib/uniswap-hooks/lib/v4-core/lib/openzeppelin-contracts/contracts/interfaces/IERC3156FlashBorrower.sol b/CVLByExample/UniswapHooks/lib/uniswap-hooks/lib/v4-core/lib/openzeppelin-contracts/contracts/interfaces/IERC3156FlashBorrower.sol new file mode 100644 index 00000000..53e17ea6 --- /dev/null +++ b/CVLByExample/UniswapHooks/lib/uniswap-hooks/lib/v4-core/lib/openzeppelin-contracts/contracts/interfaces/IERC3156FlashBorrower.sol @@ -0,0 +1,27 @@ +// SPDX-License-Identifier: MIT +// OpenZeppelin Contracts (last updated v5.0.0) (interfaces/IERC3156FlashBorrower.sol) + +pragma solidity ^0.8.20; + +/** + * @dev Interface of the ERC3156 FlashBorrower, as defined in + * https://eips.ethereum.org/EIPS/eip-3156[ERC-3156]. + */ +interface IERC3156FlashBorrower { + /** + * @dev Receive a flash loan. + * @param initiator The initiator of the loan. + * @param token The loan currency. + * @param amount The amount of tokens lent. + * @param fee The additional amount of tokens to repay. + * @param data Arbitrary data structure, intended to contain user-defined parameters. + * @return The keccak256 hash of "ERC3156FlashBorrower.onFlashLoan" + */ + function onFlashLoan( + address initiator, + address token, + uint256 amount, + uint256 fee, + bytes calldata data + ) external returns (bytes32); +} diff --git a/CVLByExample/UniswapHooks/lib/uniswap-hooks/lib/v4-core/lib/openzeppelin-contracts/contracts/interfaces/IERC3156FlashLender.sol b/CVLByExample/UniswapHooks/lib/uniswap-hooks/lib/v4-core/lib/openzeppelin-contracts/contracts/interfaces/IERC3156FlashLender.sol new file mode 100644 index 00000000..cfae3c0b --- /dev/null +++ b/CVLByExample/UniswapHooks/lib/uniswap-hooks/lib/v4-core/lib/openzeppelin-contracts/contracts/interfaces/IERC3156FlashLender.sol @@ -0,0 +1,41 @@ +// SPDX-License-Identifier: MIT +// OpenZeppelin Contracts (last updated v5.0.0) (interfaces/IERC3156FlashLender.sol) + +pragma solidity ^0.8.20; + +import {IERC3156FlashBorrower} from "./IERC3156FlashBorrower.sol"; + +/** + * @dev Interface of the ERC3156 FlashLender, as defined in + * https://eips.ethereum.org/EIPS/eip-3156[ERC-3156]. + */ +interface IERC3156FlashLender { + /** + * @dev The amount of currency available to be lended. + * @param token The loan currency. + * @return The amount of `token` that can be borrowed. + */ + function maxFlashLoan(address token) external view returns (uint256); + + /** + * @dev The fee to be charged for a given loan. + * @param token The loan currency. + * @param amount The amount of tokens lent. + * @return The amount of `token` to be charged for the loan, on top of the returned principal. + */ + function flashFee(address token, uint256 amount) external view returns (uint256); + + /** + * @dev Initiate a flash loan. + * @param receiver The receiver of the tokens in the loan, and the receiver of the callback. + * @param token The loan currency. + * @param amount The amount of tokens lent. + * @param data Arbitrary data structure, intended to contain user-defined parameters. + */ + function flashLoan( + IERC3156FlashBorrower receiver, + address token, + uint256 amount, + bytes calldata data + ) external returns (bool); +} diff --git a/CVLByExample/UniswapHooks/lib/uniswap-hooks/lib/v4-core/lib/openzeppelin-contracts/contracts/interfaces/IERC4626.sol b/CVLByExample/UniswapHooks/lib/uniswap-hooks/lib/v4-core/lib/openzeppelin-contracts/contracts/interfaces/IERC4626.sol new file mode 100644 index 00000000..cfff53b9 --- /dev/null +++ b/CVLByExample/UniswapHooks/lib/uniswap-hooks/lib/v4-core/lib/openzeppelin-contracts/contracts/interfaces/IERC4626.sol @@ -0,0 +1,230 @@ +// SPDX-License-Identifier: MIT +// OpenZeppelin Contracts (last updated v5.0.0) (interfaces/IERC4626.sol) + +pragma solidity ^0.8.20; + +import {IERC20} from "../token/ERC20/IERC20.sol"; +import {IERC20Metadata} from "../token/ERC20/extensions/IERC20Metadata.sol"; + +/** + * @dev Interface of the ERC4626 "Tokenized Vault Standard", as defined in + * https://eips.ethereum.org/EIPS/eip-4626[ERC-4626]. + */ +interface IERC4626 is IERC20, IERC20Metadata { + event Deposit(address indexed sender, address indexed owner, uint256 assets, uint256 shares); + + event Withdraw( + address indexed sender, + address indexed receiver, + address indexed owner, + uint256 assets, + uint256 shares + ); + + /** + * @dev Returns the address of the underlying token used for the Vault for accounting, depositing, and withdrawing. + * + * - MUST be an ERC-20 token contract. + * - MUST NOT revert. + */ + function asset() external view returns (address assetTokenAddress); + + /** + * @dev Returns the total amount of the underlying asset that is “managed” by Vault. + * + * - SHOULD include any compounding that occurs from yield. + * - MUST be inclusive of any fees that are charged against assets in the Vault. + * - MUST NOT revert. + */ + function totalAssets() external view returns (uint256 totalManagedAssets); + + /** + * @dev Returns the amount of shares that the Vault would exchange for the amount of assets provided, in an ideal + * scenario where all the conditions are met. + * + * - MUST NOT be inclusive of any fees that are charged against assets in the Vault. + * - MUST NOT show any variations depending on the caller. + * - MUST NOT reflect slippage or other on-chain conditions, when performing the actual exchange. + * - MUST NOT revert. + * + * NOTE: This calculation MAY NOT reflect the “per-user” price-per-share, and instead should reflect the + * “average-user’s” price-per-share, meaning what the average user should expect to see when exchanging to and + * from. + */ + function convertToShares(uint256 assets) external view returns (uint256 shares); + + /** + * @dev Returns the amount of assets that the Vault would exchange for the amount of shares provided, in an ideal + * scenario where all the conditions are met. + * + * - MUST NOT be inclusive of any fees that are charged against assets in the Vault. + * - MUST NOT show any variations depending on the caller. + * - MUST NOT reflect slippage or other on-chain conditions, when performing the actual exchange. + * - MUST NOT revert. + * + * NOTE: This calculation MAY NOT reflect the “per-user” price-per-share, and instead should reflect the + * “average-user’s” price-per-share, meaning what the average user should expect to see when exchanging to and + * from. + */ + function convertToAssets(uint256 shares) external view returns (uint256 assets); + + /** + * @dev Returns the maximum amount of the underlying asset that can be deposited into the Vault for the receiver, + * through a deposit call. + * + * - MUST return a limited value if receiver is subject to some deposit limit. + * - MUST return 2 ** 256 - 1 if there is no limit on the maximum amount of assets that may be deposited. + * - MUST NOT revert. + */ + function maxDeposit(address receiver) external view returns (uint256 maxAssets); + + /** + * @dev Allows an on-chain or off-chain user to simulate the effects of their deposit at the current block, given + * current on-chain conditions. + * + * - MUST return as close to and no more than the exact amount of Vault shares that would be minted in a deposit + * call in the same transaction. I.e. deposit should return the same or more shares as previewDeposit if called + * in the same transaction. + * - MUST NOT account for deposit limits like those returned from maxDeposit and should always act as though the + * deposit would be accepted, regardless if the user has enough tokens approved, etc. + * - MUST be inclusive of deposit fees. Integrators should be aware of the existence of deposit fees. + * - MUST NOT revert. + * + * NOTE: any unfavorable discrepancy between convertToShares and previewDeposit SHOULD be considered slippage in + * share price or some other type of condition, meaning the depositor will lose assets by depositing. + */ + function previewDeposit(uint256 assets) external view returns (uint256 shares); + + /** + * @dev Mints shares Vault shares to receiver by depositing exactly amount of underlying tokens. + * + * - MUST emit the Deposit event. + * - MAY support an additional flow in which the underlying tokens are owned by the Vault contract before the + * deposit execution, and are accounted for during deposit. + * - MUST revert if all of assets cannot be deposited (due to deposit limit being reached, slippage, the user not + * approving enough underlying tokens to the Vault contract, etc). + * + * NOTE: most implementations will require pre-approval of the Vault with the Vault’s underlying asset token. + */ + function deposit(uint256 assets, address receiver) external returns (uint256 shares); + + /** + * @dev Returns the maximum amount of the Vault shares that can be minted for the receiver, through a mint call. + * - MUST return a limited value if receiver is subject to some mint limit. + * - MUST return 2 ** 256 - 1 if there is no limit on the maximum amount of shares that may be minted. + * - MUST NOT revert. + */ + function maxMint(address receiver) external view returns (uint256 maxShares); + + /** + * @dev Allows an on-chain or off-chain user to simulate the effects of their mint at the current block, given + * current on-chain conditions. + * + * - MUST return as close to and no fewer than the exact amount of assets that would be deposited in a mint call + * in the same transaction. I.e. mint should return the same or fewer assets as previewMint if called in the + * same transaction. + * - MUST NOT account for mint limits like those returned from maxMint and should always act as though the mint + * would be accepted, regardless if the user has enough tokens approved, etc. + * - MUST be inclusive of deposit fees. Integrators should be aware of the existence of deposit fees. + * - MUST NOT revert. + * + * NOTE: any unfavorable discrepancy between convertToAssets and previewMint SHOULD be considered slippage in + * share price or some other type of condition, meaning the depositor will lose assets by minting. + */ + function previewMint(uint256 shares) external view returns (uint256 assets); + + /** + * @dev Mints exactly shares Vault shares to receiver by depositing amount of underlying tokens. + * + * - MUST emit the Deposit event. + * - MAY support an additional flow in which the underlying tokens are owned by the Vault contract before the mint + * execution, and are accounted for during mint. + * - MUST revert if all of shares cannot be minted (due to deposit limit being reached, slippage, the user not + * approving enough underlying tokens to the Vault contract, etc). + * + * NOTE: most implementations will require pre-approval of the Vault with the Vault’s underlying asset token. + */ + function mint(uint256 shares, address receiver) external returns (uint256 assets); + + /** + * @dev Returns the maximum amount of the underlying asset that can be withdrawn from the owner balance in the + * Vault, through a withdraw call. + * + * - MUST return a limited value if owner is subject to some withdrawal limit or timelock. + * - MUST NOT revert. + */ + function maxWithdraw(address owner) external view returns (uint256 maxAssets); + + /** + * @dev Allows an on-chain or off-chain user to simulate the effects of their withdrawal at the current block, + * given current on-chain conditions. + * + * - MUST return as close to and no fewer than the exact amount of Vault shares that would be burned in a withdraw + * call in the same transaction. I.e. withdraw should return the same or fewer shares as previewWithdraw if + * called + * in the same transaction. + * - MUST NOT account for withdrawal limits like those returned from maxWithdraw and should always act as though + * the withdrawal would be accepted, regardless if the user has enough shares, etc. + * - MUST be inclusive of withdrawal fees. Integrators should be aware of the existence of withdrawal fees. + * - MUST NOT revert. + * + * NOTE: any unfavorable discrepancy between convertToShares and previewWithdraw SHOULD be considered slippage in + * share price or some other type of condition, meaning the depositor will lose assets by depositing. + */ + function previewWithdraw(uint256 assets) external view returns (uint256 shares); + + /** + * @dev Burns shares from owner and sends exactly assets of underlying tokens to receiver. + * + * - MUST emit the Withdraw event. + * - MAY support an additional flow in which the underlying tokens are owned by the Vault contract before the + * withdraw execution, and are accounted for during withdraw. + * - MUST revert if all of assets cannot be withdrawn (due to withdrawal limit being reached, slippage, the owner + * not having enough shares, etc). + * + * Note that some implementations will require pre-requesting to the Vault before a withdrawal may be performed. + * Those methods should be performed separately. + */ + function withdraw(uint256 assets, address receiver, address owner) external returns (uint256 shares); + + /** + * @dev Returns the maximum amount of Vault shares that can be redeemed from the owner balance in the Vault, + * through a redeem call. + * + * - MUST return a limited value if owner is subject to some withdrawal limit or timelock. + * - MUST return balanceOf(owner) if owner is not subject to any withdrawal limit or timelock. + * - MUST NOT revert. + */ + function maxRedeem(address owner) external view returns (uint256 maxShares); + + /** + * @dev Allows an on-chain or off-chain user to simulate the effects of their redeemption at the current block, + * given current on-chain conditions. + * + * - MUST return as close to and no more than the exact amount of assets that would be withdrawn in a redeem call + * in the same transaction. I.e. redeem should return the same or more assets as previewRedeem if called in the + * same transaction. + * - MUST NOT account for redemption limits like those returned from maxRedeem and should always act as though the + * redemption would be accepted, regardless if the user has enough shares, etc. + * - MUST be inclusive of withdrawal fees. Integrators should be aware of the existence of withdrawal fees. + * - MUST NOT revert. + * + * NOTE: any unfavorable discrepancy between convertToAssets and previewRedeem SHOULD be considered slippage in + * share price or some other type of condition, meaning the depositor will lose assets by redeeming. + */ + function previewRedeem(uint256 shares) external view returns (uint256 assets); + + /** + * @dev Burns exactly shares from owner and sends assets of underlying tokens to receiver. + * + * - MUST emit the Withdraw event. + * - MAY support an additional flow in which the underlying tokens are owned by the Vault contract before the + * redeem execution, and are accounted for during redeem. + * - MUST revert if all of shares cannot be redeemed (due to withdrawal limit being reached, slippage, the owner + * not having enough shares, etc). + * + * NOTE: some implementations will require pre-requesting to the Vault before a withdrawal may be performed. + * Those methods should be performed separately. + */ + function redeem(uint256 shares, address receiver, address owner) external returns (uint256 assets); +} diff --git a/CVLByExample/UniswapHooks/lib/uniswap-hooks/lib/v4-core/lib/openzeppelin-contracts/contracts/interfaces/IERC4906.sol b/CVLByExample/UniswapHooks/lib/uniswap-hooks/lib/v4-core/lib/openzeppelin-contracts/contracts/interfaces/IERC4906.sol new file mode 100644 index 00000000..bc008e39 --- /dev/null +++ b/CVLByExample/UniswapHooks/lib/uniswap-hooks/lib/v4-core/lib/openzeppelin-contracts/contracts/interfaces/IERC4906.sol @@ -0,0 +1,20 @@ +// SPDX-License-Identifier: MIT +// OpenZeppelin Contracts (last updated v5.0.0) (interfaces/IERC4906.sol) + +pragma solidity ^0.8.20; + +import {IERC165} from "./IERC165.sol"; +import {IERC721} from "./IERC721.sol"; + +/// @title EIP-721 Metadata Update Extension +interface IERC4906 is IERC165, IERC721 { + /// @dev This event emits when the metadata of a token is changed. + /// So that the third-party platforms such as NFT market could + /// timely update the images and related attributes of the NFT. + event MetadataUpdate(uint256 _tokenId); + + /// @dev This event emits when the metadata of a range of tokens is changed. + /// So that the third-party platforms such as NFT market could + /// timely update the images and related attributes of the NFTs. + event BatchMetadataUpdate(uint256 _fromTokenId, uint256 _toTokenId); +} diff --git a/CVLByExample/UniswapHooks/lib/uniswap-hooks/lib/v4-core/lib/openzeppelin-contracts/contracts/interfaces/IERC5267.sol b/CVLByExample/UniswapHooks/lib/uniswap-hooks/lib/v4-core/lib/openzeppelin-contracts/contracts/interfaces/IERC5267.sol new file mode 100644 index 00000000..47a9fd58 --- /dev/null +++ b/CVLByExample/UniswapHooks/lib/uniswap-hooks/lib/v4-core/lib/openzeppelin-contracts/contracts/interfaces/IERC5267.sol @@ -0,0 +1,28 @@ +// SPDX-License-Identifier: MIT +// OpenZeppelin Contracts (last updated v5.0.0) (interfaces/IERC5267.sol) + +pragma solidity ^0.8.20; + +interface IERC5267 { + /** + * @dev MAY be emitted to signal that the domain could have changed. + */ + event EIP712DomainChanged(); + + /** + * @dev returns the fields and values that describe the domain separator used by this contract for EIP-712 + * signature. + */ + function eip712Domain() + external + view + returns ( + bytes1 fields, + string memory name, + string memory version, + uint256 chainId, + address verifyingContract, + bytes32 salt, + uint256[] memory extensions + ); +} diff --git a/CVLByExample/UniswapHooks/lib/uniswap-hooks/lib/v4-core/lib/openzeppelin-contracts/contracts/interfaces/IERC5313.sol b/CVLByExample/UniswapHooks/lib/uniswap-hooks/lib/v4-core/lib/openzeppelin-contracts/contracts/interfaces/IERC5313.sol new file mode 100644 index 00000000..62f8d75c --- /dev/null +++ b/CVLByExample/UniswapHooks/lib/uniswap-hooks/lib/v4-core/lib/openzeppelin-contracts/contracts/interfaces/IERC5313.sol @@ -0,0 +1,16 @@ +// SPDX-License-Identifier: MIT +// OpenZeppelin Contracts (last updated v5.0.0) (interfaces/IERC5313.sol) + +pragma solidity ^0.8.20; + +/** + * @dev Interface for the Light Contract Ownership Standard. + * + * A standardized minimal interface required to identify an account that controls a contract + */ +interface IERC5313 { + /** + * @dev Gets the address of the owner. + */ + function owner() external view returns (address); +} diff --git a/CVLByExample/UniswapHooks/lib/uniswap-hooks/lib/v4-core/lib/openzeppelin-contracts/contracts/interfaces/IERC5805.sol b/CVLByExample/UniswapHooks/lib/uniswap-hooks/lib/v4-core/lib/openzeppelin-contracts/contracts/interfaces/IERC5805.sol new file mode 100644 index 00000000..a89e22df --- /dev/null +++ b/CVLByExample/UniswapHooks/lib/uniswap-hooks/lib/v4-core/lib/openzeppelin-contracts/contracts/interfaces/IERC5805.sol @@ -0,0 +1,9 @@ +// SPDX-License-Identifier: MIT +// OpenZeppelin Contracts (last updated v5.0.0) (interfaces/IERC5805.sol) + +pragma solidity ^0.8.20; + +import {IVotes} from "../governance/utils/IVotes.sol"; +import {IERC6372} from "./IERC6372.sol"; + +interface IERC5805 is IERC6372, IVotes {} diff --git a/CVLByExample/UniswapHooks/lib/uniswap-hooks/lib/v4-core/lib/openzeppelin-contracts/contracts/interfaces/IERC6372.sol b/CVLByExample/UniswapHooks/lib/uniswap-hooks/lib/v4-core/lib/openzeppelin-contracts/contracts/interfaces/IERC6372.sol new file mode 100644 index 00000000..7d2ea4a5 --- /dev/null +++ b/CVLByExample/UniswapHooks/lib/uniswap-hooks/lib/v4-core/lib/openzeppelin-contracts/contracts/interfaces/IERC6372.sol @@ -0,0 +1,17 @@ +// SPDX-License-Identifier: MIT +// OpenZeppelin Contracts (last updated v5.0.0) (interfaces/IERC6372.sol) + +pragma solidity ^0.8.20; + +interface IERC6372 { + /** + * @dev Clock used for flagging checkpoints. Can be overridden to implement timestamp based checkpoints (and voting). + */ + function clock() external view returns (uint48); + + /** + * @dev Description of the clock + */ + // solhint-disable-next-line func-name-mixedcase + function CLOCK_MODE() external view returns (string memory); +} diff --git a/CVLByExample/UniswapHooks/lib/uniswap-hooks/lib/v4-core/lib/openzeppelin-contracts/contracts/interfaces/IERC721.sol b/CVLByExample/UniswapHooks/lib/uniswap-hooks/lib/v4-core/lib/openzeppelin-contracts/contracts/interfaces/IERC721.sol new file mode 100644 index 00000000..0ea735bb --- /dev/null +++ b/CVLByExample/UniswapHooks/lib/uniswap-hooks/lib/v4-core/lib/openzeppelin-contracts/contracts/interfaces/IERC721.sol @@ -0,0 +1,6 @@ +// SPDX-License-Identifier: MIT +// OpenZeppelin Contracts (last updated v5.0.0) (interfaces/IERC721.sol) + +pragma solidity ^0.8.20; + +import {IERC721} from "../token/ERC721/IERC721.sol"; diff --git a/CVLByExample/UniswapHooks/lib/uniswap-hooks/lib/v4-core/lib/openzeppelin-contracts/contracts/interfaces/IERC721Enumerable.sol b/CVLByExample/UniswapHooks/lib/uniswap-hooks/lib/v4-core/lib/openzeppelin-contracts/contracts/interfaces/IERC721Enumerable.sol new file mode 100644 index 00000000..d83a0562 --- /dev/null +++ b/CVLByExample/UniswapHooks/lib/uniswap-hooks/lib/v4-core/lib/openzeppelin-contracts/contracts/interfaces/IERC721Enumerable.sol @@ -0,0 +1,6 @@ +// SPDX-License-Identifier: MIT +// OpenZeppelin Contracts (last updated v5.0.0) (interfaces/IERC721Enumerable.sol) + +pragma solidity ^0.8.20; + +import {IERC721Enumerable} from "../token/ERC721/extensions/IERC721Enumerable.sol"; diff --git a/CVLByExample/UniswapHooks/lib/uniswap-hooks/lib/v4-core/lib/openzeppelin-contracts/contracts/interfaces/IERC721Metadata.sol b/CVLByExample/UniswapHooks/lib/uniswap-hooks/lib/v4-core/lib/openzeppelin-contracts/contracts/interfaces/IERC721Metadata.sol new file mode 100644 index 00000000..d79dd686 --- /dev/null +++ b/CVLByExample/UniswapHooks/lib/uniswap-hooks/lib/v4-core/lib/openzeppelin-contracts/contracts/interfaces/IERC721Metadata.sol @@ -0,0 +1,6 @@ +// SPDX-License-Identifier: MIT +// OpenZeppelin Contracts (last updated v5.0.0) (interfaces/IERC721Metadata.sol) + +pragma solidity ^0.8.20; + +import {IERC721Metadata} from "../token/ERC721/extensions/IERC721Metadata.sol"; diff --git a/CVLByExample/UniswapHooks/lib/uniswap-hooks/lib/v4-core/lib/openzeppelin-contracts/contracts/interfaces/IERC721Receiver.sol b/CVLByExample/UniswapHooks/lib/uniswap-hooks/lib/v4-core/lib/openzeppelin-contracts/contracts/interfaces/IERC721Receiver.sol new file mode 100644 index 00000000..6b2a5aa6 --- /dev/null +++ b/CVLByExample/UniswapHooks/lib/uniswap-hooks/lib/v4-core/lib/openzeppelin-contracts/contracts/interfaces/IERC721Receiver.sol @@ -0,0 +1,6 @@ +// SPDX-License-Identifier: MIT +// OpenZeppelin Contracts (last updated v5.0.0) (interfaces/IERC721Receiver.sol) + +pragma solidity ^0.8.20; + +import {IERC721Receiver} from "../token/ERC721/IERC721Receiver.sol"; diff --git a/CVLByExample/UniswapHooks/lib/uniswap-hooks/lib/v4-core/lib/openzeppelin-contracts/contracts/interfaces/IERC777.sol b/CVLByExample/UniswapHooks/lib/uniswap-hooks/lib/v4-core/lib/openzeppelin-contracts/contracts/interfaces/IERC777.sol new file mode 100644 index 00000000..56dfbef5 --- /dev/null +++ b/CVLByExample/UniswapHooks/lib/uniswap-hooks/lib/v4-core/lib/openzeppelin-contracts/contracts/interfaces/IERC777.sol @@ -0,0 +1,200 @@ +// SPDX-License-Identifier: MIT +// OpenZeppelin Contracts (last updated v5.0.0) (interfaces/IERC777.sol) + +pragma solidity ^0.8.20; + +/** + * @dev Interface of the ERC777Token standard as defined in the EIP. + * + * This contract uses the + * https://eips.ethereum.org/EIPS/eip-1820[ERC1820 registry standard] to let + * token holders and recipients react to token movements by using setting implementers + * for the associated interfaces in said registry. See {IERC1820Registry} and + * {IERC1820Implementer}. + */ +interface IERC777 { + /** + * @dev Emitted when `amount` tokens are created by `operator` and assigned to `to`. + * + * Note that some additional user `data` and `operatorData` can be logged in the event. + */ + event Minted(address indexed operator, address indexed to, uint256 amount, bytes data, bytes operatorData); + + /** + * @dev Emitted when `operator` destroys `amount` tokens from `account`. + * + * Note that some additional user `data` and `operatorData` can be logged in the event. + */ + event Burned(address indexed operator, address indexed from, uint256 amount, bytes data, bytes operatorData); + + /** + * @dev Emitted when `operator` is made operator for `tokenHolder`. + */ + event AuthorizedOperator(address indexed operator, address indexed tokenHolder); + + /** + * @dev Emitted when `operator` is revoked its operator status for `tokenHolder`. + */ + event RevokedOperator(address indexed operator, address indexed tokenHolder); + + /** + * @dev Returns the name of the token. + */ + function name() external view returns (string memory); + + /** + * @dev Returns the symbol of the token, usually a shorter version of the + * name. + */ + function symbol() external view returns (string memory); + + /** + * @dev Returns the smallest part of the token that is not divisible. This + * means all token operations (creation, movement and destruction) must have + * amounts that are a multiple of this number. + * + * For most token contracts, this value will equal 1. + */ + function granularity() external view returns (uint256); + + /** + * @dev Returns the amount of tokens in existence. + */ + function totalSupply() external view returns (uint256); + + /** + * @dev Returns the amount of tokens owned by an account (`owner`). + */ + function balanceOf(address owner) external view returns (uint256); + + /** + * @dev Moves `amount` tokens from the caller's account to `recipient`. + * + * If send or receive hooks are registered for the caller and `recipient`, + * the corresponding functions will be called with `data` and empty + * `operatorData`. See {IERC777Sender} and {IERC777Recipient}. + * + * Emits a {Sent} event. + * + * Requirements + * + * - the caller must have at least `amount` tokens. + * - `recipient` cannot be the zero address. + * - if `recipient` is a contract, it must implement the {IERC777Recipient} + * interface. + */ + function send(address recipient, uint256 amount, bytes calldata data) external; + + /** + * @dev Destroys `amount` tokens from the caller's account, reducing the + * total supply. + * + * If a send hook is registered for the caller, the corresponding function + * will be called with `data` and empty `operatorData`. See {IERC777Sender}. + * + * Emits a {Burned} event. + * + * Requirements + * + * - the caller must have at least `amount` tokens. + */ + function burn(uint256 amount, bytes calldata data) external; + + /** + * @dev Returns true if an account is an operator of `tokenHolder`. + * Operators can send and burn tokens on behalf of their owners. All + * accounts are their own operator. + * + * See {operatorSend} and {operatorBurn}. + */ + function isOperatorFor(address operator, address tokenHolder) external view returns (bool); + + /** + * @dev Make an account an operator of the caller. + * + * See {isOperatorFor}. + * + * Emits an {AuthorizedOperator} event. + * + * Requirements + * + * - `operator` cannot be calling address. + */ + function authorizeOperator(address operator) external; + + /** + * @dev Revoke an account's operator status for the caller. + * + * See {isOperatorFor} and {defaultOperators}. + * + * Emits a {RevokedOperator} event. + * + * Requirements + * + * - `operator` cannot be calling address. + */ + function revokeOperator(address operator) external; + + /** + * @dev Returns the list of default operators. These accounts are operators + * for all token holders, even if {authorizeOperator} was never called on + * them. + * + * This list is immutable, but individual holders may revoke these via + * {revokeOperator}, in which case {isOperatorFor} will return false. + */ + function defaultOperators() external view returns (address[] memory); + + /** + * @dev Moves `amount` tokens from `sender` to `recipient`. The caller must + * be an operator of `sender`. + * + * If send or receive hooks are registered for `sender` and `recipient`, + * the corresponding functions will be called with `data` and + * `operatorData`. See {IERC777Sender} and {IERC777Recipient}. + * + * Emits a {Sent} event. + * + * Requirements + * + * - `sender` cannot be the zero address. + * - `sender` must have at least `amount` tokens. + * - the caller must be an operator for `sender`. + * - `recipient` cannot be the zero address. + * - if `recipient` is a contract, it must implement the {IERC777Recipient} + * interface. + */ + function operatorSend( + address sender, + address recipient, + uint256 amount, + bytes calldata data, + bytes calldata operatorData + ) external; + + /** + * @dev Destroys `amount` tokens from `account`, reducing the total supply. + * The caller must be an operator of `account`. + * + * If a send hook is registered for `account`, the corresponding function + * will be called with `data` and `operatorData`. See {IERC777Sender}. + * + * Emits a {Burned} event. + * + * Requirements + * + * - `account` cannot be the zero address. + * - `account` must have at least `amount` tokens. + * - the caller must be an operator for `account`. + */ + function operatorBurn(address account, uint256 amount, bytes calldata data, bytes calldata operatorData) external; + + event Sent( + address indexed operator, + address indexed from, + address indexed to, + uint256 amount, + bytes data, + bytes operatorData + ); +} diff --git a/CVLByExample/UniswapHooks/lib/uniswap-hooks/lib/v4-core/lib/openzeppelin-contracts/contracts/interfaces/IERC777Recipient.sol b/CVLByExample/UniswapHooks/lib/uniswap-hooks/lib/v4-core/lib/openzeppelin-contracts/contracts/interfaces/IERC777Recipient.sol new file mode 100644 index 00000000..6378e140 --- /dev/null +++ b/CVLByExample/UniswapHooks/lib/uniswap-hooks/lib/v4-core/lib/openzeppelin-contracts/contracts/interfaces/IERC777Recipient.sol @@ -0,0 +1,35 @@ +// SPDX-License-Identifier: MIT +// OpenZeppelin Contracts (last updated v5.0.0) (interfaces/IERC777Recipient.sol) + +pragma solidity ^0.8.20; + +/** + * @dev Interface of the ERC777TokensRecipient standard as defined in the EIP. + * + * Accounts can be notified of {IERC777} tokens being sent to them by having a + * contract implement this interface (contract holders can be their own + * implementer) and registering it on the + * https://eips.ethereum.org/EIPS/eip-1820[ERC1820 global registry]. + * + * See {IERC1820Registry} and {IERC1820Implementer}. + */ +interface IERC777Recipient { + /** + * @dev Called by an {IERC777} token contract whenever tokens are being + * moved or created into a registered account (`to`). The type of operation + * is conveyed by `from` being the zero address or not. + * + * This call occurs _after_ the token contract's state is updated, so + * {IERC777-balanceOf}, etc., can be used to query the post-operation state. + * + * This function may revert to prevent the operation from being executed. + */ + function tokensReceived( + address operator, + address from, + address to, + uint256 amount, + bytes calldata userData, + bytes calldata operatorData + ) external; +} diff --git a/CVLByExample/UniswapHooks/lib/uniswap-hooks/lib/v4-core/lib/openzeppelin-contracts/contracts/interfaces/IERC777Sender.sol b/CVLByExample/UniswapHooks/lib/uniswap-hooks/lib/v4-core/lib/openzeppelin-contracts/contracts/interfaces/IERC777Sender.sol new file mode 100644 index 00000000..5c0ec0b5 --- /dev/null +++ b/CVLByExample/UniswapHooks/lib/uniswap-hooks/lib/v4-core/lib/openzeppelin-contracts/contracts/interfaces/IERC777Sender.sol @@ -0,0 +1,35 @@ +// SPDX-License-Identifier: MIT +// OpenZeppelin Contracts (last updated v5.0.0) (interfaces/IERC777Sender.sol) + +pragma solidity ^0.8.20; + +/** + * @dev Interface of the ERC777TokensSender standard as defined in the EIP. + * + * {IERC777} Token holders can be notified of operations performed on their + * tokens by having a contract implement this interface (contract holders can be + * their own implementer) and registering it on the + * https://eips.ethereum.org/EIPS/eip-1820[ERC1820 global registry]. + * + * See {IERC1820Registry} and {IERC1820Implementer}. + */ +interface IERC777Sender { + /** + * @dev Called by an {IERC777} token contract whenever a registered holder's + * (`from`) tokens are about to be moved or destroyed. The type of operation + * is conveyed by `to` being the zero address or not. + * + * This call occurs _before_ the token contract's state is updated, so + * {IERC777-balanceOf}, etc., can be used to query the pre-operation state. + * + * This function may revert to prevent the operation from being executed. + */ + function tokensToSend( + address operator, + address from, + address to, + uint256 amount, + bytes calldata userData, + bytes calldata operatorData + ) external; +} diff --git a/CVLByExample/UniswapHooks/lib/uniswap-hooks/lib/v4-core/lib/openzeppelin-contracts/contracts/interfaces/README.adoc b/CVLByExample/UniswapHooks/lib/uniswap-hooks/lib/v4-core/lib/openzeppelin-contracts/contracts/interfaces/README.adoc new file mode 100644 index 00000000..379a24a1 --- /dev/null +++ b/CVLByExample/UniswapHooks/lib/uniswap-hooks/lib/v4-core/lib/openzeppelin-contracts/contracts/interfaces/README.adoc @@ -0,0 +1,82 @@ += Interfaces + +[.readme-notice] +NOTE: This document is better viewed at https://docs.openzeppelin.com/contracts/api/interfaces + +== List of standardized interfaces +These interfaces are available as `.sol` files, and also as compiler `.json` ABI files (through the npm package). These +are useful to interact with third party contracts that implement them. + +- {IERC20} +- {IERC20Errors} +- {IERC20Metadata} +- {IERC165} +- {IERC721} +- {IERC721Receiver} +- {IERC721Enumerable} +- {IERC721Metadata} +- {IERC721Errors} +- {IERC777} +- {IERC777Recipient} +- {IERC777Sender} +- {IERC1155} +- {IERC1155Receiver} +- {IERC1155MetadataURI} +- {IERC1155Errors} +- {IERC1271} +- {IERC1363} +- {IERC1363Receiver} +- {IERC1363Spender} +- {IERC1820Implementer} +- {IERC1820Registry} +- {IERC1822Proxiable} +- {IERC2612} +- {IERC2981} +- {IERC3156FlashLender} +- {IERC3156FlashBorrower} +- {IERC4626} +- {IERC4906} +- {IERC5267} +- {IERC5313} +- {IERC5805} +- {IERC6372} + +== Detailed ABI + +{{IERC20Errors}} + +{{IERC721Errors}} + +{{IERC1155Errors}} + +{{IERC1271}} + +{{IERC1363}} + +{{IERC1363Receiver}} + +{{IERC1363Spender}} + +{{IERC1820Implementer}} + +{{IERC1820Registry}} + +{{IERC1822Proxiable}} + +{{IERC2612}} + +{{IERC2981}} + +{{IERC3156FlashLender}} + +{{IERC3156FlashBorrower}} + +{{IERC4626}} + +{{IERC5313}} + +{{IERC5267}} + +{{IERC5805}} + +{{IERC6372}} diff --git a/CVLByExample/UniswapHooks/lib/uniswap-hooks/lib/v4-core/lib/openzeppelin-contracts/contracts/interfaces/draft-IERC1822.sol b/CVLByExample/UniswapHooks/lib/uniswap-hooks/lib/v4-core/lib/openzeppelin-contracts/contracts/interfaces/draft-IERC1822.sol new file mode 100644 index 00000000..4d0f0f88 --- /dev/null +++ b/CVLByExample/UniswapHooks/lib/uniswap-hooks/lib/v4-core/lib/openzeppelin-contracts/contracts/interfaces/draft-IERC1822.sol @@ -0,0 +1,20 @@ +// SPDX-License-Identifier: MIT +// OpenZeppelin Contracts (last updated v5.0.0) (interfaces/draft-IERC1822.sol) + +pragma solidity ^0.8.20; + +/** + * @dev ERC1822: Universal Upgradeable Proxy Standard (UUPS) documents a method for upgradeability through a simplified + * proxy whose upgrades are fully controlled by the current implementation. + */ +interface IERC1822Proxiable { + /** + * @dev Returns the storage slot that the proxiable contract assumes is being used to store the implementation + * address. + * + * IMPORTANT: A proxy pointing at a proxiable contract should not be considered proxiable itself, because this risks + * bricking a proxy that upgrades to it, by delegating to itself until out of gas. Thus it is critical that this + * function revert if invoked through a proxy. + */ + function proxiableUUID() external view returns (bytes32); +} diff --git a/CVLByExample/UniswapHooks/lib/uniswap-hooks/lib/v4-core/lib/openzeppelin-contracts/contracts/interfaces/draft-IERC6093.sol b/CVLByExample/UniswapHooks/lib/uniswap-hooks/lib/v4-core/lib/openzeppelin-contracts/contracts/interfaces/draft-IERC6093.sol new file mode 100644 index 00000000..f6990e60 --- /dev/null +++ b/CVLByExample/UniswapHooks/lib/uniswap-hooks/lib/v4-core/lib/openzeppelin-contracts/contracts/interfaces/draft-IERC6093.sol @@ -0,0 +1,161 @@ +// SPDX-License-Identifier: MIT +// OpenZeppelin Contracts (last updated v5.0.0) (interfaces/draft-IERC6093.sol) +pragma solidity ^0.8.20; + +/** + * @dev Standard ERC20 Errors + * Interface of the https://eips.ethereum.org/EIPS/eip-6093[ERC-6093] custom errors for ERC20 tokens. + */ +interface IERC20Errors { + /** + * @dev Indicates an error related to the current `balance` of a `sender`. Used in transfers. + * @param sender Address whose tokens are being transferred. + * @param balance Current balance for the interacting account. + * @param needed Minimum amount required to perform a transfer. + */ + error ERC20InsufficientBalance(address sender, uint256 balance, uint256 needed); + + /** + * @dev Indicates a failure with the token `sender`. Used in transfers. + * @param sender Address whose tokens are being transferred. + */ + error ERC20InvalidSender(address sender); + + /** + * @dev Indicates a failure with the token `receiver`. Used in transfers. + * @param receiver Address to which tokens are being transferred. + */ + error ERC20InvalidReceiver(address receiver); + + /** + * @dev Indicates a failure with the `spender`’s `allowance`. Used in transfers. + * @param spender Address that may be allowed to operate on tokens without being their owner. + * @param allowance Amount of tokens a `spender` is allowed to operate with. + * @param needed Minimum amount required to perform a transfer. + */ + error ERC20InsufficientAllowance(address spender, uint256 allowance, uint256 needed); + + /** + * @dev Indicates a failure with the `approver` of a token to be approved. Used in approvals. + * @param approver Address initiating an approval operation. + */ + error ERC20InvalidApprover(address approver); + + /** + * @dev Indicates a failure with the `spender` to be approved. Used in approvals. + * @param spender Address that may be allowed to operate on tokens without being their owner. + */ + error ERC20InvalidSpender(address spender); +} + +/** + * @dev Standard ERC721 Errors + * Interface of the https://eips.ethereum.org/EIPS/eip-6093[ERC-6093] custom errors for ERC721 tokens. + */ +interface IERC721Errors { + /** + * @dev Indicates that an address can't be an owner. For example, `address(0)` is a forbidden owner in EIP-20. + * Used in balance queries. + * @param owner Address of the current owner of a token. + */ + error ERC721InvalidOwner(address owner); + + /** + * @dev Indicates a `tokenId` whose `owner` is the zero address. + * @param tokenId Identifier number of a token. + */ + error ERC721NonexistentToken(uint256 tokenId); + + /** + * @dev Indicates an error related to the ownership over a particular token. Used in transfers. + * @param sender Address whose tokens are being transferred. + * @param tokenId Identifier number of a token. + * @param owner Address of the current owner of a token. + */ + error ERC721IncorrectOwner(address sender, uint256 tokenId, address owner); + + /** + * @dev Indicates a failure with the token `sender`. Used in transfers. + * @param sender Address whose tokens are being transferred. + */ + error ERC721InvalidSender(address sender); + + /** + * @dev Indicates a failure with the token `receiver`. Used in transfers. + * @param receiver Address to which tokens are being transferred. + */ + error ERC721InvalidReceiver(address receiver); + + /** + * @dev Indicates a failure with the `operator`’s approval. Used in transfers. + * @param operator Address that may be allowed to operate on tokens without being their owner. + * @param tokenId Identifier number of a token. + */ + error ERC721InsufficientApproval(address operator, uint256 tokenId); + + /** + * @dev Indicates a failure with the `approver` of a token to be approved. Used in approvals. + * @param approver Address initiating an approval operation. + */ + error ERC721InvalidApprover(address approver); + + /** + * @dev Indicates a failure with the `operator` to be approved. Used in approvals. + * @param operator Address that may be allowed to operate on tokens without being their owner. + */ + error ERC721InvalidOperator(address operator); +} + +/** + * @dev Standard ERC1155 Errors + * Interface of the https://eips.ethereum.org/EIPS/eip-6093[ERC-6093] custom errors for ERC1155 tokens. + */ +interface IERC1155Errors { + /** + * @dev Indicates an error related to the current `balance` of a `sender`. Used in transfers. + * @param sender Address whose tokens are being transferred. + * @param balance Current balance for the interacting account. + * @param needed Minimum amount required to perform a transfer. + * @param tokenId Identifier number of a token. + */ + error ERC1155InsufficientBalance(address sender, uint256 balance, uint256 needed, uint256 tokenId); + + /** + * @dev Indicates a failure with the token `sender`. Used in transfers. + * @param sender Address whose tokens are being transferred. + */ + error ERC1155InvalidSender(address sender); + + /** + * @dev Indicates a failure with the token `receiver`. Used in transfers. + * @param receiver Address to which tokens are being transferred. + */ + error ERC1155InvalidReceiver(address receiver); + + /** + * @dev Indicates a failure with the `operator`’s approval. Used in transfers. + * @param operator Address that may be allowed to operate on tokens without being their owner. + * @param owner Address of the current owner of a token. + */ + error ERC1155MissingApprovalForAll(address operator, address owner); + + /** + * @dev Indicates a failure with the `approver` of a token to be approved. Used in approvals. + * @param approver Address initiating an approval operation. + */ + error ERC1155InvalidApprover(address approver); + + /** + * @dev Indicates a failure with the `operator` to be approved. Used in approvals. + * @param operator Address that may be allowed to operate on tokens without being their owner. + */ + error ERC1155InvalidOperator(address operator); + + /** + * @dev Indicates an array length mismatch between ids and values in a safeBatchTransferFrom operation. + * Used in batch transfers. + * @param idsLength Length of the array of token identifiers + * @param valuesLength Length of the array of token amounts + */ + error ERC1155InvalidArrayLength(uint256 idsLength, uint256 valuesLength); +} diff --git a/CVLByExample/UniswapHooks/lib/uniswap-hooks/lib/v4-core/lib/openzeppelin-contracts/contracts/metatx/ERC2771Context.sol b/CVLByExample/UniswapHooks/lib/uniswap-hooks/lib/v4-core/lib/openzeppelin-contracts/contracts/metatx/ERC2771Context.sol new file mode 100644 index 00000000..66b01220 --- /dev/null +++ b/CVLByExample/UniswapHooks/lib/uniswap-hooks/lib/v4-core/lib/openzeppelin-contracts/contracts/metatx/ERC2771Context.sol @@ -0,0 +1,86 @@ +// SPDX-License-Identifier: MIT +// OpenZeppelin Contracts (last updated v5.0.1) (metatx/ERC2771Context.sol) + +pragma solidity ^0.8.20; + +import {Context} from "../utils/Context.sol"; + +/** + * @dev Context variant with ERC2771 support. + * + * WARNING: Avoid using this pattern in contracts that rely in a specific calldata length as they'll + * be affected by any forwarder whose `msg.data` is suffixed with the `from` address according to the ERC2771 + * specification adding the address size in bytes (20) to the calldata size. An example of an unexpected + * behavior could be an unintended fallback (or another function) invocation while trying to invoke the `receive` + * function only accessible if `msg.data.length == 0`. + * + * WARNING: The usage of `delegatecall` in this contract is dangerous and may result in context corruption. + * Any forwarded request to this contract triggering a `delegatecall` to itself will result in an invalid {_msgSender} + * recovery. + */ +abstract contract ERC2771Context is Context { + /// @custom:oz-upgrades-unsafe-allow state-variable-immutable + address private immutable _trustedForwarder; + + /** + * @dev Initializes the contract with a trusted forwarder, which will be able to + * invoke functions on this contract on behalf of other accounts. + * + * NOTE: The trusted forwarder can be replaced by overriding {trustedForwarder}. + */ + /// @custom:oz-upgrades-unsafe-allow constructor + constructor(address trustedForwarder_) { + _trustedForwarder = trustedForwarder_; + } + + /** + * @dev Returns the address of the trusted forwarder. + */ + function trustedForwarder() public view virtual returns (address) { + return _trustedForwarder; + } + + /** + * @dev Indicates whether any particular address is the trusted forwarder. + */ + function isTrustedForwarder(address forwarder) public view virtual returns (bool) { + return forwarder == trustedForwarder(); + } + + /** + * @dev Override for `msg.sender`. Defaults to the original `msg.sender` whenever + * a call is not performed by the trusted forwarder or the calldata length is less than + * 20 bytes (an address length). + */ + function _msgSender() internal view virtual override returns (address) { + uint256 calldataLength = msg.data.length; + uint256 contextSuffixLength = _contextSuffixLength(); + if (isTrustedForwarder(msg.sender) && calldataLength >= contextSuffixLength) { + return address(bytes20(msg.data[calldataLength - contextSuffixLength:])); + } else { + return super._msgSender(); + } + } + + /** + * @dev Override for `msg.data`. Defaults to the original `msg.data` whenever + * a call is not performed by the trusted forwarder or the calldata length is less than + * 20 bytes (an address length). + */ + function _msgData() internal view virtual override returns (bytes calldata) { + uint256 calldataLength = msg.data.length; + uint256 contextSuffixLength = _contextSuffixLength(); + if (isTrustedForwarder(msg.sender) && calldataLength >= contextSuffixLength) { + return msg.data[:calldataLength - contextSuffixLength]; + } else { + return super._msgData(); + } + } + + /** + * @dev ERC-2771 specifies the context as being a single address (20 bytes). + */ + function _contextSuffixLength() internal view virtual override returns (uint256) { + return 20; + } +} diff --git a/CVLByExample/UniswapHooks/lib/uniswap-hooks/lib/v4-core/lib/openzeppelin-contracts/contracts/metatx/ERC2771Forwarder.sol b/CVLByExample/UniswapHooks/lib/uniswap-hooks/lib/v4-core/lib/openzeppelin-contracts/contracts/metatx/ERC2771Forwarder.sol new file mode 100644 index 00000000..4815c1a1 --- /dev/null +++ b/CVLByExample/UniswapHooks/lib/uniswap-hooks/lib/v4-core/lib/openzeppelin-contracts/contracts/metatx/ERC2771Forwarder.sol @@ -0,0 +1,370 @@ +// SPDX-License-Identifier: MIT +// OpenZeppelin Contracts (last updated v5.0.0) (metatx/ERC2771Forwarder.sol) + +pragma solidity ^0.8.20; + +import {ERC2771Context} from "./ERC2771Context.sol"; +import {ECDSA} from "../utils/cryptography/ECDSA.sol"; +import {EIP712} from "../utils/cryptography/EIP712.sol"; +import {Nonces} from "../utils/Nonces.sol"; +import {Address} from "../utils/Address.sol"; + +/** + * @dev A forwarder compatible with ERC2771 contracts. See {ERC2771Context}. + * + * This forwarder operates on forward requests that include: + * + * * `from`: An address to operate on behalf of. It is required to be equal to the request signer. + * * `to`: The address that should be called. + * * `value`: The amount of native token to attach with the requested call. + * * `gas`: The amount of gas limit that will be forwarded with the requested call. + * * `nonce`: A unique transaction ordering identifier to avoid replayability and request invalidation. + * * `deadline`: A timestamp after which the request is not executable anymore. + * * `data`: Encoded `msg.data` to send with the requested call. + * + * Relayers are able to submit batches if they are processing a high volume of requests. With high + * throughput, relayers may run into limitations of the chain such as limits on the number of + * transactions in the mempool. In these cases the recommendation is to distribute the load among + * multiple accounts. + * + * NOTE: Batching requests includes an optional refund for unused `msg.value` that is achieved by + * performing a call with empty calldata. While this is within the bounds of ERC-2771 compliance, + * if the refund receiver happens to consider the forwarder a trusted forwarder, it MUST properly + * handle `msg.data.length == 0`. `ERC2771Context` in OpenZeppelin Contracts versions prior to 4.9.3 + * do not handle this properly. + * + * ==== Security Considerations + * + * If a relayer submits a forward request, it should be willing to pay up to 100% of the gas amount + * specified in the request. This contract does not implement any kind of retribution for this gas, + * and it is assumed that there is an out of band incentive for relayers to pay for execution on + * behalf of signers. Often, the relayer is operated by a project that will consider it a user + * acquisition cost. + * + * By offering to pay for gas, relayers are at risk of having that gas used by an attacker toward + * some other purpose that is not aligned with the expected out of band incentives. If you operate a + * relayer, consider whitelisting target contracts and function selectors. When relaying ERC-721 or + * ERC-1155 transfers specifically, consider rejecting the use of the `data` field, since it can be + * used to execute arbitrary code. + */ +contract ERC2771Forwarder is EIP712, Nonces { + using ECDSA for bytes32; + + struct ForwardRequestData { + address from; + address to; + uint256 value; + uint256 gas; + uint48 deadline; + bytes data; + bytes signature; + } + + bytes32 internal constant _FORWARD_REQUEST_TYPEHASH = + keccak256( + "ForwardRequest(address from,address to,uint256 value,uint256 gas,uint256 nonce,uint48 deadline,bytes data)" + ); + + /** + * @dev Emitted when a `ForwardRequest` is executed. + * + * NOTE: An unsuccessful forward request could be due to an invalid signature, an expired deadline, + * or simply a revert in the requested call. The contract guarantees that the relayer is not able to force + * the requested call to run out of gas. + */ + event ExecutedForwardRequest(address indexed signer, uint256 nonce, bool success); + + /** + * @dev The request `from` doesn't match with the recovered `signer`. + */ + error ERC2771ForwarderInvalidSigner(address signer, address from); + + /** + * @dev The `requestedValue` doesn't match with the available `msgValue`. + */ + error ERC2771ForwarderMismatchedValue(uint256 requestedValue, uint256 msgValue); + + /** + * @dev The request `deadline` has expired. + */ + error ERC2771ForwarderExpiredRequest(uint48 deadline); + + /** + * @dev The request target doesn't trust the `forwarder`. + */ + error ERC2771UntrustfulTarget(address target, address forwarder); + + /** + * @dev See {EIP712-constructor}. + */ + constructor(string memory name) EIP712(name, "1") {} + + /** + * @dev Returns `true` if a request is valid for a provided `signature` at the current block timestamp. + * + * A transaction is considered valid when the target trusts this forwarder, the request hasn't expired + * (deadline is not met), and the signer matches the `from` parameter of the signed request. + * + * NOTE: A request may return false here but it won't cause {executeBatch} to revert if a refund + * receiver is provided. + */ + function verify(ForwardRequestData calldata request) public view virtual returns (bool) { + (bool isTrustedForwarder, bool active, bool signerMatch, ) = _validate(request); + return isTrustedForwarder && active && signerMatch; + } + + /** + * @dev Executes a `request` on behalf of `signature`'s signer using the ERC-2771 protocol. The gas + * provided to the requested call may not be exactly the amount requested, but the call will not run + * out of gas. Will revert if the request is invalid or the call reverts, in this case the nonce is not consumed. + * + * Requirements: + * + * - The request value should be equal to the provided `msg.value`. + * - The request should be valid according to {verify}. + */ + function execute(ForwardRequestData calldata request) public payable virtual { + // We make sure that msg.value and request.value match exactly. + // If the request is invalid or the call reverts, this whole function + // will revert, ensuring value isn't stuck. + if (msg.value != request.value) { + revert ERC2771ForwarderMismatchedValue(request.value, msg.value); + } + + if (!_execute(request, true)) { + revert Address.FailedInnerCall(); + } + } + + /** + * @dev Batch version of {execute} with optional refunding and atomic execution. + * + * In case a batch contains at least one invalid request (see {verify}), the + * request will be skipped and the `refundReceiver` parameter will receive back the + * unused requested value at the end of the execution. This is done to prevent reverting + * the entire batch when a request is invalid or has already been submitted. + * + * If the `refundReceiver` is the `address(0)`, this function will revert when at least + * one of the requests was not valid instead of skipping it. This could be useful if + * a batch is required to get executed atomically (at least at the top-level). For example, + * refunding (and thus atomicity) can be opt-out if the relayer is using a service that avoids + * including reverted transactions. + * + * Requirements: + * + * - The sum of the requests' values should be equal to the provided `msg.value`. + * - All of the requests should be valid (see {verify}) when `refundReceiver` is the zero address. + * + * NOTE: Setting a zero `refundReceiver` guarantees an all-or-nothing requests execution only for + * the first-level forwarded calls. In case a forwarded request calls to a contract with another + * subcall, the second-level call may revert without the top-level call reverting. + */ + function executeBatch( + ForwardRequestData[] calldata requests, + address payable refundReceiver + ) public payable virtual { + bool atomic = refundReceiver == address(0); + + uint256 requestsValue; + uint256 refundValue; + + for (uint256 i; i < requests.length; ++i) { + requestsValue += requests[i].value; + bool success = _execute(requests[i], atomic); + if (!success) { + refundValue += requests[i].value; + } + } + + // The batch should revert if there's a mismatched msg.value provided + // to avoid request value tampering + if (requestsValue != msg.value) { + revert ERC2771ForwarderMismatchedValue(requestsValue, msg.value); + } + + // Some requests with value were invalid (possibly due to frontrunning). + // To avoid leaving ETH in the contract this value is refunded. + if (refundValue != 0) { + // We know refundReceiver != address(0) && requestsValue == msg.value + // meaning we can ensure refundValue is not taken from the original contract's balance + // and refundReceiver is a known account. + Address.sendValue(refundReceiver, refundValue); + } + } + + /** + * @dev Validates if the provided request can be executed at current block timestamp with + * the given `request.signature` on behalf of `request.signer`. + */ + function _validate( + ForwardRequestData calldata request + ) internal view virtual returns (bool isTrustedForwarder, bool active, bool signerMatch, address signer) { + (bool isValid, address recovered) = _recoverForwardRequestSigner(request); + + return ( + _isTrustedByTarget(request.to), + request.deadline >= block.timestamp, + isValid && recovered == request.from, + recovered + ); + } + + /** + * @dev Returns a tuple with the recovered the signer of an EIP712 forward request message hash + * and a boolean indicating if the signature is valid. + * + * NOTE: The signature is considered valid if {ECDSA-tryRecover} indicates no recover error for it. + */ + function _recoverForwardRequestSigner( + ForwardRequestData calldata request + ) internal view virtual returns (bool, address) { + (address recovered, ECDSA.RecoverError err, ) = _hashTypedDataV4( + keccak256( + abi.encode( + _FORWARD_REQUEST_TYPEHASH, + request.from, + request.to, + request.value, + request.gas, + nonces(request.from), + request.deadline, + keccak256(request.data) + ) + ) + ).tryRecover(request.signature); + + return (err == ECDSA.RecoverError.NoError, recovered); + } + + /** + * @dev Validates and executes a signed request returning the request call `success` value. + * + * Internal function without msg.value validation. + * + * Requirements: + * + * - The caller must have provided enough gas to forward with the call. + * - The request must be valid (see {verify}) if the `requireValidRequest` is true. + * + * Emits an {ExecutedForwardRequest} event. + * + * IMPORTANT: Using this function doesn't check that all the `msg.value` was sent, potentially + * leaving value stuck in the contract. + */ + function _execute( + ForwardRequestData calldata request, + bool requireValidRequest + ) internal virtual returns (bool success) { + (bool isTrustedForwarder, bool active, bool signerMatch, address signer) = _validate(request); + + // Need to explicitly specify if a revert is required since non-reverting is default for + // batches and reversion is opt-in since it could be useful in some scenarios + if (requireValidRequest) { + if (!isTrustedForwarder) { + revert ERC2771UntrustfulTarget(request.to, address(this)); + } + + if (!active) { + revert ERC2771ForwarderExpiredRequest(request.deadline); + } + + if (!signerMatch) { + revert ERC2771ForwarderInvalidSigner(signer, request.from); + } + } + + // Ignore an invalid request because requireValidRequest = false + if (isTrustedForwarder && signerMatch && active) { + // Nonce should be used before the call to prevent reusing by reentrancy + uint256 currentNonce = _useNonce(signer); + + uint256 reqGas = request.gas; + address to = request.to; + uint256 value = request.value; + bytes memory data = abi.encodePacked(request.data, request.from); + + uint256 gasLeft; + + assembly { + success := call(reqGas, to, value, add(data, 0x20), mload(data), 0, 0) + gasLeft := gas() + } + + _checkForwardedGas(gasLeft, request); + + emit ExecutedForwardRequest(signer, currentNonce, success); + } + } + + /** + * @dev Returns whether the target trusts this forwarder. + * + * This function performs a static call to the target contract calling the + * {ERC2771Context-isTrustedForwarder} function. + */ + function _isTrustedByTarget(address target) private view returns (bool) { + bytes memory encodedParams = abi.encodeCall(ERC2771Context.isTrustedForwarder, (address(this))); + + bool success; + uint256 returnSize; + uint256 returnValue; + /// @solidity memory-safe-assembly + assembly { + // Perform the staticcal and save the result in the scratch space. + // | Location | Content | Content (Hex) | + // |-----------|----------|--------------------------------------------------------------------| + // | | | result ↓ | + // | 0x00:0x1F | selector | 0x0000000000000000000000000000000000000000000000000000000000000001 | + success := staticcall(gas(), target, add(encodedParams, 0x20), mload(encodedParams), 0, 0x20) + returnSize := returndatasize() + returnValue := mload(0) + } + + return success && returnSize >= 0x20 && returnValue > 0; + } + + /** + * @dev Checks if the requested gas was correctly forwarded to the callee. + * + * As a consequence of https://eips.ethereum.org/EIPS/eip-150[EIP-150]: + * - At most `gasleft() - floor(gasleft() / 64)` is forwarded to the callee. + * - At least `floor(gasleft() / 64)` is kept in the caller. + * + * It reverts consuming all the available gas if the forwarded gas is not the requested gas. + * + * IMPORTANT: The `gasLeft` parameter should be measured exactly at the end of the forwarded call. + * Any gas consumed in between will make room for bypassing this check. + */ + function _checkForwardedGas(uint256 gasLeft, ForwardRequestData calldata request) private pure { + // To avoid insufficient gas griefing attacks, as referenced in https://ronan.eth.limo/blog/ethereum-gas-dangers/ + // + // A malicious relayer can attempt to shrink the gas forwarded so that the underlying call reverts out-of-gas + // but the forwarding itself still succeeds. In order to make sure that the subcall received sufficient gas, + // we will inspect gasleft() after the forwarding. + // + // Let X be the gas available before the subcall, such that the subcall gets at most X * 63 / 64. + // We can't know X after CALL dynamic costs, but we want it to be such that X * 63 / 64 >= req.gas. + // Let Y be the gas used in the subcall. gasleft() measured immediately after the subcall will be gasleft() = X - Y. + // If the subcall ran out of gas, then Y = X * 63 / 64 and gasleft() = X - Y = X / 64. + // Under this assumption req.gas / 63 > gasleft() is true is true if and only if + // req.gas / 63 > X / 64, or equivalently req.gas > X * 63 / 64. + // This means that if the subcall runs out of gas we are able to detect that insufficient gas was passed. + // + // We will now also see that req.gas / 63 > gasleft() implies that req.gas >= X * 63 / 64. + // The contract guarantees Y <= req.gas, thus gasleft() = X - Y >= X - req.gas. + // - req.gas / 63 > gasleft() + // - req.gas / 63 >= X - req.gas + // - req.gas >= X * 63 / 64 + // In other words if req.gas < X * 63 / 64 then req.gas / 63 <= gasleft(), thus if the relayer behaves honestly + // the forwarding does not revert. + if (gasLeft < request.gas / 63) { + // We explicitly trigger invalid opcode to consume all gas and bubble-up the effects, since + // neither revert or assert consume all gas since Solidity 0.8.20 + // https://docs.soliditylang.org/en/v0.8.20/control-structures.html#panic-via-assert-and-error-via-require + /// @solidity memory-safe-assembly + assembly { + invalid() + } + } + } +} diff --git a/CVLByExample/UniswapHooks/lib/uniswap-hooks/lib/v4-core/lib/openzeppelin-contracts/contracts/metatx/README.adoc b/CVLByExample/UniswapHooks/lib/uniswap-hooks/lib/v4-core/lib/openzeppelin-contracts/contracts/metatx/README.adoc new file mode 100644 index 00000000..c02fb103 --- /dev/null +++ b/CVLByExample/UniswapHooks/lib/uniswap-hooks/lib/v4-core/lib/openzeppelin-contracts/contracts/metatx/README.adoc @@ -0,0 +1,17 @@ += Meta Transactions + +[.readme-notice] +NOTE: This document is better viewed at https://docs.openzeppelin.com/contracts/api/metatx + +This directory includes contracts for adding meta-transaction capabilities (i.e. abstracting the execution context from the transaction origin) following the https://eips.ethereum.org/EIPS/eip-2771[ERC-2771 specification]. + +- {ERC2771Context}: Provides a mechanism to override the sender and calldata of the execution context (`msg.sender` and `msg.data`) with a custom value specified by a trusted forwarder. +- {ERC2771Forwarder}: A production-ready forwarder that relays operation requests signed off-chain by an EOA. + +== Core + +{{ERC2771Context}} + +== Utils + +{{ERC2771Forwarder}} diff --git a/CVLByExample/UniswapHooks/lib/uniswap-hooks/lib/v4-core/lib/openzeppelin-contracts/contracts/mocks/AccessManagedTarget.sol b/CVLByExample/UniswapHooks/lib/uniswap-hooks/lib/v4-core/lib/openzeppelin-contracts/contracts/mocks/AccessManagedTarget.sol new file mode 100644 index 00000000..673feeda --- /dev/null +++ b/CVLByExample/UniswapHooks/lib/uniswap-hooks/lib/v4-core/lib/openzeppelin-contracts/contracts/mocks/AccessManagedTarget.sol @@ -0,0 +1,34 @@ +// SPDX-License-Identifier: MIT + +pragma solidity ^0.8.20; + +import {AccessManaged} from "../access/manager/AccessManaged.sol"; +import {StorageSlot} from "../utils/StorageSlot.sol"; + +abstract contract AccessManagedTarget is AccessManaged { + event CalledRestricted(address caller); + event CalledUnrestricted(address caller); + event CalledFallback(address caller); + + function fnRestricted() public restricted { + emit CalledRestricted(msg.sender); + } + + function fnUnrestricted() public { + emit CalledUnrestricted(msg.sender); + } + + function setIsConsumingScheduledOp(bool isConsuming, bytes32 slot) external { + // Memory layout is 0x....<_consumingSchedule (boolean)> + bytes32 mask = bytes32(uint256(1 << 160)); + if (isConsuming) { + StorageSlot.getBytes32Slot(slot).value |= mask; + } else { + StorageSlot.getBytes32Slot(slot).value &= ~mask; + } + } + + fallback() external { + emit CalledFallback(msg.sender); + } +} diff --git a/CVLByExample/UniswapHooks/lib/uniswap-hooks/lib/v4-core/lib/openzeppelin-contracts/contracts/mocks/ArraysMock.sol b/CVLByExample/UniswapHooks/lib/uniswap-hooks/lib/v4-core/lib/openzeppelin-contracts/contracts/mocks/ArraysMock.sol new file mode 100644 index 00000000..a00def29 --- /dev/null +++ b/CVLByExample/UniswapHooks/lib/uniswap-hooks/lib/v4-core/lib/openzeppelin-contracts/contracts/mocks/ArraysMock.sol @@ -0,0 +1,51 @@ +// SPDX-License-Identifier: MIT + +pragma solidity ^0.8.20; + +import {Arrays} from "../utils/Arrays.sol"; + +contract Uint256ArraysMock { + using Arrays for uint256[]; + + uint256[] private _array; + + constructor(uint256[] memory array) { + _array = array; + } + + function findUpperBound(uint256 element) external view returns (uint256) { + return _array.findUpperBound(element); + } + + function unsafeAccess(uint256 pos) external view returns (uint256) { + return _array.unsafeAccess(pos).value; + } +} + +contract AddressArraysMock { + using Arrays for address[]; + + address[] private _array; + + constructor(address[] memory array) { + _array = array; + } + + function unsafeAccess(uint256 pos) external view returns (address) { + return _array.unsafeAccess(pos).value; + } +} + +contract Bytes32ArraysMock { + using Arrays for bytes32[]; + + bytes32[] private _array; + + constructor(bytes32[] memory array) { + _array = array; + } + + function unsafeAccess(uint256 pos) external view returns (bytes32) { + return _array.unsafeAccess(pos).value; + } +} diff --git a/CVLByExample/UniswapHooks/lib/uniswap-hooks/lib/v4-core/lib/openzeppelin-contracts/contracts/mocks/AuthorityMock.sol b/CVLByExample/UniswapHooks/lib/uniswap-hooks/lib/v4-core/lib/openzeppelin-contracts/contracts/mocks/AuthorityMock.sol new file mode 100644 index 00000000..bf2434b0 --- /dev/null +++ b/CVLByExample/UniswapHooks/lib/uniswap-hooks/lib/v4-core/lib/openzeppelin-contracts/contracts/mocks/AuthorityMock.sol @@ -0,0 +1,69 @@ +// SPDX-License-Identifier: MIT + +pragma solidity ^0.8.20; + +import {IAccessManaged} from "../access/manager/IAccessManaged.sol"; +import {IAuthority} from "../access/manager/IAuthority.sol"; + +contract NotAuthorityMock is IAuthority { + function canCall(address /* caller */, address /* target */, bytes4 /* selector */) external pure returns (bool) { + revert("AuthorityNoDelayMock: not implemented"); + } +} + +contract AuthorityNoDelayMock is IAuthority { + bool _immediate; + + function canCall( + address /* caller */, + address /* target */, + bytes4 /* selector */ + ) external view returns (bool immediate) { + return _immediate; + } + + function _setImmediate(bool immediate) external { + _immediate = immediate; + } +} + +contract AuthorityDelayMock { + bool _immediate; + uint32 _delay; + + function canCall( + address /* caller */, + address /* target */, + bytes4 /* selector */ + ) external view returns (bool immediate, uint32 delay) { + return (_immediate, _delay); + } + + function _setImmediate(bool immediate) external { + _immediate = immediate; + } + + function _setDelay(uint32 delay) external { + _delay = delay; + } +} + +contract AuthorityNoResponse { + function canCall(address /* caller */, address /* target */, bytes4 /* selector */) external view {} +} + +contract AuthoritiyObserveIsConsuming { + event ConsumeScheduledOpCalled(address caller, bytes data, bytes4 isConsuming); + + function canCall( + address /* caller */, + address /* target */, + bytes4 /* selector */ + ) external pure returns (bool immediate, uint32 delay) { + return (false, 1); + } + + function consumeScheduledOp(address caller, bytes memory data) public { + emit ConsumeScheduledOpCalled(caller, data, IAccessManaged(msg.sender).isConsumingScheduledOp()); + } +} diff --git a/CVLByExample/UniswapHooks/lib/uniswap-hooks/lib/v4-core/lib/openzeppelin-contracts/contracts/mocks/Base64Dirty.sol b/CVLByExample/UniswapHooks/lib/uniswap-hooks/lib/v4-core/lib/openzeppelin-contracts/contracts/mocks/Base64Dirty.sol new file mode 100644 index 00000000..238bd26a --- /dev/null +++ b/CVLByExample/UniswapHooks/lib/uniswap-hooks/lib/v4-core/lib/openzeppelin-contracts/contracts/mocks/Base64Dirty.sol @@ -0,0 +1,19 @@ +// SPDX-License-Identifier: MIT + +pragma solidity ^0.8.20; + +import {Base64} from "../utils/Base64.sol"; + +contract Base64Dirty { + struct A { + uint256 value; + } + + function encode(bytes memory input) public pure returns (string memory) { + A memory unused = A({value: type(uint256).max}); + // To silence warning + unused; + + return Base64.encode(input); + } +} diff --git a/CVLByExample/UniswapHooks/lib/uniswap-hooks/lib/v4-core/lib/openzeppelin-contracts/contracts/mocks/CallReceiverMock.sol b/CVLByExample/UniswapHooks/lib/uniswap-hooks/lib/v4-core/lib/openzeppelin-contracts/contracts/mocks/CallReceiverMock.sol new file mode 100644 index 00000000..e371c7db --- /dev/null +++ b/CVLByExample/UniswapHooks/lib/uniswap-hooks/lib/v4-core/lib/openzeppelin-contracts/contracts/mocks/CallReceiverMock.sol @@ -0,0 +1,73 @@ +// SPDX-License-Identifier: MIT + +pragma solidity ^0.8.20; + +contract CallReceiverMock { + event MockFunctionCalled(); + event MockFunctionCalledWithArgs(uint256 a, uint256 b); + + uint256[] private _array; + + function mockFunction() public payable returns (string memory) { + emit MockFunctionCalled(); + + return "0x1234"; + } + + function mockFunctionEmptyReturn() public payable { + emit MockFunctionCalled(); + } + + function mockFunctionWithArgs(uint256 a, uint256 b) public payable returns (string memory) { + emit MockFunctionCalledWithArgs(a, b); + + return "0x1234"; + } + + function mockFunctionNonPayable() public returns (string memory) { + emit MockFunctionCalled(); + + return "0x1234"; + } + + function mockStaticFunction() public pure returns (string memory) { + return "0x1234"; + } + + function mockFunctionRevertsNoReason() public payable { + revert(); + } + + function mockFunctionRevertsReason() public payable { + revert("CallReceiverMock: reverting"); + } + + function mockFunctionThrows() public payable { + assert(false); + } + + function mockFunctionOutOfGas() public payable { + for (uint256 i = 0; ; ++i) { + _array.push(i); + } + } + + function mockFunctionWritesStorage(bytes32 slot, bytes32 value) public returns (string memory) { + assembly { + sstore(slot, value) + } + return "0x1234"; + } +} + +contract CallReceiverMockTrustingForwarder is CallReceiverMock { + address private _trustedForwarder; + + constructor(address trustedForwarder_) { + _trustedForwarder = trustedForwarder_; + } + + function isTrustedForwarder(address forwarder) public view virtual returns (bool) { + return forwarder == _trustedForwarder; + } +} diff --git a/CVLByExample/UniswapHooks/lib/uniswap-hooks/lib/v4-core/lib/openzeppelin-contracts/contracts/mocks/ContextMock.sol b/CVLByExample/UniswapHooks/lib/uniswap-hooks/lib/v4-core/lib/openzeppelin-contracts/contracts/mocks/ContextMock.sol new file mode 100644 index 00000000..199b2a97 --- /dev/null +++ b/CVLByExample/UniswapHooks/lib/uniswap-hooks/lib/v4-core/lib/openzeppelin-contracts/contracts/mocks/ContextMock.sol @@ -0,0 +1,35 @@ +// SPDX-License-Identifier: MIT + +pragma solidity ^0.8.20; + +import {Context} from "../utils/Context.sol"; + +contract ContextMock is Context { + event Sender(address sender); + + function msgSender() public { + emit Sender(_msgSender()); + } + + event Data(bytes data, uint256 integerValue, string stringValue); + + function msgData(uint256 integerValue, string memory stringValue) public { + emit Data(_msgData(), integerValue, stringValue); + } + + event DataShort(bytes data); + + function msgDataShort() public { + emit DataShort(_msgData()); + } +} + +contract ContextMockCaller { + function callSender(ContextMock context) public { + context.msgSender(); + } + + function callData(ContextMock context, uint256 integerValue, string memory stringValue) public { + context.msgData(integerValue, stringValue); + } +} diff --git a/CVLByExample/UniswapHooks/lib/uniswap-hooks/lib/v4-core/lib/openzeppelin-contracts/contracts/mocks/DummyImplementation.sol b/CVLByExample/UniswapHooks/lib/uniswap-hooks/lib/v4-core/lib/openzeppelin-contracts/contracts/mocks/DummyImplementation.sol new file mode 100644 index 00000000..4925c89d --- /dev/null +++ b/CVLByExample/UniswapHooks/lib/uniswap-hooks/lib/v4-core/lib/openzeppelin-contracts/contracts/mocks/DummyImplementation.sol @@ -0,0 +1,65 @@ +// SPDX-License-Identifier: MIT + +pragma solidity ^0.8.20; + +import {ERC1967Utils} from "../proxy/ERC1967/ERC1967Utils.sol"; +import {StorageSlot} from "../utils/StorageSlot.sol"; + +abstract contract Impl { + function version() public pure virtual returns (string memory); +} + +contract DummyImplementation { + uint256 public value; + string public text; + uint256[] public values; + + function initializeNonPayable() public { + value = 10; + } + + function initializePayable() public payable { + value = 100; + } + + function initializeNonPayableWithValue(uint256 _value) public { + value = _value; + } + + function initializePayableWithValue(uint256 _value) public payable { + value = _value; + } + + function initialize(uint256 _value, string memory _text, uint256[] memory _values) public { + value = _value; + text = _text; + values = _values; + } + + function get() public pure returns (bool) { + return true; + } + + function version() public pure virtual returns (string memory) { + return "V1"; + } + + function reverts() public pure { + require(false, "DummyImplementation reverted"); + } + + // Use for forcing an unsafe TransparentUpgradeableProxy admin override + function unsafeOverrideAdmin(address newAdmin) public { + StorageSlot.getAddressSlot(ERC1967Utils.ADMIN_SLOT).value = newAdmin; + } +} + +contract DummyImplementationV2 is DummyImplementation { + function migrate(uint256 newVal) public payable { + value = newVal; + } + + function version() public pure override returns (string memory) { + return "V2"; + } +} diff --git a/CVLByExample/UniswapHooks/lib/uniswap-hooks/lib/v4-core/lib/openzeppelin-contracts/contracts/mocks/EIP712Verifier.sol b/CVLByExample/UniswapHooks/lib/uniswap-hooks/lib/v4-core/lib/openzeppelin-contracts/contracts/mocks/EIP712Verifier.sol new file mode 100644 index 00000000..fe32a218 --- /dev/null +++ b/CVLByExample/UniswapHooks/lib/uniswap-hooks/lib/v4-core/lib/openzeppelin-contracts/contracts/mocks/EIP712Verifier.sol @@ -0,0 +1,16 @@ +// SPDX-License-Identifier: MIT + +pragma solidity ^0.8.20; + +import {ECDSA} from "../utils/cryptography/ECDSA.sol"; +import {EIP712} from "../utils/cryptography/EIP712.sol"; + +abstract contract EIP712Verifier is EIP712 { + function verify(bytes memory signature, address signer, address mailTo, string memory mailContents) external view { + bytes32 digest = _hashTypedDataV4( + keccak256(abi.encode(keccak256("Mail(address to,string contents)"), mailTo, keccak256(bytes(mailContents)))) + ); + address recoveredSigner = ECDSA.recover(digest, signature); + require(recoveredSigner == signer); + } +} diff --git a/CVLByExample/UniswapHooks/lib/uniswap-hooks/lib/v4-core/lib/openzeppelin-contracts/contracts/mocks/ERC1271WalletMock.sol b/CVLByExample/UniswapHooks/lib/uniswap-hooks/lib/v4-core/lib/openzeppelin-contracts/contracts/mocks/ERC1271WalletMock.sol new file mode 100644 index 00000000..cba7d47d --- /dev/null +++ b/CVLByExample/UniswapHooks/lib/uniswap-hooks/lib/v4-core/lib/openzeppelin-contracts/contracts/mocks/ERC1271WalletMock.sol @@ -0,0 +1,24 @@ +// SPDX-License-Identifier: MIT + +pragma solidity ^0.8.20; + +import {Ownable} from "../access/Ownable.sol"; +import {IERC1271} from "../interfaces/IERC1271.sol"; +import {ECDSA} from "../utils/cryptography/ECDSA.sol"; + +contract ERC1271WalletMock is Ownable, IERC1271 { + constructor(address originalOwner) Ownable(originalOwner) {} + + function isValidSignature(bytes32 hash, bytes memory signature) public view returns (bytes4 magicValue) { + return ECDSA.recover(hash, signature) == owner() ? this.isValidSignature.selector : bytes4(0); + } +} + +contract ERC1271MaliciousMock is IERC1271 { + function isValidSignature(bytes32, bytes memory) public pure returns (bytes4) { + assembly { + mstore(0, 0xffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff) + return(0, 32) + } + } +} diff --git a/CVLByExample/UniswapHooks/lib/uniswap-hooks/lib/v4-core/lib/openzeppelin-contracts/contracts/mocks/ERC165/ERC165InterfacesSupported.sol b/CVLByExample/UniswapHooks/lib/uniswap-hooks/lib/v4-core/lib/openzeppelin-contracts/contracts/mocks/ERC165/ERC165InterfacesSupported.sol new file mode 100644 index 00000000..4010b210 --- /dev/null +++ b/CVLByExample/UniswapHooks/lib/uniswap-hooks/lib/v4-core/lib/openzeppelin-contracts/contracts/mocks/ERC165/ERC165InterfacesSupported.sol @@ -0,0 +1,58 @@ +// SPDX-License-Identifier: MIT + +pragma solidity ^0.8.20; + +import {IERC165} from "../../utils/introspection/IERC165.sol"; + +/** + * https://eips.ethereum.org/EIPS/eip-214#specification + * From the specification: + * > Any attempts to make state-changing operations inside an execution instance with STATIC set to true will instead + * throw an exception. + * > These operations include [...], LOG0, LOG1, LOG2, [...] + * + * therefore, because this contract is staticcall'd we need to not emit events (which is how solidity-coverage works) + * solidity-coverage ignores the /mocks folder, so we duplicate its implementation here to avoid instrumenting it + */ +contract SupportsInterfaceWithLookupMock is IERC165 { + /* + * bytes4(keccak256('supportsInterface(bytes4)')) == 0x01ffc9a7 + */ + bytes4 public constant INTERFACE_ID_ERC165 = 0x01ffc9a7; + + /** + * @dev A mapping of interface id to whether or not it's supported. + */ + mapping(bytes4 interfaceId => bool) private _supportedInterfaces; + + /** + * @dev A contract implementing SupportsInterfaceWithLookup + * implement ERC165 itself. + */ + constructor() { + _registerInterface(INTERFACE_ID_ERC165); + } + + /** + * @dev Implement supportsInterface(bytes4) using a lookup table. + */ + function supportsInterface(bytes4 interfaceId) public view override returns (bool) { + return _supportedInterfaces[interfaceId]; + } + + /** + * @dev Private method for registering an interface. + */ + function _registerInterface(bytes4 interfaceId) internal { + require(interfaceId != 0xffffffff, "ERC165InterfacesSupported: invalid interface id"); + _supportedInterfaces[interfaceId] = true; + } +} + +contract ERC165InterfacesSupported is SupportsInterfaceWithLookupMock { + constructor(bytes4[] memory interfaceIds) { + for (uint256 i = 0; i < interfaceIds.length; i++) { + _registerInterface(interfaceIds[i]); + } + } +} diff --git a/CVLByExample/UniswapHooks/lib/uniswap-hooks/lib/v4-core/lib/openzeppelin-contracts/contracts/mocks/ERC165/ERC165MaliciousData.sol b/CVLByExample/UniswapHooks/lib/uniswap-hooks/lib/v4-core/lib/openzeppelin-contracts/contracts/mocks/ERC165/ERC165MaliciousData.sol new file mode 100644 index 00000000..35427567 --- /dev/null +++ b/CVLByExample/UniswapHooks/lib/uniswap-hooks/lib/v4-core/lib/openzeppelin-contracts/contracts/mocks/ERC165/ERC165MaliciousData.sol @@ -0,0 +1,12 @@ +// SPDX-License-Identifier: MIT + +pragma solidity ^0.8.20; + +contract ERC165MaliciousData { + function supportsInterface(bytes4) public pure returns (bool) { + assembly { + mstore(0, 0xffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff) + return(0, 32) + } + } +} diff --git a/CVLByExample/UniswapHooks/lib/uniswap-hooks/lib/v4-core/lib/openzeppelin-contracts/contracts/mocks/ERC165/ERC165MissingData.sol b/CVLByExample/UniswapHooks/lib/uniswap-hooks/lib/v4-core/lib/openzeppelin-contracts/contracts/mocks/ERC165/ERC165MissingData.sol new file mode 100644 index 00000000..fec43391 --- /dev/null +++ b/CVLByExample/UniswapHooks/lib/uniswap-hooks/lib/v4-core/lib/openzeppelin-contracts/contracts/mocks/ERC165/ERC165MissingData.sol @@ -0,0 +1,7 @@ +// SPDX-License-Identifier: MIT + +pragma solidity ^0.8.20; + +contract ERC165MissingData { + function supportsInterface(bytes4 interfaceId) public view {} // missing return +} diff --git a/CVLByExample/UniswapHooks/lib/uniswap-hooks/lib/v4-core/lib/openzeppelin-contracts/contracts/mocks/ERC165/ERC165NotSupported.sol b/CVLByExample/UniswapHooks/lib/uniswap-hooks/lib/v4-core/lib/openzeppelin-contracts/contracts/mocks/ERC165/ERC165NotSupported.sol new file mode 100644 index 00000000..78ef9c8e --- /dev/null +++ b/CVLByExample/UniswapHooks/lib/uniswap-hooks/lib/v4-core/lib/openzeppelin-contracts/contracts/mocks/ERC165/ERC165NotSupported.sol @@ -0,0 +1,5 @@ +// SPDX-License-Identifier: MIT + +pragma solidity ^0.8.20; + +contract ERC165NotSupported {} diff --git a/CVLByExample/UniswapHooks/lib/uniswap-hooks/lib/v4-core/lib/openzeppelin-contracts/contracts/mocks/ERC165/ERC165ReturnBomb.sol b/CVLByExample/UniswapHooks/lib/uniswap-hooks/lib/v4-core/lib/openzeppelin-contracts/contracts/mocks/ERC165/ERC165ReturnBomb.sol new file mode 100644 index 00000000..4bfacfd6 --- /dev/null +++ b/CVLByExample/UniswapHooks/lib/uniswap-hooks/lib/v4-core/lib/openzeppelin-contracts/contracts/mocks/ERC165/ERC165ReturnBomb.sol @@ -0,0 +1,18 @@ +// SPDX-License-Identifier: MIT + +pragma solidity ^0.8.20; + +import {IERC165} from "../../utils/introspection/IERC165.sol"; + +contract ERC165ReturnBombMock is IERC165 { + function supportsInterface(bytes4 interfaceId) public pure override returns (bool) { + if (interfaceId == type(IERC165).interfaceId) { + assembly { + mstore(0, 1) + } + } + assembly { + return(0, 101500) + } + } +} diff --git a/CVLByExample/UniswapHooks/lib/uniswap-hooks/lib/v4-core/lib/openzeppelin-contracts/contracts/mocks/ERC2771ContextMock.sol b/CVLByExample/UniswapHooks/lib/uniswap-hooks/lib/v4-core/lib/openzeppelin-contracts/contracts/mocks/ERC2771ContextMock.sol new file mode 100644 index 00000000..33887cf4 --- /dev/null +++ b/CVLByExample/UniswapHooks/lib/uniswap-hooks/lib/v4-core/lib/openzeppelin-contracts/contracts/mocks/ERC2771ContextMock.sol @@ -0,0 +1,28 @@ +// SPDX-License-Identifier: MIT + +pragma solidity ^0.8.20; + +import {ContextMock} from "./ContextMock.sol"; +import {Context} from "../utils/Context.sol"; +import {Multicall} from "../utils/Multicall.sol"; +import {ERC2771Context} from "../metatx/ERC2771Context.sol"; + +// By inheriting from ERC2771Context, Context's internal functions are overridden automatically +contract ERC2771ContextMock is ContextMock, ERC2771Context, Multicall { + /// @custom:oz-upgrades-unsafe-allow constructor + constructor(address trustedForwarder) ERC2771Context(trustedForwarder) { + emit Sender(_msgSender()); // _msgSender() should be accessible during construction + } + + function _msgSender() internal view override(Context, ERC2771Context) returns (address) { + return ERC2771Context._msgSender(); + } + + function _msgData() internal view override(Context, ERC2771Context) returns (bytes calldata) { + return ERC2771Context._msgData(); + } + + function _contextSuffixLength() internal view override(Context, ERC2771Context) returns (uint256) { + return ERC2771Context._contextSuffixLength(); + } +} diff --git a/CVLByExample/UniswapHooks/lib/uniswap-hooks/lib/v4-core/lib/openzeppelin-contracts/contracts/mocks/ERC3156FlashBorrowerMock.sol b/CVLByExample/UniswapHooks/lib/uniswap-hooks/lib/v4-core/lib/openzeppelin-contracts/contracts/mocks/ERC3156FlashBorrowerMock.sol new file mode 100644 index 00000000..261fea17 --- /dev/null +++ b/CVLByExample/UniswapHooks/lib/uniswap-hooks/lib/v4-core/lib/openzeppelin-contracts/contracts/mocks/ERC3156FlashBorrowerMock.sol @@ -0,0 +1,53 @@ +// SPDX-License-Identifier: MIT + +pragma solidity ^0.8.20; + +import {IERC20} from "../token/ERC20/IERC20.sol"; +import {IERC3156FlashBorrower} from "../interfaces/IERC3156.sol"; +import {Address} from "../utils/Address.sol"; + +/** + * @dev WARNING: this IERC3156FlashBorrower mock implementation is for testing purposes ONLY. + * Writing a secure flash lock borrower is not an easy task, and should be done with the utmost care. + * This is not an example of how it should be done, and no pattern present in this mock should be considered secure. + * Following best practices, always have your contract properly audited before using them to manipulate important funds on + * live networks. + */ +contract ERC3156FlashBorrowerMock is IERC3156FlashBorrower { + bytes32 internal constant _RETURN_VALUE = keccak256("ERC3156FlashBorrower.onFlashLoan"); + + bool immutable _enableApprove; + bool immutable _enableReturn; + + event BalanceOf(address token, address account, uint256 value); + event TotalSupply(address token, uint256 value); + + constructor(bool enableReturn, bool enableApprove) { + _enableApprove = enableApprove; + _enableReturn = enableReturn; + } + + function onFlashLoan( + address /*initiator*/, + address token, + uint256 amount, + uint256 fee, + bytes calldata data + ) public returns (bytes32) { + require(msg.sender == token); + + emit BalanceOf(token, address(this), IERC20(token).balanceOf(address(this))); + emit TotalSupply(token, IERC20(token).totalSupply()); + + if (data.length > 0) { + // WARNING: This code is for testing purposes only! Do not use. + Address.functionCall(token, data); + } + + if (_enableApprove) { + IERC20(token).approve(token, amount + fee); + } + + return _enableReturn ? _RETURN_VALUE : bytes32(0); + } +} diff --git a/CVLByExample/UniswapHooks/lib/uniswap-hooks/lib/v4-core/lib/openzeppelin-contracts/contracts/mocks/EtherReceiverMock.sol b/CVLByExample/UniswapHooks/lib/uniswap-hooks/lib/v4-core/lib/openzeppelin-contracts/contracts/mocks/EtherReceiverMock.sol new file mode 100644 index 00000000..1b1c9363 --- /dev/null +++ b/CVLByExample/UniswapHooks/lib/uniswap-hooks/lib/v4-core/lib/openzeppelin-contracts/contracts/mocks/EtherReceiverMock.sol @@ -0,0 +1,17 @@ +// SPDX-License-Identifier: MIT + +pragma solidity ^0.8.20; + +contract EtherReceiverMock { + bool private _acceptEther; + + function setAcceptEther(bool acceptEther) public { + _acceptEther = acceptEther; + } + + receive() external payable { + if (!_acceptEther) { + revert(); + } + } +} diff --git a/CVLByExample/UniswapHooks/lib/uniswap-hooks/lib/v4-core/lib/openzeppelin-contracts/contracts/mocks/InitializableMock.sol b/CVLByExample/UniswapHooks/lib/uniswap-hooks/lib/v4-core/lib/openzeppelin-contracts/contracts/mocks/InitializableMock.sol new file mode 100644 index 00000000..7f76caac --- /dev/null +++ b/CVLByExample/UniswapHooks/lib/uniswap-hooks/lib/v4-core/lib/openzeppelin-contracts/contracts/mocks/InitializableMock.sol @@ -0,0 +1,130 @@ +// SPDX-License-Identifier: MIT + +pragma solidity ^0.8.20; + +import {Initializable} from "../proxy/utils/Initializable.sol"; + +/** + * @title InitializableMock + * @dev This contract is a mock to test initializable functionality + */ +contract InitializableMock is Initializable { + bool public initializerRan; + bool public onlyInitializingRan; + uint256 public x; + + function isInitializing() public view returns (bool) { + return _isInitializing(); + } + + function initialize() public initializer { + initializerRan = true; + } + + function initializeOnlyInitializing() public onlyInitializing { + onlyInitializingRan = true; + } + + function initializerNested() public initializer { + initialize(); + } + + function onlyInitializingNested() public initializer { + initializeOnlyInitializing(); + } + + function initializeWithX(uint256 _x) public payable initializer { + x = _x; + } + + function nonInitializable(uint256 _x) public payable { + x = _x; + } + + function fail() public pure { + require(false, "InitializableMock forced failure"); + } +} + +contract ConstructorInitializableMock is Initializable { + bool public initializerRan; + bool public onlyInitializingRan; + + constructor() initializer { + initialize(); + initializeOnlyInitializing(); + } + + function initialize() public initializer { + initializerRan = true; + } + + function initializeOnlyInitializing() public onlyInitializing { + onlyInitializingRan = true; + } +} + +contract ChildConstructorInitializableMock is ConstructorInitializableMock { + bool public childInitializerRan; + + constructor() initializer { + childInitialize(); + } + + function childInitialize() public initializer { + childInitializerRan = true; + } +} + +contract ReinitializerMock is Initializable { + uint256 public counter; + + function getInitializedVersion() public view returns (uint64) { + return _getInitializedVersion(); + } + + function initialize() public initializer { + doStuff(); + } + + function reinitialize(uint64 i) public reinitializer(i) { + doStuff(); + } + + function nestedReinitialize(uint64 i, uint64 j) public reinitializer(i) { + reinitialize(j); + } + + function chainReinitialize(uint64 i, uint64 j) public { + reinitialize(i); + reinitialize(j); + } + + function disableInitializers() public { + _disableInitializers(); + } + + function doStuff() public onlyInitializing { + counter++; + } +} + +contract DisableNew is Initializable { + constructor() { + _disableInitializers(); + } +} + +contract DisableOld is Initializable { + constructor() initializer {} +} + +contract DisableBad1 is DisableNew, DisableOld {} + +contract DisableBad2 is Initializable { + constructor() initializer { + _disableInitializers(); + } +} + +contract DisableOk is DisableOld, DisableNew {} diff --git a/CVLByExample/UniswapHooks/lib/uniswap-hooks/lib/v4-core/lib/openzeppelin-contracts/contracts/mocks/MulticallTest.sol b/CVLByExample/UniswapHooks/lib/uniswap-hooks/lib/v4-core/lib/openzeppelin-contracts/contracts/mocks/MulticallTest.sol new file mode 100644 index 00000000..74be7d8b --- /dev/null +++ b/CVLByExample/UniswapHooks/lib/uniswap-hooks/lib/v4-core/lib/openzeppelin-contracts/contracts/mocks/MulticallTest.sol @@ -0,0 +1,23 @@ +// SPDX-License-Identifier: MIT + +pragma solidity ^0.8.20; + +import {ERC20MulticallMock} from "./token/ERC20MulticallMock.sol"; + +contract MulticallTest { + function checkReturnValues( + ERC20MulticallMock multicallToken, + address[] calldata recipients, + uint256[] calldata amounts + ) external { + bytes[] memory calls = new bytes[](recipients.length); + for (uint256 i = 0; i < recipients.length; i++) { + calls[i] = abi.encodeCall(multicallToken.transfer, (recipients[i], amounts[i])); + } + + bytes[] memory results = multicallToken.multicall(calls); + for (uint256 i = 0; i < results.length; i++) { + require(abi.decode(results[i], (bool))); + } + } +} diff --git a/CVLByExample/UniswapHooks/lib/uniswap-hooks/lib/v4-core/lib/openzeppelin-contracts/contracts/mocks/MultipleInheritanceInitializableMocks.sol b/CVLByExample/UniswapHooks/lib/uniswap-hooks/lib/v4-core/lib/openzeppelin-contracts/contracts/mocks/MultipleInheritanceInitializableMocks.sol new file mode 100644 index 00000000..51030acd --- /dev/null +++ b/CVLByExample/UniswapHooks/lib/uniswap-hooks/lib/v4-core/lib/openzeppelin-contracts/contracts/mocks/MultipleInheritanceInitializableMocks.sol @@ -0,0 +1,131 @@ +// SPDX-License-Identifier: MIT + +pragma solidity ^0.8.20; + +import {Initializable} from "../proxy/utils/Initializable.sol"; + +// Sample contracts showing upgradeability with multiple inheritance. +// Child contract inherits from Father and Mother contracts, and Father extends from Gramps. +// +// Human +// / \ +// | Gramps +// | | +// Mother Father +// | | +// -- Child -- + +/** + * Sample base initializable contract that is a human + */ +contract SampleHuman is Initializable { + bool public isHuman; + + function initialize() public initializer { + __SampleHuman_init(); + } + + // solhint-disable-next-line func-name-mixedcase + function __SampleHuman_init() internal onlyInitializing { + __SampleHuman_init_unchained(); + } + + // solhint-disable-next-line func-name-mixedcase + function __SampleHuman_init_unchained() internal onlyInitializing { + isHuman = true; + } +} + +/** + * Sample base initializable contract that defines a field mother + */ +contract SampleMother is Initializable, SampleHuman { + uint256 public mother; + + function initialize(uint256 value) public initializer { + __SampleMother_init(value); + } + + // solhint-disable-next-line func-name-mixedcase + function __SampleMother_init(uint256 value) internal onlyInitializing { + __SampleHuman_init(); + __SampleMother_init_unchained(value); + } + + // solhint-disable-next-line func-name-mixedcase + function __SampleMother_init_unchained(uint256 value) internal onlyInitializing { + mother = value; + } +} + +/** + * Sample base initializable contract that defines a field gramps + */ +contract SampleGramps is Initializable, SampleHuman { + string public gramps; + + function initialize(string memory value) public initializer { + __SampleGramps_init(value); + } + + // solhint-disable-next-line func-name-mixedcase + function __SampleGramps_init(string memory value) internal onlyInitializing { + __SampleHuman_init(); + __SampleGramps_init_unchained(value); + } + + // solhint-disable-next-line func-name-mixedcase + function __SampleGramps_init_unchained(string memory value) internal onlyInitializing { + gramps = value; + } +} + +/** + * Sample base initializable contract that defines a field father and extends from gramps + */ +contract SampleFather is Initializable, SampleGramps { + uint256 public father; + + function initialize(string memory _gramps, uint256 _father) public initializer { + __SampleFather_init(_gramps, _father); + } + + // solhint-disable-next-line func-name-mixedcase + function __SampleFather_init(string memory _gramps, uint256 _father) internal onlyInitializing { + __SampleGramps_init(_gramps); + __SampleFather_init_unchained(_father); + } + + // solhint-disable-next-line func-name-mixedcase + function __SampleFather_init_unchained(uint256 _father) internal onlyInitializing { + father = _father; + } +} + +/** + * Child extends from mother, father (gramps) + */ +contract SampleChild is Initializable, SampleMother, SampleFather { + uint256 public child; + + function initialize(uint256 _mother, string memory _gramps, uint256 _father, uint256 _child) public initializer { + __SampleChild_init(_mother, _gramps, _father, _child); + } + + // solhint-disable-next-line func-name-mixedcase + function __SampleChild_init( + uint256 _mother, + string memory _gramps, + uint256 _father, + uint256 _child + ) internal onlyInitializing { + __SampleMother_init(_mother); + __SampleFather_init(_gramps, _father); + __SampleChild_init_unchained(_child); + } + + // solhint-disable-next-line func-name-mixedcase + function __SampleChild_init_unchained(uint256 _child) internal onlyInitializing { + child = _child; + } +} diff --git a/CVLByExample/UniswapHooks/lib/uniswap-hooks/lib/v4-core/lib/openzeppelin-contracts/contracts/mocks/PausableMock.sol b/CVLByExample/UniswapHooks/lib/uniswap-hooks/lib/v4-core/lib/openzeppelin-contracts/contracts/mocks/PausableMock.sol new file mode 100644 index 00000000..fa701e2c --- /dev/null +++ b/CVLByExample/UniswapHooks/lib/uniswap-hooks/lib/v4-core/lib/openzeppelin-contracts/contracts/mocks/PausableMock.sol @@ -0,0 +1,31 @@ +// SPDX-License-Identifier: MIT + +pragma solidity ^0.8.20; + +import {Pausable} from "../utils/Pausable.sol"; + +contract PausableMock is Pausable { + bool public drasticMeasureTaken; + uint256 public count; + + constructor() { + drasticMeasureTaken = false; + count = 0; + } + + function normalProcess() external whenNotPaused { + count++; + } + + function drasticMeasure() external whenPaused { + drasticMeasureTaken = true; + } + + function pause() external { + _pause(); + } + + function unpause() external { + _unpause(); + } +} diff --git a/CVLByExample/UniswapHooks/lib/uniswap-hooks/lib/v4-core/lib/openzeppelin-contracts/contracts/mocks/ReentrancyAttack.sol b/CVLByExample/UniswapHooks/lib/uniswap-hooks/lib/v4-core/lib/openzeppelin-contracts/contracts/mocks/ReentrancyAttack.sol new file mode 100644 index 00000000..3df2d1c2 --- /dev/null +++ b/CVLByExample/UniswapHooks/lib/uniswap-hooks/lib/v4-core/lib/openzeppelin-contracts/contracts/mocks/ReentrancyAttack.sol @@ -0,0 +1,12 @@ +// SPDX-License-Identifier: MIT + +pragma solidity ^0.8.20; + +import {Context} from "../utils/Context.sol"; + +contract ReentrancyAttack is Context { + function callSender(bytes calldata data) public { + (bool success, ) = _msgSender().call(data); + require(success, "ReentrancyAttack: failed call"); + } +} diff --git a/CVLByExample/UniswapHooks/lib/uniswap-hooks/lib/v4-core/lib/openzeppelin-contracts/contracts/mocks/ReentrancyMock.sol b/CVLByExample/UniswapHooks/lib/uniswap-hooks/lib/v4-core/lib/openzeppelin-contracts/contracts/mocks/ReentrancyMock.sol new file mode 100644 index 00000000..39e2d5ed --- /dev/null +++ b/CVLByExample/UniswapHooks/lib/uniswap-hooks/lib/v4-core/lib/openzeppelin-contracts/contracts/mocks/ReentrancyMock.sol @@ -0,0 +1,50 @@ +// SPDX-License-Identifier: MIT + +pragma solidity ^0.8.20; + +import {ReentrancyGuard} from "../utils/ReentrancyGuard.sol"; +import {ReentrancyAttack} from "./ReentrancyAttack.sol"; + +contract ReentrancyMock is ReentrancyGuard { + uint256 public counter; + + constructor() { + counter = 0; + } + + function callback() external nonReentrant { + _count(); + } + + function countLocalRecursive(uint256 n) public nonReentrant { + if (n > 0) { + _count(); + countLocalRecursive(n - 1); + } + } + + function countThisRecursive(uint256 n) public nonReentrant { + if (n > 0) { + _count(); + (bool success, ) = address(this).call(abi.encodeCall(this.countThisRecursive, (n - 1))); + require(success, "ReentrancyMock: failed call"); + } + } + + function countAndCall(ReentrancyAttack attacker) public nonReentrant { + _count(); + attacker.callSender(abi.encodeCall(this.callback, ())); + } + + function _count() private { + counter += 1; + } + + function guardedCheckEntered() public nonReentrant { + require(_reentrancyGuardEntered()); + } + + function unguardedCheckNotEntered() public view { + require(!_reentrancyGuardEntered()); + } +} diff --git a/CVLByExample/UniswapHooks/lib/uniswap-hooks/lib/v4-core/lib/openzeppelin-contracts/contracts/mocks/RegressionImplementation.sol b/CVLByExample/UniswapHooks/lib/uniswap-hooks/lib/v4-core/lib/openzeppelin-contracts/contracts/mocks/RegressionImplementation.sol new file mode 100644 index 00000000..19b9706d --- /dev/null +++ b/CVLByExample/UniswapHooks/lib/uniswap-hooks/lib/v4-core/lib/openzeppelin-contracts/contracts/mocks/RegressionImplementation.sol @@ -0,0 +1,61 @@ +// SPDX-License-Identifier: MIT + +pragma solidity ^0.8.20; + +import {Initializable} from "../proxy/utils/Initializable.sol"; + +contract Implementation1 is Initializable { + uint256 internal _value; + + function initialize() public initializer {} + + function setValue(uint256 _number) public { + _value = _number; + } +} + +contract Implementation2 is Initializable { + uint256 internal _value; + + function initialize() public initializer {} + + function setValue(uint256 _number) public { + _value = _number; + } + + function getValue() public view returns (uint256) { + return _value; + } +} + +contract Implementation3 is Initializable { + uint256 internal _value; + + function initialize() public initializer {} + + function setValue(uint256 _number) public { + _value = _number; + } + + function getValue(uint256 _number) public view returns (uint256) { + return _value + _number; + } +} + +contract Implementation4 is Initializable { + uint256 internal _value; + + function initialize() public initializer {} + + function setValue(uint256 _number) public { + _value = _number; + } + + function getValue() public view returns (uint256) { + return _value; + } + + fallback() external { + _value = 1; + } +} diff --git a/CVLByExample/UniswapHooks/lib/uniswap-hooks/lib/v4-core/lib/openzeppelin-contracts/contracts/mocks/SingleInheritanceInitializableMocks.sol b/CVLByExample/UniswapHooks/lib/uniswap-hooks/lib/v4-core/lib/openzeppelin-contracts/contracts/mocks/SingleInheritanceInitializableMocks.sol new file mode 100644 index 00000000..0bd3c614 --- /dev/null +++ b/CVLByExample/UniswapHooks/lib/uniswap-hooks/lib/v4-core/lib/openzeppelin-contracts/contracts/mocks/SingleInheritanceInitializableMocks.sol @@ -0,0 +1,49 @@ +// SPDX-License-Identifier: MIT + +pragma solidity ^0.8.20; + +import {Initializable} from "../proxy/utils/Initializable.sol"; + +/** + * @title MigratableMockV1 + * @dev This contract is a mock to test initializable functionality through migrations + */ +contract MigratableMockV1 is Initializable { + uint256 public x; + + function initialize(uint256 value) public payable initializer { + x = value; + } +} + +/** + * @title MigratableMockV2 + * @dev This contract is a mock to test migratable functionality with params + */ +contract MigratableMockV2 is MigratableMockV1 { + bool internal _migratedV2; + uint256 public y; + + function migrate(uint256 value, uint256 anotherValue) public payable { + require(!_migratedV2); + x = value; + y = anotherValue; + _migratedV2 = true; + } +} + +/** + * @title MigratableMockV3 + * @dev This contract is a mock to test migratable functionality without params + */ +contract MigratableMockV3 is MigratableMockV2 { + bool internal _migratedV3; + + function migrate() public payable { + require(!_migratedV3); + uint256 oldX = x; + x = y; + y = oldX; + _migratedV3 = true; + } +} diff --git a/CVLByExample/UniswapHooks/lib/uniswap-hooks/lib/v4-core/lib/openzeppelin-contracts/contracts/mocks/Stateless.sol b/CVLByExample/UniswapHooks/lib/uniswap-hooks/lib/v4-core/lib/openzeppelin-contracts/contracts/mocks/Stateless.sol new file mode 100644 index 00000000..56f5b4c6 --- /dev/null +++ b/CVLByExample/UniswapHooks/lib/uniswap-hooks/lib/v4-core/lib/openzeppelin-contracts/contracts/mocks/Stateless.sol @@ -0,0 +1,36 @@ +// SPDX-License-Identifier: MIT + +pragma solidity ^0.8.20; + +// We keep these imports and a dummy contract just to we can run the test suite after transpilation. + +import {Address} from "../utils/Address.sol"; +import {Arrays} from "../utils/Arrays.sol"; +import {AuthorityUtils} from "../access/manager/AuthorityUtils.sol"; +import {Base64} from "../utils/Base64.sol"; +import {BitMaps} from "../utils/structs/BitMaps.sol"; +import {Checkpoints} from "../utils/structs/Checkpoints.sol"; +import {Clones} from "../proxy/Clones.sol"; +import {Create2} from "../utils/Create2.sol"; +import {DoubleEndedQueue} from "../utils/structs/DoubleEndedQueue.sol"; +import {ECDSA} from "../utils/cryptography/ECDSA.sol"; +import {EnumerableMap} from "../utils/structs/EnumerableMap.sol"; +import {EnumerableSet} from "../utils/structs/EnumerableSet.sol"; +import {ERC1155Holder} from "../token/ERC1155/utils/ERC1155Holder.sol"; +import {ERC165} from "../utils/introspection/ERC165.sol"; +import {ERC165Checker} from "../utils/introspection/ERC165Checker.sol"; +import {ERC1967Utils} from "../proxy/ERC1967/ERC1967Utils.sol"; +import {ERC721Holder} from "../token/ERC721/utils/ERC721Holder.sol"; +import {Math} from "../utils/math/Math.sol"; +import {MerkleProof} from "../utils/cryptography/MerkleProof.sol"; +import {MessageHashUtils} from "../utils/cryptography/MessageHashUtils.sol"; +import {SafeCast} from "../utils/math/SafeCast.sol"; +import {SafeERC20} from "../token/ERC20/utils/SafeERC20.sol"; +import {ShortStrings} from "../utils/ShortStrings.sol"; +import {SignatureChecker} from "../utils/cryptography/SignatureChecker.sol"; +import {SignedMath} from "../utils/math/SignedMath.sol"; +import {StorageSlot} from "../utils/StorageSlot.sol"; +import {Strings} from "../utils/Strings.sol"; +import {Time} from "../utils/types/Time.sol"; + +contract Dummy1234 {} diff --git a/CVLByExample/UniswapHooks/lib/uniswap-hooks/lib/v4-core/lib/openzeppelin-contracts/contracts/mocks/StorageSlotMock.sol b/CVLByExample/UniswapHooks/lib/uniswap-hooks/lib/v4-core/lib/openzeppelin-contracts/contracts/mocks/StorageSlotMock.sol new file mode 100644 index 00000000..dbdad7a2 --- /dev/null +++ b/CVLByExample/UniswapHooks/lib/uniswap-hooks/lib/v4-core/lib/openzeppelin-contracts/contracts/mocks/StorageSlotMock.sol @@ -0,0 +1,77 @@ +// SPDX-License-Identifier: MIT + +pragma solidity ^0.8.20; + +import {StorageSlot} from "../utils/StorageSlot.sol"; + +contract StorageSlotMock { + using StorageSlot for *; + + function setBoolean(bytes32 slot, bool value) public { + slot.getBooleanSlot().value = value; + } + + function setAddress(bytes32 slot, address value) public { + slot.getAddressSlot().value = value; + } + + function setBytes32(bytes32 slot, bytes32 value) public { + slot.getBytes32Slot().value = value; + } + + function setUint256(bytes32 slot, uint256 value) public { + slot.getUint256Slot().value = value; + } + + function getBoolean(bytes32 slot) public view returns (bool) { + return slot.getBooleanSlot().value; + } + + function getAddress(bytes32 slot) public view returns (address) { + return slot.getAddressSlot().value; + } + + function getBytes32(bytes32 slot) public view returns (bytes32) { + return slot.getBytes32Slot().value; + } + + function getUint256(bytes32 slot) public view returns (uint256) { + return slot.getUint256Slot().value; + } + + mapping(uint256 key => string) public stringMap; + + function setString(bytes32 slot, string calldata value) public { + slot.getStringSlot().value = value; + } + + function setStringStorage(uint256 key, string calldata value) public { + stringMap[key].getStringSlot().value = value; + } + + function getString(bytes32 slot) public view returns (string memory) { + return slot.getStringSlot().value; + } + + function getStringStorage(uint256 key) public view returns (string memory) { + return stringMap[key].getStringSlot().value; + } + + mapping(uint256 key => bytes) public bytesMap; + + function setBytes(bytes32 slot, bytes calldata value) public { + slot.getBytesSlot().value = value; + } + + function setBytesStorage(uint256 key, bytes calldata value) public { + bytesMap[key].getBytesSlot().value = value; + } + + function getBytes(bytes32 slot) public view returns (bytes memory) { + return slot.getBytesSlot().value; + } + + function getBytesStorage(uint256 key) public view returns (bytes memory) { + return bytesMap[key].getBytesSlot().value; + } +} diff --git a/CVLByExample/UniswapHooks/lib/uniswap-hooks/lib/v4-core/lib/openzeppelin-contracts/contracts/mocks/TimelockReentrant.sol b/CVLByExample/UniswapHooks/lib/uniswap-hooks/lib/v4-core/lib/openzeppelin-contracts/contracts/mocks/TimelockReentrant.sol new file mode 100644 index 00000000..aab676a5 --- /dev/null +++ b/CVLByExample/UniswapHooks/lib/uniswap-hooks/lib/v4-core/lib/openzeppelin-contracts/contracts/mocks/TimelockReentrant.sol @@ -0,0 +1,26 @@ +// SPDX-License-Identifier: MIT +pragma solidity ^0.8.20; + +import {Address} from "../utils/Address.sol"; + +contract TimelockReentrant { + address private _reenterTarget; + bytes private _reenterData; + bool _reentered; + + function disableReentrancy() external { + _reentered = true; + } + + function enableRentrancy(address target, bytes calldata data) external { + _reenterTarget = target; + _reenterData = data; + } + + function reenter() external { + if (!_reentered) { + _reentered = true; + Address.functionCall(_reenterTarget, _reenterData); + } + } +} diff --git a/CVLByExample/UniswapHooks/lib/uniswap-hooks/lib/v4-core/lib/openzeppelin-contracts/contracts/mocks/UpgradeableBeaconMock.sol b/CVLByExample/UniswapHooks/lib/uniswap-hooks/lib/v4-core/lib/openzeppelin-contracts/contracts/mocks/UpgradeableBeaconMock.sol new file mode 100644 index 00000000..354ac02f --- /dev/null +++ b/CVLByExample/UniswapHooks/lib/uniswap-hooks/lib/v4-core/lib/openzeppelin-contracts/contracts/mocks/UpgradeableBeaconMock.sol @@ -0,0 +1,27 @@ +// SPDX-License-Identifier: MIT +pragma solidity ^0.8.20; + +import {IBeacon} from "../proxy/beacon/IBeacon.sol"; + +contract UpgradeableBeaconMock is IBeacon { + address public implementation; + + constructor(address impl) { + implementation = impl; + } +} + +interface IProxyExposed { + // solhint-disable-next-line func-name-mixedcase + function $getBeacon() external view returns (address); +} + +contract UpgradeableBeaconReentrantMock is IBeacon { + error BeaconProxyBeaconSlotAddress(address beacon); + + function implementation() external view override returns (address) { + // Revert with the beacon seen in the proxy at the moment of calling to check if it's + // set before the call. + revert BeaconProxyBeaconSlotAddress(IProxyExposed(msg.sender).$getBeacon()); + } +} diff --git a/CVLByExample/UniswapHooks/lib/uniswap-hooks/lib/v4-core/lib/openzeppelin-contracts/contracts/mocks/VotesMock.sol b/CVLByExample/UniswapHooks/lib/uniswap-hooks/lib/v4-core/lib/openzeppelin-contracts/contracts/mocks/VotesMock.sol new file mode 100644 index 00000000..e28d6b55 --- /dev/null +++ b/CVLByExample/UniswapHooks/lib/uniswap-hooks/lib/v4-core/lib/openzeppelin-contracts/contracts/mocks/VotesMock.sol @@ -0,0 +1,42 @@ +// SPDX-License-Identifier: MIT + +pragma solidity ^0.8.20; + +import {Votes} from "../governance/utils/Votes.sol"; + +abstract contract VotesMock is Votes { + mapping(address voter => uint256) private _votingUnits; + + function getTotalSupply() public view returns (uint256) { + return _getTotalSupply(); + } + + function delegate(address account, address newDelegation) public { + return _delegate(account, newDelegation); + } + + function _getVotingUnits(address account) internal view override returns (uint256) { + return _votingUnits[account]; + } + + function _mint(address account, uint256 votes) internal { + _votingUnits[account] += votes; + _transferVotingUnits(address(0), account, votes); + } + + function _burn(address account, uint256 votes) internal { + _votingUnits[account] += votes; + _transferVotingUnits(account, address(0), votes); + } +} + +abstract contract VotesTimestampMock is VotesMock { + function clock() public view override returns (uint48) { + return uint48(block.timestamp); + } + + // solhint-disable-next-line func-name-mixedcase + function CLOCK_MODE() public view virtual override returns (string memory) { + return "mode=timestamp"; + } +} diff --git a/CVLByExample/UniswapHooks/lib/uniswap-hooks/lib/v4-core/lib/openzeppelin-contracts/contracts/mocks/compound/CompTimelock.sol b/CVLByExample/UniswapHooks/lib/uniswap-hooks/lib/v4-core/lib/openzeppelin-contracts/contracts/mocks/compound/CompTimelock.sol new file mode 100644 index 00000000..c72ed083 --- /dev/null +++ b/CVLByExample/UniswapHooks/lib/uniswap-hooks/lib/v4-core/lib/openzeppelin-contracts/contracts/mocks/compound/CompTimelock.sol @@ -0,0 +1,174 @@ +// SPDX-License-Identifier: BSD-3-Clause +// solhint-disable private-vars-leading-underscore +/** + * Copyright 2020 Compound Labs, Inc. + * + * Redistribution and use in source and binary forms, with or without modification, are permitted provided that the + * following conditions are met: + * + * 1. Redistributions of source code must retain the above copyright notice, this list of conditions and the following + * disclaimer. + * + * 2. Redistributions in binary form must reproduce the above copyright notice, this list of conditions and the + * following disclaimer in the documentation and/or other materials provided with the distribution. + * + * 3. Neither the name of the copyright holder nor the names of its contributors may be used to endorse or promote + * products derived from this software without specific prior written permission. + * + * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, + * INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE + * DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, + * SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR + * SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, + * WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE + * OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. + */ + +pragma solidity ^0.8.20; + +contract CompTimelock { + event NewAdmin(address indexed newAdmin); + event NewPendingAdmin(address indexed newPendingAdmin); + event NewDelay(uint256 indexed newDelay); + event CancelTransaction( + bytes32 indexed txHash, + address indexed target, + uint256 value, + string signature, + bytes data, + uint256 eta + ); + event ExecuteTransaction( + bytes32 indexed txHash, + address indexed target, + uint256 value, + string signature, + bytes data, + uint256 eta + ); + event QueueTransaction( + bytes32 indexed txHash, + address indexed target, + uint256 value, + string signature, + bytes data, + uint256 eta + ); + + uint256 public constant GRACE_PERIOD = 14 days; + uint256 public constant MINIMUM_DELAY = 2 days; + uint256 public constant MAXIMUM_DELAY = 30 days; + + address public admin; + address public pendingAdmin; + uint256 public delay; + + mapping(bytes32 => bool) public queuedTransactions; + + constructor(address admin_, uint256 delay_) { + require(delay_ >= MINIMUM_DELAY, "Timelock::constructor: Delay must exceed minimum delay."); + require(delay_ <= MAXIMUM_DELAY, "Timelock::setDelay: Delay must not exceed maximum delay."); + + admin = admin_; + delay = delay_; + } + + receive() external payable {} + + function setDelay(uint256 delay_) public { + require(msg.sender == address(this), "Timelock::setDelay: Call must come from Timelock."); + require(delay_ >= MINIMUM_DELAY, "Timelock::setDelay: Delay must exceed minimum delay."); + require(delay_ <= MAXIMUM_DELAY, "Timelock::setDelay: Delay must not exceed maximum delay."); + delay = delay_; + + emit NewDelay(delay); + } + + function acceptAdmin() public { + require(msg.sender == pendingAdmin, "Timelock::acceptAdmin: Call must come from pendingAdmin."); + admin = msg.sender; + pendingAdmin = address(0); + + emit NewAdmin(admin); + } + + function setPendingAdmin(address pendingAdmin_) public { + require(msg.sender == address(this), "Timelock::setPendingAdmin: Call must come from Timelock."); + pendingAdmin = pendingAdmin_; + + emit NewPendingAdmin(pendingAdmin); + } + + function queueTransaction( + address target, + uint256 value, + string memory signature, + bytes memory data, + uint256 eta + ) public returns (bytes32) { + require(msg.sender == admin, "Timelock::queueTransaction: Call must come from admin."); + require( + eta >= getBlockTimestamp() + delay, + "Timelock::queueTransaction: Estimated execution block must satisfy delay." + ); + + bytes32 txHash = keccak256(abi.encode(target, value, signature, data, eta)); + queuedTransactions[txHash] = true; + + emit QueueTransaction(txHash, target, value, signature, data, eta); + return txHash; + } + + function cancelTransaction( + address target, + uint256 value, + string memory signature, + bytes memory data, + uint256 eta + ) public { + require(msg.sender == admin, "Timelock::cancelTransaction: Call must come from admin."); + + bytes32 txHash = keccak256(abi.encode(target, value, signature, data, eta)); + queuedTransactions[txHash] = false; + + emit CancelTransaction(txHash, target, value, signature, data, eta); + } + + function executeTransaction( + address target, + uint256 value, + string memory signature, + bytes memory data, + uint256 eta + ) public payable returns (bytes memory) { + require(msg.sender == admin, "Timelock::executeTransaction: Call must come from admin."); + + bytes32 txHash = keccak256(abi.encode(target, value, signature, data, eta)); + require(queuedTransactions[txHash], "Timelock::executeTransaction: Transaction hasn't been queued."); + require(getBlockTimestamp() >= eta, "Timelock::executeTransaction: Transaction hasn't surpassed time lock."); + require(getBlockTimestamp() <= eta + GRACE_PERIOD, "Timelock::executeTransaction: Transaction is stale."); + + queuedTransactions[txHash] = false; + + bytes memory callData; + + if (bytes(signature).length == 0) { + callData = data; + } else { + callData = abi.encodePacked(bytes4(keccak256(bytes(signature))), data); + } + + // solium-disable-next-line security/no-call-value + (bool success, bytes memory returnData) = target.call{value: value}(callData); + require(success, "Timelock::executeTransaction: Transaction execution reverted."); + + emit ExecuteTransaction(txHash, target, value, signature, data, eta); + + return returnData; + } + + function getBlockTimestamp() internal view returns (uint256) { + // solium-disable-next-line security/no-block-members + return block.timestamp; + } +} diff --git a/CVLByExample/UniswapHooks/lib/uniswap-hooks/lib/v4-core/lib/openzeppelin-contracts/contracts/mocks/governance/GovernorMock.sol b/CVLByExample/UniswapHooks/lib/uniswap-hooks/lib/v4-core/lib/openzeppelin-contracts/contracts/mocks/governance/GovernorMock.sol new file mode 100644 index 00000000..69668d28 --- /dev/null +++ b/CVLByExample/UniswapHooks/lib/uniswap-hooks/lib/v4-core/lib/openzeppelin-contracts/contracts/mocks/governance/GovernorMock.sol @@ -0,0 +1,14 @@ +// SPDX-License-Identifier: MIT + +pragma solidity ^0.8.20; + +import {Governor} from "../../governance/Governor.sol"; +import {GovernorSettings} from "../../governance/extensions/GovernorSettings.sol"; +import {GovernorCountingSimple} from "../../governance/extensions/GovernorCountingSimple.sol"; +import {GovernorVotesQuorumFraction} from "../../governance/extensions/GovernorVotesQuorumFraction.sol"; + +abstract contract GovernorMock is GovernorSettings, GovernorVotesQuorumFraction, GovernorCountingSimple { + function proposalThreshold() public view override(Governor, GovernorSettings) returns (uint256) { + return super.proposalThreshold(); + } +} diff --git a/CVLByExample/UniswapHooks/lib/uniswap-hooks/lib/v4-core/lib/openzeppelin-contracts/contracts/mocks/governance/GovernorPreventLateQuorumMock.sol b/CVLByExample/UniswapHooks/lib/uniswap-hooks/lib/v4-core/lib/openzeppelin-contracts/contracts/mocks/governance/GovernorPreventLateQuorumMock.sol new file mode 100644 index 00000000..fde0863c --- /dev/null +++ b/CVLByExample/UniswapHooks/lib/uniswap-hooks/lib/v4-core/lib/openzeppelin-contracts/contracts/mocks/governance/GovernorPreventLateQuorumMock.sol @@ -0,0 +1,46 @@ +// SPDX-License-Identifier: MIT + +pragma solidity ^0.8.20; + +import {Governor} from "../../governance/Governor.sol"; +import {GovernorPreventLateQuorum} from "../../governance/extensions/GovernorPreventLateQuorum.sol"; +import {GovernorSettings} from "../../governance/extensions/GovernorSettings.sol"; +import {GovernorCountingSimple} from "../../governance/extensions/GovernorCountingSimple.sol"; +import {GovernorVotes} from "../../governance/extensions/GovernorVotes.sol"; + +abstract contract GovernorPreventLateQuorumMock is + GovernorSettings, + GovernorVotes, + GovernorCountingSimple, + GovernorPreventLateQuorum +{ + uint256 private _quorum; + + constructor(uint256 quorum_) { + _quorum = quorum_; + } + + function quorum(uint256) public view override returns (uint256) { + return _quorum; + } + + function proposalDeadline( + uint256 proposalId + ) public view override(Governor, GovernorPreventLateQuorum) returns (uint256) { + return super.proposalDeadline(proposalId); + } + + function proposalThreshold() public view override(Governor, GovernorSettings) returns (uint256) { + return super.proposalThreshold(); + } + + function _castVote( + uint256 proposalId, + address account, + uint8 support, + string memory reason, + bytes memory params + ) internal override(Governor, GovernorPreventLateQuorum) returns (uint256) { + return super._castVote(proposalId, account, support, reason, params); + } +} diff --git a/CVLByExample/UniswapHooks/lib/uniswap-hooks/lib/v4-core/lib/openzeppelin-contracts/contracts/mocks/governance/GovernorStorageMock.sol b/CVLByExample/UniswapHooks/lib/uniswap-hooks/lib/v4-core/lib/openzeppelin-contracts/contracts/mocks/governance/GovernorStorageMock.sol new file mode 100644 index 00000000..88c6bf90 --- /dev/null +++ b/CVLByExample/UniswapHooks/lib/uniswap-hooks/lib/v4-core/lib/openzeppelin-contracts/contracts/mocks/governance/GovernorStorageMock.sol @@ -0,0 +1,79 @@ +// SPDX-License-Identifier: MIT + +pragma solidity ^0.8.19; + +import {IGovernor, Governor} from "../../governance/Governor.sol"; +import {GovernorTimelockControl} from "../../governance/extensions/GovernorTimelockControl.sol"; +import {GovernorSettings} from "../../governance/extensions/GovernorSettings.sol"; +import {GovernorCountingSimple} from "../../governance/extensions/GovernorCountingSimple.sol"; +import {GovernorVotesQuorumFraction} from "../../governance/extensions/GovernorVotesQuorumFraction.sol"; +import {GovernorStorage} from "../../governance/extensions/GovernorStorage.sol"; + +abstract contract GovernorStorageMock is + GovernorSettings, + GovernorTimelockControl, + GovernorVotesQuorumFraction, + GovernorCountingSimple, + GovernorStorage +{ + function quorum(uint256 blockNumber) public view override(Governor, GovernorVotesQuorumFraction) returns (uint256) { + return super.quorum(blockNumber); + } + + function state(uint256 proposalId) public view override(Governor, GovernorTimelockControl) returns (ProposalState) { + return super.state(proposalId); + } + + function proposalThreshold() public view override(Governor, GovernorSettings) returns (uint256) { + return super.proposalThreshold(); + } + + function proposalNeedsQueuing( + uint256 proposalId + ) public view virtual override(Governor, GovernorTimelockControl) returns (bool) { + return super.proposalNeedsQueuing(proposalId); + } + + function _propose( + address[] memory targets, + uint256[] memory values, + bytes[] memory calldatas, + string memory description, + address proposer + ) internal virtual override(Governor, GovernorStorage) returns (uint256) { + return super._propose(targets, values, calldatas, description, proposer); + } + + function _queueOperations( + uint256 proposalId, + address[] memory targets, + uint256[] memory values, + bytes[] memory calldatas, + bytes32 descriptionHash + ) internal override(Governor, GovernorTimelockControl) returns (uint48) { + return super._queueOperations(proposalId, targets, values, calldatas, descriptionHash); + } + + function _executeOperations( + uint256 proposalId, + address[] memory targets, + uint256[] memory values, + bytes[] memory calldatas, + bytes32 descriptionHash + ) internal override(Governor, GovernorTimelockControl) { + super._executeOperations(proposalId, targets, values, calldatas, descriptionHash); + } + + function _cancel( + address[] memory targets, + uint256[] memory values, + bytes[] memory calldatas, + bytes32 descriptionHash + ) internal override(Governor, GovernorTimelockControl) returns (uint256) { + return super._cancel(targets, values, calldatas, descriptionHash); + } + + function _executor() internal view override(Governor, GovernorTimelockControl) returns (address) { + return super._executor(); + } +} diff --git a/CVLByExample/UniswapHooks/lib/uniswap-hooks/lib/v4-core/lib/openzeppelin-contracts/contracts/mocks/governance/GovernorTimelockAccessMock.sol b/CVLByExample/UniswapHooks/lib/uniswap-hooks/lib/v4-core/lib/openzeppelin-contracts/contracts/mocks/governance/GovernorTimelockAccessMock.sol new file mode 100644 index 00000000..3d1bbeee --- /dev/null +++ b/CVLByExample/UniswapHooks/lib/uniswap-hooks/lib/v4-core/lib/openzeppelin-contracts/contracts/mocks/governance/GovernorTimelockAccessMock.sol @@ -0,0 +1,70 @@ +// SPDX-License-Identifier: MIT + +pragma solidity ^0.8.20; + +import {IGovernor, Governor} from "../../governance/Governor.sol"; +import {GovernorTimelockAccess} from "../../governance/extensions/GovernorTimelockAccess.sol"; +import {GovernorSettings} from "../../governance/extensions/GovernorSettings.sol"; +import {GovernorCountingSimple} from "../../governance/extensions/GovernorCountingSimple.sol"; +import {GovernorVotesQuorumFraction} from "../../governance/extensions/GovernorVotesQuorumFraction.sol"; + +abstract contract GovernorTimelockAccessMock is + GovernorSettings, + GovernorTimelockAccess, + GovernorVotesQuorumFraction, + GovernorCountingSimple +{ + function nonGovernanceFunction() external {} + + function quorum(uint256 blockNumber) public view override(Governor, GovernorVotesQuorumFraction) returns (uint256) { + return super.quorum(blockNumber); + } + + function proposalThreshold() public view override(Governor, GovernorSettings) returns (uint256) { + return super.proposalThreshold(); + } + + function proposalNeedsQueuing( + uint256 proposalId + ) public view virtual override(Governor, GovernorTimelockAccess) returns (bool) { + return super.proposalNeedsQueuing(proposalId); + } + + function propose( + address[] memory targets, + uint256[] memory values, + bytes[] memory calldatas, + string memory description + ) public override(Governor, GovernorTimelockAccess) returns (uint256) { + return super.propose(targets, values, calldatas, description); + } + + function _queueOperations( + uint256 proposalId, + address[] memory targets, + uint256[] memory values, + bytes[] memory calldatas, + bytes32 descriptionHash + ) internal override(Governor, GovernorTimelockAccess) returns (uint48) { + return super._queueOperations(proposalId, targets, values, calldatas, descriptionHash); + } + + function _executeOperations( + uint256 proposalId, + address[] memory targets, + uint256[] memory values, + bytes[] memory calldatas, + bytes32 descriptionHash + ) internal override(Governor, GovernorTimelockAccess) { + super._executeOperations(proposalId, targets, values, calldatas, descriptionHash); + } + + function _cancel( + address[] memory targets, + uint256[] memory values, + bytes[] memory calldatas, + bytes32 descriptionHash + ) internal override(Governor, GovernorTimelockAccess) returns (uint256) { + return super._cancel(targets, values, calldatas, descriptionHash); + } +} diff --git a/CVLByExample/UniswapHooks/lib/uniswap-hooks/lib/v4-core/lib/openzeppelin-contracts/contracts/mocks/governance/GovernorTimelockCompoundMock.sol b/CVLByExample/UniswapHooks/lib/uniswap-hooks/lib/v4-core/lib/openzeppelin-contracts/contracts/mocks/governance/GovernorTimelockCompoundMock.sol new file mode 100644 index 00000000..03ef6251 --- /dev/null +++ b/CVLByExample/UniswapHooks/lib/uniswap-hooks/lib/v4-core/lib/openzeppelin-contracts/contracts/mocks/governance/GovernorTimelockCompoundMock.sol @@ -0,0 +1,69 @@ +// SPDX-License-Identifier: MIT + +pragma solidity ^0.8.20; + +import {IGovernor, Governor} from "../../governance/Governor.sol"; +import {GovernorTimelockCompound} from "../../governance/extensions/GovernorTimelockCompound.sol"; +import {GovernorSettings} from "../../governance/extensions/GovernorSettings.sol"; +import {GovernorCountingSimple} from "../../governance/extensions/GovernorCountingSimple.sol"; +import {GovernorVotesQuorumFraction} from "../../governance/extensions/GovernorVotesQuorumFraction.sol"; + +abstract contract GovernorTimelockCompoundMock is + GovernorSettings, + GovernorTimelockCompound, + GovernorVotesQuorumFraction, + GovernorCountingSimple +{ + function quorum(uint256 blockNumber) public view override(Governor, GovernorVotesQuorumFraction) returns (uint256) { + return super.quorum(blockNumber); + } + + function state( + uint256 proposalId + ) public view override(Governor, GovernorTimelockCompound) returns (ProposalState) { + return super.state(proposalId); + } + + function proposalThreshold() public view override(Governor, GovernorSettings) returns (uint256) { + return super.proposalThreshold(); + } + + function proposalNeedsQueuing( + uint256 proposalId + ) public view virtual override(Governor, GovernorTimelockCompound) returns (bool) { + return super.proposalNeedsQueuing(proposalId); + } + + function _queueOperations( + uint256 proposalId, + address[] memory targets, + uint256[] memory values, + bytes[] memory calldatas, + bytes32 descriptionHash + ) internal override(Governor, GovernorTimelockCompound) returns (uint48) { + return super._queueOperations(proposalId, targets, values, calldatas, descriptionHash); + } + + function _executeOperations( + uint256 proposalId, + address[] memory targets, + uint256[] memory values, + bytes[] memory calldatas, + bytes32 descriptionHash + ) internal override(Governor, GovernorTimelockCompound) { + super._executeOperations(proposalId, targets, values, calldatas, descriptionHash); + } + + function _cancel( + address[] memory targets, + uint256[] memory values, + bytes[] memory calldatas, + bytes32 descriptionHash + ) internal override(Governor, GovernorTimelockCompound) returns (uint256) { + return super._cancel(targets, values, calldatas, descriptionHash); + } + + function _executor() internal view override(Governor, GovernorTimelockCompound) returns (address) { + return super._executor(); + } +} diff --git a/CVLByExample/UniswapHooks/lib/uniswap-hooks/lib/v4-core/lib/openzeppelin-contracts/contracts/mocks/governance/GovernorTimelockControlMock.sol b/CVLByExample/UniswapHooks/lib/uniswap-hooks/lib/v4-core/lib/openzeppelin-contracts/contracts/mocks/governance/GovernorTimelockControlMock.sol new file mode 100644 index 00000000..edaccc0b --- /dev/null +++ b/CVLByExample/UniswapHooks/lib/uniswap-hooks/lib/v4-core/lib/openzeppelin-contracts/contracts/mocks/governance/GovernorTimelockControlMock.sol @@ -0,0 +1,69 @@ +// SPDX-License-Identifier: MIT + +pragma solidity ^0.8.20; + +import {IGovernor, Governor} from "../../governance/Governor.sol"; +import {GovernorTimelockControl} from "../../governance/extensions/GovernorTimelockControl.sol"; +import {GovernorSettings} from "../../governance/extensions/GovernorSettings.sol"; +import {GovernorCountingSimple} from "../../governance/extensions/GovernorCountingSimple.sol"; +import {GovernorVotesQuorumFraction} from "../../governance/extensions/GovernorVotesQuorumFraction.sol"; + +abstract contract GovernorTimelockControlMock is + GovernorSettings, + GovernorTimelockControl, + GovernorVotesQuorumFraction, + GovernorCountingSimple +{ + function quorum(uint256 blockNumber) public view override(Governor, GovernorVotesQuorumFraction) returns (uint256) { + return super.quorum(blockNumber); + } + + function state(uint256 proposalId) public view override(Governor, GovernorTimelockControl) returns (ProposalState) { + return super.state(proposalId); + } + + function proposalThreshold() public view override(Governor, GovernorSettings) returns (uint256) { + return super.proposalThreshold(); + } + + function proposalNeedsQueuing( + uint256 proposalId + ) public view virtual override(Governor, GovernorTimelockControl) returns (bool) { + return super.proposalNeedsQueuing(proposalId); + } + + function _queueOperations( + uint256 proposalId, + address[] memory targets, + uint256[] memory values, + bytes[] memory calldatas, + bytes32 descriptionHash + ) internal override(Governor, GovernorTimelockControl) returns (uint48) { + return super._queueOperations(proposalId, targets, values, calldatas, descriptionHash); + } + + function _executeOperations( + uint256 proposalId, + address[] memory targets, + uint256[] memory values, + bytes[] memory calldatas, + bytes32 descriptionHash + ) internal override(Governor, GovernorTimelockControl) { + super._executeOperations(proposalId, targets, values, calldatas, descriptionHash); + } + + function _cancel( + address[] memory targets, + uint256[] memory values, + bytes[] memory calldatas, + bytes32 descriptionHash + ) internal override(Governor, GovernorTimelockControl) returns (uint256) { + return super._cancel(targets, values, calldatas, descriptionHash); + } + + function _executor() internal view override(Governor, GovernorTimelockControl) returns (address) { + return super._executor(); + } + + function nonGovernanceFunction() external {} +} diff --git a/CVLByExample/UniswapHooks/lib/uniswap-hooks/lib/v4-core/lib/openzeppelin-contracts/contracts/mocks/governance/GovernorVoteMock.sol b/CVLByExample/UniswapHooks/lib/uniswap-hooks/lib/v4-core/lib/openzeppelin-contracts/contracts/mocks/governance/GovernorVoteMock.sol new file mode 100644 index 00000000..e6949b5b --- /dev/null +++ b/CVLByExample/UniswapHooks/lib/uniswap-hooks/lib/v4-core/lib/openzeppelin-contracts/contracts/mocks/governance/GovernorVoteMock.sol @@ -0,0 +1,20 @@ +// SPDX-License-Identifier: MIT + +pragma solidity ^0.8.20; + +import {GovernorCountingSimple} from "../../governance/extensions/GovernorCountingSimple.sol"; +import {GovernorVotes} from "../../governance/extensions/GovernorVotes.sol"; + +abstract contract GovernorVoteMocks is GovernorVotes, GovernorCountingSimple { + function quorum(uint256) public pure override returns (uint256) { + return 0; + } + + function votingDelay() public pure override returns (uint256) { + return 4; + } + + function votingPeriod() public pure override returns (uint256) { + return 16; + } +} diff --git a/CVLByExample/UniswapHooks/lib/uniswap-hooks/lib/v4-core/lib/openzeppelin-contracts/contracts/mocks/governance/GovernorWithParamsMock.sol b/CVLByExample/UniswapHooks/lib/uniswap-hooks/lib/v4-core/lib/openzeppelin-contracts/contracts/mocks/governance/GovernorWithParamsMock.sol new file mode 100644 index 00000000..d535f811 --- /dev/null +++ b/CVLByExample/UniswapHooks/lib/uniswap-hooks/lib/v4-core/lib/openzeppelin-contracts/contracts/mocks/governance/GovernorWithParamsMock.sol @@ -0,0 +1,51 @@ +// SPDX-License-Identifier: MIT + +pragma solidity ^0.8.20; + +import {Governor} from "../../governance/Governor.sol"; +import {GovernorCountingSimple} from "../../governance/extensions/GovernorCountingSimple.sol"; +import {GovernorVotes} from "../../governance/extensions/GovernorVotes.sol"; + +abstract contract GovernorWithParamsMock is GovernorVotes, GovernorCountingSimple { + event CountParams(uint256 uintParam, string strParam); + + function quorum(uint256) public pure override returns (uint256) { + return 0; + } + + function votingDelay() public pure override returns (uint256) { + return 4; + } + + function votingPeriod() public pure override returns (uint256) { + return 16; + } + + function _getVotes( + address account, + uint256 blockNumber, + bytes memory params + ) internal view override(Governor, GovernorVotes) returns (uint256) { + uint256 reduction = 0; + // If the user provides parameters, we reduce the voting weight by the amount of the integer param + if (params.length > 0) { + (reduction, ) = abi.decode(params, (uint256, string)); + } + // reverts on overflow + return super._getVotes(account, blockNumber, params) - reduction; + } + + function _countVote( + uint256 proposalId, + address account, + uint8 support, + uint256 weight, + bytes memory params + ) internal override(Governor, GovernorCountingSimple) { + if (params.length > 0) { + (uint256 _uintParam, string memory _strParam) = abi.decode(params, (uint256, string)); + emit CountParams(_uintParam, _strParam); + } + return super._countVote(proposalId, account, support, weight, params); + } +} diff --git a/CVLByExample/UniswapHooks/lib/uniswap-hooks/lib/v4-core/lib/openzeppelin-contracts/contracts/mocks/proxy/BadBeacon.sol b/CVLByExample/UniswapHooks/lib/uniswap-hooks/lib/v4-core/lib/openzeppelin-contracts/contracts/mocks/proxy/BadBeacon.sol new file mode 100644 index 00000000..f3153a84 --- /dev/null +++ b/CVLByExample/UniswapHooks/lib/uniswap-hooks/lib/v4-core/lib/openzeppelin-contracts/contracts/mocks/proxy/BadBeacon.sol @@ -0,0 +1,11 @@ +// SPDX-License-Identifier: MIT + +pragma solidity ^0.8.20; + +contract BadBeaconNoImpl {} + +contract BadBeaconNotContract { + function implementation() external pure returns (address) { + return address(0x1); + } +} diff --git a/CVLByExample/UniswapHooks/lib/uniswap-hooks/lib/v4-core/lib/openzeppelin-contracts/contracts/mocks/proxy/ClashingImplementation.sol b/CVLByExample/UniswapHooks/lib/uniswap-hooks/lib/v4-core/lib/openzeppelin-contracts/contracts/mocks/proxy/ClashingImplementation.sol new file mode 100644 index 00000000..43d5a34f --- /dev/null +++ b/CVLByExample/UniswapHooks/lib/uniswap-hooks/lib/v4-core/lib/openzeppelin-contracts/contracts/mocks/proxy/ClashingImplementation.sol @@ -0,0 +1,19 @@ +// SPDX-License-Identifier: MIT + +pragma solidity ^0.8.20; + +/** + * @dev Implementation contract with a payable changeAdmin(address) function made to clash with + * TransparentUpgradeableProxy's to test correct functioning of the Transparent Proxy feature. + */ +contract ClashingImplementation { + event ClashingImplementationCall(); + + function upgradeToAndCall(address, bytes calldata) external payable { + emit ClashingImplementationCall(); + } + + function delegatedFunction() external pure returns (bool) { + return true; + } +} diff --git a/CVLByExample/UniswapHooks/lib/uniswap-hooks/lib/v4-core/lib/openzeppelin-contracts/contracts/mocks/proxy/UUPSUpgradeableMock.sol b/CVLByExample/UniswapHooks/lib/uniswap-hooks/lib/v4-core/lib/openzeppelin-contracts/contracts/mocks/proxy/UUPSUpgradeableMock.sol new file mode 100644 index 00000000..a5f2d4a2 --- /dev/null +++ b/CVLByExample/UniswapHooks/lib/uniswap-hooks/lib/v4-core/lib/openzeppelin-contracts/contracts/mocks/proxy/UUPSUpgradeableMock.sol @@ -0,0 +1,35 @@ +// SPDX-License-Identifier: MIT + +pragma solidity ^0.8.20; + +import {UUPSUpgradeable} from "../../proxy/utils/UUPSUpgradeable.sol"; +import {ERC1967Utils} from "../../proxy/ERC1967/ERC1967Utils.sol"; + +contract NonUpgradeableMock { + uint256 internal _counter; + + function current() external view returns (uint256) { + return _counter; + } + + function increment() external { + ++_counter; + } +} + +contract UUPSUpgradeableMock is NonUpgradeableMock, UUPSUpgradeable { + // Not having any checks in this function is dangerous! Do not do this outside tests! + function _authorizeUpgrade(address) internal override {} +} + +contract UUPSUpgradeableUnsafeMock is UUPSUpgradeableMock { + function upgradeToAndCall(address newImplementation, bytes memory data) public payable override { + ERC1967Utils.upgradeToAndCall(newImplementation, data); + } +} + +contract UUPSUnsupportedProxiableUUID is UUPSUpgradeableMock { + function proxiableUUID() external pure override returns (bytes32) { + return keccak256("invalid UUID"); + } +} diff --git a/CVLByExample/UniswapHooks/lib/uniswap-hooks/lib/v4-core/lib/openzeppelin-contracts/contracts/mocks/token/ERC1155ReceiverMock.sol b/CVLByExample/UniswapHooks/lib/uniswap-hooks/lib/v4-core/lib/openzeppelin-contracts/contracts/mocks/token/ERC1155ReceiverMock.sol new file mode 100644 index 00000000..2a85d1df --- /dev/null +++ b/CVLByExample/UniswapHooks/lib/uniswap-hooks/lib/v4-core/lib/openzeppelin-contracts/contracts/mocks/token/ERC1155ReceiverMock.sol @@ -0,0 +1,74 @@ +// SPDX-License-Identifier: MIT + +pragma solidity ^0.8.20; + +import {IERC1155Receiver} from "../../token/ERC1155/IERC1155Receiver.sol"; +import {ERC165} from "../../utils/introspection/ERC165.sol"; + +contract ERC1155ReceiverMock is ERC165, IERC1155Receiver { + enum RevertType { + None, + RevertWithoutMessage, + RevertWithMessage, + RevertWithCustomError, + Panic + } + + bytes4 private immutable _recRetval; + bytes4 private immutable _batRetval; + RevertType private immutable _error; + + event Received(address operator, address from, uint256 id, uint256 value, bytes data, uint256 gas); + event BatchReceived(address operator, address from, uint256[] ids, uint256[] values, bytes data, uint256 gas); + error CustomError(bytes4); + + constructor(bytes4 recRetval, bytes4 batRetval, RevertType error) { + _recRetval = recRetval; + _batRetval = batRetval; + _error = error; + } + + function onERC1155Received( + address operator, + address from, + uint256 id, + uint256 value, + bytes calldata data + ) external returns (bytes4) { + if (_error == RevertType.RevertWithoutMessage) { + revert(); + } else if (_error == RevertType.RevertWithMessage) { + revert("ERC1155ReceiverMock: reverting on receive"); + } else if (_error == RevertType.RevertWithCustomError) { + revert CustomError(_recRetval); + } else if (_error == RevertType.Panic) { + uint256 a = uint256(0) / uint256(0); + a; + } + + emit Received(operator, from, id, value, data, gasleft()); + return _recRetval; + } + + function onERC1155BatchReceived( + address operator, + address from, + uint256[] calldata ids, + uint256[] calldata values, + bytes calldata data + ) external returns (bytes4) { + if (_error == RevertType.RevertWithoutMessage) { + revert(); + } else if (_error == RevertType.RevertWithMessage) { + revert("ERC1155ReceiverMock: reverting on batch receive"); + } else if (_error == RevertType.RevertWithCustomError) { + revert CustomError(_recRetval); + } else if (_error == RevertType.Panic) { + uint256 a = uint256(0) / uint256(0); + a; + } + + emit BatchReceived(operator, from, ids, values, data, gasleft()); + return _batRetval; + } +} diff --git a/CVLByExample/UniswapHooks/lib/uniswap-hooks/lib/v4-core/lib/openzeppelin-contracts/contracts/mocks/token/ERC20ApprovalMock.sol b/CVLByExample/UniswapHooks/lib/uniswap-hooks/lib/v4-core/lib/openzeppelin-contracts/contracts/mocks/token/ERC20ApprovalMock.sol new file mode 100644 index 00000000..ff33a36d --- /dev/null +++ b/CVLByExample/UniswapHooks/lib/uniswap-hooks/lib/v4-core/lib/openzeppelin-contracts/contracts/mocks/token/ERC20ApprovalMock.sol @@ -0,0 +1,10 @@ +// SPDX-License-Identifier: MIT +pragma solidity ^0.8.20; + +import {ERC20} from "../../token/ERC20/ERC20.sol"; + +abstract contract ERC20ApprovalMock is ERC20 { + function _approve(address owner, address spender, uint256 amount, bool) internal virtual override { + super._approve(owner, spender, amount, true); + } +} diff --git a/CVLByExample/UniswapHooks/lib/uniswap-hooks/lib/v4-core/lib/openzeppelin-contracts/contracts/mocks/token/ERC20DecimalsMock.sol b/CVLByExample/UniswapHooks/lib/uniswap-hooks/lib/v4-core/lib/openzeppelin-contracts/contracts/mocks/token/ERC20DecimalsMock.sol new file mode 100644 index 00000000..a26e1f52 --- /dev/null +++ b/CVLByExample/UniswapHooks/lib/uniswap-hooks/lib/v4-core/lib/openzeppelin-contracts/contracts/mocks/token/ERC20DecimalsMock.sol @@ -0,0 +1,17 @@ +// SPDX-License-Identifier: MIT + +pragma solidity ^0.8.20; + +import {ERC20} from "../../token/ERC20/ERC20.sol"; + +abstract contract ERC20DecimalsMock is ERC20 { + uint8 private immutable _decimals; + + constructor(uint8 decimals_) { + _decimals = decimals_; + } + + function decimals() public view override returns (uint8) { + return _decimals; + } +} diff --git a/CVLByExample/UniswapHooks/lib/uniswap-hooks/lib/v4-core/lib/openzeppelin-contracts/contracts/mocks/token/ERC20ExcessDecimalsMock.sol b/CVLByExample/UniswapHooks/lib/uniswap-hooks/lib/v4-core/lib/openzeppelin-contracts/contracts/mocks/token/ERC20ExcessDecimalsMock.sol new file mode 100644 index 00000000..4627efd3 --- /dev/null +++ b/CVLByExample/UniswapHooks/lib/uniswap-hooks/lib/v4-core/lib/openzeppelin-contracts/contracts/mocks/token/ERC20ExcessDecimalsMock.sol @@ -0,0 +1,9 @@ +// SPDX-License-Identifier: MIT + +pragma solidity ^0.8.20; + +contract ERC20ExcessDecimalsMock { + function decimals() public pure returns (uint256) { + return type(uint256).max; + } +} diff --git a/CVLByExample/UniswapHooks/lib/uniswap-hooks/lib/v4-core/lib/openzeppelin-contracts/contracts/mocks/token/ERC20FlashMintMock.sol b/CVLByExample/UniswapHooks/lib/uniswap-hooks/lib/v4-core/lib/openzeppelin-contracts/contracts/mocks/token/ERC20FlashMintMock.sol new file mode 100644 index 00000000..508573c2 --- /dev/null +++ b/CVLByExample/UniswapHooks/lib/uniswap-hooks/lib/v4-core/lib/openzeppelin-contracts/contracts/mocks/token/ERC20FlashMintMock.sol @@ -0,0 +1,26 @@ +// SPDX-License-Identifier: MIT + +pragma solidity ^0.8.20; + +import {ERC20FlashMint} from "../../token/ERC20/extensions/ERC20FlashMint.sol"; + +abstract contract ERC20FlashMintMock is ERC20FlashMint { + uint256 _flashFeeAmount; + address _flashFeeReceiverAddress; + + function setFlashFee(uint256 amount) public { + _flashFeeAmount = amount; + } + + function _flashFee(address, uint256) internal view override returns (uint256) { + return _flashFeeAmount; + } + + function setFlashFeeReceiver(address receiver) public { + _flashFeeReceiverAddress = receiver; + } + + function _flashFeeReceiver() internal view override returns (address) { + return _flashFeeReceiverAddress; + } +} diff --git a/CVLByExample/UniswapHooks/lib/uniswap-hooks/lib/v4-core/lib/openzeppelin-contracts/contracts/mocks/token/ERC20ForceApproveMock.sol b/CVLByExample/UniswapHooks/lib/uniswap-hooks/lib/v4-core/lib/openzeppelin-contracts/contracts/mocks/token/ERC20ForceApproveMock.sol new file mode 100644 index 00000000..36c0f574 --- /dev/null +++ b/CVLByExample/UniswapHooks/lib/uniswap-hooks/lib/v4-core/lib/openzeppelin-contracts/contracts/mocks/token/ERC20ForceApproveMock.sol @@ -0,0 +1,13 @@ +// SPDX-License-Identifier: MIT + +pragma solidity ^0.8.20; + +import {ERC20} from "../../token/ERC20/ERC20.sol"; + +// contract that replicate USDT (0xdac17f958d2ee523a2206206994597c13d831ec7) approval behavior +abstract contract ERC20ForceApproveMock is ERC20 { + function approve(address spender, uint256 amount) public virtual override returns (bool) { + require(amount == 0 || allowance(msg.sender, spender) == 0, "USDT approval failure"); + return super.approve(spender, amount); + } +} diff --git a/CVLByExample/UniswapHooks/lib/uniswap-hooks/lib/v4-core/lib/openzeppelin-contracts/contracts/mocks/token/ERC20Mock.sol b/CVLByExample/UniswapHooks/lib/uniswap-hooks/lib/v4-core/lib/openzeppelin-contracts/contracts/mocks/token/ERC20Mock.sol new file mode 100644 index 00000000..39ab1295 --- /dev/null +++ b/CVLByExample/UniswapHooks/lib/uniswap-hooks/lib/v4-core/lib/openzeppelin-contracts/contracts/mocks/token/ERC20Mock.sol @@ -0,0 +1,16 @@ +// SPDX-License-Identifier: MIT +pragma solidity ^0.8.20; + +import {ERC20} from "../../token/ERC20/ERC20.sol"; + +contract ERC20Mock is ERC20 { + constructor() ERC20("ERC20Mock", "E20M") {} + + function mint(address account, uint256 amount) external { + _mint(account, amount); + } + + function burn(address account, uint256 amount) external { + _burn(account, amount); + } +} diff --git a/CVLByExample/UniswapHooks/lib/uniswap-hooks/lib/v4-core/lib/openzeppelin-contracts/contracts/mocks/token/ERC20MulticallMock.sol b/CVLByExample/UniswapHooks/lib/uniswap-hooks/lib/v4-core/lib/openzeppelin-contracts/contracts/mocks/token/ERC20MulticallMock.sol new file mode 100644 index 00000000..dce3e705 --- /dev/null +++ b/CVLByExample/UniswapHooks/lib/uniswap-hooks/lib/v4-core/lib/openzeppelin-contracts/contracts/mocks/token/ERC20MulticallMock.sol @@ -0,0 +1,8 @@ +// SPDX-License-Identifier: MIT + +pragma solidity ^0.8.20; + +import {ERC20} from "../../token/ERC20/ERC20.sol"; +import {Multicall} from "../../utils/Multicall.sol"; + +abstract contract ERC20MulticallMock is ERC20, Multicall {} diff --git a/CVLByExample/UniswapHooks/lib/uniswap-hooks/lib/v4-core/lib/openzeppelin-contracts/contracts/mocks/token/ERC20NoReturnMock.sol b/CVLByExample/UniswapHooks/lib/uniswap-hooks/lib/v4-core/lib/openzeppelin-contracts/contracts/mocks/token/ERC20NoReturnMock.sol new file mode 100644 index 00000000..2129537b --- /dev/null +++ b/CVLByExample/UniswapHooks/lib/uniswap-hooks/lib/v4-core/lib/openzeppelin-contracts/contracts/mocks/token/ERC20NoReturnMock.sol @@ -0,0 +1,28 @@ +// SPDX-License-Identifier: MIT + +pragma solidity ^0.8.20; + +import {ERC20} from "../../token/ERC20/ERC20.sol"; + +abstract contract ERC20NoReturnMock is ERC20 { + function transfer(address to, uint256 amount) public override returns (bool) { + super.transfer(to, amount); + assembly { + return(0, 0) + } + } + + function transferFrom(address from, address to, uint256 amount) public override returns (bool) { + super.transferFrom(from, to, amount); + assembly { + return(0, 0) + } + } + + function approve(address spender, uint256 amount) public override returns (bool) { + super.approve(spender, amount); + assembly { + return(0, 0) + } + } +} diff --git a/CVLByExample/UniswapHooks/lib/uniswap-hooks/lib/v4-core/lib/openzeppelin-contracts/contracts/mocks/token/ERC20Reentrant.sol b/CVLByExample/UniswapHooks/lib/uniswap-hooks/lib/v4-core/lib/openzeppelin-contracts/contracts/mocks/token/ERC20Reentrant.sol new file mode 100644 index 00000000..813913f7 --- /dev/null +++ b/CVLByExample/UniswapHooks/lib/uniswap-hooks/lib/v4-core/lib/openzeppelin-contracts/contracts/mocks/token/ERC20Reentrant.sol @@ -0,0 +1,39 @@ +// SPDX-License-Identifier: MIT +pragma solidity ^0.8.20; + +import {ERC20} from "../../token/ERC20/ERC20.sol"; +import {Address} from "../../utils/Address.sol"; + +contract ERC20Reentrant is ERC20("TEST", "TST") { + enum Type { + No, + Before, + After + } + + Type private _reenterType; + address private _reenterTarget; + bytes private _reenterData; + + function scheduleReenter(Type when, address target, bytes calldata data) external { + _reenterType = when; + _reenterTarget = target; + _reenterData = data; + } + + function functionCall(address target, bytes memory data) public returns (bytes memory) { + return Address.functionCall(target, data); + } + + function _update(address from, address to, uint256 amount) internal override { + if (_reenterType == Type.Before) { + _reenterType = Type.No; + functionCall(_reenterTarget, _reenterData); + } + super._update(from, to, amount); + if (_reenterType == Type.After) { + _reenterType = Type.No; + functionCall(_reenterTarget, _reenterData); + } + } +} diff --git a/CVLByExample/UniswapHooks/lib/uniswap-hooks/lib/v4-core/lib/openzeppelin-contracts/contracts/mocks/token/ERC20ReturnFalseMock.sol b/CVLByExample/UniswapHooks/lib/uniswap-hooks/lib/v4-core/lib/openzeppelin-contracts/contracts/mocks/token/ERC20ReturnFalseMock.sol new file mode 100644 index 00000000..94bff32f --- /dev/null +++ b/CVLByExample/UniswapHooks/lib/uniswap-hooks/lib/v4-core/lib/openzeppelin-contracts/contracts/mocks/token/ERC20ReturnFalseMock.sol @@ -0,0 +1,19 @@ +// SPDX-License-Identifier: MIT + +pragma solidity ^0.8.20; + +import {ERC20} from "../../token/ERC20/ERC20.sol"; + +abstract contract ERC20ReturnFalseMock is ERC20 { + function transfer(address, uint256) public pure override returns (bool) { + return false; + } + + function transferFrom(address, address, uint256) public pure override returns (bool) { + return false; + } + + function approve(address, uint256) public pure override returns (bool) { + return false; + } +} diff --git a/CVLByExample/UniswapHooks/lib/uniswap-hooks/lib/v4-core/lib/openzeppelin-contracts/contracts/mocks/token/ERC20VotesLegacyMock.sol b/CVLByExample/UniswapHooks/lib/uniswap-hooks/lib/v4-core/lib/openzeppelin-contracts/contracts/mocks/token/ERC20VotesLegacyMock.sol new file mode 100644 index 00000000..3246fd42 --- /dev/null +++ b/CVLByExample/UniswapHooks/lib/uniswap-hooks/lib/v4-core/lib/openzeppelin-contracts/contracts/mocks/token/ERC20VotesLegacyMock.sol @@ -0,0 +1,253 @@ +// SPDX-License-Identifier: MIT + +pragma solidity ^0.8.20; + +import {ERC20Permit} from "../../token/ERC20/extensions/ERC20Permit.sol"; +import {Math} from "../../utils/math/Math.sol"; +import {IVotes} from "../../governance/utils/IVotes.sol"; +import {SafeCast} from "../../utils/math/SafeCast.sol"; +import {ECDSA} from "../../utils/cryptography/ECDSA.sol"; + +/** + * @dev Copied from the master branch at commit 86de1e8b6c3fa6b4efa4a5435869d2521be0f5f5 + */ +abstract contract ERC20VotesLegacyMock is IVotes, ERC20Permit { + struct Checkpoint { + uint32 fromBlock; + uint224 votes; + } + + bytes32 private constant _DELEGATION_TYPEHASH = + keccak256("Delegation(address delegatee,uint256 nonce,uint256 expiry)"); + + mapping(address account => address) private _delegatee; + mapping(address delegatee => Checkpoint[]) private _checkpoints; + Checkpoint[] private _totalSupplyCheckpoints; + + /** + * @dev Get the `pos`-th checkpoint for `account`. + */ + function checkpoints(address account, uint32 pos) public view virtual returns (Checkpoint memory) { + return _checkpoints[account][pos]; + } + + /** + * @dev Get number of checkpoints for `account`. + */ + function numCheckpoints(address account) public view virtual returns (uint32) { + return SafeCast.toUint32(_checkpoints[account].length); + } + + /** + * @dev Get the address `account` is currently delegating to. + */ + function delegates(address account) public view virtual returns (address) { + return _delegatee[account]; + } + + /** + * @dev Gets the current votes balance for `account` + */ + function getVotes(address account) public view virtual returns (uint256) { + uint256 pos = _checkpoints[account].length; + unchecked { + return pos == 0 ? 0 : _checkpoints[account][pos - 1].votes; + } + } + + /** + * @dev Retrieve the number of votes for `account` at the end of `blockNumber`. + * + * Requirements: + * + * - `blockNumber` must have been already mined + */ + function getPastVotes(address account, uint256 blockNumber) public view virtual returns (uint256) { + require(blockNumber < block.number, "ERC20Votes: block not yet mined"); + return _checkpointsLookup(_checkpoints[account], blockNumber); + } + + /** + * @dev Retrieve the `totalSupply` at the end of `blockNumber`. Note, this value is the sum of all balances. + * It is NOT the sum of all the delegated votes! + * + * Requirements: + * + * - `blockNumber` must have been already mined + */ + function getPastTotalSupply(uint256 blockNumber) public view virtual returns (uint256) { + require(blockNumber < block.number, "ERC20Votes: block not yet mined"); + return _checkpointsLookup(_totalSupplyCheckpoints, blockNumber); + } + + /** + * @dev Lookup a value in a list of (sorted) checkpoints. + */ + function _checkpointsLookup(Checkpoint[] storage ckpts, uint256 blockNumber) private view returns (uint256) { + // We run a binary search to look for the earliest checkpoint taken after `blockNumber`. + // + // Initially we check if the block is recent to narrow the search range. + // During the loop, the index of the wanted checkpoint remains in the range [low-1, high). + // With each iteration, either `low` or `high` is moved towards the middle of the range to maintain the + // invariant. + // - If the middle checkpoint is after `blockNumber`, we look in [low, mid) + // - If the middle checkpoint is before or equal to `blockNumber`, we look in [mid+1, high) + // Once we reach a single value (when low == high), we've found the right checkpoint at the index high-1, if not + // out of bounds (in which case we're looking too far in the past and the result is 0). + // Note that if the latest checkpoint available is exactly for `blockNumber`, we end up with an index that is + // past the end of the array, so we technically don't find a checkpoint after `blockNumber`, but it works out + // the same. + uint256 length = ckpts.length; + + uint256 low = 0; + uint256 high = length; + + if (length > 5) { + uint256 mid = length - Math.sqrt(length); + if (_unsafeAccess(ckpts, mid).fromBlock > blockNumber) { + high = mid; + } else { + low = mid + 1; + } + } + + while (low < high) { + uint256 mid = Math.average(low, high); + if (_unsafeAccess(ckpts, mid).fromBlock > blockNumber) { + high = mid; + } else { + low = mid + 1; + } + } + + unchecked { + return high == 0 ? 0 : _unsafeAccess(ckpts, high - 1).votes; + } + } + + /** + * @dev Delegate votes from the sender to `delegatee`. + */ + function delegate(address delegatee) public virtual { + _delegate(_msgSender(), delegatee); + } + + /** + * @dev Delegates votes from signer to `delegatee` + */ + function delegateBySig( + address delegatee, + uint256 nonce, + uint256 expiry, + uint8 v, + bytes32 r, + bytes32 s + ) public virtual { + require(block.timestamp <= expiry, "ERC20Votes: signature expired"); + address signer = ECDSA.recover( + _hashTypedDataV4(keccak256(abi.encode(_DELEGATION_TYPEHASH, delegatee, nonce, expiry))), + v, + r, + s + ); + require(nonce == _useNonce(signer), "ERC20Votes: invalid nonce"); + _delegate(signer, delegatee); + } + + /** + * @dev Maximum token supply. Defaults to `type(uint224).max` (2^224^ - 1). + */ + function _maxSupply() internal view virtual returns (uint224) { + return type(uint224).max; + } + + /** + * @dev Move voting power when tokens are transferred. + * + * Emits a {IVotes-DelegateVotesChanged} event. + */ + function _update(address from, address to, uint256 amount) internal virtual override { + super._update(from, to, amount); + + if (from == address(0)) { + require(totalSupply() <= _maxSupply(), "ERC20Votes: total supply risks overflowing votes"); + _writeCheckpoint(_totalSupplyCheckpoints, _add, amount); + } + + if (to == address(0)) { + _writeCheckpoint(_totalSupplyCheckpoints, _subtract, amount); + } + + _moveVotingPower(delegates(from), delegates(to), amount); + } + + /** + * @dev Change delegation for `delegator` to `delegatee`. + * + * Emits events {IVotes-DelegateChanged} and {IVotes-DelegateVotesChanged}. + */ + function _delegate(address delegator, address delegatee) internal virtual { + address currentDelegate = delegates(delegator); + uint256 delegatorBalance = balanceOf(delegator); + _delegatee[delegator] = delegatee; + + emit DelegateChanged(delegator, currentDelegate, delegatee); + + _moveVotingPower(currentDelegate, delegatee, delegatorBalance); + } + + function _moveVotingPower(address src, address dst, uint256 amount) private { + if (src != dst && amount > 0) { + if (src != address(0)) { + (uint256 oldWeight, uint256 newWeight) = _writeCheckpoint(_checkpoints[src], _subtract, amount); + emit DelegateVotesChanged(src, oldWeight, newWeight); + } + + if (dst != address(0)) { + (uint256 oldWeight, uint256 newWeight) = _writeCheckpoint(_checkpoints[dst], _add, amount); + emit DelegateVotesChanged(dst, oldWeight, newWeight); + } + } + } + + function _writeCheckpoint( + Checkpoint[] storage ckpts, + function(uint256, uint256) view returns (uint256) op, + uint256 delta + ) private returns (uint256 oldWeight, uint256 newWeight) { + uint256 pos = ckpts.length; + + unchecked { + Checkpoint memory oldCkpt = pos == 0 ? Checkpoint(0, 0) : _unsafeAccess(ckpts, pos - 1); + + oldWeight = oldCkpt.votes; + newWeight = op(oldWeight, delta); + + if (pos > 0 && oldCkpt.fromBlock == block.number) { + _unsafeAccess(ckpts, pos - 1).votes = SafeCast.toUint224(newWeight); + } else { + ckpts.push( + Checkpoint({fromBlock: SafeCast.toUint32(block.number), votes: SafeCast.toUint224(newWeight)}) + ); + } + } + } + + function _add(uint256 a, uint256 b) private pure returns (uint256) { + return a + b; + } + + function _subtract(uint256 a, uint256 b) private pure returns (uint256) { + return a - b; + } + + /** + * @dev Access an element of the array without performing bounds check. The position is assumed to be within bounds. + */ + function _unsafeAccess(Checkpoint[] storage ckpts, uint256 pos) private pure returns (Checkpoint storage result) { + assembly { + mstore(0, ckpts.slot) + result.slot := add(keccak256(0, 0x20), pos) + } + } +} diff --git a/CVLByExample/UniswapHooks/lib/uniswap-hooks/lib/v4-core/lib/openzeppelin-contracts/contracts/mocks/token/ERC4626LimitsMock.sol b/CVLByExample/UniswapHooks/lib/uniswap-hooks/lib/v4-core/lib/openzeppelin-contracts/contracts/mocks/token/ERC4626LimitsMock.sol new file mode 100644 index 00000000..a845365a --- /dev/null +++ b/CVLByExample/UniswapHooks/lib/uniswap-hooks/lib/v4-core/lib/openzeppelin-contracts/contracts/mocks/token/ERC4626LimitsMock.sol @@ -0,0 +1,23 @@ +// SPDX-License-Identifier: MIT + +pragma solidity ^0.8.20; + +import {ERC4626} from "../../token/ERC20/extensions/ERC4626.sol"; + +abstract contract ERC4626LimitsMock is ERC4626 { + uint256 _maxDeposit; + uint256 _maxMint; + + constructor() { + _maxDeposit = 100 ether; + _maxMint = 100 ether; + } + + function maxDeposit(address) public view override returns (uint256) { + return _maxDeposit; + } + + function maxMint(address) public view override returns (uint256) { + return _maxMint; + } +} diff --git a/CVLByExample/UniswapHooks/lib/uniswap-hooks/lib/v4-core/lib/openzeppelin-contracts/contracts/mocks/token/ERC4626Mock.sol b/CVLByExample/UniswapHooks/lib/uniswap-hooks/lib/v4-core/lib/openzeppelin-contracts/contracts/mocks/token/ERC4626Mock.sol new file mode 100644 index 00000000..22ac5e8c --- /dev/null +++ b/CVLByExample/UniswapHooks/lib/uniswap-hooks/lib/v4-core/lib/openzeppelin-contracts/contracts/mocks/token/ERC4626Mock.sol @@ -0,0 +1,17 @@ +// SPDX-License-Identifier: MIT +pragma solidity ^0.8.20; + +import {IERC20, ERC20} from "../../token/ERC20/ERC20.sol"; +import {ERC4626} from "../../token/ERC20/extensions/ERC4626.sol"; + +contract ERC4626Mock is ERC4626 { + constructor(address underlying) ERC20("ERC4626Mock", "E4626M") ERC4626(IERC20(underlying)) {} + + function mint(address account, uint256 amount) external { + _mint(account, amount); + } + + function burn(address account, uint256 amount) external { + _burn(account, amount); + } +} diff --git a/CVLByExample/UniswapHooks/lib/uniswap-hooks/lib/v4-core/lib/openzeppelin-contracts/contracts/mocks/token/ERC4626OffsetMock.sol b/CVLByExample/UniswapHooks/lib/uniswap-hooks/lib/v4-core/lib/openzeppelin-contracts/contracts/mocks/token/ERC4626OffsetMock.sol new file mode 100644 index 00000000..3dde0952 --- /dev/null +++ b/CVLByExample/UniswapHooks/lib/uniswap-hooks/lib/v4-core/lib/openzeppelin-contracts/contracts/mocks/token/ERC4626OffsetMock.sol @@ -0,0 +1,17 @@ +// SPDX-License-Identifier: MIT + +pragma solidity ^0.8.20; + +import {ERC4626} from "../../token/ERC20/extensions/ERC4626.sol"; + +abstract contract ERC4626OffsetMock is ERC4626 { + uint8 private immutable _offset; + + constructor(uint8 offset_) { + _offset = offset_; + } + + function _decimalsOffset() internal view virtual override returns (uint8) { + return _offset; + } +} diff --git a/CVLByExample/UniswapHooks/lib/uniswap-hooks/lib/v4-core/lib/openzeppelin-contracts/contracts/mocks/token/ERC4646FeesMock.sol b/CVLByExample/UniswapHooks/lib/uniswap-hooks/lib/v4-core/lib/openzeppelin-contracts/contracts/mocks/token/ERC4646FeesMock.sol new file mode 100644 index 00000000..368b078e --- /dev/null +++ b/CVLByExample/UniswapHooks/lib/uniswap-hooks/lib/v4-core/lib/openzeppelin-contracts/contracts/mocks/token/ERC4646FeesMock.sol @@ -0,0 +1,40 @@ +// SPDX-License-Identifier: MIT + +pragma solidity ^0.8.20; + +import {ERC4626Fees} from "../docs/ERC4626Fees.sol"; + +abstract contract ERC4626FeesMock is ERC4626Fees { + uint256 private immutable _entryFeeBasisPointValue; + address private immutable _entryFeeRecipientValue; + uint256 private immutable _exitFeeBasisPointValue; + address private immutable _exitFeeRecipientValue; + + constructor( + uint256 entryFeeBasisPoints, + address entryFeeRecipient, + uint256 exitFeeBasisPoints, + address exitFeeRecipient + ) { + _entryFeeBasisPointValue = entryFeeBasisPoints; + _entryFeeRecipientValue = entryFeeRecipient; + _exitFeeBasisPointValue = exitFeeBasisPoints; + _exitFeeRecipientValue = exitFeeRecipient; + } + + function _entryFeeBasisPoints() internal view virtual override returns (uint256) { + return _entryFeeBasisPointValue; + } + + function _entryFeeRecipient() internal view virtual override returns (address) { + return _entryFeeRecipientValue; + } + + function _exitFeeBasisPoints() internal view virtual override returns (uint256) { + return _exitFeeBasisPointValue; + } + + function _exitFeeRecipient() internal view virtual override returns (address) { + return _exitFeeRecipientValue; + } +} diff --git a/CVLByExample/UniswapHooks/lib/uniswap-hooks/lib/v4-core/lib/openzeppelin-contracts/contracts/mocks/token/ERC721ConsecutiveEnumerableMock.sol b/CVLByExample/UniswapHooks/lib/uniswap-hooks/lib/v4-core/lib/openzeppelin-contracts/contracts/mocks/token/ERC721ConsecutiveEnumerableMock.sol new file mode 100644 index 00000000..7732ae4a --- /dev/null +++ b/CVLByExample/UniswapHooks/lib/uniswap-hooks/lib/v4-core/lib/openzeppelin-contracts/contracts/mocks/token/ERC721ConsecutiveEnumerableMock.sol @@ -0,0 +1,42 @@ +// SPDX-License-Identifier: MIT + +pragma solidity ^0.8.20; + +import {ERC721} from "../../token/ERC721/ERC721.sol"; +import {ERC721Consecutive} from "../../token/ERC721/extensions/ERC721Consecutive.sol"; +import {ERC721Enumerable} from "../../token/ERC721/extensions/ERC721Enumerable.sol"; + +contract ERC721ConsecutiveEnumerableMock is ERC721Consecutive, ERC721Enumerable { + constructor( + string memory name, + string memory symbol, + address[] memory receivers, + uint96[] memory amounts + ) ERC721(name, symbol) { + for (uint256 i = 0; i < receivers.length; ++i) { + _mintConsecutive(receivers[i], amounts[i]); + } + } + + function supportsInterface( + bytes4 interfaceId + ) public view virtual override(ERC721, ERC721Enumerable) returns (bool) { + return super.supportsInterface(interfaceId); + } + + function _ownerOf(uint256 tokenId) internal view virtual override(ERC721, ERC721Consecutive) returns (address) { + return super._ownerOf(tokenId); + } + + function _update( + address to, + uint256 tokenId, + address auth + ) internal virtual override(ERC721Consecutive, ERC721Enumerable) returns (address) { + return super._update(to, tokenId, auth); + } + + function _increaseBalance(address account, uint128 amount) internal virtual override(ERC721, ERC721Enumerable) { + super._increaseBalance(account, amount); + } +} diff --git a/CVLByExample/UniswapHooks/lib/uniswap-hooks/lib/v4-core/lib/openzeppelin-contracts/contracts/mocks/token/ERC721ConsecutiveMock.sol b/CVLByExample/UniswapHooks/lib/uniswap-hooks/lib/v4-core/lib/openzeppelin-contracts/contracts/mocks/token/ERC721ConsecutiveMock.sol new file mode 100644 index 00000000..10986471 --- /dev/null +++ b/CVLByExample/UniswapHooks/lib/uniswap-hooks/lib/v4-core/lib/openzeppelin-contracts/contracts/mocks/token/ERC721ConsecutiveMock.sol @@ -0,0 +1,61 @@ +// SPDX-License-Identifier: MIT + +pragma solidity ^0.8.20; + +import {ERC721} from "../../token/ERC721/ERC721.sol"; +import {ERC721Consecutive} from "../../token/ERC721/extensions/ERC721Consecutive.sol"; +import {ERC721Pausable} from "../../token/ERC721/extensions/ERC721Pausable.sol"; +import {ERC721Votes} from "../../token/ERC721/extensions/ERC721Votes.sol"; +import {EIP712} from "../../utils/cryptography/EIP712.sol"; + +/** + * @title ERC721ConsecutiveMock + */ +contract ERC721ConsecutiveMock is ERC721Consecutive, ERC721Pausable, ERC721Votes { + uint96 private immutable _offset; + + constructor( + string memory name, + string memory symbol, + uint96 offset, + address[] memory delegates, + address[] memory receivers, + uint96[] memory amounts + ) ERC721(name, symbol) EIP712(name, "1") { + _offset = offset; + + for (uint256 i = 0; i < delegates.length; ++i) { + _delegate(delegates[i], delegates[i]); + } + + for (uint256 i = 0; i < receivers.length; ++i) { + _mintConsecutive(receivers[i], amounts[i]); + } + } + + function _firstConsecutiveId() internal view virtual override returns (uint96) { + return _offset; + } + + function _ownerOf(uint256 tokenId) internal view virtual override(ERC721, ERC721Consecutive) returns (address) { + return super._ownerOf(tokenId); + } + + function _update( + address to, + uint256 tokenId, + address auth + ) internal virtual override(ERC721Consecutive, ERC721Pausable, ERC721Votes) returns (address) { + return super._update(to, tokenId, auth); + } + + function _increaseBalance(address account, uint128 amount) internal virtual override(ERC721, ERC721Votes) { + super._increaseBalance(account, amount); + } +} + +contract ERC721ConsecutiveNoConstructorMintMock is ERC721Consecutive { + constructor(string memory name, string memory symbol) ERC721(name, symbol) { + _mint(msg.sender, 0); + } +} diff --git a/CVLByExample/UniswapHooks/lib/uniswap-hooks/lib/v4-core/lib/openzeppelin-contracts/contracts/mocks/token/ERC721ReceiverMock.sol b/CVLByExample/UniswapHooks/lib/uniswap-hooks/lib/v4-core/lib/openzeppelin-contracts/contracts/mocks/token/ERC721ReceiverMock.sol new file mode 100644 index 00000000..14120f5d --- /dev/null +++ b/CVLByExample/UniswapHooks/lib/uniswap-hooks/lib/v4-core/lib/openzeppelin-contracts/contracts/mocks/token/ERC721ReceiverMock.sol @@ -0,0 +1,47 @@ +// SPDX-License-Identifier: MIT + +pragma solidity ^0.8.20; + +import {IERC721Receiver} from "../../token/ERC721/IERC721Receiver.sol"; + +contract ERC721ReceiverMock is IERC721Receiver { + enum RevertType { + None, + RevertWithoutMessage, + RevertWithMessage, + RevertWithCustomError, + Panic + } + + bytes4 private immutable _retval; + RevertType private immutable _error; + + event Received(address operator, address from, uint256 tokenId, bytes data, uint256 gas); + error CustomError(bytes4); + + constructor(bytes4 retval, RevertType error) { + _retval = retval; + _error = error; + } + + function onERC721Received( + address operator, + address from, + uint256 tokenId, + bytes memory data + ) public returns (bytes4) { + if (_error == RevertType.RevertWithoutMessage) { + revert(); + } else if (_error == RevertType.RevertWithMessage) { + revert("ERC721ReceiverMock: reverting"); + } else if (_error == RevertType.RevertWithCustomError) { + revert CustomError(_retval); + } else if (_error == RevertType.Panic) { + uint256 a = uint256(0) / uint256(0); + a; + } + + emit Received(operator, from, tokenId, data, gasleft()); + return _retval; + } +} diff --git a/CVLByExample/UniswapHooks/lib/uniswap-hooks/lib/v4-core/lib/openzeppelin-contracts/contracts/mocks/token/ERC721URIStorageMock.sol b/CVLByExample/UniswapHooks/lib/uniswap-hooks/lib/v4-core/lib/openzeppelin-contracts/contracts/mocks/token/ERC721URIStorageMock.sol new file mode 100644 index 00000000..254435e0 --- /dev/null +++ b/CVLByExample/UniswapHooks/lib/uniswap-hooks/lib/v4-core/lib/openzeppelin-contracts/contracts/mocks/token/ERC721URIStorageMock.sol @@ -0,0 +1,17 @@ +// SPDX-License-Identifier: MIT + +pragma solidity ^0.8.20; + +import {ERC721URIStorage} from "../../token/ERC721/extensions/ERC721URIStorage.sol"; + +abstract contract ERC721URIStorageMock is ERC721URIStorage { + string private _baseTokenURI; + + function _baseURI() internal view virtual override returns (string memory) { + return _baseTokenURI; + } + + function setBaseURI(string calldata newBaseTokenURI) public { + _baseTokenURI = newBaseTokenURI; + } +} diff --git a/CVLByExample/UniswapHooks/lib/uniswap-hooks/lib/v4-core/lib/openzeppelin-contracts/contracts/mocks/token/VotesTimestamp.sol b/CVLByExample/UniswapHooks/lib/uniswap-hooks/lib/v4-core/lib/openzeppelin-contracts/contracts/mocks/token/VotesTimestamp.sol new file mode 100644 index 00000000..78fdfae9 --- /dev/null +++ b/CVLByExample/UniswapHooks/lib/uniswap-hooks/lib/v4-core/lib/openzeppelin-contracts/contracts/mocks/token/VotesTimestamp.sol @@ -0,0 +1,29 @@ +// SPDX-License-Identifier: MIT + +pragma solidity ^0.8.20; + +import {ERC20Votes} from "../../token/ERC20/extensions/ERC20Votes.sol"; +import {ERC721Votes} from "../../token/ERC721/extensions/ERC721Votes.sol"; +import {SafeCast} from "../../utils/math/SafeCast.sol"; + +abstract contract ERC20VotesTimestampMock is ERC20Votes { + function clock() public view virtual override returns (uint48) { + return SafeCast.toUint48(block.timestamp); + } + + // solhint-disable-next-line func-name-mixedcase + function CLOCK_MODE() public view virtual override returns (string memory) { + return "mode=timestamp"; + } +} + +abstract contract ERC721VotesTimestampMock is ERC721Votes { + function clock() public view virtual override returns (uint48) { + return SafeCast.toUint48(block.timestamp); + } + + // solhint-disable-next-line func-name-mixedcase + function CLOCK_MODE() public view virtual override returns (string memory) { + return "mode=timestamp"; + } +} diff --git a/CVLByExample/UniswapHooks/lib/uniswap-hooks/lib/v4-core/lib/openzeppelin-contracts/contracts/package.json b/CVLByExample/UniswapHooks/lib/uniswap-hooks/lib/v4-core/lib/openzeppelin-contracts/contracts/package.json new file mode 100644 index 00000000..845e8c40 --- /dev/null +++ b/CVLByExample/UniswapHooks/lib/uniswap-hooks/lib/v4-core/lib/openzeppelin-contracts/contracts/package.json @@ -0,0 +1,32 @@ +{ + "name": "@openzeppelin/contracts", + "description": "Secure Smart Contract library for Solidity", + "version": "5.0.2", + "files": [ + "**/*.sol", + "/build/contracts/*.json", + "!/mocks/**/*" + ], + "scripts": { + "prepack": "bash ../scripts/prepack.sh", + "prepare-docs": "cd ..; npm run prepare-docs" + }, + "repository": { + "type": "git", + "url": "https://github.com/OpenZeppelin/openzeppelin-contracts.git" + }, + "keywords": [ + "solidity", + "ethereum", + "smart", + "contracts", + "security", + "zeppelin" + ], + "author": "OpenZeppelin Community ", + "license": "MIT", + "bugs": { + "url": "https://github.com/OpenZeppelin/openzeppelin-contracts/issues" + }, + "homepage": "https://openzeppelin.com/contracts/" +} diff --git a/CVLByExample/UniswapHooks/lib/uniswap-hooks/lib/v4-core/lib/openzeppelin-contracts/contracts/proxy/Clones.sol b/CVLByExample/UniswapHooks/lib/uniswap-hooks/lib/v4-core/lib/openzeppelin-contracts/contracts/proxy/Clones.sol new file mode 100644 index 00000000..95e467d3 --- /dev/null +++ b/CVLByExample/UniswapHooks/lib/uniswap-hooks/lib/v4-core/lib/openzeppelin-contracts/contracts/proxy/Clones.sol @@ -0,0 +1,95 @@ +// SPDX-License-Identifier: MIT +// OpenZeppelin Contracts (last updated v5.0.0) (proxy/Clones.sol) + +pragma solidity ^0.8.20; + +/** + * @dev https://eips.ethereum.org/EIPS/eip-1167[EIP 1167] is a standard for + * deploying minimal proxy contracts, also known as "clones". + * + * > To simply and cheaply clone contract functionality in an immutable way, this standard specifies + * > a minimal bytecode implementation that delegates all calls to a known, fixed address. + * + * The library includes functions to deploy a proxy using either `create` (traditional deployment) or `create2` + * (salted deterministic deployment). It also includes functions to predict the addresses of clones deployed using the + * deterministic method. + */ +library Clones { + /** + * @dev A clone instance deployment failed. + */ + error ERC1167FailedCreateClone(); + + /** + * @dev Deploys and returns the address of a clone that mimics the behaviour of `implementation`. + * + * This function uses the create opcode, which should never revert. + */ + function clone(address implementation) internal returns (address instance) { + /// @solidity memory-safe-assembly + assembly { + // Cleans the upper 96 bits of the `implementation` word, then packs the first 3 bytes + // of the `implementation` address with the bytecode before the address. + mstore(0x00, or(shr(0xe8, shl(0x60, implementation)), 0x3d602d80600a3d3981f3363d3d373d3d3d363d73000000)) + // Packs the remaining 17 bytes of `implementation` with the bytecode after the address. + mstore(0x20, or(shl(0x78, implementation), 0x5af43d82803e903d91602b57fd5bf3)) + instance := create(0, 0x09, 0x37) + } + if (instance == address(0)) { + revert ERC1167FailedCreateClone(); + } + } + + /** + * @dev Deploys and returns the address of a clone that mimics the behaviour of `implementation`. + * + * This function uses the create2 opcode and a `salt` to deterministically deploy + * the clone. Using the same `implementation` and `salt` multiple time will revert, since + * the clones cannot be deployed twice at the same address. + */ + function cloneDeterministic(address implementation, bytes32 salt) internal returns (address instance) { + /// @solidity memory-safe-assembly + assembly { + // Cleans the upper 96 bits of the `implementation` word, then packs the first 3 bytes + // of the `implementation` address with the bytecode before the address. + mstore(0x00, or(shr(0xe8, shl(0x60, implementation)), 0x3d602d80600a3d3981f3363d3d373d3d3d363d73000000)) + // Packs the remaining 17 bytes of `implementation` with the bytecode after the address. + mstore(0x20, or(shl(0x78, implementation), 0x5af43d82803e903d91602b57fd5bf3)) + instance := create2(0, 0x09, 0x37, salt) + } + if (instance == address(0)) { + revert ERC1167FailedCreateClone(); + } + } + + /** + * @dev Computes the address of a clone deployed using {Clones-cloneDeterministic}. + */ + function predictDeterministicAddress( + address implementation, + bytes32 salt, + address deployer + ) internal pure returns (address predicted) { + /// @solidity memory-safe-assembly + assembly { + let ptr := mload(0x40) + mstore(add(ptr, 0x38), deployer) + mstore(add(ptr, 0x24), 0x5af43d82803e903d91602b57fd5bf3ff) + mstore(add(ptr, 0x14), implementation) + mstore(ptr, 0x3d602d80600a3d3981f3363d3d373d3d3d363d73) + mstore(add(ptr, 0x58), salt) + mstore(add(ptr, 0x78), keccak256(add(ptr, 0x0c), 0x37)) + predicted := keccak256(add(ptr, 0x43), 0x55) + } + } + + /** + * @dev Computes the address of a clone deployed using {Clones-cloneDeterministic}. + */ + function predictDeterministicAddress( + address implementation, + bytes32 salt + ) internal view returns (address predicted) { + return predictDeterministicAddress(implementation, salt, address(this)); + } +} diff --git a/CVLByExample/UniswapHooks/lib/uniswap-hooks/lib/v4-core/lib/openzeppelin-contracts/contracts/proxy/ERC1967/ERC1967Proxy.sol b/CVLByExample/UniswapHooks/lib/uniswap-hooks/lib/v4-core/lib/openzeppelin-contracts/contracts/proxy/ERC1967/ERC1967Proxy.sol new file mode 100644 index 00000000..0fa61b5b --- /dev/null +++ b/CVLByExample/UniswapHooks/lib/uniswap-hooks/lib/v4-core/lib/openzeppelin-contracts/contracts/proxy/ERC1967/ERC1967Proxy.sol @@ -0,0 +1,40 @@ +// SPDX-License-Identifier: MIT +// OpenZeppelin Contracts (last updated v5.0.0) (proxy/ERC1967/ERC1967Proxy.sol) + +pragma solidity ^0.8.20; + +import {Proxy} from "../Proxy.sol"; +import {ERC1967Utils} from "./ERC1967Utils.sol"; + +/** + * @dev This contract implements an upgradeable proxy. It is upgradeable because calls are delegated to an + * implementation address that can be changed. This address is stored in storage in the location specified by + * https://eips.ethereum.org/EIPS/eip-1967[EIP1967], so that it doesn't conflict with the storage layout of the + * implementation behind the proxy. + */ +contract ERC1967Proxy is Proxy { + /** + * @dev Initializes the upgradeable proxy with an initial implementation specified by `implementation`. + * + * If `_data` is nonempty, it's used as data in a delegate call to `implementation`. This will typically be an + * encoded function call, and allows initializing the storage of the proxy like a Solidity constructor. + * + * Requirements: + * + * - If `data` is empty, `msg.value` must be zero. + */ + constructor(address implementation, bytes memory _data) payable { + ERC1967Utils.upgradeToAndCall(implementation, _data); + } + + /** + * @dev Returns the current implementation address. + * + * TIP: To get this value clients can read directly from the storage slot shown below (specified by EIP1967) using + * the https://eth.wiki/json-rpc/API#eth_getstorageat[`eth_getStorageAt`] RPC call. + * `0x360894a13ba1a3210667c828492db98dca3e2076cc3735a920a3ca505d382bbc` + */ + function _implementation() internal view virtual override returns (address) { + return ERC1967Utils.getImplementation(); + } +} diff --git a/CVLByExample/UniswapHooks/lib/uniswap-hooks/lib/v4-core/lib/openzeppelin-contracts/contracts/proxy/ERC1967/ERC1967Utils.sol b/CVLByExample/UniswapHooks/lib/uniswap-hooks/lib/v4-core/lib/openzeppelin-contracts/contracts/proxy/ERC1967/ERC1967Utils.sol new file mode 100644 index 00000000..e55bae20 --- /dev/null +++ b/CVLByExample/UniswapHooks/lib/uniswap-hooks/lib/v4-core/lib/openzeppelin-contracts/contracts/proxy/ERC1967/ERC1967Utils.sol @@ -0,0 +1,193 @@ +// SPDX-License-Identifier: MIT +// OpenZeppelin Contracts (last updated v5.0.0) (proxy/ERC1967/ERC1967Utils.sol) + +pragma solidity ^0.8.20; + +import {IBeacon} from "../beacon/IBeacon.sol"; +import {Address} from "../../utils/Address.sol"; +import {StorageSlot} from "../../utils/StorageSlot.sol"; + +/** + * @dev This abstract contract provides getters and event emitting update functions for + * https://eips.ethereum.org/EIPS/eip-1967[EIP1967] slots. + */ +library ERC1967Utils { + // We re-declare ERC-1967 events here because they can't be used directly from IERC1967. + // This will be fixed in Solidity 0.8.21. At that point we should remove these events. + /** + * @dev Emitted when the implementation is upgraded. + */ + event Upgraded(address indexed implementation); + + /** + * @dev Emitted when the admin account has changed. + */ + event AdminChanged(address previousAdmin, address newAdmin); + + /** + * @dev Emitted when the beacon is changed. + */ + event BeaconUpgraded(address indexed beacon); + + /** + * @dev Storage slot with the address of the current implementation. + * This is the keccak-256 hash of "eip1967.proxy.implementation" subtracted by 1. + */ + // solhint-disable-next-line private-vars-leading-underscore + bytes32 internal constant IMPLEMENTATION_SLOT = 0x360894a13ba1a3210667c828492db98dca3e2076cc3735a920a3ca505d382bbc; + + /** + * @dev The `implementation` of the proxy is invalid. + */ + error ERC1967InvalidImplementation(address implementation); + + /** + * @dev The `admin` of the proxy is invalid. + */ + error ERC1967InvalidAdmin(address admin); + + /** + * @dev The `beacon` of the proxy is invalid. + */ + error ERC1967InvalidBeacon(address beacon); + + /** + * @dev An upgrade function sees `msg.value > 0` that may be lost. + */ + error ERC1967NonPayable(); + + /** + * @dev Returns the current implementation address. + */ + function getImplementation() internal view returns (address) { + return StorageSlot.getAddressSlot(IMPLEMENTATION_SLOT).value; + } + + /** + * @dev Stores a new address in the EIP1967 implementation slot. + */ + function _setImplementation(address newImplementation) private { + if (newImplementation.code.length == 0) { + revert ERC1967InvalidImplementation(newImplementation); + } + StorageSlot.getAddressSlot(IMPLEMENTATION_SLOT).value = newImplementation; + } + + /** + * @dev Performs implementation upgrade with additional setup call if data is nonempty. + * This function is payable only if the setup call is performed, otherwise `msg.value` is rejected + * to avoid stuck value in the contract. + * + * Emits an {IERC1967-Upgraded} event. + */ + function upgradeToAndCall(address newImplementation, bytes memory data) internal { + _setImplementation(newImplementation); + emit Upgraded(newImplementation); + + if (data.length > 0) { + Address.functionDelegateCall(newImplementation, data); + } else { + _checkNonPayable(); + } + } + + /** + * @dev Storage slot with the admin of the contract. + * This is the keccak-256 hash of "eip1967.proxy.admin" subtracted by 1. + */ + // solhint-disable-next-line private-vars-leading-underscore + bytes32 internal constant ADMIN_SLOT = 0xb53127684a568b3173ae13b9f8a6016e243e63b6e8ee1178d6a717850b5d6103; + + /** + * @dev Returns the current admin. + * + * TIP: To get this value clients can read directly from the storage slot shown below (specified by EIP1967) using + * the https://eth.wiki/json-rpc/API#eth_getstorageat[`eth_getStorageAt`] RPC call. + * `0xb53127684a568b3173ae13b9f8a6016e243e63b6e8ee1178d6a717850b5d6103` + */ + function getAdmin() internal view returns (address) { + return StorageSlot.getAddressSlot(ADMIN_SLOT).value; + } + + /** + * @dev Stores a new address in the EIP1967 admin slot. + */ + function _setAdmin(address newAdmin) private { + if (newAdmin == address(0)) { + revert ERC1967InvalidAdmin(address(0)); + } + StorageSlot.getAddressSlot(ADMIN_SLOT).value = newAdmin; + } + + /** + * @dev Changes the admin of the proxy. + * + * Emits an {IERC1967-AdminChanged} event. + */ + function changeAdmin(address newAdmin) internal { + emit AdminChanged(getAdmin(), newAdmin); + _setAdmin(newAdmin); + } + + /** + * @dev The storage slot of the UpgradeableBeacon contract which defines the implementation for this proxy. + * This is the keccak-256 hash of "eip1967.proxy.beacon" subtracted by 1. + */ + // solhint-disable-next-line private-vars-leading-underscore + bytes32 internal constant BEACON_SLOT = 0xa3f0ad74e5423aebfd80d3ef4346578335a9a72aeaee59ff6cb3582b35133d50; + + /** + * @dev Returns the current beacon. + */ + function getBeacon() internal view returns (address) { + return StorageSlot.getAddressSlot(BEACON_SLOT).value; + } + + /** + * @dev Stores a new beacon in the EIP1967 beacon slot. + */ + function _setBeacon(address newBeacon) private { + if (newBeacon.code.length == 0) { + revert ERC1967InvalidBeacon(newBeacon); + } + + StorageSlot.getAddressSlot(BEACON_SLOT).value = newBeacon; + + address beaconImplementation = IBeacon(newBeacon).implementation(); + if (beaconImplementation.code.length == 0) { + revert ERC1967InvalidImplementation(beaconImplementation); + } + } + + /** + * @dev Change the beacon and trigger a setup call if data is nonempty. + * This function is payable only if the setup call is performed, otherwise `msg.value` is rejected + * to avoid stuck value in the contract. + * + * Emits an {IERC1967-BeaconUpgraded} event. + * + * CAUTION: Invoking this function has no effect on an instance of {BeaconProxy} since v5, since + * it uses an immutable beacon without looking at the value of the ERC-1967 beacon slot for + * efficiency. + */ + function upgradeBeaconToAndCall(address newBeacon, bytes memory data) internal { + _setBeacon(newBeacon); + emit BeaconUpgraded(newBeacon); + + if (data.length > 0) { + Address.functionDelegateCall(IBeacon(newBeacon).implementation(), data); + } else { + _checkNonPayable(); + } + } + + /** + * @dev Reverts if `msg.value` is not zero. It can be used to avoid `msg.value` stuck in the contract + * if an upgrade doesn't perform an initialization call. + */ + function _checkNonPayable() private { + if (msg.value > 0) { + revert ERC1967NonPayable(); + } + } +} diff --git a/CVLByExample/UniswapHooks/lib/uniswap-hooks/lib/v4-core/lib/openzeppelin-contracts/contracts/proxy/Proxy.sol b/CVLByExample/UniswapHooks/lib/uniswap-hooks/lib/v4-core/lib/openzeppelin-contracts/contracts/proxy/Proxy.sol new file mode 100644 index 00000000..0e736512 --- /dev/null +++ b/CVLByExample/UniswapHooks/lib/uniswap-hooks/lib/v4-core/lib/openzeppelin-contracts/contracts/proxy/Proxy.sol @@ -0,0 +1,69 @@ +// SPDX-License-Identifier: MIT +// OpenZeppelin Contracts (last updated v5.0.0) (proxy/Proxy.sol) + +pragma solidity ^0.8.20; + +/** + * @dev This abstract contract provides a fallback function that delegates all calls to another contract using the EVM + * instruction `delegatecall`. We refer to the second contract as the _implementation_ behind the proxy, and it has to + * be specified by overriding the virtual {_implementation} function. + * + * Additionally, delegation to the implementation can be triggered manually through the {_fallback} function, or to a + * different contract through the {_delegate} function. + * + * The success and return data of the delegated call will be returned back to the caller of the proxy. + */ +abstract contract Proxy { + /** + * @dev Delegates the current call to `implementation`. + * + * This function does not return to its internal call site, it will return directly to the external caller. + */ + function _delegate(address implementation) internal virtual { + assembly { + // Copy msg.data. We take full control of memory in this inline assembly + // block because it will not return to Solidity code. We overwrite the + // Solidity scratch pad at memory position 0. + calldatacopy(0, 0, calldatasize()) + + // Call the implementation. + // out and outsize are 0 because we don't know the size yet. + let result := delegatecall(gas(), implementation, 0, calldatasize(), 0, 0) + + // Copy the returned data. + returndatacopy(0, 0, returndatasize()) + + switch result + // delegatecall returns 0 on error. + case 0 { + revert(0, returndatasize()) + } + default { + return(0, returndatasize()) + } + } + } + + /** + * @dev This is a virtual function that should be overridden so it returns the address to which the fallback + * function and {_fallback} should delegate. + */ + function _implementation() internal view virtual returns (address); + + /** + * @dev Delegates the current call to the address returned by `_implementation()`. + * + * This function does not return to its internal call site, it will return directly to the external caller. + */ + function _fallback() internal virtual { + _delegate(_implementation()); + } + + /** + * @dev Fallback function that delegates calls to the address returned by `_implementation()`. Will run if no other + * function in the contract matches the call data. + */ + fallback() external payable virtual { + _fallback(); + } +} diff --git a/CVLByExample/UniswapHooks/lib/uniswap-hooks/lib/v4-core/lib/openzeppelin-contracts/contracts/proxy/README.adoc b/CVLByExample/UniswapHooks/lib/uniswap-hooks/lib/v4-core/lib/openzeppelin-contracts/contracts/proxy/README.adoc new file mode 100644 index 00000000..3c4a78d1 --- /dev/null +++ b/CVLByExample/UniswapHooks/lib/uniswap-hooks/lib/v4-core/lib/openzeppelin-contracts/contracts/proxy/README.adoc @@ -0,0 +1,87 @@ += Proxies + +[.readme-notice] +NOTE: This document is better viewed at https://docs.openzeppelin.com/contracts/api/proxy + +This is a low-level set of contracts implementing different proxy patterns with and without upgradeability. For an in-depth overview of this pattern check out the xref:upgrades-plugins::proxies.adoc[Proxy Upgrade Pattern] page. + +Most of the proxies below are built on an abstract base contract. + +- {Proxy}: Abstract contract implementing the core delegation functionality. + +In order to avoid clashes with the storage variables of the implementation contract behind a proxy, we use https://eips.ethereum.org/EIPS/eip-1967[EIP1967] storage slots. + +- {ERC1967Utils}: Internal functions to get and set the storage slots defined in EIP1967. +- {ERC1967Proxy}: A proxy using EIP1967 storage slots. Not upgradeable by default. + +There are two alternative ways to add upgradeability to an ERC1967 proxy. Their differences are explained below in <>. + +- {TransparentUpgradeableProxy}: A proxy with a built-in immutable admin and upgrade interface. +- {UUPSUpgradeable}: An upgradeability mechanism to be included in the implementation contract. + +CAUTION: Using upgradeable proxies correctly and securely is a difficult task that requires deep knowledge of the proxy pattern, Solidity, and the EVM. Unless you want a lot of low level control, we recommend using the xref:upgrades-plugins::index.adoc[OpenZeppelin Upgrades Plugins] for Truffle and Hardhat. + +A different family of proxies are beacon proxies. This pattern, popularized by Dharma, allows multiple proxies to be upgraded to a different implementation in a single transaction. + +- {BeaconProxy}: A proxy that retrieves its implementation from a beacon contract. +- {UpgradeableBeacon}: A beacon contract with a built in admin that can upgrade the {BeaconProxy} pointing to it. + +In this pattern, the proxy contract doesn't hold the implementation address in storage like an ERC1967 proxy. Instead, the address is stored in a separate beacon contract. The `upgrade` operations are sent to the beacon instead of to the proxy contract, and all proxies that follow that beacon are automatically upgraded. + +Outside the realm of upgradeability, proxies can also be useful to make cheap contract clones, such as those created by an on-chain factory contract that creates many instances of the same contract. These instances are designed to be both cheap to deploy, and cheap to call. + +- {Clones}: A library that can deploy cheap minimal non-upgradeable proxies. + +[[transparent-vs-uups]] +== Transparent vs UUPS Proxies + +The original proxies included in OpenZeppelin followed the https://blog.openzeppelin.com/the-transparent-proxy-pattern/[Transparent Proxy Pattern]. While this pattern is still provided, our recommendation is now shifting towards UUPS proxies, which are both lightweight and versatile. The name UUPS comes from https://eips.ethereum.org/EIPS/eip-1822[EIP1822], which first documented the pattern. + +While both of these share the same interface for upgrades, in UUPS proxies the upgrade is handled by the implementation, and can eventually be removed. Transparent proxies, on the other hand, include the upgrade and admin logic in the proxy itself. This means {TransparentUpgradeableProxy} is more expensive to deploy than what is possible with UUPS proxies. + +UUPS proxies are implemented using an {ERC1967Proxy}. Note that this proxy is not by itself upgradeable. It is the role of the implementation to include, alongside the contract's logic, all the code necessary to update the implementation's address that is stored at a specific slot in the proxy's storage space. This is where the {UUPSUpgradeable} contract comes in. Inheriting from it (and overriding the {xref-UUPSUpgradeable-_authorizeUpgrade-address-}[`_authorizeUpgrade`] function with the relevant access control mechanism) will turn your contract into a UUPS compliant implementation. + +Note that since both proxies use the same storage slot for the implementation address, using a UUPS compliant implementation with a {TransparentUpgradeableProxy} might allow non-admins to perform upgrade operations. + +By default, the upgrade functionality included in {UUPSUpgradeable} contains a security mechanism that will prevent any upgrades to a non UUPS compliant implementation. This prevents upgrades to an implementation contract that wouldn't contain the necessary upgrade mechanism, as it would lock the upgradeability of the proxy forever. This security mechanism can be bypassed by either of: + +- Adding a flag mechanism in the implementation that will disable the upgrade function when triggered. +- Upgrading to an implementation that features an upgrade mechanism without the additional security check, and then upgrading again to another implementation without the upgrade mechanism. + +The current implementation of this security mechanism uses https://eips.ethereum.org/EIPS/eip-1822[EIP1822] to detect the storage slot used by the implementation. A previous implementation, now deprecated, relied on a rollback check. It is possible to upgrade from a contract using the old mechanism to a new one. The inverse is however not possible, as old implementations (before version 4.5) did not include the `ERC1822` interface. + +== Core + +{{Proxy}} + +== ERC1967 + +{{IERC1967}} + +{{ERC1967Proxy}} + +{{ERC1967Utils}} + +== Transparent Proxy + +{{TransparentUpgradeableProxy}} + +{{ProxyAdmin}} + +== Beacon + +{{BeaconProxy}} + +{{IBeacon}} + +{{UpgradeableBeacon}} + +== Minimal Clones + +{{Clones}} + +== Utils + +{{Initializable}} + +{{UUPSUpgradeable}} diff --git a/CVLByExample/UniswapHooks/lib/uniswap-hooks/lib/v4-core/lib/openzeppelin-contracts/contracts/proxy/beacon/BeaconProxy.sol b/CVLByExample/UniswapHooks/lib/uniswap-hooks/lib/v4-core/lib/openzeppelin-contracts/contracts/proxy/beacon/BeaconProxy.sol new file mode 100644 index 00000000..05e26e5d --- /dev/null +++ b/CVLByExample/UniswapHooks/lib/uniswap-hooks/lib/v4-core/lib/openzeppelin-contracts/contracts/proxy/beacon/BeaconProxy.sol @@ -0,0 +1,57 @@ +// SPDX-License-Identifier: MIT +// OpenZeppelin Contracts (last updated v5.0.0) (proxy/beacon/BeaconProxy.sol) + +pragma solidity ^0.8.20; + +import {IBeacon} from "./IBeacon.sol"; +import {Proxy} from "../Proxy.sol"; +import {ERC1967Utils} from "../ERC1967/ERC1967Utils.sol"; + +/** + * @dev This contract implements a proxy that gets the implementation address for each call from an {UpgradeableBeacon}. + * + * The beacon address can only be set once during construction, and cannot be changed afterwards. It is stored in an + * immutable variable to avoid unnecessary storage reads, and also in the beacon storage slot specified by + * https://eips.ethereum.org/EIPS/eip-1967[EIP1967] so that it can be accessed externally. + * + * CAUTION: Since the beacon address can never be changed, you must ensure that you either control the beacon, or trust + * the beacon to not upgrade the implementation maliciously. + * + * IMPORTANT: Do not use the implementation logic to modify the beacon storage slot. Doing so would leave the proxy in + * an inconsistent state where the beacon storage slot does not match the beacon address. + */ +contract BeaconProxy is Proxy { + // An immutable address for the beacon to avoid unnecessary SLOADs before each delegate call. + address private immutable _beacon; + + /** + * @dev Initializes the proxy with `beacon`. + * + * If `data` is nonempty, it's used as data in a delegate call to the implementation returned by the beacon. This + * will typically be an encoded function call, and allows initializing the storage of the proxy like a Solidity + * constructor. + * + * Requirements: + * + * - `beacon` must be a contract with the interface {IBeacon}. + * - If `data` is empty, `msg.value` must be zero. + */ + constructor(address beacon, bytes memory data) payable { + ERC1967Utils.upgradeBeaconToAndCall(beacon, data); + _beacon = beacon; + } + + /** + * @dev Returns the current implementation address of the associated beacon. + */ + function _implementation() internal view virtual override returns (address) { + return IBeacon(_getBeacon()).implementation(); + } + + /** + * @dev Returns the beacon. + */ + function _getBeacon() internal view virtual returns (address) { + return _beacon; + } +} diff --git a/CVLByExample/UniswapHooks/lib/uniswap-hooks/lib/v4-core/lib/openzeppelin-contracts/contracts/proxy/beacon/IBeacon.sol b/CVLByExample/UniswapHooks/lib/uniswap-hooks/lib/v4-core/lib/openzeppelin-contracts/contracts/proxy/beacon/IBeacon.sol new file mode 100644 index 00000000..36a3c76e --- /dev/null +++ b/CVLByExample/UniswapHooks/lib/uniswap-hooks/lib/v4-core/lib/openzeppelin-contracts/contracts/proxy/beacon/IBeacon.sol @@ -0,0 +1,16 @@ +// SPDX-License-Identifier: MIT +// OpenZeppelin Contracts (last updated v5.0.0) (proxy/beacon/IBeacon.sol) + +pragma solidity ^0.8.20; + +/** + * @dev This is the interface that {BeaconProxy} expects of its beacon. + */ +interface IBeacon { + /** + * @dev Must return an address that can be used as a delegate call target. + * + * {UpgradeableBeacon} will check that this address is a contract. + */ + function implementation() external view returns (address); +} diff --git a/CVLByExample/UniswapHooks/lib/uniswap-hooks/lib/v4-core/lib/openzeppelin-contracts/contracts/proxy/beacon/UpgradeableBeacon.sol b/CVLByExample/UniswapHooks/lib/uniswap-hooks/lib/v4-core/lib/openzeppelin-contracts/contracts/proxy/beacon/UpgradeableBeacon.sol new file mode 100644 index 00000000..8db9bd23 --- /dev/null +++ b/CVLByExample/UniswapHooks/lib/uniswap-hooks/lib/v4-core/lib/openzeppelin-contracts/contracts/proxy/beacon/UpgradeableBeacon.sol @@ -0,0 +1,70 @@ +// SPDX-License-Identifier: MIT +// OpenZeppelin Contracts (last updated v5.0.0) (proxy/beacon/UpgradeableBeacon.sol) + +pragma solidity ^0.8.20; + +import {IBeacon} from "./IBeacon.sol"; +import {Ownable} from "../../access/Ownable.sol"; + +/** + * @dev This contract is used in conjunction with one or more instances of {BeaconProxy} to determine their + * implementation contract, which is where they will delegate all function calls. + * + * An owner is able to change the implementation the beacon points to, thus upgrading the proxies that use this beacon. + */ +contract UpgradeableBeacon is IBeacon, Ownable { + address private _implementation; + + /** + * @dev The `implementation` of the beacon is invalid. + */ + error BeaconInvalidImplementation(address implementation); + + /** + * @dev Emitted when the implementation returned by the beacon is changed. + */ + event Upgraded(address indexed implementation); + + /** + * @dev Sets the address of the initial implementation, and the initial owner who can upgrade the beacon. + */ + constructor(address implementation_, address initialOwner) Ownable(initialOwner) { + _setImplementation(implementation_); + } + + /** + * @dev Returns the current implementation address. + */ + function implementation() public view virtual returns (address) { + return _implementation; + } + + /** + * @dev Upgrades the beacon to a new implementation. + * + * Emits an {Upgraded} event. + * + * Requirements: + * + * - msg.sender must be the owner of the contract. + * - `newImplementation` must be a contract. + */ + function upgradeTo(address newImplementation) public virtual onlyOwner { + _setImplementation(newImplementation); + } + + /** + * @dev Sets the implementation contract address for this beacon + * + * Requirements: + * + * - `newImplementation` must be a contract. + */ + function _setImplementation(address newImplementation) private { + if (newImplementation.code.length == 0) { + revert BeaconInvalidImplementation(newImplementation); + } + _implementation = newImplementation; + emit Upgraded(newImplementation); + } +} diff --git a/CVLByExample/UniswapHooks/lib/uniswap-hooks/lib/v4-core/lib/openzeppelin-contracts/contracts/proxy/transparent/ProxyAdmin.sol b/CVLByExample/UniswapHooks/lib/uniswap-hooks/lib/v4-core/lib/openzeppelin-contracts/contracts/proxy/transparent/ProxyAdmin.sol new file mode 100644 index 00000000..dab55ef2 --- /dev/null +++ b/CVLByExample/UniswapHooks/lib/uniswap-hooks/lib/v4-core/lib/openzeppelin-contracts/contracts/proxy/transparent/ProxyAdmin.sol @@ -0,0 +1,45 @@ +// SPDX-License-Identifier: MIT +// OpenZeppelin Contracts (last updated v5.0.0) (proxy/transparent/ProxyAdmin.sol) + +pragma solidity ^0.8.20; + +import {ITransparentUpgradeableProxy} from "./TransparentUpgradeableProxy.sol"; +import {Ownable} from "../../access/Ownable.sol"; + +/** + * @dev This is an auxiliary contract meant to be assigned as the admin of a {TransparentUpgradeableProxy}. For an + * explanation of why you would want to use this see the documentation for {TransparentUpgradeableProxy}. + */ +contract ProxyAdmin is Ownable { + /** + * @dev The version of the upgrade interface of the contract. If this getter is missing, both `upgrade(address)` + * and `upgradeAndCall(address,bytes)` are present, and `upgradeTo` must be used if no function should be called, + * while `upgradeAndCall` will invoke the `receive` function if the second argument is the empty byte string. + * If the getter returns `"5.0.0"`, only `upgradeAndCall(address,bytes)` is present, and the second argument must + * be the empty byte string if no function should be called, making it impossible to invoke the `receive` function + * during an upgrade. + */ + string public constant UPGRADE_INTERFACE_VERSION = "5.0.0"; + + /** + * @dev Sets the initial owner who can perform upgrades. + */ + constructor(address initialOwner) Ownable(initialOwner) {} + + /** + * @dev Upgrades `proxy` to `implementation` and calls a function on the new implementation. + * See {TransparentUpgradeableProxy-_dispatchUpgradeToAndCall}. + * + * Requirements: + * + * - This contract must be the admin of `proxy`. + * - If `data` is empty, `msg.value` must be zero. + */ + function upgradeAndCall( + ITransparentUpgradeableProxy proxy, + address implementation, + bytes memory data + ) public payable virtual onlyOwner { + proxy.upgradeToAndCall{value: msg.value}(implementation, data); + } +} diff --git a/CVLByExample/UniswapHooks/lib/uniswap-hooks/lib/v4-core/lib/openzeppelin-contracts/contracts/proxy/transparent/TransparentUpgradeableProxy.sol b/CVLByExample/UniswapHooks/lib/uniswap-hooks/lib/v4-core/lib/openzeppelin-contracts/contracts/proxy/transparent/TransparentUpgradeableProxy.sol new file mode 100644 index 00000000..b2021c74 --- /dev/null +++ b/CVLByExample/UniswapHooks/lib/uniswap-hooks/lib/v4-core/lib/openzeppelin-contracts/contracts/proxy/transparent/TransparentUpgradeableProxy.sol @@ -0,0 +1,116 @@ +// SPDX-License-Identifier: MIT +// OpenZeppelin Contracts (last updated v5.0.0) (proxy/transparent/TransparentUpgradeableProxy.sol) + +pragma solidity ^0.8.20; + +import {ERC1967Utils} from "../ERC1967/ERC1967Utils.sol"; +import {ERC1967Proxy} from "../ERC1967/ERC1967Proxy.sol"; +import {IERC1967} from "../../interfaces/IERC1967.sol"; +import {ProxyAdmin} from "./ProxyAdmin.sol"; + +/** + * @dev Interface for {TransparentUpgradeableProxy}. In order to implement transparency, {TransparentUpgradeableProxy} + * does not implement this interface directly, and its upgradeability mechanism is implemented by an internal dispatch + * mechanism. The compiler is unaware that these functions are implemented by {TransparentUpgradeableProxy} and will not + * include them in the ABI so this interface must be used to interact with it. + */ +interface ITransparentUpgradeableProxy is IERC1967 { + function upgradeToAndCall(address, bytes calldata) external payable; +} + +/** + * @dev This contract implements a proxy that is upgradeable through an associated {ProxyAdmin} instance. + * + * To avoid https://medium.com/nomic-labs-blog/malicious-backdoors-in-ethereum-proxies-62629adf3357[proxy selector + * clashing], which can potentially be used in an attack, this contract uses the + * https://blog.openzeppelin.com/the-transparent-proxy-pattern/[transparent proxy pattern]. This pattern implies two + * things that go hand in hand: + * + * 1. If any account other than the admin calls the proxy, the call will be forwarded to the implementation, even if + * that call matches the {ITransparentUpgradeableProxy-upgradeToAndCall} function exposed by the proxy itself. + * 2. If the admin calls the proxy, it can call the `upgradeToAndCall` function but any other call won't be forwarded to + * the implementation. If the admin tries to call a function on the implementation it will fail with an error indicating + * the proxy admin cannot fallback to the target implementation. + * + * These properties mean that the admin account can only be used for upgrading the proxy, so it's best if it's a + * dedicated account that is not used for anything else. This will avoid headaches due to sudden errors when trying to + * call a function from the proxy implementation. For this reason, the proxy deploys an instance of {ProxyAdmin} and + * allows upgrades only if they come through it. You should think of the `ProxyAdmin` instance as the administrative + * interface of the proxy, including the ability to change who can trigger upgrades by transferring ownership. + * + * NOTE: The real interface of this proxy is that defined in `ITransparentUpgradeableProxy`. This contract does not + * inherit from that interface, and instead `upgradeToAndCall` is implicitly implemented using a custom dispatch + * mechanism in `_fallback`. Consequently, the compiler will not produce an ABI for this contract. This is necessary to + * fully implement transparency without decoding reverts caused by selector clashes between the proxy and the + * implementation. + * + * NOTE: This proxy does not inherit from {Context} deliberately. The {ProxyAdmin} of this contract won't send a + * meta-transaction in any way, and any other meta-transaction setup should be made in the implementation contract. + * + * IMPORTANT: This contract avoids unnecessary storage reads by setting the admin only during construction as an + * immutable variable, preventing any changes thereafter. However, the admin slot defined in ERC-1967 can still be + * overwritten by the implementation logic pointed to by this proxy. In such cases, the contract may end up in an + * undesirable state where the admin slot is different from the actual admin. + * + * WARNING: It is not recommended to extend this contract to add additional external functions. If you do so, the + * compiler will not check that there are no selector conflicts, due to the note above. A selector clash between any new + * function and the functions declared in {ITransparentUpgradeableProxy} will be resolved in favor of the new one. This + * could render the `upgradeToAndCall` function inaccessible, preventing upgradeability and compromising transparency. + */ +contract TransparentUpgradeableProxy is ERC1967Proxy { + // An immutable address for the admin to avoid unnecessary SLOADs before each call + // at the expense of removing the ability to change the admin once it's set. + // This is acceptable if the admin is always a ProxyAdmin instance or similar contract + // with its own ability to transfer the permissions to another account. + address private immutable _admin; + + /** + * @dev The proxy caller is the current admin, and can't fallback to the proxy target. + */ + error ProxyDeniedAdminAccess(); + + /** + * @dev Initializes an upgradeable proxy managed by an instance of a {ProxyAdmin} with an `initialOwner`, + * backed by the implementation at `_logic`, and optionally initialized with `_data` as explained in + * {ERC1967Proxy-constructor}. + */ + constructor(address _logic, address initialOwner, bytes memory _data) payable ERC1967Proxy(_logic, _data) { + _admin = address(new ProxyAdmin(initialOwner)); + // Set the storage value and emit an event for ERC-1967 compatibility + ERC1967Utils.changeAdmin(_proxyAdmin()); + } + + /** + * @dev Returns the admin of this proxy. + */ + function _proxyAdmin() internal virtual returns (address) { + return _admin; + } + + /** + * @dev If caller is the admin process the call internally, otherwise transparently fallback to the proxy behavior. + */ + function _fallback() internal virtual override { + if (msg.sender == _proxyAdmin()) { + if (msg.sig != ITransparentUpgradeableProxy.upgradeToAndCall.selector) { + revert ProxyDeniedAdminAccess(); + } else { + _dispatchUpgradeToAndCall(); + } + } else { + super._fallback(); + } + } + + /** + * @dev Upgrade the implementation of the proxy. See {ERC1967Utils-upgradeToAndCall}. + * + * Requirements: + * + * - If `data` is empty, `msg.value` must be zero. + */ + function _dispatchUpgradeToAndCall() private { + (address newImplementation, bytes memory data) = abi.decode(msg.data[4:], (address, bytes)); + ERC1967Utils.upgradeToAndCall(newImplementation, data); + } +} diff --git a/CVLByExample/UniswapHooks/lib/uniswap-hooks/lib/v4-core/lib/openzeppelin-contracts/contracts/proxy/utils/Initializable.sol b/CVLByExample/UniswapHooks/lib/uniswap-hooks/lib/v4-core/lib/openzeppelin-contracts/contracts/proxy/utils/Initializable.sol new file mode 100644 index 00000000..b3d82b58 --- /dev/null +++ b/CVLByExample/UniswapHooks/lib/uniswap-hooks/lib/v4-core/lib/openzeppelin-contracts/contracts/proxy/utils/Initializable.sol @@ -0,0 +1,228 @@ +// SPDX-License-Identifier: MIT +// OpenZeppelin Contracts (last updated v5.0.0) (proxy/utils/Initializable.sol) + +pragma solidity ^0.8.20; + +/** + * @dev This is a base contract to aid in writing upgradeable contracts, or any kind of contract that will be deployed + * behind a proxy. Since proxied contracts do not make use of a constructor, it's common to move constructor logic to an + * external initializer function, usually called `initialize`. It then becomes necessary to protect this initializer + * function so it can only be called once. The {initializer} modifier provided by this contract will have this effect. + * + * The initialization functions use a version number. Once a version number is used, it is consumed and cannot be + * reused. This mechanism prevents re-execution of each "step" but allows the creation of new initialization steps in + * case an upgrade adds a module that needs to be initialized. + * + * For example: + * + * [.hljs-theme-light.nopadding] + * ```solidity + * contract MyToken is ERC20Upgradeable { + * function initialize() initializer public { + * __ERC20_init("MyToken", "MTK"); + * } + * } + * + * contract MyTokenV2 is MyToken, ERC20PermitUpgradeable { + * function initializeV2() reinitializer(2) public { + * __ERC20Permit_init("MyToken"); + * } + * } + * ``` + * + * TIP: To avoid leaving the proxy in an uninitialized state, the initializer function should be called as early as + * possible by providing the encoded function call as the `_data` argument to {ERC1967Proxy-constructor}. + * + * CAUTION: When used with inheritance, manual care must be taken to not invoke a parent initializer twice, or to ensure + * that all initializers are idempotent. This is not verified automatically as constructors are by Solidity. + * + * [CAUTION] + * ==== + * Avoid leaving a contract uninitialized. + * + * An uninitialized contract can be taken over by an attacker. This applies to both a proxy and its implementation + * contract, which may impact the proxy. To prevent the implementation contract from being used, you should invoke + * the {_disableInitializers} function in the constructor to automatically lock it when it is deployed: + * + * [.hljs-theme-light.nopadding] + * ``` + * /// @custom:oz-upgrades-unsafe-allow constructor + * constructor() { + * _disableInitializers(); + * } + * ``` + * ==== + */ +abstract contract Initializable { + /** + * @dev Storage of the initializable contract. + * + * It's implemented on a custom ERC-7201 namespace to reduce the risk of storage collisions + * when using with upgradeable contracts. + * + * @custom:storage-location erc7201:openzeppelin.storage.Initializable + */ + struct InitializableStorage { + /** + * @dev Indicates that the contract has been initialized. + */ + uint64 _initialized; + /** + * @dev Indicates that the contract is in the process of being initialized. + */ + bool _initializing; + } + + // keccak256(abi.encode(uint256(keccak256("openzeppelin.storage.Initializable")) - 1)) & ~bytes32(uint256(0xff)) + bytes32 private constant INITIALIZABLE_STORAGE = 0xf0c57e16840df040f15088dc2f81fe391c3923bec73e23a9662efc9c229c6a00; + + /** + * @dev The contract is already initialized. + */ + error InvalidInitialization(); + + /** + * @dev The contract is not initializing. + */ + error NotInitializing(); + + /** + * @dev Triggered when the contract has been initialized or reinitialized. + */ + event Initialized(uint64 version); + + /** + * @dev A modifier that defines a protected initializer function that can be invoked at most once. In its scope, + * `onlyInitializing` functions can be used to initialize parent contracts. + * + * Similar to `reinitializer(1)`, except that in the context of a constructor an `initializer` may be invoked any + * number of times. This behavior in the constructor can be useful during testing and is not expected to be used in + * production. + * + * Emits an {Initialized} event. + */ + modifier initializer() { + // solhint-disable-next-line var-name-mixedcase + InitializableStorage storage $ = _getInitializableStorage(); + + // Cache values to avoid duplicated sloads + bool isTopLevelCall = !$._initializing; + uint64 initialized = $._initialized; + + // Allowed calls: + // - initialSetup: the contract is not in the initializing state and no previous version was + // initialized + // - construction: the contract is initialized at version 1 (no reininitialization) and the + // current contract is just being deployed + bool initialSetup = initialized == 0 && isTopLevelCall; + bool construction = initialized == 1 && address(this).code.length == 0; + + if (!initialSetup && !construction) { + revert InvalidInitialization(); + } + $._initialized = 1; + if (isTopLevelCall) { + $._initializing = true; + } + _; + if (isTopLevelCall) { + $._initializing = false; + emit Initialized(1); + } + } + + /** + * @dev A modifier that defines a protected reinitializer function that can be invoked at most once, and only if the + * contract hasn't been initialized to a greater version before. In its scope, `onlyInitializing` functions can be + * used to initialize parent contracts. + * + * A reinitializer may be used after the original initialization step. This is essential to configure modules that + * are added through upgrades and that require initialization. + * + * When `version` is 1, this modifier is similar to `initializer`, except that functions marked with `reinitializer` + * cannot be nested. If one is invoked in the context of another, execution will revert. + * + * Note that versions can jump in increments greater than 1; this implies that if multiple reinitializers coexist in + * a contract, executing them in the right order is up to the developer or operator. + * + * WARNING: Setting the version to 2**64 - 1 will prevent any future reinitialization. + * + * Emits an {Initialized} event. + */ + modifier reinitializer(uint64 version) { + // solhint-disable-next-line var-name-mixedcase + InitializableStorage storage $ = _getInitializableStorage(); + + if ($._initializing || $._initialized >= version) { + revert InvalidInitialization(); + } + $._initialized = version; + $._initializing = true; + _; + $._initializing = false; + emit Initialized(version); + } + + /** + * @dev Modifier to protect an initialization function so that it can only be invoked by functions with the + * {initializer} and {reinitializer} modifiers, directly or indirectly. + */ + modifier onlyInitializing() { + _checkInitializing(); + _; + } + + /** + * @dev Reverts if the contract is not in an initializing state. See {onlyInitializing}. + */ + function _checkInitializing() internal view virtual { + if (!_isInitializing()) { + revert NotInitializing(); + } + } + + /** + * @dev Locks the contract, preventing any future reinitialization. This cannot be part of an initializer call. + * Calling this in the constructor of a contract will prevent that contract from being initialized or reinitialized + * to any version. It is recommended to use this to lock implementation contracts that are designed to be called + * through proxies. + * + * Emits an {Initialized} event the first time it is successfully executed. + */ + function _disableInitializers() internal virtual { + // solhint-disable-next-line var-name-mixedcase + InitializableStorage storage $ = _getInitializableStorage(); + + if ($._initializing) { + revert InvalidInitialization(); + } + if ($._initialized != type(uint64).max) { + $._initialized = type(uint64).max; + emit Initialized(type(uint64).max); + } + } + + /** + * @dev Returns the highest version that has been initialized. See {reinitializer}. + */ + function _getInitializedVersion() internal view returns (uint64) { + return _getInitializableStorage()._initialized; + } + + /** + * @dev Returns `true` if the contract is currently initializing. See {onlyInitializing}. + */ + function _isInitializing() internal view returns (bool) { + return _getInitializableStorage()._initializing; + } + + /** + * @dev Returns a pointer to the storage namespace. + */ + // solhint-disable-next-line var-name-mixedcase + function _getInitializableStorage() private pure returns (InitializableStorage storage $) { + assembly { + $.slot := INITIALIZABLE_STORAGE + } + } +} diff --git a/CVLByExample/UniswapHooks/lib/uniswap-hooks/lib/v4-core/lib/openzeppelin-contracts/contracts/proxy/utils/UUPSUpgradeable.sol b/CVLByExample/UniswapHooks/lib/uniswap-hooks/lib/v4-core/lib/openzeppelin-contracts/contracts/proxy/utils/UUPSUpgradeable.sol new file mode 100644 index 00000000..8a4e693a --- /dev/null +++ b/CVLByExample/UniswapHooks/lib/uniswap-hooks/lib/v4-core/lib/openzeppelin-contracts/contracts/proxy/utils/UUPSUpgradeable.sol @@ -0,0 +1,147 @@ +// SPDX-License-Identifier: MIT +// OpenZeppelin Contracts (last updated v5.0.0) (proxy/utils/UUPSUpgradeable.sol) + +pragma solidity ^0.8.20; + +import {IERC1822Proxiable} from "../../interfaces/draft-IERC1822.sol"; +import {ERC1967Utils} from "../ERC1967/ERC1967Utils.sol"; + +/** + * @dev An upgradeability mechanism designed for UUPS proxies. The functions included here can perform an upgrade of an + * {ERC1967Proxy}, when this contract is set as the implementation behind such a proxy. + * + * A security mechanism ensures that an upgrade does not turn off upgradeability accidentally, although this risk is + * reinstated if the upgrade retains upgradeability but removes the security mechanism, e.g. by replacing + * `UUPSUpgradeable` with a custom implementation of upgrades. + * + * The {_authorizeUpgrade} function must be overridden to include access restriction to the upgrade mechanism. + */ +abstract contract UUPSUpgradeable is IERC1822Proxiable { + /// @custom:oz-upgrades-unsafe-allow state-variable-immutable + address private immutable __self = address(this); + + /** + * @dev The version of the upgrade interface of the contract. If this getter is missing, both `upgradeTo(address)` + * and `upgradeToAndCall(address,bytes)` are present, and `upgradeTo` must be used if no function should be called, + * while `upgradeToAndCall` will invoke the `receive` function if the second argument is the empty byte string. + * If the getter returns `"5.0.0"`, only `upgradeToAndCall(address,bytes)` is present, and the second argument must + * be the empty byte string if no function should be called, making it impossible to invoke the `receive` function + * during an upgrade. + */ + string public constant UPGRADE_INTERFACE_VERSION = "5.0.0"; + + /** + * @dev The call is from an unauthorized context. + */ + error UUPSUnauthorizedCallContext(); + + /** + * @dev The storage `slot` is unsupported as a UUID. + */ + error UUPSUnsupportedProxiableUUID(bytes32 slot); + + /** + * @dev Check that the execution is being performed through a delegatecall call and that the execution context is + * a proxy contract with an implementation (as defined in ERC1967) pointing to self. This should only be the case + * for UUPS and transparent proxies that are using the current contract as their implementation. Execution of a + * function through ERC1167 minimal proxies (clones) would not normally pass this test, but is not guaranteed to + * fail. + */ + modifier onlyProxy() { + _checkProxy(); + _; + } + + /** + * @dev Check that the execution is not being performed through a delegate call. This allows a function to be + * callable on the implementing contract but not through proxies. + */ + modifier notDelegated() { + _checkNotDelegated(); + _; + } + + /** + * @dev Implementation of the ERC1822 {proxiableUUID} function. This returns the storage slot used by the + * implementation. It is used to validate the implementation's compatibility when performing an upgrade. + * + * IMPORTANT: A proxy pointing at a proxiable contract should not be considered proxiable itself, because this risks + * bricking a proxy that upgrades to it, by delegating to itself until out of gas. Thus it is critical that this + * function revert if invoked through a proxy. This is guaranteed by the `notDelegated` modifier. + */ + function proxiableUUID() external view virtual notDelegated returns (bytes32) { + return ERC1967Utils.IMPLEMENTATION_SLOT; + } + + /** + * @dev Upgrade the implementation of the proxy to `newImplementation`, and subsequently execute the function call + * encoded in `data`. + * + * Calls {_authorizeUpgrade}. + * + * Emits an {Upgraded} event. + * + * @custom:oz-upgrades-unsafe-allow-reachable delegatecall + */ + function upgradeToAndCall(address newImplementation, bytes memory data) public payable virtual onlyProxy { + _authorizeUpgrade(newImplementation); + _upgradeToAndCallUUPS(newImplementation, data); + } + + /** + * @dev Reverts if the execution is not performed via delegatecall or the execution + * context is not of a proxy with an ERC1967-compliant implementation pointing to self. + * See {_onlyProxy}. + */ + function _checkProxy() internal view virtual { + if ( + address(this) == __self || // Must be called through delegatecall + ERC1967Utils.getImplementation() != __self // Must be called through an active proxy + ) { + revert UUPSUnauthorizedCallContext(); + } + } + + /** + * @dev Reverts if the execution is performed via delegatecall. + * See {notDelegated}. + */ + function _checkNotDelegated() internal view virtual { + if (address(this) != __self) { + // Must not be called through delegatecall + revert UUPSUnauthorizedCallContext(); + } + } + + /** + * @dev Function that should revert when `msg.sender` is not authorized to upgrade the contract. Called by + * {upgradeToAndCall}. + * + * Normally, this function will use an xref:access.adoc[access control] modifier such as {Ownable-onlyOwner}. + * + * ```solidity + * function _authorizeUpgrade(address) internal onlyOwner {} + * ``` + */ + function _authorizeUpgrade(address newImplementation) internal virtual; + + /** + * @dev Performs an implementation upgrade with a security check for UUPS proxies, and additional setup call. + * + * As a security check, {proxiableUUID} is invoked in the new implementation, and the return value + * is expected to be the implementation slot in ERC1967. + * + * Emits an {IERC1967-Upgraded} event. + */ + function _upgradeToAndCallUUPS(address newImplementation, bytes memory data) private { + try IERC1822Proxiable(newImplementation).proxiableUUID() returns (bytes32 slot) { + if (slot != ERC1967Utils.IMPLEMENTATION_SLOT) { + revert UUPSUnsupportedProxiableUUID(slot); + } + ERC1967Utils.upgradeToAndCall(newImplementation, data); + } catch { + // The implementation is not UUPS + revert ERC1967Utils.ERC1967InvalidImplementation(newImplementation); + } + } +} diff --git a/CVLByExample/UniswapHooks/lib/uniswap-hooks/lib/v4-core/lib/openzeppelin-contracts/contracts/token/ERC1155/ERC1155.sol b/CVLByExample/UniswapHooks/lib/uniswap-hooks/lib/v4-core/lib/openzeppelin-contracts/contracts/token/ERC1155/ERC1155.sol new file mode 100644 index 00000000..316f3291 --- /dev/null +++ b/CVLByExample/UniswapHooks/lib/uniswap-hooks/lib/v4-core/lib/openzeppelin-contracts/contracts/token/ERC1155/ERC1155.sol @@ -0,0 +1,468 @@ +// SPDX-License-Identifier: MIT +// OpenZeppelin Contracts (last updated v5.0.0) (token/ERC1155/ERC1155.sol) + +pragma solidity ^0.8.20; + +import {IERC1155} from "./IERC1155.sol"; +import {IERC1155Receiver} from "./IERC1155Receiver.sol"; +import {IERC1155MetadataURI} from "./extensions/IERC1155MetadataURI.sol"; +import {Context} from "../../utils/Context.sol"; +import {IERC165, ERC165} from "../../utils/introspection/ERC165.sol"; +import {Arrays} from "../../utils/Arrays.sol"; +import {IERC1155Errors} from "../../interfaces/draft-IERC6093.sol"; + +/** + * @dev Implementation of the basic standard multi-token. + * See https://eips.ethereum.org/EIPS/eip-1155 + * Originally based on code by Enjin: https://github.com/enjin/erc-1155 + */ +abstract contract ERC1155 is Context, ERC165, IERC1155, IERC1155MetadataURI, IERC1155Errors { + using Arrays for uint256[]; + using Arrays for address[]; + + mapping(uint256 id => mapping(address account => uint256)) private _balances; + + mapping(address account => mapping(address operator => bool)) private _operatorApprovals; + + // Used as the URI for all token types by relying on ID substitution, e.g. https://token-cdn-domain/{id}.json + string private _uri; + + /** + * @dev See {_setURI}. + */ + constructor(string memory uri_) { + _setURI(uri_); + } + + /** + * @dev See {IERC165-supportsInterface}. + */ + function supportsInterface(bytes4 interfaceId) public view virtual override(ERC165, IERC165) returns (bool) { + return + interfaceId == type(IERC1155).interfaceId || + interfaceId == type(IERC1155MetadataURI).interfaceId || + super.supportsInterface(interfaceId); + } + + /** + * @dev See {IERC1155MetadataURI-uri}. + * + * This implementation returns the same URI for *all* token types. It relies + * on the token type ID substitution mechanism + * https://eips.ethereum.org/EIPS/eip-1155#metadata[defined in the EIP]. + * + * Clients calling this function must replace the `\{id\}` substring with the + * actual token type ID. + */ + function uri(uint256 /* id */) public view virtual returns (string memory) { + return _uri; + } + + /** + * @dev See {IERC1155-balanceOf}. + */ + function balanceOf(address account, uint256 id) public view virtual returns (uint256) { + return _balances[id][account]; + } + + /** + * @dev See {IERC1155-balanceOfBatch}. + * + * Requirements: + * + * - `accounts` and `ids` must have the same length. + */ + function balanceOfBatch( + address[] memory accounts, + uint256[] memory ids + ) public view virtual returns (uint256[] memory) { + if (accounts.length != ids.length) { + revert ERC1155InvalidArrayLength(ids.length, accounts.length); + } + + uint256[] memory batchBalances = new uint256[](accounts.length); + + for (uint256 i = 0; i < accounts.length; ++i) { + batchBalances[i] = balanceOf(accounts.unsafeMemoryAccess(i), ids.unsafeMemoryAccess(i)); + } + + return batchBalances; + } + + /** + * @dev See {IERC1155-setApprovalForAll}. + */ + function setApprovalForAll(address operator, bool approved) public virtual { + _setApprovalForAll(_msgSender(), operator, approved); + } + + /** + * @dev See {IERC1155-isApprovedForAll}. + */ + function isApprovedForAll(address account, address operator) public view virtual returns (bool) { + return _operatorApprovals[account][operator]; + } + + /** + * @dev See {IERC1155-safeTransferFrom}. + */ + function safeTransferFrom(address from, address to, uint256 id, uint256 value, bytes memory data) public virtual { + address sender = _msgSender(); + if (from != sender && !isApprovedForAll(from, sender)) { + revert ERC1155MissingApprovalForAll(sender, from); + } + _safeTransferFrom(from, to, id, value, data); + } + + /** + * @dev See {IERC1155-safeBatchTransferFrom}. + */ + function safeBatchTransferFrom( + address from, + address to, + uint256[] memory ids, + uint256[] memory values, + bytes memory data + ) public virtual { + address sender = _msgSender(); + if (from != sender && !isApprovedForAll(from, sender)) { + revert ERC1155MissingApprovalForAll(sender, from); + } + _safeBatchTransferFrom(from, to, ids, values, data); + } + + /** + * @dev Transfers a `value` amount of tokens of type `id` from `from` to `to`. Will mint (or burn) if `from` + * (or `to`) is the zero address. + * + * Emits a {TransferSingle} event if the arrays contain one element, and {TransferBatch} otherwise. + * + * Requirements: + * + * - If `to` refers to a smart contract, it must implement either {IERC1155Receiver-onERC1155Received} + * or {IERC1155Receiver-onERC1155BatchReceived} and return the acceptance magic value. + * - `ids` and `values` must have the same length. + * + * NOTE: The ERC-1155 acceptance check is not performed in this function. See {_updateWithAcceptanceCheck} instead. + */ + function _update(address from, address to, uint256[] memory ids, uint256[] memory values) internal virtual { + if (ids.length != values.length) { + revert ERC1155InvalidArrayLength(ids.length, values.length); + } + + address operator = _msgSender(); + + for (uint256 i = 0; i < ids.length; ++i) { + uint256 id = ids.unsafeMemoryAccess(i); + uint256 value = values.unsafeMemoryAccess(i); + + if (from != address(0)) { + uint256 fromBalance = _balances[id][from]; + if (fromBalance < value) { + revert ERC1155InsufficientBalance(from, fromBalance, value, id); + } + unchecked { + // Overflow not possible: value <= fromBalance + _balances[id][from] = fromBalance - value; + } + } + + if (to != address(0)) { + _balances[id][to] += value; + } + } + + if (ids.length == 1) { + uint256 id = ids.unsafeMemoryAccess(0); + uint256 value = values.unsafeMemoryAccess(0); + emit TransferSingle(operator, from, to, id, value); + } else { + emit TransferBatch(operator, from, to, ids, values); + } + } + + /** + * @dev Version of {_update} that performs the token acceptance check by calling + * {IERC1155Receiver-onERC1155Received} or {IERC1155Receiver-onERC1155BatchReceived} on the receiver address if it + * contains code (eg. is a smart contract at the moment of execution). + * + * IMPORTANT: Overriding this function is discouraged because it poses a reentrancy risk from the receiver. So any + * update to the contract state after this function would break the check-effect-interaction pattern. Consider + * overriding {_update} instead. + */ + function _updateWithAcceptanceCheck( + address from, + address to, + uint256[] memory ids, + uint256[] memory values, + bytes memory data + ) internal virtual { + _update(from, to, ids, values); + if (to != address(0)) { + address operator = _msgSender(); + if (ids.length == 1) { + uint256 id = ids.unsafeMemoryAccess(0); + uint256 value = values.unsafeMemoryAccess(0); + _doSafeTransferAcceptanceCheck(operator, from, to, id, value, data); + } else { + _doSafeBatchTransferAcceptanceCheck(operator, from, to, ids, values, data); + } + } + } + + /** + * @dev Transfers a `value` tokens of token type `id` from `from` to `to`. + * + * Emits a {TransferSingle} event. + * + * Requirements: + * + * - `to` cannot be the zero address. + * - `from` must have a balance of tokens of type `id` of at least `value` amount. + * - If `to` refers to a smart contract, it must implement {IERC1155Receiver-onERC1155Received} and return the + * acceptance magic value. + */ + function _safeTransferFrom(address from, address to, uint256 id, uint256 value, bytes memory data) internal { + if (to == address(0)) { + revert ERC1155InvalidReceiver(address(0)); + } + if (from == address(0)) { + revert ERC1155InvalidSender(address(0)); + } + (uint256[] memory ids, uint256[] memory values) = _asSingletonArrays(id, value); + _updateWithAcceptanceCheck(from, to, ids, values, data); + } + + /** + * @dev xref:ROOT:erc1155.adoc#batch-operations[Batched] version of {_safeTransferFrom}. + * + * Emits a {TransferBatch} event. + * + * Requirements: + * + * - If `to` refers to a smart contract, it must implement {IERC1155Receiver-onERC1155BatchReceived} and return the + * acceptance magic value. + * - `ids` and `values` must have the same length. + */ + function _safeBatchTransferFrom( + address from, + address to, + uint256[] memory ids, + uint256[] memory values, + bytes memory data + ) internal { + if (to == address(0)) { + revert ERC1155InvalidReceiver(address(0)); + } + if (from == address(0)) { + revert ERC1155InvalidSender(address(0)); + } + _updateWithAcceptanceCheck(from, to, ids, values, data); + } + + /** + * @dev Sets a new URI for all token types, by relying on the token type ID + * substitution mechanism + * https://eips.ethereum.org/EIPS/eip-1155#metadata[defined in the EIP]. + * + * By this mechanism, any occurrence of the `\{id\}` substring in either the + * URI or any of the values in the JSON file at said URI will be replaced by + * clients with the token type ID. + * + * For example, the `https://token-cdn-domain/\{id\}.json` URI would be + * interpreted by clients as + * `https://token-cdn-domain/000000000000000000000000000000000000000000000000000000000004cce0.json` + * for token type ID 0x4cce0. + * + * See {uri}. + * + * Because these URIs cannot be meaningfully represented by the {URI} event, + * this function emits no events. + */ + function _setURI(string memory newuri) internal virtual { + _uri = newuri; + } + + /** + * @dev Creates a `value` amount of tokens of type `id`, and assigns them to `to`. + * + * Emits a {TransferSingle} event. + * + * Requirements: + * + * - `to` cannot be the zero address. + * - If `to` refers to a smart contract, it must implement {IERC1155Receiver-onERC1155Received} and return the + * acceptance magic value. + */ + function _mint(address to, uint256 id, uint256 value, bytes memory data) internal { + if (to == address(0)) { + revert ERC1155InvalidReceiver(address(0)); + } + (uint256[] memory ids, uint256[] memory values) = _asSingletonArrays(id, value); + _updateWithAcceptanceCheck(address(0), to, ids, values, data); + } + + /** + * @dev xref:ROOT:erc1155.adoc#batch-operations[Batched] version of {_mint}. + * + * Emits a {TransferBatch} event. + * + * Requirements: + * + * - `ids` and `values` must have the same length. + * - `to` cannot be the zero address. + * - If `to` refers to a smart contract, it must implement {IERC1155Receiver-onERC1155BatchReceived} and return the + * acceptance magic value. + */ + function _mintBatch(address to, uint256[] memory ids, uint256[] memory values, bytes memory data) internal { + if (to == address(0)) { + revert ERC1155InvalidReceiver(address(0)); + } + _updateWithAcceptanceCheck(address(0), to, ids, values, data); + } + + /** + * @dev Destroys a `value` amount of tokens of type `id` from `from` + * + * Emits a {TransferSingle} event. + * + * Requirements: + * + * - `from` cannot be the zero address. + * - `from` must have at least `value` amount of tokens of type `id`. + */ + function _burn(address from, uint256 id, uint256 value) internal { + if (from == address(0)) { + revert ERC1155InvalidSender(address(0)); + } + (uint256[] memory ids, uint256[] memory values) = _asSingletonArrays(id, value); + _updateWithAcceptanceCheck(from, address(0), ids, values, ""); + } + + /** + * @dev xref:ROOT:erc1155.adoc#batch-operations[Batched] version of {_burn}. + * + * Emits a {TransferBatch} event. + * + * Requirements: + * + * - `from` cannot be the zero address. + * - `from` must have at least `value` amount of tokens of type `id`. + * - `ids` and `values` must have the same length. + */ + function _burnBatch(address from, uint256[] memory ids, uint256[] memory values) internal { + if (from == address(0)) { + revert ERC1155InvalidSender(address(0)); + } + _updateWithAcceptanceCheck(from, address(0), ids, values, ""); + } + + /** + * @dev Approve `operator` to operate on all of `owner` tokens + * + * Emits an {ApprovalForAll} event. + * + * Requirements: + * + * - `operator` cannot be the zero address. + */ + function _setApprovalForAll(address owner, address operator, bool approved) internal virtual { + if (operator == address(0)) { + revert ERC1155InvalidOperator(address(0)); + } + _operatorApprovals[owner][operator] = approved; + emit ApprovalForAll(owner, operator, approved); + } + + /** + * @dev Performs an acceptance check by calling {IERC1155-onERC1155Received} on the `to` address + * if it contains code at the moment of execution. + */ + function _doSafeTransferAcceptanceCheck( + address operator, + address from, + address to, + uint256 id, + uint256 value, + bytes memory data + ) private { + if (to.code.length > 0) { + try IERC1155Receiver(to).onERC1155Received(operator, from, id, value, data) returns (bytes4 response) { + if (response != IERC1155Receiver.onERC1155Received.selector) { + // Tokens rejected + revert ERC1155InvalidReceiver(to); + } + } catch (bytes memory reason) { + if (reason.length == 0) { + // non-ERC1155Receiver implementer + revert ERC1155InvalidReceiver(to); + } else { + /// @solidity memory-safe-assembly + assembly { + revert(add(32, reason), mload(reason)) + } + } + } + } + } + + /** + * @dev Performs a batch acceptance check by calling {IERC1155-onERC1155BatchReceived} on the `to` address + * if it contains code at the moment of execution. + */ + function _doSafeBatchTransferAcceptanceCheck( + address operator, + address from, + address to, + uint256[] memory ids, + uint256[] memory values, + bytes memory data + ) private { + if (to.code.length > 0) { + try IERC1155Receiver(to).onERC1155BatchReceived(operator, from, ids, values, data) returns ( + bytes4 response + ) { + if (response != IERC1155Receiver.onERC1155BatchReceived.selector) { + // Tokens rejected + revert ERC1155InvalidReceiver(to); + } + } catch (bytes memory reason) { + if (reason.length == 0) { + // non-ERC1155Receiver implementer + revert ERC1155InvalidReceiver(to); + } else { + /// @solidity memory-safe-assembly + assembly { + revert(add(32, reason), mload(reason)) + } + } + } + } + } + + /** + * @dev Creates an array in memory with only one value for each of the elements provided. + */ + function _asSingletonArrays( + uint256 element1, + uint256 element2 + ) private pure returns (uint256[] memory array1, uint256[] memory array2) { + /// @solidity memory-safe-assembly + assembly { + // Load the free memory pointer + array1 := mload(0x40) + // Set array length to 1 + mstore(array1, 1) + // Store the single element at the next word after the length (where content starts) + mstore(add(array1, 0x20), element1) + + // Repeat for next array locating it right after the first array + array2 := add(array1, 0x40) + mstore(array2, 1) + mstore(add(array2, 0x20), element2) + + // Update the free memory pointer by pointing after the second array + mstore(0x40, add(array2, 0x40)) + } + } +} diff --git a/CVLByExample/UniswapHooks/lib/uniswap-hooks/lib/v4-core/lib/openzeppelin-contracts/contracts/token/ERC1155/IERC1155.sol b/CVLByExample/UniswapHooks/lib/uniswap-hooks/lib/v4-core/lib/openzeppelin-contracts/contracts/token/ERC1155/IERC1155.sol new file mode 100644 index 00000000..1c99a417 --- /dev/null +++ b/CVLByExample/UniswapHooks/lib/uniswap-hooks/lib/v4-core/lib/openzeppelin-contracts/contracts/token/ERC1155/IERC1155.sol @@ -0,0 +1,127 @@ +// SPDX-License-Identifier: MIT +// OpenZeppelin Contracts (last updated v5.0.1) (token/ERC1155/IERC1155.sol) + +pragma solidity ^0.8.20; + +import {IERC165} from "../../utils/introspection/IERC165.sol"; + +/** + * @dev Required interface of an ERC1155 compliant contract, as defined in the + * https://eips.ethereum.org/EIPS/eip-1155[EIP]. + */ +interface IERC1155 is IERC165 { + /** + * @dev Emitted when `value` amount of tokens of type `id` are transferred from `from` to `to` by `operator`. + */ + event TransferSingle(address indexed operator, address indexed from, address indexed to, uint256 id, uint256 value); + + /** + * @dev Equivalent to multiple {TransferSingle} events, where `operator`, `from` and `to` are the same for all + * transfers. + */ + event TransferBatch( + address indexed operator, + address indexed from, + address indexed to, + uint256[] ids, + uint256[] values + ); + + /** + * @dev Emitted when `account` grants or revokes permission to `operator` to transfer their tokens, according to + * `approved`. + */ + event ApprovalForAll(address indexed account, address indexed operator, bool approved); + + /** + * @dev Emitted when the URI for token type `id` changes to `value`, if it is a non-programmatic URI. + * + * If an {URI} event was emitted for `id`, the standard + * https://eips.ethereum.org/EIPS/eip-1155#metadata-extensions[guarantees] that `value` will equal the value + * returned by {IERC1155MetadataURI-uri}. + */ + event URI(string value, uint256 indexed id); + + /** + * @dev Returns the value of tokens of token type `id` owned by `account`. + * + * Requirements: + * + * - `account` cannot be the zero address. + */ + function balanceOf(address account, uint256 id) external view returns (uint256); + + /** + * @dev xref:ROOT:erc1155.adoc#batch-operations[Batched] version of {balanceOf}. + * + * Requirements: + * + * - `accounts` and `ids` must have the same length. + */ + function balanceOfBatch( + address[] calldata accounts, + uint256[] calldata ids + ) external view returns (uint256[] memory); + + /** + * @dev Grants or revokes permission to `operator` to transfer the caller's tokens, according to `approved`, + * + * Emits an {ApprovalForAll} event. + * + * Requirements: + * + * - `operator` cannot be the caller. + */ + function setApprovalForAll(address operator, bool approved) external; + + /** + * @dev Returns true if `operator` is approved to transfer ``account``'s tokens. + * + * See {setApprovalForAll}. + */ + function isApprovedForAll(address account, address operator) external view returns (bool); + + /** + * @dev Transfers a `value` amount of tokens of type `id` from `from` to `to`. + * + * WARNING: This function can potentially allow a reentrancy attack when transferring tokens + * to an untrusted contract, when invoking {onERC1155Received} on the receiver. + * Ensure to follow the checks-effects-interactions pattern and consider employing + * reentrancy guards when interacting with untrusted contracts. + * + * Emits a {TransferSingle} event. + * + * Requirements: + * + * - `to` cannot be the zero address. + * - If the caller is not `from`, it must have been approved to spend ``from``'s tokens via {setApprovalForAll}. + * - `from` must have a balance of tokens of type `id` of at least `value` amount. + * - If `to` refers to a smart contract, it must implement {IERC1155Receiver-onERC1155Received} and return the + * acceptance magic value. + */ + function safeTransferFrom(address from, address to, uint256 id, uint256 value, bytes calldata data) external; + + /** + * @dev xref:ROOT:erc1155.adoc#batch-operations[Batched] version of {safeTransferFrom}. + * + * WARNING: This function can potentially allow a reentrancy attack when transferring tokens + * to an untrusted contract, when invoking {onERC1155BatchReceived} on the receiver. + * Ensure to follow the checks-effects-interactions pattern and consider employing + * reentrancy guards when interacting with untrusted contracts. + * + * Emits either a {TransferSingle} or a {TransferBatch} event, depending on the length of the array arguments. + * + * Requirements: + * + * - `ids` and `values` must have the same length. + * - If `to` refers to a smart contract, it must implement {IERC1155Receiver-onERC1155BatchReceived} and return the + * acceptance magic value. + */ + function safeBatchTransferFrom( + address from, + address to, + uint256[] calldata ids, + uint256[] calldata values, + bytes calldata data + ) external; +} diff --git a/CVLByExample/UniswapHooks/lib/uniswap-hooks/lib/v4-core/lib/openzeppelin-contracts/contracts/token/ERC1155/IERC1155Receiver.sol b/CVLByExample/UniswapHooks/lib/uniswap-hooks/lib/v4-core/lib/openzeppelin-contracts/contracts/token/ERC1155/IERC1155Receiver.sol new file mode 100644 index 00000000..0f6e2bf8 --- /dev/null +++ b/CVLByExample/UniswapHooks/lib/uniswap-hooks/lib/v4-core/lib/openzeppelin-contracts/contracts/token/ERC1155/IERC1155Receiver.sol @@ -0,0 +1,59 @@ +// SPDX-License-Identifier: MIT +// OpenZeppelin Contracts (last updated v5.0.0) (token/ERC1155/IERC1155Receiver.sol) + +pragma solidity ^0.8.20; + +import {IERC165} from "../../utils/introspection/IERC165.sol"; + +/** + * @dev Interface that must be implemented by smart contracts in order to receive + * ERC-1155 token transfers. + */ +interface IERC1155Receiver is IERC165 { + /** + * @dev Handles the receipt of a single ERC1155 token type. This function is + * called at the end of a `safeTransferFrom` after the balance has been updated. + * + * NOTE: To accept the transfer, this must return + * `bytes4(keccak256("onERC1155Received(address,address,uint256,uint256,bytes)"))` + * (i.e. 0xf23a6e61, or its own function selector). + * + * @param operator The address which initiated the transfer (i.e. msg.sender) + * @param from The address which previously owned the token + * @param id The ID of the token being transferred + * @param value The amount of tokens being transferred + * @param data Additional data with no specified format + * @return `bytes4(keccak256("onERC1155Received(address,address,uint256,uint256,bytes)"))` if transfer is allowed + */ + function onERC1155Received( + address operator, + address from, + uint256 id, + uint256 value, + bytes calldata data + ) external returns (bytes4); + + /** + * @dev Handles the receipt of a multiple ERC1155 token types. This function + * is called at the end of a `safeBatchTransferFrom` after the balances have + * been updated. + * + * NOTE: To accept the transfer(s), this must return + * `bytes4(keccak256("onERC1155BatchReceived(address,address,uint256[],uint256[],bytes)"))` + * (i.e. 0xbc197c81, or its own function selector). + * + * @param operator The address which initiated the batch transfer (i.e. msg.sender) + * @param from The address which previously owned the token + * @param ids An array containing ids of each token being transferred (order and length must match values array) + * @param values An array containing amounts of each token being transferred (order and length must match ids array) + * @param data Additional data with no specified format + * @return `bytes4(keccak256("onERC1155BatchReceived(address,address,uint256[],uint256[],bytes)"))` if transfer is allowed + */ + function onERC1155BatchReceived( + address operator, + address from, + uint256[] calldata ids, + uint256[] calldata values, + bytes calldata data + ) external returns (bytes4); +} diff --git a/CVLByExample/UniswapHooks/lib/uniswap-hooks/lib/v4-core/lib/openzeppelin-contracts/contracts/token/ERC1155/README.adoc b/CVLByExample/UniswapHooks/lib/uniswap-hooks/lib/v4-core/lib/openzeppelin-contracts/contracts/token/ERC1155/README.adoc new file mode 100644 index 00000000..1a56358e --- /dev/null +++ b/CVLByExample/UniswapHooks/lib/uniswap-hooks/lib/v4-core/lib/openzeppelin-contracts/contracts/token/ERC1155/README.adoc @@ -0,0 +1,41 @@ += ERC 1155 + +[.readme-notice] +NOTE: This document is better viewed at https://docs.openzeppelin.com/contracts/api/token/erc1155 + +This set of interfaces and contracts are all related to the https://eips.ethereum.org/EIPS/eip-1155[ERC1155 Multi Token Standard]. + +The EIP consists of three interfaces which fulfill different roles, found here as {IERC1155}, {IERC1155MetadataURI} and {IERC1155Receiver}. + +{ERC1155} implements the mandatory {IERC1155} interface, as well as the optional extension {IERC1155MetadataURI}, by relying on the substitution mechanism to use the same URI for all token types, dramatically reducing gas costs. + +Additionally there are multiple custom extensions, including: + +* designation of addresses that can pause token transfers for all users ({ERC1155Pausable}). +* destruction of own tokens ({ERC1155Burnable}). + +NOTE: This core set of contracts is designed to be unopinionated, allowing developers to access the internal functions in ERC1155 (such as <>) and expose them as external functions in the way they prefer. + +== Core + +{{IERC1155}} + +{{IERC1155MetadataURI}} + +{{ERC1155}} + +{{IERC1155Receiver}} + +== Extensions + +{{ERC1155Pausable}} + +{{ERC1155Burnable}} + +{{ERC1155Supply}} + +{{ERC1155URIStorage}} + +== Utilities + +{{ERC1155Holder}} diff --git a/CVLByExample/UniswapHooks/lib/uniswap-hooks/lib/v4-core/lib/openzeppelin-contracts/contracts/token/ERC1155/extensions/ERC1155Burnable.sol b/CVLByExample/UniswapHooks/lib/uniswap-hooks/lib/v4-core/lib/openzeppelin-contracts/contracts/token/ERC1155/extensions/ERC1155Burnable.sol new file mode 100644 index 00000000..fd6ad61d --- /dev/null +++ b/CVLByExample/UniswapHooks/lib/uniswap-hooks/lib/v4-core/lib/openzeppelin-contracts/contracts/token/ERC1155/extensions/ERC1155Burnable.sol @@ -0,0 +1,28 @@ +// SPDX-License-Identifier: MIT +// OpenZeppelin Contracts (last updated v5.0.0) (token/ERC1155/extensions/ERC1155Burnable.sol) + +pragma solidity ^0.8.20; + +import {ERC1155} from "../ERC1155.sol"; + +/** + * @dev Extension of {ERC1155} that allows token holders to destroy both their + * own tokens and those that they have been approved to use. + */ +abstract contract ERC1155Burnable is ERC1155 { + function burn(address account, uint256 id, uint256 value) public virtual { + if (account != _msgSender() && !isApprovedForAll(account, _msgSender())) { + revert ERC1155MissingApprovalForAll(_msgSender(), account); + } + + _burn(account, id, value); + } + + function burnBatch(address account, uint256[] memory ids, uint256[] memory values) public virtual { + if (account != _msgSender() && !isApprovedForAll(account, _msgSender())) { + revert ERC1155MissingApprovalForAll(_msgSender(), account); + } + + _burnBatch(account, ids, values); + } +} diff --git a/CVLByExample/UniswapHooks/lib/uniswap-hooks/lib/v4-core/lib/openzeppelin-contracts/contracts/token/ERC1155/extensions/ERC1155Pausable.sol b/CVLByExample/UniswapHooks/lib/uniswap-hooks/lib/v4-core/lib/openzeppelin-contracts/contracts/token/ERC1155/extensions/ERC1155Pausable.sol new file mode 100644 index 00000000..529a4652 --- /dev/null +++ b/CVLByExample/UniswapHooks/lib/uniswap-hooks/lib/v4-core/lib/openzeppelin-contracts/contracts/token/ERC1155/extensions/ERC1155Pausable.sol @@ -0,0 +1,38 @@ +// SPDX-License-Identifier: MIT +// OpenZeppelin Contracts (last updated v5.0.0) (token/ERC1155/extensions/ERC1155Pausable.sol) + +pragma solidity ^0.8.20; + +import {ERC1155} from "../ERC1155.sol"; +import {Pausable} from "../../../utils/Pausable.sol"; + +/** + * @dev ERC1155 token with pausable token transfers, minting and burning. + * + * Useful for scenarios such as preventing trades until the end of an evaluation + * period, or having an emergency switch for freezing all token transfers in the + * event of a large bug. + * + * IMPORTANT: This contract does not include public pause and unpause functions. In + * addition to inheriting this contract, you must define both functions, invoking the + * {Pausable-_pause} and {Pausable-_unpause} internal functions, with appropriate + * access control, e.g. using {AccessControl} or {Ownable}. Not doing so will + * make the contract pause mechanism of the contract unreachable, and thus unusable. + */ +abstract contract ERC1155Pausable is ERC1155, Pausable { + /** + * @dev See {ERC1155-_update}. + * + * Requirements: + * + * - the contract must not be paused. + */ + function _update( + address from, + address to, + uint256[] memory ids, + uint256[] memory values + ) internal virtual override whenNotPaused { + super._update(from, to, ids, values); + } +} diff --git a/CVLByExample/UniswapHooks/lib/uniswap-hooks/lib/v4-core/lib/openzeppelin-contracts/contracts/token/ERC1155/extensions/ERC1155Supply.sol b/CVLByExample/UniswapHooks/lib/uniswap-hooks/lib/v4-core/lib/openzeppelin-contracts/contracts/token/ERC1155/extensions/ERC1155Supply.sol new file mode 100644 index 00000000..cef11b4c --- /dev/null +++ b/CVLByExample/UniswapHooks/lib/uniswap-hooks/lib/v4-core/lib/openzeppelin-contracts/contracts/token/ERC1155/extensions/ERC1155Supply.sol @@ -0,0 +1,87 @@ +// SPDX-License-Identifier: MIT +// OpenZeppelin Contracts (last updated v5.0.0) (token/ERC1155/extensions/ERC1155Supply.sol) + +pragma solidity ^0.8.20; + +import {ERC1155} from "../ERC1155.sol"; + +/** + * @dev Extension of ERC1155 that adds tracking of total supply per id. + * + * Useful for scenarios where Fungible and Non-fungible tokens have to be + * clearly identified. Note: While a totalSupply of 1 might mean the + * corresponding is an NFT, there is no guarantees that no other token with the + * same id are not going to be minted. + * + * NOTE: This contract implies a global limit of 2**256 - 1 to the number of tokens + * that can be minted. + * + * CAUTION: This extension should not be added in an upgrade to an already deployed contract. + */ +abstract contract ERC1155Supply is ERC1155 { + mapping(uint256 id => uint256) private _totalSupply; + uint256 private _totalSupplyAll; + + /** + * @dev Total value of tokens in with a given id. + */ + function totalSupply(uint256 id) public view virtual returns (uint256) { + return _totalSupply[id]; + } + + /** + * @dev Total value of tokens. + */ + function totalSupply() public view virtual returns (uint256) { + return _totalSupplyAll; + } + + /** + * @dev Indicates whether any token exist with a given id, or not. + */ + function exists(uint256 id) public view virtual returns (bool) { + return totalSupply(id) > 0; + } + + /** + * @dev See {ERC1155-_update}. + */ + function _update( + address from, + address to, + uint256[] memory ids, + uint256[] memory values + ) internal virtual override { + super._update(from, to, ids, values); + + if (from == address(0)) { + uint256 totalMintValue = 0; + for (uint256 i = 0; i < ids.length; ++i) { + uint256 value = values[i]; + // Overflow check required: The rest of the code assumes that totalSupply never overflows + _totalSupply[ids[i]] += value; + totalMintValue += value; + } + // Overflow check required: The rest of the code assumes that totalSupplyAll never overflows + _totalSupplyAll += totalMintValue; + } + + if (to == address(0)) { + uint256 totalBurnValue = 0; + for (uint256 i = 0; i < ids.length; ++i) { + uint256 value = values[i]; + + unchecked { + // Overflow not possible: values[i] <= balanceOf(from, ids[i]) <= totalSupply(ids[i]) + _totalSupply[ids[i]] -= value; + // Overflow not possible: sum_i(values[i]) <= sum_i(totalSupply(ids[i])) <= totalSupplyAll + totalBurnValue += value; + } + } + unchecked { + // Overflow not possible: totalBurnValue = sum_i(values[i]) <= sum_i(totalSupply(ids[i])) <= totalSupplyAll + _totalSupplyAll -= totalBurnValue; + } + } + } +} diff --git a/CVLByExample/UniswapHooks/lib/uniswap-hooks/lib/v4-core/lib/openzeppelin-contracts/contracts/token/ERC1155/extensions/ERC1155URIStorage.sol b/CVLByExample/UniswapHooks/lib/uniswap-hooks/lib/v4-core/lib/openzeppelin-contracts/contracts/token/ERC1155/extensions/ERC1155URIStorage.sol new file mode 100644 index 00000000..c2a5bdce --- /dev/null +++ b/CVLByExample/UniswapHooks/lib/uniswap-hooks/lib/v4-core/lib/openzeppelin-contracts/contracts/token/ERC1155/extensions/ERC1155URIStorage.sol @@ -0,0 +1,61 @@ +// SPDX-License-Identifier: MIT +// OpenZeppelin Contracts (last updated v5.0.0) (token/ERC1155/extensions/ERC1155URIStorage.sol) + +pragma solidity ^0.8.20; + +import {Strings} from "../../../utils/Strings.sol"; +import {ERC1155} from "../ERC1155.sol"; + +/** + * @dev ERC1155 token with storage based token URI management. + * Inspired by the ERC721URIStorage extension + */ +abstract contract ERC1155URIStorage is ERC1155 { + using Strings for uint256; + + // Optional base URI + string private _baseURI = ""; + + // Optional mapping for token URIs + mapping(uint256 tokenId => string) private _tokenURIs; + + /** + * @dev See {IERC1155MetadataURI-uri}. + * + * This implementation returns the concatenation of the `_baseURI` + * and the token-specific uri if the latter is set + * + * This enables the following behaviors: + * + * - if `_tokenURIs[tokenId]` is set, then the result is the concatenation + * of `_baseURI` and `_tokenURIs[tokenId]` (keep in mind that `_baseURI` + * is empty per default); + * + * - if `_tokenURIs[tokenId]` is NOT set then we fallback to `super.uri()` + * which in most cases will contain `ERC1155._uri`; + * + * - if `_tokenURIs[tokenId]` is NOT set, and if the parents do not have a + * uri value set, then the result is empty. + */ + function uri(uint256 tokenId) public view virtual override returns (string memory) { + string memory tokenURI = _tokenURIs[tokenId]; + + // If token URI is set, concatenate base URI and tokenURI (via string.concat). + return bytes(tokenURI).length > 0 ? string.concat(_baseURI, tokenURI) : super.uri(tokenId); + } + + /** + * @dev Sets `tokenURI` as the tokenURI of `tokenId`. + */ + function _setURI(uint256 tokenId, string memory tokenURI) internal virtual { + _tokenURIs[tokenId] = tokenURI; + emit URI(uri(tokenId), tokenId); + } + + /** + * @dev Sets `baseURI` as the `_baseURI` for all tokens + */ + function _setBaseURI(string memory baseURI) internal virtual { + _baseURI = baseURI; + } +} diff --git a/CVLByExample/UniswapHooks/lib/uniswap-hooks/lib/v4-core/lib/openzeppelin-contracts/contracts/token/ERC1155/extensions/IERC1155MetadataURI.sol b/CVLByExample/UniswapHooks/lib/uniswap-hooks/lib/v4-core/lib/openzeppelin-contracts/contracts/token/ERC1155/extensions/IERC1155MetadataURI.sol new file mode 100644 index 00000000..e3fb74df --- /dev/null +++ b/CVLByExample/UniswapHooks/lib/uniswap-hooks/lib/v4-core/lib/openzeppelin-contracts/contracts/token/ERC1155/extensions/IERC1155MetadataURI.sol @@ -0,0 +1,20 @@ +// SPDX-License-Identifier: MIT +// OpenZeppelin Contracts (last updated v5.0.0) (token/ERC1155/extensions/IERC1155MetadataURI.sol) + +pragma solidity ^0.8.20; + +import {IERC1155} from "../IERC1155.sol"; + +/** + * @dev Interface of the optional ERC1155MetadataExtension interface, as defined + * in the https://eips.ethereum.org/EIPS/eip-1155#metadata-extensions[EIP]. + */ +interface IERC1155MetadataURI is IERC1155 { + /** + * @dev Returns the URI for token type `id`. + * + * If the `\{id\}` substring is present in the URI, it must be replaced by + * clients with the actual token type ID. + */ + function uri(uint256 id) external view returns (string memory); +} diff --git a/CVLByExample/UniswapHooks/lib/uniswap-hooks/lib/v4-core/lib/openzeppelin-contracts/contracts/token/ERC1155/utils/ERC1155Holder.sol b/CVLByExample/UniswapHooks/lib/uniswap-hooks/lib/v4-core/lib/openzeppelin-contracts/contracts/token/ERC1155/utils/ERC1155Holder.sol new file mode 100644 index 00000000..b108cdbf --- /dev/null +++ b/CVLByExample/UniswapHooks/lib/uniswap-hooks/lib/v4-core/lib/openzeppelin-contracts/contracts/token/ERC1155/utils/ERC1155Holder.sol @@ -0,0 +1,42 @@ +// SPDX-License-Identifier: MIT +// OpenZeppelin Contracts (last updated v5.0.0) (token/ERC1155/utils/ERC1155Holder.sol) + +pragma solidity ^0.8.20; + +import {IERC165, ERC165} from "../../../utils/introspection/ERC165.sol"; +import {IERC1155Receiver} from "../IERC1155Receiver.sol"; + +/** + * @dev Simple implementation of `IERC1155Receiver` that will allow a contract to hold ERC1155 tokens. + * + * IMPORTANT: When inheriting this contract, you must include a way to use the received tokens, otherwise they will be + * stuck. + */ +abstract contract ERC1155Holder is ERC165, IERC1155Receiver { + /** + * @dev See {IERC165-supportsInterface}. + */ + function supportsInterface(bytes4 interfaceId) public view virtual override(ERC165, IERC165) returns (bool) { + return interfaceId == type(IERC1155Receiver).interfaceId || super.supportsInterface(interfaceId); + } + + function onERC1155Received( + address, + address, + uint256, + uint256, + bytes memory + ) public virtual override returns (bytes4) { + return this.onERC1155Received.selector; + } + + function onERC1155BatchReceived( + address, + address, + uint256[] memory, + uint256[] memory, + bytes memory + ) public virtual override returns (bytes4) { + return this.onERC1155BatchReceived.selector; + } +} diff --git a/CVLByExample/UniswapHooks/lib/uniswap-hooks/lib/v4-core/lib/openzeppelin-contracts/contracts/token/ERC20/ERC20.sol b/CVLByExample/UniswapHooks/lib/uniswap-hooks/lib/v4-core/lib/openzeppelin-contracts/contracts/token/ERC20/ERC20.sol new file mode 100644 index 00000000..1fde5279 --- /dev/null +++ b/CVLByExample/UniswapHooks/lib/uniswap-hooks/lib/v4-core/lib/openzeppelin-contracts/contracts/token/ERC20/ERC20.sol @@ -0,0 +1,316 @@ +// SPDX-License-Identifier: MIT +// OpenZeppelin Contracts (last updated v5.0.0) (token/ERC20/ERC20.sol) + +pragma solidity ^0.8.20; + +import {IERC20} from "./IERC20.sol"; +import {IERC20Metadata} from "./extensions/IERC20Metadata.sol"; +import {Context} from "../../utils/Context.sol"; +import {IERC20Errors} from "../../interfaces/draft-IERC6093.sol"; + +/** + * @dev Implementation of the {IERC20} interface. + * + * This implementation is agnostic to the way tokens are created. This means + * that a supply mechanism has to be added in a derived contract using {_mint}. + * + * TIP: For a detailed writeup see our guide + * https://forum.openzeppelin.com/t/how-to-implement-erc20-supply-mechanisms/226[How + * to implement supply mechanisms]. + * + * The default value of {decimals} is 18. To change this, you should override + * this function so it returns a different value. + * + * We have followed general OpenZeppelin Contracts guidelines: functions revert + * instead returning `false` on failure. This behavior is nonetheless + * conventional and does not conflict with the expectations of ERC20 + * applications. + * + * Additionally, an {Approval} event is emitted on calls to {transferFrom}. + * This allows applications to reconstruct the allowance for all accounts just + * by listening to said events. Other implementations of the EIP may not emit + * these events, as it isn't required by the specification. + */ +abstract contract ERC20 is Context, IERC20, IERC20Metadata, IERC20Errors { + mapping(address account => uint256) private _balances; + + mapping(address account => mapping(address spender => uint256)) private _allowances; + + uint256 private _totalSupply; + + string private _name; + string private _symbol; + + /** + * @dev Sets the values for {name} and {symbol}. + * + * All two of these values are immutable: they can only be set once during + * construction. + */ + constructor(string memory name_, string memory symbol_) { + _name = name_; + _symbol = symbol_; + } + + /** + * @dev Returns the name of the token. + */ + function name() public view virtual returns (string memory) { + return _name; + } + + /** + * @dev Returns the symbol of the token, usually a shorter version of the + * name. + */ + function symbol() public view virtual returns (string memory) { + return _symbol; + } + + /** + * @dev Returns the number of decimals used to get its user representation. + * For example, if `decimals` equals `2`, a balance of `505` tokens should + * be displayed to a user as `5.05` (`505 / 10 ** 2`). + * + * Tokens usually opt for a value of 18, imitating the relationship between + * Ether and Wei. This is the default value returned by this function, unless + * it's overridden. + * + * NOTE: This information is only used for _display_ purposes: it in + * no way affects any of the arithmetic of the contract, including + * {IERC20-balanceOf} and {IERC20-transfer}. + */ + function decimals() public view virtual returns (uint8) { + return 18; + } + + /** + * @dev See {IERC20-totalSupply}. + */ + function totalSupply() public view virtual returns (uint256) { + return _totalSupply; + } + + /** + * @dev See {IERC20-balanceOf}. + */ + function balanceOf(address account) public view virtual returns (uint256) { + return _balances[account]; + } + + /** + * @dev See {IERC20-transfer}. + * + * Requirements: + * + * - `to` cannot be the zero address. + * - the caller must have a balance of at least `value`. + */ + function transfer(address to, uint256 value) public virtual returns (bool) { + address owner = _msgSender(); + _transfer(owner, to, value); + return true; + } + + /** + * @dev See {IERC20-allowance}. + */ + function allowance(address owner, address spender) public view virtual returns (uint256) { + return _allowances[owner][spender]; + } + + /** + * @dev See {IERC20-approve}. + * + * NOTE: If `value` is the maximum `uint256`, the allowance is not updated on + * `transferFrom`. This is semantically equivalent to an infinite approval. + * + * Requirements: + * + * - `spender` cannot be the zero address. + */ + function approve(address spender, uint256 value) public virtual returns (bool) { + address owner = _msgSender(); + _approve(owner, spender, value); + return true; + } + + /** + * @dev See {IERC20-transferFrom}. + * + * Emits an {Approval} event indicating the updated allowance. This is not + * required by the EIP. See the note at the beginning of {ERC20}. + * + * NOTE: Does not update the allowance if the current allowance + * is the maximum `uint256`. + * + * Requirements: + * + * - `from` and `to` cannot be the zero address. + * - `from` must have a balance of at least `value`. + * - the caller must have allowance for ``from``'s tokens of at least + * `value`. + */ + function transferFrom(address from, address to, uint256 value) public virtual returns (bool) { + address spender = _msgSender(); + _spendAllowance(from, spender, value); + _transfer(from, to, value); + return true; + } + + /** + * @dev Moves a `value` amount of tokens from `from` to `to`. + * + * This internal function is equivalent to {transfer}, and can be used to + * e.g. implement automatic token fees, slashing mechanisms, etc. + * + * Emits a {Transfer} event. + * + * NOTE: This function is not virtual, {_update} should be overridden instead. + */ + function _transfer(address from, address to, uint256 value) internal { + if (from == address(0)) { + revert ERC20InvalidSender(address(0)); + } + if (to == address(0)) { + revert ERC20InvalidReceiver(address(0)); + } + _update(from, to, value); + } + + /** + * @dev Transfers a `value` amount of tokens from `from` to `to`, or alternatively mints (or burns) if `from` + * (or `to`) is the zero address. All customizations to transfers, mints, and burns should be done by overriding + * this function. + * + * Emits a {Transfer} event. + */ + function _update(address from, address to, uint256 value) internal virtual { + if (from == address(0)) { + // Overflow check required: The rest of the code assumes that totalSupply never overflows + _totalSupply += value; + } else { + uint256 fromBalance = _balances[from]; + if (fromBalance < value) { + revert ERC20InsufficientBalance(from, fromBalance, value); + } + unchecked { + // Overflow not possible: value <= fromBalance <= totalSupply. + _balances[from] = fromBalance - value; + } + } + + if (to == address(0)) { + unchecked { + // Overflow not possible: value <= totalSupply or value <= fromBalance <= totalSupply. + _totalSupply -= value; + } + } else { + unchecked { + // Overflow not possible: balance + value is at most totalSupply, which we know fits into a uint256. + _balances[to] += value; + } + } + + emit Transfer(from, to, value); + } + + /** + * @dev Creates a `value` amount of tokens and assigns them to `account`, by transferring it from address(0). + * Relies on the `_update` mechanism + * + * Emits a {Transfer} event with `from` set to the zero address. + * + * NOTE: This function is not virtual, {_update} should be overridden instead. + */ + function _mint(address account, uint256 value) internal { + if (account == address(0)) { + revert ERC20InvalidReceiver(address(0)); + } + _update(address(0), account, value); + } + + /** + * @dev Destroys a `value` amount of tokens from `account`, lowering the total supply. + * Relies on the `_update` mechanism. + * + * Emits a {Transfer} event with `to` set to the zero address. + * + * NOTE: This function is not virtual, {_update} should be overridden instead + */ + function _burn(address account, uint256 value) internal { + if (account == address(0)) { + revert ERC20InvalidSender(address(0)); + } + _update(account, address(0), value); + } + + /** + * @dev Sets `value` as the allowance of `spender` over the `owner` s tokens. + * + * This internal function is equivalent to `approve`, and can be used to + * e.g. set automatic allowances for certain subsystems, etc. + * + * Emits an {Approval} event. + * + * Requirements: + * + * - `owner` cannot be the zero address. + * - `spender` cannot be the zero address. + * + * Overrides to this logic should be done to the variant with an additional `bool emitEvent` argument. + */ + function _approve(address owner, address spender, uint256 value) internal { + _approve(owner, spender, value, true); + } + + /** + * @dev Variant of {_approve} with an optional flag to enable or disable the {Approval} event. + * + * By default (when calling {_approve}) the flag is set to true. On the other hand, approval changes made by + * `_spendAllowance` during the `transferFrom` operation set the flag to false. This saves gas by not emitting any + * `Approval` event during `transferFrom` operations. + * + * Anyone who wishes to continue emitting `Approval` events on the`transferFrom` operation can force the flag to + * true using the following override: + * ``` + * function _approve(address owner, address spender, uint256 value, bool) internal virtual override { + * super._approve(owner, spender, value, true); + * } + * ``` + * + * Requirements are the same as {_approve}. + */ + function _approve(address owner, address spender, uint256 value, bool emitEvent) internal virtual { + if (owner == address(0)) { + revert ERC20InvalidApprover(address(0)); + } + if (spender == address(0)) { + revert ERC20InvalidSpender(address(0)); + } + _allowances[owner][spender] = value; + if (emitEvent) { + emit Approval(owner, spender, value); + } + } + + /** + * @dev Updates `owner` s allowance for `spender` based on spent `value`. + * + * Does not update the allowance value in case of infinite allowance. + * Revert if not enough allowance is available. + * + * Does not emit an {Approval} event. + */ + function _spendAllowance(address owner, address spender, uint256 value) internal virtual { + uint256 currentAllowance = allowance(owner, spender); + if (currentAllowance != type(uint256).max) { + if (currentAllowance < value) { + revert ERC20InsufficientAllowance(spender, currentAllowance, value); + } + unchecked { + _approve(owner, spender, currentAllowance - value, false); + } + } + } +} diff --git a/CVLByExample/UniswapHooks/lib/uniswap-hooks/lib/v4-core/lib/openzeppelin-contracts/contracts/token/ERC20/IERC20.sol b/CVLByExample/UniswapHooks/lib/uniswap-hooks/lib/v4-core/lib/openzeppelin-contracts/contracts/token/ERC20/IERC20.sol new file mode 100644 index 00000000..db01cf4c --- /dev/null +++ b/CVLByExample/UniswapHooks/lib/uniswap-hooks/lib/v4-core/lib/openzeppelin-contracts/contracts/token/ERC20/IERC20.sol @@ -0,0 +1,79 @@ +// SPDX-License-Identifier: MIT +// OpenZeppelin Contracts (last updated v5.0.0) (token/ERC20/IERC20.sol) + +pragma solidity ^0.8.20; + +/** + * @dev Interface of the ERC20 standard as defined in the EIP. + */ +interface IERC20 { + /** + * @dev Emitted when `value` tokens are moved from one account (`from`) to + * another (`to`). + * + * Note that `value` may be zero. + */ + event Transfer(address indexed from, address indexed to, uint256 value); + + /** + * @dev Emitted when the allowance of a `spender` for an `owner` is set by + * a call to {approve}. `value` is the new allowance. + */ + event Approval(address indexed owner, address indexed spender, uint256 value); + + /** + * @dev Returns the value of tokens in existence. + */ + function totalSupply() external view returns (uint256); + + /** + * @dev Returns the value of tokens owned by `account`. + */ + function balanceOf(address account) external view returns (uint256); + + /** + * @dev Moves a `value` amount of tokens from the caller's account to `to`. + * + * Returns a boolean value indicating whether the operation succeeded. + * + * Emits a {Transfer} event. + */ + function transfer(address to, uint256 value) external returns (bool); + + /** + * @dev Returns the remaining number of tokens that `spender` will be + * allowed to spend on behalf of `owner` through {transferFrom}. This is + * zero by default. + * + * This value changes when {approve} or {transferFrom} are called. + */ + function allowance(address owner, address spender) external view returns (uint256); + + /** + * @dev Sets a `value` amount of tokens as the allowance of `spender` over the + * caller's tokens. + * + * Returns a boolean value indicating whether the operation succeeded. + * + * IMPORTANT: Beware that changing an allowance with this method brings the risk + * that someone may use both the old and the new allowance by unfortunate + * transaction ordering. One possible solution to mitigate this race + * condition is to first reduce the spender's allowance to 0 and set the + * desired value afterwards: + * https://github.com/ethereum/EIPs/issues/20#issuecomment-263524729 + * + * Emits an {Approval} event. + */ + function approve(address spender, uint256 value) external returns (bool); + + /** + * @dev Moves a `value` amount of tokens from `from` to `to` using the + * allowance mechanism. `value` is then deducted from the caller's + * allowance. + * + * Returns a boolean value indicating whether the operation succeeded. + * + * Emits a {Transfer} event. + */ + function transferFrom(address from, address to, uint256 value) external returns (bool); +} diff --git a/CVLByExample/UniswapHooks/lib/uniswap-hooks/lib/v4-core/lib/openzeppelin-contracts/contracts/token/ERC20/README.adoc b/CVLByExample/UniswapHooks/lib/uniswap-hooks/lib/v4-core/lib/openzeppelin-contracts/contracts/token/ERC20/README.adoc new file mode 100644 index 00000000..2c508802 --- /dev/null +++ b/CVLByExample/UniswapHooks/lib/uniswap-hooks/lib/v4-core/lib/openzeppelin-contracts/contracts/token/ERC20/README.adoc @@ -0,0 +1,67 @@ += ERC 20 + +[.readme-notice] +NOTE: This document is better viewed at https://docs.openzeppelin.com/contracts/api/token/erc20 + +This set of interfaces, contracts, and utilities are all related to the https://eips.ethereum.org/EIPS/eip-20[ERC20 Token Standard]. + +TIP: For an overview of ERC20 tokens and a walk through on how to create a token contract read our xref:ROOT:erc20.adoc[ERC20 guide]. + +There are a few core contracts that implement the behavior specified in the EIP: + +* {IERC20}: the interface all ERC20 implementations should conform to. +* {IERC20Metadata}: the extended ERC20 interface including the <>, <> and <> functions. +* {ERC20}: the implementation of the ERC20 interface, including the <>, <> and <> optional standard extension to the base interface. + +Additionally there are multiple custom extensions, including: + +* {ERC20Permit}: gasless approval of tokens (standardized as ERC2612). +* {ERC20Burnable}: destruction of own tokens. +* {ERC20Capped}: enforcement of a cap to the total supply when minting tokens. +* {ERC20Pausable}: ability to pause token transfers. +* {ERC20FlashMint}: token level support for flash loans through the minting and burning of ephemeral tokens (standardized as ERC3156). +* {ERC20Votes}: support for voting and vote delegation. +* {ERC20Wrapper}: wrapper to create an ERC20 backed by another ERC20, with deposit and withdraw methods. Useful in conjunction with {ERC20Votes}. +* {ERC4626}: tokenized vault that manages shares (represented as ERC20) that are backed by assets (another ERC20). + +Finally, there are some utilities to interact with ERC20 contracts in various ways: + +* {SafeERC20}: a wrapper around the interface that eliminates the need to handle boolean return values. + +Other utilities that support ERC20 assets can be found in codebase: + +* ERC20 tokens can be timelocked (held tokens for a beneficiary until a specified time) or vested (released following a given schedule) using a {VestingWallet}. + +NOTE: This core set of contracts is designed to be unopinionated, allowing developers to access the internal functions in ERC20 (such as <>) and expose them as external functions in the way they prefer. + +== Core + +{{IERC20}} + +{{IERC20Metadata}} + +{{ERC20}} + +== Extensions + +{{IERC20Permit}} + +{{ERC20Permit}} + +{{ERC20Burnable}} + +{{ERC20Capped}} + +{{ERC20Pausable}} + +{{ERC20Votes}} + +{{ERC20Wrapper}} + +{{ERC20FlashMint}} + +{{ERC4626}} + +== Utilities + +{{SafeERC20}} diff --git a/CVLByExample/UniswapHooks/lib/uniswap-hooks/lib/v4-core/lib/openzeppelin-contracts/contracts/token/ERC20/extensions/ERC20Burnable.sol b/CVLByExample/UniswapHooks/lib/uniswap-hooks/lib/v4-core/lib/openzeppelin-contracts/contracts/token/ERC20/extensions/ERC20Burnable.sol new file mode 100644 index 00000000..4d482d8e --- /dev/null +++ b/CVLByExample/UniswapHooks/lib/uniswap-hooks/lib/v4-core/lib/openzeppelin-contracts/contracts/token/ERC20/extensions/ERC20Burnable.sol @@ -0,0 +1,39 @@ +// SPDX-License-Identifier: MIT +// OpenZeppelin Contracts (last updated v5.0.0) (token/ERC20/extensions/ERC20Burnable.sol) + +pragma solidity ^0.8.20; + +import {ERC20} from "../ERC20.sol"; +import {Context} from "../../../utils/Context.sol"; + +/** + * @dev Extension of {ERC20} that allows token holders to destroy both their own + * tokens and those that they have an allowance for, in a way that can be + * recognized off-chain (via event analysis). + */ +abstract contract ERC20Burnable is Context, ERC20 { + /** + * @dev Destroys a `value` amount of tokens from the caller. + * + * See {ERC20-_burn}. + */ + function burn(uint256 value) public virtual { + _burn(_msgSender(), value); + } + + /** + * @dev Destroys a `value` amount of tokens from `account`, deducting from + * the caller's allowance. + * + * See {ERC20-_burn} and {ERC20-allowance}. + * + * Requirements: + * + * - the caller must have allowance for ``accounts``'s tokens of at least + * `value`. + */ + function burnFrom(address account, uint256 value) public virtual { + _spendAllowance(account, _msgSender(), value); + _burn(account, value); + } +} diff --git a/CVLByExample/UniswapHooks/lib/uniswap-hooks/lib/v4-core/lib/openzeppelin-contracts/contracts/token/ERC20/extensions/ERC20Capped.sol b/CVLByExample/UniswapHooks/lib/uniswap-hooks/lib/v4-core/lib/openzeppelin-contracts/contracts/token/ERC20/extensions/ERC20Capped.sol new file mode 100644 index 00000000..56bafb3a --- /dev/null +++ b/CVLByExample/UniswapHooks/lib/uniswap-hooks/lib/v4-core/lib/openzeppelin-contracts/contracts/token/ERC20/extensions/ERC20Capped.sol @@ -0,0 +1,56 @@ +// SPDX-License-Identifier: MIT +// OpenZeppelin Contracts (last updated v5.0.0) (token/ERC20/extensions/ERC20Capped.sol) + +pragma solidity ^0.8.20; + +import {ERC20} from "../ERC20.sol"; + +/** + * @dev Extension of {ERC20} that adds a cap to the supply of tokens. + */ +abstract contract ERC20Capped is ERC20 { + uint256 private immutable _cap; + + /** + * @dev Total supply cap has been exceeded. + */ + error ERC20ExceededCap(uint256 increasedSupply, uint256 cap); + + /** + * @dev The supplied cap is not a valid cap. + */ + error ERC20InvalidCap(uint256 cap); + + /** + * @dev Sets the value of the `cap`. This value is immutable, it can only be + * set once during construction. + */ + constructor(uint256 cap_) { + if (cap_ == 0) { + revert ERC20InvalidCap(0); + } + _cap = cap_; + } + + /** + * @dev Returns the cap on the token's total supply. + */ + function cap() public view virtual returns (uint256) { + return _cap; + } + + /** + * @dev See {ERC20-_update}. + */ + function _update(address from, address to, uint256 value) internal virtual override { + super._update(from, to, value); + + if (from == address(0)) { + uint256 maxSupply = cap(); + uint256 supply = totalSupply(); + if (supply > maxSupply) { + revert ERC20ExceededCap(supply, maxSupply); + } + } + } +} diff --git a/CVLByExample/UniswapHooks/lib/uniswap-hooks/lib/v4-core/lib/openzeppelin-contracts/contracts/token/ERC20/extensions/ERC20FlashMint.sol b/CVLByExample/UniswapHooks/lib/uniswap-hooks/lib/v4-core/lib/openzeppelin-contracts/contracts/token/ERC20/extensions/ERC20FlashMint.sol new file mode 100644 index 00000000..0e893127 --- /dev/null +++ b/CVLByExample/UniswapHooks/lib/uniswap-hooks/lib/v4-core/lib/openzeppelin-contracts/contracts/token/ERC20/extensions/ERC20FlashMint.sol @@ -0,0 +1,134 @@ +// SPDX-License-Identifier: MIT +// OpenZeppelin Contracts (last updated v5.0.0) (token/ERC20/extensions/ERC20FlashMint.sol) + +pragma solidity ^0.8.20; + +import {IERC3156FlashBorrower} from "../../../interfaces/IERC3156FlashBorrower.sol"; +import {IERC3156FlashLender} from "../../../interfaces/IERC3156FlashLender.sol"; +import {ERC20} from "../ERC20.sol"; + +/** + * @dev Implementation of the ERC3156 Flash loans extension, as defined in + * https://eips.ethereum.org/EIPS/eip-3156[ERC-3156]. + * + * Adds the {flashLoan} method, which provides flash loan support at the token + * level. By default there is no fee, but this can be changed by overriding {flashFee}. + * + * NOTE: When this extension is used along with the {ERC20Capped} or {ERC20Votes} extensions, + * {maxFlashLoan} will not correctly reflect the maximum that can be flash minted. We recommend + * overriding {maxFlashLoan} so that it correctly reflects the supply cap. + */ +abstract contract ERC20FlashMint is ERC20, IERC3156FlashLender { + bytes32 private constant RETURN_VALUE = keccak256("ERC3156FlashBorrower.onFlashLoan"); + + /** + * @dev The loan token is not valid. + */ + error ERC3156UnsupportedToken(address token); + + /** + * @dev The requested loan exceeds the max loan value for `token`. + */ + error ERC3156ExceededMaxLoan(uint256 maxLoan); + + /** + * @dev The receiver of a flashloan is not a valid {onFlashLoan} implementer. + */ + error ERC3156InvalidReceiver(address receiver); + + /** + * @dev Returns the maximum amount of tokens available for loan. + * @param token The address of the token that is requested. + * @return The amount of token that can be loaned. + * + * NOTE: This function does not consider any form of supply cap, so in case + * it's used in a token with a cap like {ERC20Capped}, make sure to override this + * function to integrate the cap instead of `type(uint256).max`. + */ + function maxFlashLoan(address token) public view virtual returns (uint256) { + return token == address(this) ? type(uint256).max - totalSupply() : 0; + } + + /** + * @dev Returns the fee applied when doing flash loans. This function calls + * the {_flashFee} function which returns the fee applied when doing flash + * loans. + * @param token The token to be flash loaned. + * @param value The amount of tokens to be loaned. + * @return The fees applied to the corresponding flash loan. + */ + function flashFee(address token, uint256 value) public view virtual returns (uint256) { + if (token != address(this)) { + revert ERC3156UnsupportedToken(token); + } + return _flashFee(token, value); + } + + /** + * @dev Returns the fee applied when doing flash loans. By default this + * implementation has 0 fees. This function can be overloaded to make + * the flash loan mechanism deflationary. + * @param token The token to be flash loaned. + * @param value The amount of tokens to be loaned. + * @return The fees applied to the corresponding flash loan. + */ + function _flashFee(address token, uint256 value) internal view virtual returns (uint256) { + // silence warning about unused variable without the addition of bytecode. + token; + value; + return 0; + } + + /** + * @dev Returns the receiver address of the flash fee. By default this + * implementation returns the address(0) which means the fee amount will be burnt. + * This function can be overloaded to change the fee receiver. + * @return The address for which the flash fee will be sent to. + */ + function _flashFeeReceiver() internal view virtual returns (address) { + return address(0); + } + + /** + * @dev Performs a flash loan. New tokens are minted and sent to the + * `receiver`, who is required to implement the {IERC3156FlashBorrower} + * interface. By the end of the flash loan, the receiver is expected to own + * value + fee tokens and have them approved back to the token contract itself so + * they can be burned. + * @param receiver The receiver of the flash loan. Should implement the + * {IERC3156FlashBorrower-onFlashLoan} interface. + * @param token The token to be flash loaned. Only `address(this)` is + * supported. + * @param value The amount of tokens to be loaned. + * @param data An arbitrary datafield that is passed to the receiver. + * @return `true` if the flash loan was successful. + */ + // This function can reenter, but it doesn't pose a risk because it always preserves the property that the amount + // minted at the beginning is always recovered and burned at the end, or else the entire function will revert. + // slither-disable-next-line reentrancy-no-eth + function flashLoan( + IERC3156FlashBorrower receiver, + address token, + uint256 value, + bytes calldata data + ) public virtual returns (bool) { + uint256 maxLoan = maxFlashLoan(token); + if (value > maxLoan) { + revert ERC3156ExceededMaxLoan(maxLoan); + } + uint256 fee = flashFee(token, value); + _mint(address(receiver), value); + if (receiver.onFlashLoan(_msgSender(), token, value, fee, data) != RETURN_VALUE) { + revert ERC3156InvalidReceiver(address(receiver)); + } + address flashFeeReceiver = _flashFeeReceiver(); + _spendAllowance(address(receiver), address(this), value + fee); + if (fee == 0 || flashFeeReceiver == address(0)) { + _burn(address(receiver), value + fee); + } else { + _burn(address(receiver), value); + _transfer(address(receiver), flashFeeReceiver, fee); + } + return true; + } +} diff --git a/CVLByExample/UniswapHooks/lib/uniswap-hooks/lib/v4-core/lib/openzeppelin-contracts/contracts/token/ERC20/extensions/ERC20Pausable.sol b/CVLByExample/UniswapHooks/lib/uniswap-hooks/lib/v4-core/lib/openzeppelin-contracts/contracts/token/ERC20/extensions/ERC20Pausable.sol new file mode 100644 index 00000000..8fe832b9 --- /dev/null +++ b/CVLByExample/UniswapHooks/lib/uniswap-hooks/lib/v4-core/lib/openzeppelin-contracts/contracts/token/ERC20/extensions/ERC20Pausable.sol @@ -0,0 +1,33 @@ +// SPDX-License-Identifier: MIT +// OpenZeppelin Contracts (last updated v5.0.0) (token/ERC20/extensions/ERC20Pausable.sol) + +pragma solidity ^0.8.20; + +import {ERC20} from "../ERC20.sol"; +import {Pausable} from "../../../utils/Pausable.sol"; + +/** + * @dev ERC20 token with pausable token transfers, minting and burning. + * + * Useful for scenarios such as preventing trades until the end of an evaluation + * period, or having an emergency switch for freezing all token transfers in the + * event of a large bug. + * + * IMPORTANT: This contract does not include public pause and unpause functions. In + * addition to inheriting this contract, you must define both functions, invoking the + * {Pausable-_pause} and {Pausable-_unpause} internal functions, with appropriate + * access control, e.g. using {AccessControl} or {Ownable}. Not doing so will + * make the contract pause mechanism of the contract unreachable, and thus unusable. + */ +abstract contract ERC20Pausable is ERC20, Pausable { + /** + * @dev See {ERC20-_update}. + * + * Requirements: + * + * - the contract must not be paused. + */ + function _update(address from, address to, uint256 value) internal virtual override whenNotPaused { + super._update(from, to, value); + } +} diff --git a/CVLByExample/UniswapHooks/lib/uniswap-hooks/lib/v4-core/lib/openzeppelin-contracts/contracts/token/ERC20/extensions/ERC20Permit.sol b/CVLByExample/UniswapHooks/lib/uniswap-hooks/lib/v4-core/lib/openzeppelin-contracts/contracts/token/ERC20/extensions/ERC20Permit.sol new file mode 100644 index 00000000..36667adf --- /dev/null +++ b/CVLByExample/UniswapHooks/lib/uniswap-hooks/lib/v4-core/lib/openzeppelin-contracts/contracts/token/ERC20/extensions/ERC20Permit.sol @@ -0,0 +1,83 @@ +// SPDX-License-Identifier: MIT +// OpenZeppelin Contracts (last updated v5.0.0) (token/ERC20/extensions/ERC20Permit.sol) + +pragma solidity ^0.8.20; + +import {IERC20Permit} from "./IERC20Permit.sol"; +import {ERC20} from "../ERC20.sol"; +import {ECDSA} from "../../../utils/cryptography/ECDSA.sol"; +import {EIP712} from "../../../utils/cryptography/EIP712.sol"; +import {Nonces} from "../../../utils/Nonces.sol"; + +/** + * @dev Implementation of the ERC20 Permit extension allowing approvals to be made via signatures, as defined in + * https://eips.ethereum.org/EIPS/eip-2612[EIP-2612]. + * + * Adds the {permit} method, which can be used to change an account's ERC20 allowance (see {IERC20-allowance}) by + * presenting a message signed by the account. By not relying on `{IERC20-approve}`, the token holder account doesn't + * need to send a transaction, and thus is not required to hold Ether at all. + */ +abstract contract ERC20Permit is ERC20, IERC20Permit, EIP712, Nonces { + bytes32 private constant PERMIT_TYPEHASH = + keccak256("Permit(address owner,address spender,uint256 value,uint256 nonce,uint256 deadline)"); + + /** + * @dev Permit deadline has expired. + */ + error ERC2612ExpiredSignature(uint256 deadline); + + /** + * @dev Mismatched signature. + */ + error ERC2612InvalidSigner(address signer, address owner); + + /** + * @dev Initializes the {EIP712} domain separator using the `name` parameter, and setting `version` to `"1"`. + * + * It's a good idea to use the same `name` that is defined as the ERC20 token name. + */ + constructor(string memory name) EIP712(name, "1") {} + + /** + * @inheritdoc IERC20Permit + */ + function permit( + address owner, + address spender, + uint256 value, + uint256 deadline, + uint8 v, + bytes32 r, + bytes32 s + ) public virtual { + if (block.timestamp > deadline) { + revert ERC2612ExpiredSignature(deadline); + } + + bytes32 structHash = keccak256(abi.encode(PERMIT_TYPEHASH, owner, spender, value, _useNonce(owner), deadline)); + + bytes32 hash = _hashTypedDataV4(structHash); + + address signer = ECDSA.recover(hash, v, r, s); + if (signer != owner) { + revert ERC2612InvalidSigner(signer, owner); + } + + _approve(owner, spender, value); + } + + /** + * @inheritdoc IERC20Permit + */ + function nonces(address owner) public view virtual override(IERC20Permit, Nonces) returns (uint256) { + return super.nonces(owner); + } + + /** + * @inheritdoc IERC20Permit + */ + // solhint-disable-next-line func-name-mixedcase + function DOMAIN_SEPARATOR() external view virtual returns (bytes32) { + return _domainSeparatorV4(); + } +} diff --git a/CVLByExample/UniswapHooks/lib/uniswap-hooks/lib/v4-core/lib/openzeppelin-contracts/contracts/token/ERC20/extensions/ERC20Votes.sol b/CVLByExample/UniswapHooks/lib/uniswap-hooks/lib/v4-core/lib/openzeppelin-contracts/contracts/token/ERC20/extensions/ERC20Votes.sol new file mode 100644 index 00000000..6aa6ed05 --- /dev/null +++ b/CVLByExample/UniswapHooks/lib/uniswap-hooks/lib/v4-core/lib/openzeppelin-contracts/contracts/token/ERC20/extensions/ERC20Votes.sol @@ -0,0 +1,83 @@ +// SPDX-License-Identifier: MIT +// OpenZeppelin Contracts (last updated v5.0.0) (token/ERC20/extensions/ERC20Votes.sol) + +pragma solidity ^0.8.20; + +import {ERC20} from "../ERC20.sol"; +import {Votes} from "../../../governance/utils/Votes.sol"; +import {Checkpoints} from "../../../utils/structs/Checkpoints.sol"; + +/** + * @dev Extension of ERC20 to support Compound-like voting and delegation. This version is more generic than Compound's, + * and supports token supply up to 2^208^ - 1, while COMP is limited to 2^96^ - 1. + * + * NOTE: This contract does not provide interface compatibility with Compound's COMP token. + * + * This extension keeps a history (checkpoints) of each account's vote power. Vote power can be delegated either + * by calling the {delegate} function directly, or by providing a signature to be used with {delegateBySig}. Voting + * power can be queried through the public accessors {getVotes} and {getPastVotes}. + * + * By default, token balance does not account for voting power. This makes transfers cheaper. The downside is that it + * requires users to delegate to themselves in order to activate checkpoints and have their voting power tracked. + */ +abstract contract ERC20Votes is ERC20, Votes { + /** + * @dev Total supply cap has been exceeded, introducing a risk of votes overflowing. + */ + error ERC20ExceededSafeSupply(uint256 increasedSupply, uint256 cap); + + /** + * @dev Maximum token supply. Defaults to `type(uint208).max` (2^208^ - 1). + * + * This maximum is enforced in {_update}. It limits the total supply of the token, which is otherwise a uint256, + * so that checkpoints can be stored in the Trace208 structure used by {{Votes}}. Increasing this value will not + * remove the underlying limitation, and will cause {_update} to fail because of a math overflow in + * {_transferVotingUnits}. An override could be used to further restrict the total supply (to a lower value) if + * additional logic requires it. When resolving override conflicts on this function, the minimum should be + * returned. + */ + function _maxSupply() internal view virtual returns (uint256) { + return type(uint208).max; + } + + /** + * @dev Move voting power when tokens are transferred. + * + * Emits a {IVotes-DelegateVotesChanged} event. + */ + function _update(address from, address to, uint256 value) internal virtual override { + super._update(from, to, value); + if (from == address(0)) { + uint256 supply = totalSupply(); + uint256 cap = _maxSupply(); + if (supply > cap) { + revert ERC20ExceededSafeSupply(supply, cap); + } + } + _transferVotingUnits(from, to, value); + } + + /** + * @dev Returns the voting units of an `account`. + * + * WARNING: Overriding this function may compromise the internal vote accounting. + * `ERC20Votes` assumes tokens map to voting units 1:1 and this is not easy to change. + */ + function _getVotingUnits(address account) internal view virtual override returns (uint256) { + return balanceOf(account); + } + + /** + * @dev Get number of checkpoints for `account`. + */ + function numCheckpoints(address account) public view virtual returns (uint32) { + return _numCheckpoints(account); + } + + /** + * @dev Get the `pos`-th checkpoint for `account`. + */ + function checkpoints(address account, uint32 pos) public view virtual returns (Checkpoints.Checkpoint208 memory) { + return _checkpoints(account, pos); + } +} diff --git a/CVLByExample/UniswapHooks/lib/uniswap-hooks/lib/v4-core/lib/openzeppelin-contracts/contracts/token/ERC20/extensions/ERC20Wrapper.sol b/CVLByExample/UniswapHooks/lib/uniswap-hooks/lib/v4-core/lib/openzeppelin-contracts/contracts/token/ERC20/extensions/ERC20Wrapper.sol new file mode 100644 index 00000000..61448803 --- /dev/null +++ b/CVLByExample/UniswapHooks/lib/uniswap-hooks/lib/v4-core/lib/openzeppelin-contracts/contracts/token/ERC20/extensions/ERC20Wrapper.sol @@ -0,0 +1,86 @@ +// SPDX-License-Identifier: MIT +// OpenZeppelin Contracts (last updated v5.0.0) (token/ERC20/extensions/ERC20Wrapper.sol) + +pragma solidity ^0.8.20; + +import {IERC20, IERC20Metadata, ERC20} from "../ERC20.sol"; +import {SafeERC20} from "../utils/SafeERC20.sol"; + +/** + * @dev Extension of the ERC20 token contract to support token wrapping. + * + * Users can deposit and withdraw "underlying tokens" and receive a matching number of "wrapped tokens". This is useful + * in conjunction with other modules. For example, combining this wrapping mechanism with {ERC20Votes} will allow the + * wrapping of an existing "basic" ERC20 into a governance token. + */ +abstract contract ERC20Wrapper is ERC20 { + IERC20 private immutable _underlying; + + /** + * @dev The underlying token couldn't be wrapped. + */ + error ERC20InvalidUnderlying(address token); + + constructor(IERC20 underlyingToken) { + if (underlyingToken == this) { + revert ERC20InvalidUnderlying(address(this)); + } + _underlying = underlyingToken; + } + + /** + * @dev See {ERC20-decimals}. + */ + function decimals() public view virtual override returns (uint8) { + try IERC20Metadata(address(_underlying)).decimals() returns (uint8 value) { + return value; + } catch { + return super.decimals(); + } + } + + /** + * @dev Returns the address of the underlying ERC-20 token that is being wrapped. + */ + function underlying() public view returns (IERC20) { + return _underlying; + } + + /** + * @dev Allow a user to deposit underlying tokens and mint the corresponding number of wrapped tokens. + */ + function depositFor(address account, uint256 value) public virtual returns (bool) { + address sender = _msgSender(); + if (sender == address(this)) { + revert ERC20InvalidSender(address(this)); + } + if (account == address(this)) { + revert ERC20InvalidReceiver(account); + } + SafeERC20.safeTransferFrom(_underlying, sender, address(this), value); + _mint(account, value); + return true; + } + + /** + * @dev Allow a user to burn a number of wrapped tokens and withdraw the corresponding number of underlying tokens. + */ + function withdrawTo(address account, uint256 value) public virtual returns (bool) { + if (account == address(this)) { + revert ERC20InvalidReceiver(account); + } + _burn(_msgSender(), value); + SafeERC20.safeTransfer(_underlying, account, value); + return true; + } + + /** + * @dev Mint wrapped token to cover any underlyingTokens that would have been transferred by mistake. Internal + * function that can be exposed with access control if desired. + */ + function _recover(address account) internal virtual returns (uint256) { + uint256 value = _underlying.balanceOf(address(this)) - totalSupply(); + _mint(account, value); + return value; + } +} diff --git a/CVLByExample/UniswapHooks/lib/uniswap-hooks/lib/v4-core/lib/openzeppelin-contracts/contracts/token/ERC20/extensions/ERC4626.sol b/CVLByExample/UniswapHooks/lib/uniswap-hooks/lib/v4-core/lib/openzeppelin-contracts/contracts/token/ERC20/extensions/ERC4626.sol new file mode 100644 index 00000000..ec608723 --- /dev/null +++ b/CVLByExample/UniswapHooks/lib/uniswap-hooks/lib/v4-core/lib/openzeppelin-contracts/contracts/token/ERC20/extensions/ERC4626.sol @@ -0,0 +1,286 @@ +// SPDX-License-Identifier: MIT +// OpenZeppelin Contracts (last updated v5.0.0) (token/ERC20/extensions/ERC4626.sol) + +pragma solidity ^0.8.20; + +import {IERC20, IERC20Metadata, ERC20} from "../ERC20.sol"; +import {SafeERC20} from "../utils/SafeERC20.sol"; +import {IERC4626} from "../../../interfaces/IERC4626.sol"; +import {Math} from "../../../utils/math/Math.sol"; + +/** + * @dev Implementation of the ERC4626 "Tokenized Vault Standard" as defined in + * https://eips.ethereum.org/EIPS/eip-4626[EIP-4626]. + * + * This extension allows the minting and burning of "shares" (represented using the ERC20 inheritance) in exchange for + * underlying "assets" through standardized {deposit}, {mint}, {redeem} and {burn} workflows. This contract extends + * the ERC20 standard. Any additional extensions included along it would affect the "shares" token represented by this + * contract and not the "assets" token which is an independent contract. + * + * [CAUTION] + * ==== + * In empty (or nearly empty) ERC-4626 vaults, deposits are at high risk of being stolen through frontrunning + * with a "donation" to the vault that inflates the price of a share. This is variously known as a donation or inflation + * attack and is essentially a problem of slippage. Vault deployers can protect against this attack by making an initial + * deposit of a non-trivial amount of the asset, such that price manipulation becomes infeasible. Withdrawals may + * similarly be affected by slippage. Users can protect against this attack as well as unexpected slippage in general by + * verifying the amount received is as expected, using a wrapper that performs these checks such as + * https://github.com/fei-protocol/ERC4626#erc4626router-and-base[ERC4626Router]. + * + * Since v4.9, this implementation uses virtual assets and shares to mitigate that risk. The `_decimalsOffset()` + * corresponds to an offset in the decimal representation between the underlying asset's decimals and the vault + * decimals. This offset also determines the rate of virtual shares to virtual assets in the vault, which itself + * determines the initial exchange rate. While not fully preventing the attack, analysis shows that the default offset + * (0) makes it non-profitable, as a result of the value being captured by the virtual shares (out of the attacker's + * donation) matching the attacker's expected gains. With a larger offset, the attack becomes orders of magnitude more + * expensive than it is profitable. More details about the underlying math can be found + * xref:erc4626.adoc#inflation-attack[here]. + * + * The drawback of this approach is that the virtual shares do capture (a very small) part of the value being accrued + * to the vault. Also, if the vault experiences losses, the users try to exit the vault, the virtual shares and assets + * will cause the first user to exit to experience reduced losses in detriment to the last users that will experience + * bigger losses. Developers willing to revert back to the pre-v4.9 behavior just need to override the + * `_convertToShares` and `_convertToAssets` functions. + * + * To learn more, check out our xref:ROOT:erc4626.adoc[ERC-4626 guide]. + * ==== + */ +abstract contract ERC4626 is ERC20, IERC4626 { + using Math for uint256; + + IERC20 private immutable _asset; + uint8 private immutable _underlyingDecimals; + + /** + * @dev Attempted to deposit more assets than the max amount for `receiver`. + */ + error ERC4626ExceededMaxDeposit(address receiver, uint256 assets, uint256 max); + + /** + * @dev Attempted to mint more shares than the max amount for `receiver`. + */ + error ERC4626ExceededMaxMint(address receiver, uint256 shares, uint256 max); + + /** + * @dev Attempted to withdraw more assets than the max amount for `receiver`. + */ + error ERC4626ExceededMaxWithdraw(address owner, uint256 assets, uint256 max); + + /** + * @dev Attempted to redeem more shares than the max amount for `receiver`. + */ + error ERC4626ExceededMaxRedeem(address owner, uint256 shares, uint256 max); + + /** + * @dev Set the underlying asset contract. This must be an ERC20-compatible contract (ERC20 or ERC777). + */ + constructor(IERC20 asset_) { + (bool success, uint8 assetDecimals) = _tryGetAssetDecimals(asset_); + _underlyingDecimals = success ? assetDecimals : 18; + _asset = asset_; + } + + /** + * @dev Attempts to fetch the asset decimals. A return value of false indicates that the attempt failed in some way. + */ + function _tryGetAssetDecimals(IERC20 asset_) private view returns (bool, uint8) { + (bool success, bytes memory encodedDecimals) = address(asset_).staticcall( + abi.encodeCall(IERC20Metadata.decimals, ()) + ); + if (success && encodedDecimals.length >= 32) { + uint256 returnedDecimals = abi.decode(encodedDecimals, (uint256)); + if (returnedDecimals <= type(uint8).max) { + return (true, uint8(returnedDecimals)); + } + } + return (false, 0); + } + + /** + * @dev Decimals are computed by adding the decimal offset on top of the underlying asset's decimals. This + * "original" value is cached during construction of the vault contract. If this read operation fails (e.g., the + * asset has not been created yet), a default of 18 is used to represent the underlying asset's decimals. + * + * See {IERC20Metadata-decimals}. + */ + function decimals() public view virtual override(IERC20Metadata, ERC20) returns (uint8) { + return _underlyingDecimals + _decimalsOffset(); + } + + /** @dev See {IERC4626-asset}. */ + function asset() public view virtual returns (address) { + return address(_asset); + } + + /** @dev See {IERC4626-totalAssets}. */ + function totalAssets() public view virtual returns (uint256) { + return _asset.balanceOf(address(this)); + } + + /** @dev See {IERC4626-convertToShares}. */ + function convertToShares(uint256 assets) public view virtual returns (uint256) { + return _convertToShares(assets, Math.Rounding.Floor); + } + + /** @dev See {IERC4626-convertToAssets}. */ + function convertToAssets(uint256 shares) public view virtual returns (uint256) { + return _convertToAssets(shares, Math.Rounding.Floor); + } + + /** @dev See {IERC4626-maxDeposit}. */ + function maxDeposit(address) public view virtual returns (uint256) { + return type(uint256).max; + } + + /** @dev See {IERC4626-maxMint}. */ + function maxMint(address) public view virtual returns (uint256) { + return type(uint256).max; + } + + /** @dev See {IERC4626-maxWithdraw}. */ + function maxWithdraw(address owner) public view virtual returns (uint256) { + return _convertToAssets(balanceOf(owner), Math.Rounding.Floor); + } + + /** @dev See {IERC4626-maxRedeem}. */ + function maxRedeem(address owner) public view virtual returns (uint256) { + return balanceOf(owner); + } + + /** @dev See {IERC4626-previewDeposit}. */ + function previewDeposit(uint256 assets) public view virtual returns (uint256) { + return _convertToShares(assets, Math.Rounding.Floor); + } + + /** @dev See {IERC4626-previewMint}. */ + function previewMint(uint256 shares) public view virtual returns (uint256) { + return _convertToAssets(shares, Math.Rounding.Ceil); + } + + /** @dev See {IERC4626-previewWithdraw}. */ + function previewWithdraw(uint256 assets) public view virtual returns (uint256) { + return _convertToShares(assets, Math.Rounding.Ceil); + } + + /** @dev See {IERC4626-previewRedeem}. */ + function previewRedeem(uint256 shares) public view virtual returns (uint256) { + return _convertToAssets(shares, Math.Rounding.Floor); + } + + /** @dev See {IERC4626-deposit}. */ + function deposit(uint256 assets, address receiver) public virtual returns (uint256) { + uint256 maxAssets = maxDeposit(receiver); + if (assets > maxAssets) { + revert ERC4626ExceededMaxDeposit(receiver, assets, maxAssets); + } + + uint256 shares = previewDeposit(assets); + _deposit(_msgSender(), receiver, assets, shares); + + return shares; + } + + /** @dev See {IERC4626-mint}. + * + * As opposed to {deposit}, minting is allowed even if the vault is in a state where the price of a share is zero. + * In this case, the shares will be minted without requiring any assets to be deposited. + */ + function mint(uint256 shares, address receiver) public virtual returns (uint256) { + uint256 maxShares = maxMint(receiver); + if (shares > maxShares) { + revert ERC4626ExceededMaxMint(receiver, shares, maxShares); + } + + uint256 assets = previewMint(shares); + _deposit(_msgSender(), receiver, assets, shares); + + return assets; + } + + /** @dev See {IERC4626-withdraw}. */ + function withdraw(uint256 assets, address receiver, address owner) public virtual returns (uint256) { + uint256 maxAssets = maxWithdraw(owner); + if (assets > maxAssets) { + revert ERC4626ExceededMaxWithdraw(owner, assets, maxAssets); + } + + uint256 shares = previewWithdraw(assets); + _withdraw(_msgSender(), receiver, owner, assets, shares); + + return shares; + } + + /** @dev See {IERC4626-redeem}. */ + function redeem(uint256 shares, address receiver, address owner) public virtual returns (uint256) { + uint256 maxShares = maxRedeem(owner); + if (shares > maxShares) { + revert ERC4626ExceededMaxRedeem(owner, shares, maxShares); + } + + uint256 assets = previewRedeem(shares); + _withdraw(_msgSender(), receiver, owner, assets, shares); + + return assets; + } + + /** + * @dev Internal conversion function (from assets to shares) with support for rounding direction. + */ + function _convertToShares(uint256 assets, Math.Rounding rounding) internal view virtual returns (uint256) { + return assets.mulDiv(totalSupply() + 10 ** _decimalsOffset(), totalAssets() + 1, rounding); + } + + /** + * @dev Internal conversion function (from shares to assets) with support for rounding direction. + */ + function _convertToAssets(uint256 shares, Math.Rounding rounding) internal view virtual returns (uint256) { + return shares.mulDiv(totalAssets() + 1, totalSupply() + 10 ** _decimalsOffset(), rounding); + } + + /** + * @dev Deposit/mint common workflow. + */ + function _deposit(address caller, address receiver, uint256 assets, uint256 shares) internal virtual { + // If _asset is ERC777, `transferFrom` can trigger a reentrancy BEFORE the transfer happens through the + // `tokensToSend` hook. On the other hand, the `tokenReceived` hook, that is triggered after the transfer, + // calls the vault, which is assumed not malicious. + // + // Conclusion: we need to do the transfer before we mint so that any reentrancy would happen before the + // assets are transferred and before the shares are minted, which is a valid state. + // slither-disable-next-line reentrancy-no-eth + SafeERC20.safeTransferFrom(_asset, caller, address(this), assets); + _mint(receiver, shares); + + emit Deposit(caller, receiver, assets, shares); + } + + /** + * @dev Withdraw/redeem common workflow. + */ + function _withdraw( + address caller, + address receiver, + address owner, + uint256 assets, + uint256 shares + ) internal virtual { + if (caller != owner) { + _spendAllowance(owner, caller, shares); + } + + // If _asset is ERC777, `transfer` can trigger a reentrancy AFTER the transfer happens through the + // `tokensReceived` hook. On the other hand, the `tokensToSend` hook, that is triggered before the transfer, + // calls the vault, which is assumed not malicious. + // + // Conclusion: we need to do the transfer after the burn so that any reentrancy would happen after the + // shares are burned and after the assets are transferred, which is a valid state. + _burn(owner, shares); + SafeERC20.safeTransfer(_asset, receiver, assets); + + emit Withdraw(caller, receiver, owner, assets, shares); + } + + function _decimalsOffset() internal view virtual returns (uint8) { + return 0; + } +} diff --git a/CVLByExample/UniswapHooks/lib/uniswap-hooks/lib/v4-core/lib/openzeppelin-contracts/contracts/token/ERC20/extensions/IERC20Metadata.sol b/CVLByExample/UniswapHooks/lib/uniswap-hooks/lib/v4-core/lib/openzeppelin-contracts/contracts/token/ERC20/extensions/IERC20Metadata.sol new file mode 100644 index 00000000..1a38cba3 --- /dev/null +++ b/CVLByExample/UniswapHooks/lib/uniswap-hooks/lib/v4-core/lib/openzeppelin-contracts/contracts/token/ERC20/extensions/IERC20Metadata.sol @@ -0,0 +1,26 @@ +// SPDX-License-Identifier: MIT +// OpenZeppelin Contracts (last updated v5.0.0) (token/ERC20/extensions/IERC20Metadata.sol) + +pragma solidity ^0.8.20; + +import {IERC20} from "../IERC20.sol"; + +/** + * @dev Interface for the optional metadata functions from the ERC20 standard. + */ +interface IERC20Metadata is IERC20 { + /** + * @dev Returns the name of the token. + */ + function name() external view returns (string memory); + + /** + * @dev Returns the symbol of the token. + */ + function symbol() external view returns (string memory); + + /** + * @dev Returns the decimals places of the token. + */ + function decimals() external view returns (uint8); +} diff --git a/CVLByExample/UniswapHooks/lib/uniswap-hooks/lib/v4-core/lib/openzeppelin-contracts/contracts/token/ERC20/extensions/IERC20Permit.sol b/CVLByExample/UniswapHooks/lib/uniswap-hooks/lib/v4-core/lib/openzeppelin-contracts/contracts/token/ERC20/extensions/IERC20Permit.sol new file mode 100644 index 00000000..5af48101 --- /dev/null +++ b/CVLByExample/UniswapHooks/lib/uniswap-hooks/lib/v4-core/lib/openzeppelin-contracts/contracts/token/ERC20/extensions/IERC20Permit.sol @@ -0,0 +1,90 @@ +// SPDX-License-Identifier: MIT +// OpenZeppelin Contracts (last updated v5.0.0) (token/ERC20/extensions/IERC20Permit.sol) + +pragma solidity ^0.8.20; + +/** + * @dev Interface of the ERC20 Permit extension allowing approvals to be made via signatures, as defined in + * https://eips.ethereum.org/EIPS/eip-2612[EIP-2612]. + * + * Adds the {permit} method, which can be used to change an account's ERC20 allowance (see {IERC20-allowance}) by + * presenting a message signed by the account. By not relying on {IERC20-approve}, the token holder account doesn't + * need to send a transaction, and thus is not required to hold Ether at all. + * + * ==== Security Considerations + * + * There are two important considerations concerning the use of `permit`. The first is that a valid permit signature + * expresses an allowance, and it should not be assumed to convey additional meaning. In particular, it should not be + * considered as an intention to spend the allowance in any specific way. The second is that because permits have + * built-in replay protection and can be submitted by anyone, they can be frontrun. A protocol that uses permits should + * take this into consideration and allow a `permit` call to fail. Combining these two aspects, a pattern that may be + * generally recommended is: + * + * ```solidity + * function doThingWithPermit(..., uint256 value, uint256 deadline, uint8 v, bytes32 r, bytes32 s) public { + * try token.permit(msg.sender, address(this), value, deadline, v, r, s) {} catch {} + * doThing(..., value); + * } + * + * function doThing(..., uint256 value) public { + * token.safeTransferFrom(msg.sender, address(this), value); + * ... + * } + * ``` + * + * Observe that: 1) `msg.sender` is used as the owner, leaving no ambiguity as to the signer intent, and 2) the use of + * `try/catch` allows the permit to fail and makes the code tolerant to frontrunning. (See also + * {SafeERC20-safeTransferFrom}). + * + * Additionally, note that smart contract wallets (such as Argent or Safe) are not able to produce permit signatures, so + * contracts should have entry points that don't rely on permit. + */ +interface IERC20Permit { + /** + * @dev Sets `value` as the allowance of `spender` over ``owner``'s tokens, + * given ``owner``'s signed approval. + * + * IMPORTANT: The same issues {IERC20-approve} has related to transaction + * ordering also apply here. + * + * Emits an {Approval} event. + * + * Requirements: + * + * - `spender` cannot be the zero address. + * - `deadline` must be a timestamp in the future. + * - `v`, `r` and `s` must be a valid `secp256k1` signature from `owner` + * over the EIP712-formatted function arguments. + * - the signature must use ``owner``'s current nonce (see {nonces}). + * + * For more information on the signature format, see the + * https://eips.ethereum.org/EIPS/eip-2612#specification[relevant EIP + * section]. + * + * CAUTION: See Security Considerations above. + */ + function permit( + address owner, + address spender, + uint256 value, + uint256 deadline, + uint8 v, + bytes32 r, + bytes32 s + ) external; + + /** + * @dev Returns the current nonce for `owner`. This value must be + * included whenever a signature is generated for {permit}. + * + * Every successful call to {permit} increases ``owner``'s nonce by one. This + * prevents a signature from being used multiple times. + */ + function nonces(address owner) external view returns (uint256); + + /** + * @dev Returns the domain separator used in the encoding of the signature for {permit}, as defined by {EIP712}. + */ + // solhint-disable-next-line func-name-mixedcase + function DOMAIN_SEPARATOR() external view returns (bytes32); +} diff --git a/CVLByExample/UniswapHooks/lib/uniswap-hooks/lib/v4-core/lib/openzeppelin-contracts/contracts/token/ERC20/utils/SafeERC20.sol b/CVLByExample/UniswapHooks/lib/uniswap-hooks/lib/v4-core/lib/openzeppelin-contracts/contracts/token/ERC20/utils/SafeERC20.sol new file mode 100644 index 00000000..bb65709b --- /dev/null +++ b/CVLByExample/UniswapHooks/lib/uniswap-hooks/lib/v4-core/lib/openzeppelin-contracts/contracts/token/ERC20/utils/SafeERC20.sol @@ -0,0 +1,118 @@ +// SPDX-License-Identifier: MIT +// OpenZeppelin Contracts (last updated v5.0.0) (token/ERC20/utils/SafeERC20.sol) + +pragma solidity ^0.8.20; + +import {IERC20} from "../IERC20.sol"; +import {IERC20Permit} from "../extensions/IERC20Permit.sol"; +import {Address} from "../../../utils/Address.sol"; + +/** + * @title SafeERC20 + * @dev Wrappers around ERC20 operations that throw on failure (when the token + * contract returns false). Tokens that return no value (and instead revert or + * throw on failure) are also supported, non-reverting calls are assumed to be + * successful. + * To use this library you can add a `using SafeERC20 for IERC20;` statement to your contract, + * which allows you to call the safe operations as `token.safeTransfer(...)`, etc. + */ +library SafeERC20 { + using Address for address; + + /** + * @dev An operation with an ERC20 token failed. + */ + error SafeERC20FailedOperation(address token); + + /** + * @dev Indicates a failed `decreaseAllowance` request. + */ + error SafeERC20FailedDecreaseAllowance(address spender, uint256 currentAllowance, uint256 requestedDecrease); + + /** + * @dev Transfer `value` amount of `token` from the calling contract to `to`. If `token` returns no value, + * non-reverting calls are assumed to be successful. + */ + function safeTransfer(IERC20 token, address to, uint256 value) internal { + _callOptionalReturn(token, abi.encodeCall(token.transfer, (to, value))); + } + + /** + * @dev Transfer `value` amount of `token` from `from` to `to`, spending the approval given by `from` to the + * calling contract. If `token` returns no value, non-reverting calls are assumed to be successful. + */ + function safeTransferFrom(IERC20 token, address from, address to, uint256 value) internal { + _callOptionalReturn(token, abi.encodeCall(token.transferFrom, (from, to, value))); + } + + /** + * @dev Increase the calling contract's allowance toward `spender` by `value`. If `token` returns no value, + * non-reverting calls are assumed to be successful. + */ + function safeIncreaseAllowance(IERC20 token, address spender, uint256 value) internal { + uint256 oldAllowance = token.allowance(address(this), spender); + forceApprove(token, spender, oldAllowance + value); + } + + /** + * @dev Decrease the calling contract's allowance toward `spender` by `requestedDecrease`. If `token` returns no + * value, non-reverting calls are assumed to be successful. + */ + function safeDecreaseAllowance(IERC20 token, address spender, uint256 requestedDecrease) internal { + unchecked { + uint256 currentAllowance = token.allowance(address(this), spender); + if (currentAllowance < requestedDecrease) { + revert SafeERC20FailedDecreaseAllowance(spender, currentAllowance, requestedDecrease); + } + forceApprove(token, spender, currentAllowance - requestedDecrease); + } + } + + /** + * @dev Set the calling contract's allowance toward `spender` to `value`. If `token` returns no value, + * non-reverting calls are assumed to be successful. Meant to be used with tokens that require the approval + * to be set to zero before setting it to a non-zero value, such as USDT. + */ + function forceApprove(IERC20 token, address spender, uint256 value) internal { + bytes memory approvalCall = abi.encodeCall(token.approve, (spender, value)); + + if (!_callOptionalReturnBool(token, approvalCall)) { + _callOptionalReturn(token, abi.encodeCall(token.approve, (spender, 0))); + _callOptionalReturn(token, approvalCall); + } + } + + /** + * @dev Imitates a Solidity high-level call (i.e. a regular function call to a contract), relaxing the requirement + * on the return value: the return value is optional (but if data is returned, it must not be false). + * @param token The token targeted by the call. + * @param data The call data (encoded using abi.encode or one of its variants). + */ + function _callOptionalReturn(IERC20 token, bytes memory data) private { + // We need to perform a low level call here, to bypass Solidity's return data size checking mechanism, since + // we're implementing it ourselves. We use {Address-functionCall} to perform this call, which verifies that + // the target address contains contract code and also asserts for success in the low-level call. + + bytes memory returndata = address(token).functionCall(data); + if (returndata.length != 0 && !abi.decode(returndata, (bool))) { + revert SafeERC20FailedOperation(address(token)); + } + } + + /** + * @dev Imitates a Solidity high-level call (i.e. a regular function call to a contract), relaxing the requirement + * on the return value: the return value is optional (but if data is returned, it must not be false). + * @param token The token targeted by the call. + * @param data The call data (encoded using abi.encode or one of its variants). + * + * This is a variant of {_callOptionalReturn} that silents catches all reverts and returns a bool instead. + */ + function _callOptionalReturnBool(IERC20 token, bytes memory data) private returns (bool) { + // We need to perform a low level call here, to bypass Solidity's return data size checking mechanism, since + // we're implementing it ourselves. We cannot use {Address-functionCall} here since this should return false + // and not revert is the subcall reverts. + + (bool success, bytes memory returndata) = address(token).call(data); + return success && (returndata.length == 0 || abi.decode(returndata, (bool))) && address(token).code.length > 0; + } +} diff --git a/CVLByExample/UniswapHooks/lib/uniswap-hooks/lib/v4-core/lib/openzeppelin-contracts/contracts/token/ERC721/ERC721.sol b/CVLByExample/UniswapHooks/lib/uniswap-hooks/lib/v4-core/lib/openzeppelin-contracts/contracts/token/ERC721/ERC721.sol new file mode 100644 index 00000000..98a80e52 --- /dev/null +++ b/CVLByExample/UniswapHooks/lib/uniswap-hooks/lib/v4-core/lib/openzeppelin-contracts/contracts/token/ERC721/ERC721.sol @@ -0,0 +1,483 @@ +// SPDX-License-Identifier: MIT +// OpenZeppelin Contracts (last updated v5.0.0) (token/ERC721/ERC721.sol) + +pragma solidity ^0.8.20; + +import {IERC721} from "./IERC721.sol"; +import {IERC721Receiver} from "./IERC721Receiver.sol"; +import {IERC721Metadata} from "./extensions/IERC721Metadata.sol"; +import {Context} from "../../utils/Context.sol"; +import {Strings} from "../../utils/Strings.sol"; +import {IERC165, ERC165} from "../../utils/introspection/ERC165.sol"; +import {IERC721Errors} from "../../interfaces/draft-IERC6093.sol"; + +/** + * @dev Implementation of https://eips.ethereum.org/EIPS/eip-721[ERC721] Non-Fungible Token Standard, including + * the Metadata extension, but not including the Enumerable extension, which is available separately as + * {ERC721Enumerable}. + */ +abstract contract ERC721 is Context, ERC165, IERC721, IERC721Metadata, IERC721Errors { + using Strings for uint256; + + // Token name + string private _name; + + // Token symbol + string private _symbol; + + mapping(uint256 tokenId => address) private _owners; + + mapping(address owner => uint256) private _balances; + + mapping(uint256 tokenId => address) private _tokenApprovals; + + mapping(address owner => mapping(address operator => bool)) private _operatorApprovals; + + /** + * @dev Initializes the contract by setting a `name` and a `symbol` to the token collection. + */ + constructor(string memory name_, string memory symbol_) { + _name = name_; + _symbol = symbol_; + } + + /** + * @dev See {IERC165-supportsInterface}. + */ + function supportsInterface(bytes4 interfaceId) public view virtual override(ERC165, IERC165) returns (bool) { + return + interfaceId == type(IERC721).interfaceId || + interfaceId == type(IERC721Metadata).interfaceId || + super.supportsInterface(interfaceId); + } + + /** + * @dev See {IERC721-balanceOf}. + */ + function balanceOf(address owner) public view virtual returns (uint256) { + if (owner == address(0)) { + revert ERC721InvalidOwner(address(0)); + } + return _balances[owner]; + } + + /** + * @dev See {IERC721-ownerOf}. + */ + function ownerOf(uint256 tokenId) public view virtual returns (address) { + return _requireOwned(tokenId); + } + + /** + * @dev See {IERC721Metadata-name}. + */ + function name() public view virtual returns (string memory) { + return _name; + } + + /** + * @dev See {IERC721Metadata-symbol}. + */ + function symbol() public view virtual returns (string memory) { + return _symbol; + } + + /** + * @dev See {IERC721Metadata-tokenURI}. + */ + function tokenURI(uint256 tokenId) public view virtual returns (string memory) { + _requireOwned(tokenId); + + string memory baseURI = _baseURI(); + return bytes(baseURI).length > 0 ? string.concat(baseURI, tokenId.toString()) : ""; + } + + /** + * @dev Base URI for computing {tokenURI}. If set, the resulting URI for each + * token will be the concatenation of the `baseURI` and the `tokenId`. Empty + * by default, can be overridden in child contracts. + */ + function _baseURI() internal view virtual returns (string memory) { + return ""; + } + + /** + * @dev See {IERC721-approve}. + */ + function approve(address to, uint256 tokenId) public virtual { + _approve(to, tokenId, _msgSender()); + } + + /** + * @dev See {IERC721-getApproved}. + */ + function getApproved(uint256 tokenId) public view virtual returns (address) { + _requireOwned(tokenId); + + return _getApproved(tokenId); + } + + /** + * @dev See {IERC721-setApprovalForAll}. + */ + function setApprovalForAll(address operator, bool approved) public virtual { + _setApprovalForAll(_msgSender(), operator, approved); + } + + /** + * @dev See {IERC721-isApprovedForAll}. + */ + function isApprovedForAll(address owner, address operator) public view virtual returns (bool) { + return _operatorApprovals[owner][operator]; + } + + /** + * @dev See {IERC721-transferFrom}. + */ + function transferFrom(address from, address to, uint256 tokenId) public virtual { + if (to == address(0)) { + revert ERC721InvalidReceiver(address(0)); + } + // Setting an "auth" arguments enables the `_isAuthorized` check which verifies that the token exists + // (from != 0). Therefore, it is not needed to verify that the return value is not 0 here. + address previousOwner = _update(to, tokenId, _msgSender()); + if (previousOwner != from) { + revert ERC721IncorrectOwner(from, tokenId, previousOwner); + } + } + + /** + * @dev See {IERC721-safeTransferFrom}. + */ + function safeTransferFrom(address from, address to, uint256 tokenId) public { + safeTransferFrom(from, to, tokenId, ""); + } + + /** + * @dev See {IERC721-safeTransferFrom}. + */ + function safeTransferFrom(address from, address to, uint256 tokenId, bytes memory data) public virtual { + transferFrom(from, to, tokenId); + _checkOnERC721Received(from, to, tokenId, data); + } + + /** + * @dev Returns the owner of the `tokenId`. Does NOT revert if token doesn't exist + * + * IMPORTANT: Any overrides to this function that add ownership of tokens not tracked by the + * core ERC721 logic MUST be matched with the use of {_increaseBalance} to keep balances + * consistent with ownership. The invariant to preserve is that for any address `a` the value returned by + * `balanceOf(a)` must be equal to the number of tokens such that `_ownerOf(tokenId)` is `a`. + */ + function _ownerOf(uint256 tokenId) internal view virtual returns (address) { + return _owners[tokenId]; + } + + /** + * @dev Returns the approved address for `tokenId`. Returns 0 if `tokenId` is not minted. + */ + function _getApproved(uint256 tokenId) internal view virtual returns (address) { + return _tokenApprovals[tokenId]; + } + + /** + * @dev Returns whether `spender` is allowed to manage `owner`'s tokens, or `tokenId` in + * particular (ignoring whether it is owned by `owner`). + * + * WARNING: This function assumes that `owner` is the actual owner of `tokenId` and does not verify this + * assumption. + */ + function _isAuthorized(address owner, address spender, uint256 tokenId) internal view virtual returns (bool) { + return + spender != address(0) && + (owner == spender || isApprovedForAll(owner, spender) || _getApproved(tokenId) == spender); + } + + /** + * @dev Checks if `spender` can operate on `tokenId`, assuming the provided `owner` is the actual owner. + * Reverts if `spender` does not have approval from the provided `owner` for the given token or for all its assets + * the `spender` for the specific `tokenId`. + * + * WARNING: This function assumes that `owner` is the actual owner of `tokenId` and does not verify this + * assumption. + */ + function _checkAuthorized(address owner, address spender, uint256 tokenId) internal view virtual { + if (!_isAuthorized(owner, spender, tokenId)) { + if (owner == address(0)) { + revert ERC721NonexistentToken(tokenId); + } else { + revert ERC721InsufficientApproval(spender, tokenId); + } + } + } + + /** + * @dev Unsafe write access to the balances, used by extensions that "mint" tokens using an {ownerOf} override. + * + * NOTE: the value is limited to type(uint128).max. This protect against _balance overflow. It is unrealistic that + * a uint256 would ever overflow from increments when these increments are bounded to uint128 values. + * + * WARNING: Increasing an account's balance using this function tends to be paired with an override of the + * {_ownerOf} function to resolve the ownership of the corresponding tokens so that balances and ownership + * remain consistent with one another. + */ + function _increaseBalance(address account, uint128 value) internal virtual { + unchecked { + _balances[account] += value; + } + } + + /** + * @dev Transfers `tokenId` from its current owner to `to`, or alternatively mints (or burns) if the current owner + * (or `to`) is the zero address. Returns the owner of the `tokenId` before the update. + * + * The `auth` argument is optional. If the value passed is non 0, then this function will check that + * `auth` is either the owner of the token, or approved to operate on the token (by the owner). + * + * Emits a {Transfer} event. + * + * NOTE: If overriding this function in a way that tracks balances, see also {_increaseBalance}. + */ + function _update(address to, uint256 tokenId, address auth) internal virtual returns (address) { + address from = _ownerOf(tokenId); + + // Perform (optional) operator check + if (auth != address(0)) { + _checkAuthorized(from, auth, tokenId); + } + + // Execute the update + if (from != address(0)) { + // Clear approval. No need to re-authorize or emit the Approval event + _approve(address(0), tokenId, address(0), false); + + unchecked { + _balances[from] -= 1; + } + } + + if (to != address(0)) { + unchecked { + _balances[to] += 1; + } + } + + _owners[tokenId] = to; + + emit Transfer(from, to, tokenId); + + return from; + } + + /** + * @dev Mints `tokenId` and transfers it to `to`. + * + * WARNING: Usage of this method is discouraged, use {_safeMint} whenever possible + * + * Requirements: + * + * - `tokenId` must not exist. + * - `to` cannot be the zero address. + * + * Emits a {Transfer} event. + */ + function _mint(address to, uint256 tokenId) internal { + if (to == address(0)) { + revert ERC721InvalidReceiver(address(0)); + } + address previousOwner = _update(to, tokenId, address(0)); + if (previousOwner != address(0)) { + revert ERC721InvalidSender(address(0)); + } + } + + /** + * @dev Mints `tokenId`, transfers it to `to` and checks for `to` acceptance. + * + * Requirements: + * + * - `tokenId` must not exist. + * - If `to` refers to a smart contract, it must implement {IERC721Receiver-onERC721Received}, which is called upon a safe transfer. + * + * Emits a {Transfer} event. + */ + function _safeMint(address to, uint256 tokenId) internal { + _safeMint(to, tokenId, ""); + } + + /** + * @dev Same as {xref-ERC721-_safeMint-address-uint256-}[`_safeMint`], with an additional `data` parameter which is + * forwarded in {IERC721Receiver-onERC721Received} to contract recipients. + */ + function _safeMint(address to, uint256 tokenId, bytes memory data) internal virtual { + _mint(to, tokenId); + _checkOnERC721Received(address(0), to, tokenId, data); + } + + /** + * @dev Destroys `tokenId`. + * The approval is cleared when the token is burned. + * This is an internal function that does not check if the sender is authorized to operate on the token. + * + * Requirements: + * + * - `tokenId` must exist. + * + * Emits a {Transfer} event. + */ + function _burn(uint256 tokenId) internal { + address previousOwner = _update(address(0), tokenId, address(0)); + if (previousOwner == address(0)) { + revert ERC721NonexistentToken(tokenId); + } + } + + /** + * @dev Transfers `tokenId` from `from` to `to`. + * As opposed to {transferFrom}, this imposes no restrictions on msg.sender. + * + * Requirements: + * + * - `to` cannot be the zero address. + * - `tokenId` token must be owned by `from`. + * + * Emits a {Transfer} event. + */ + function _transfer(address from, address to, uint256 tokenId) internal { + if (to == address(0)) { + revert ERC721InvalidReceiver(address(0)); + } + address previousOwner = _update(to, tokenId, address(0)); + if (previousOwner == address(0)) { + revert ERC721NonexistentToken(tokenId); + } else if (previousOwner != from) { + revert ERC721IncorrectOwner(from, tokenId, previousOwner); + } + } + + /** + * @dev Safely transfers `tokenId` token from `from` to `to`, checking that contract recipients + * are aware of the ERC721 standard to prevent tokens from being forever locked. + * + * `data` is additional data, it has no specified format and it is sent in call to `to`. + * + * This internal function is like {safeTransferFrom} in the sense that it invokes + * {IERC721Receiver-onERC721Received} on the receiver, and can be used to e.g. + * implement alternative mechanisms to perform token transfer, such as signature-based. + * + * Requirements: + * + * - `tokenId` token must exist and be owned by `from`. + * - `to` cannot be the zero address. + * - `from` cannot be the zero address. + * - If `to` refers to a smart contract, it must implement {IERC721Receiver-onERC721Received}, which is called upon a safe transfer. + * + * Emits a {Transfer} event. + */ + function _safeTransfer(address from, address to, uint256 tokenId) internal { + _safeTransfer(from, to, tokenId, ""); + } + + /** + * @dev Same as {xref-ERC721-_safeTransfer-address-address-uint256-}[`_safeTransfer`], with an additional `data` parameter which is + * forwarded in {IERC721Receiver-onERC721Received} to contract recipients. + */ + function _safeTransfer(address from, address to, uint256 tokenId, bytes memory data) internal virtual { + _transfer(from, to, tokenId); + _checkOnERC721Received(from, to, tokenId, data); + } + + /** + * @dev Approve `to` to operate on `tokenId` + * + * The `auth` argument is optional. If the value passed is non 0, then this function will check that `auth` is + * either the owner of the token, or approved to operate on all tokens held by this owner. + * + * Emits an {Approval} event. + * + * Overrides to this logic should be done to the variant with an additional `bool emitEvent` argument. + */ + function _approve(address to, uint256 tokenId, address auth) internal { + _approve(to, tokenId, auth, true); + } + + /** + * @dev Variant of `_approve` with an optional flag to enable or disable the {Approval} event. The event is not + * emitted in the context of transfers. + */ + function _approve(address to, uint256 tokenId, address auth, bool emitEvent) internal virtual { + // Avoid reading the owner unless necessary + if (emitEvent || auth != address(0)) { + address owner = _requireOwned(tokenId); + + // We do not use _isAuthorized because single-token approvals should not be able to call approve + if (auth != address(0) && owner != auth && !isApprovedForAll(owner, auth)) { + revert ERC721InvalidApprover(auth); + } + + if (emitEvent) { + emit Approval(owner, to, tokenId); + } + } + + _tokenApprovals[tokenId] = to; + } + + /** + * @dev Approve `operator` to operate on all of `owner` tokens + * + * Requirements: + * - operator can't be the address zero. + * + * Emits an {ApprovalForAll} event. + */ + function _setApprovalForAll(address owner, address operator, bool approved) internal virtual { + if (operator == address(0)) { + revert ERC721InvalidOperator(operator); + } + _operatorApprovals[owner][operator] = approved; + emit ApprovalForAll(owner, operator, approved); + } + + /** + * @dev Reverts if the `tokenId` doesn't have a current owner (it hasn't been minted, or it has been burned). + * Returns the owner. + * + * Overrides to ownership logic should be done to {_ownerOf}. + */ + function _requireOwned(uint256 tokenId) internal view returns (address) { + address owner = _ownerOf(tokenId); + if (owner == address(0)) { + revert ERC721NonexistentToken(tokenId); + } + return owner; + } + + /** + * @dev Private function to invoke {IERC721Receiver-onERC721Received} on a target address. This will revert if the + * recipient doesn't accept the token transfer. The call is not executed if the target address is not a contract. + * + * @param from address representing the previous owner of the given token ID + * @param to target address that will receive the tokens + * @param tokenId uint256 ID of the token to be transferred + * @param data bytes optional data to send along with the call + */ + function _checkOnERC721Received(address from, address to, uint256 tokenId, bytes memory data) private { + if (to.code.length > 0) { + try IERC721Receiver(to).onERC721Received(_msgSender(), from, tokenId, data) returns (bytes4 retval) { + if (retval != IERC721Receiver.onERC721Received.selector) { + revert ERC721InvalidReceiver(to); + } + } catch (bytes memory reason) { + if (reason.length == 0) { + revert ERC721InvalidReceiver(to); + } else { + /// @solidity memory-safe-assembly + assembly { + revert(add(32, reason), mload(reason)) + } + } + } + } + } +} diff --git a/CVLByExample/UniswapHooks/lib/uniswap-hooks/lib/v4-core/lib/openzeppelin-contracts/contracts/token/ERC721/IERC721.sol b/CVLByExample/UniswapHooks/lib/uniswap-hooks/lib/v4-core/lib/openzeppelin-contracts/contracts/token/ERC721/IERC721.sol new file mode 100644 index 00000000..12f32363 --- /dev/null +++ b/CVLByExample/UniswapHooks/lib/uniswap-hooks/lib/v4-core/lib/openzeppelin-contracts/contracts/token/ERC721/IERC721.sol @@ -0,0 +1,135 @@ +// SPDX-License-Identifier: MIT +// OpenZeppelin Contracts (last updated v5.0.0) (token/ERC721/IERC721.sol) + +pragma solidity ^0.8.20; + +import {IERC165} from "../../utils/introspection/IERC165.sol"; + +/** + * @dev Required interface of an ERC721 compliant contract. + */ +interface IERC721 is IERC165 { + /** + * @dev Emitted when `tokenId` token is transferred from `from` to `to`. + */ + event Transfer(address indexed from, address indexed to, uint256 indexed tokenId); + + /** + * @dev Emitted when `owner` enables `approved` to manage the `tokenId` token. + */ + event Approval(address indexed owner, address indexed approved, uint256 indexed tokenId); + + /** + * @dev Emitted when `owner` enables or disables (`approved`) `operator` to manage all of its assets. + */ + event ApprovalForAll(address indexed owner, address indexed operator, bool approved); + + /** + * @dev Returns the number of tokens in ``owner``'s account. + */ + function balanceOf(address owner) external view returns (uint256 balance); + + /** + * @dev Returns the owner of the `tokenId` token. + * + * Requirements: + * + * - `tokenId` must exist. + */ + function ownerOf(uint256 tokenId) external view returns (address owner); + + /** + * @dev Safely transfers `tokenId` token from `from` to `to`. + * + * Requirements: + * + * - `from` cannot be the zero address. + * - `to` cannot be the zero address. + * - `tokenId` token must exist and be owned by `from`. + * - If the caller is not `from`, it must be approved to move this token by either {approve} or {setApprovalForAll}. + * - If `to` refers to a smart contract, it must implement {IERC721Receiver-onERC721Received}, which is called upon + * a safe transfer. + * + * Emits a {Transfer} event. + */ + function safeTransferFrom(address from, address to, uint256 tokenId, bytes calldata data) external; + + /** + * @dev Safely transfers `tokenId` token from `from` to `to`, checking first that contract recipients + * are aware of the ERC721 protocol to prevent tokens from being forever locked. + * + * Requirements: + * + * - `from` cannot be the zero address. + * - `to` cannot be the zero address. + * - `tokenId` token must exist and be owned by `from`. + * - If the caller is not `from`, it must have been allowed to move this token by either {approve} or + * {setApprovalForAll}. + * - If `to` refers to a smart contract, it must implement {IERC721Receiver-onERC721Received}, which is called upon + * a safe transfer. + * + * Emits a {Transfer} event. + */ + function safeTransferFrom(address from, address to, uint256 tokenId) external; + + /** + * @dev Transfers `tokenId` token from `from` to `to`. + * + * WARNING: Note that the caller is responsible to confirm that the recipient is capable of receiving ERC721 + * or else they may be permanently lost. Usage of {safeTransferFrom} prevents loss, though the caller must + * understand this adds an external call which potentially creates a reentrancy vulnerability. + * + * Requirements: + * + * - `from` cannot be the zero address. + * - `to` cannot be the zero address. + * - `tokenId` token must be owned by `from`. + * - If the caller is not `from`, it must be approved to move this token by either {approve} or {setApprovalForAll}. + * + * Emits a {Transfer} event. + */ + function transferFrom(address from, address to, uint256 tokenId) external; + + /** + * @dev Gives permission to `to` to transfer `tokenId` token to another account. + * The approval is cleared when the token is transferred. + * + * Only a single account can be approved at a time, so approving the zero address clears previous approvals. + * + * Requirements: + * + * - The caller must own the token or be an approved operator. + * - `tokenId` must exist. + * + * Emits an {Approval} event. + */ + function approve(address to, uint256 tokenId) external; + + /** + * @dev Approve or remove `operator` as an operator for the caller. + * Operators can call {transferFrom} or {safeTransferFrom} for any token owned by the caller. + * + * Requirements: + * + * - The `operator` cannot be the address zero. + * + * Emits an {ApprovalForAll} event. + */ + function setApprovalForAll(address operator, bool approved) external; + + /** + * @dev Returns the account approved for `tokenId` token. + * + * Requirements: + * + * - `tokenId` must exist. + */ + function getApproved(uint256 tokenId) external view returns (address operator); + + /** + * @dev Returns if the `operator` is allowed to manage all of the assets of `owner`. + * + * See {setApprovalForAll} + */ + function isApprovedForAll(address owner, address operator) external view returns (bool); +} diff --git a/CVLByExample/UniswapHooks/lib/uniswap-hooks/lib/v4-core/lib/openzeppelin-contracts/contracts/token/ERC721/IERC721Receiver.sol b/CVLByExample/UniswapHooks/lib/uniswap-hooks/lib/v4-core/lib/openzeppelin-contracts/contracts/token/ERC721/IERC721Receiver.sol new file mode 100644 index 00000000..f9dc1332 --- /dev/null +++ b/CVLByExample/UniswapHooks/lib/uniswap-hooks/lib/v4-core/lib/openzeppelin-contracts/contracts/token/ERC721/IERC721Receiver.sol @@ -0,0 +1,28 @@ +// SPDX-License-Identifier: MIT +// OpenZeppelin Contracts (last updated v5.0.0) (token/ERC721/IERC721Receiver.sol) + +pragma solidity ^0.8.20; + +/** + * @title ERC721 token receiver interface + * @dev Interface for any contract that wants to support safeTransfers + * from ERC721 asset contracts. + */ +interface IERC721Receiver { + /** + * @dev Whenever an {IERC721} `tokenId` token is transferred to this contract via {IERC721-safeTransferFrom} + * by `operator` from `from`, this function is called. + * + * It must return its Solidity selector to confirm the token transfer. + * If any other value is returned or the interface is not implemented by the recipient, the transfer will be + * reverted. + * + * The selector can be obtained in Solidity with `IERC721Receiver.onERC721Received.selector`. + */ + function onERC721Received( + address operator, + address from, + uint256 tokenId, + bytes calldata data + ) external returns (bytes4); +} diff --git a/CVLByExample/UniswapHooks/lib/uniswap-hooks/lib/v4-core/lib/openzeppelin-contracts/contracts/token/ERC721/README.adoc b/CVLByExample/UniswapHooks/lib/uniswap-hooks/lib/v4-core/lib/openzeppelin-contracts/contracts/token/ERC721/README.adoc new file mode 100644 index 00000000..40ae919d --- /dev/null +++ b/CVLByExample/UniswapHooks/lib/uniswap-hooks/lib/v4-core/lib/openzeppelin-contracts/contracts/token/ERC721/README.adoc @@ -0,0 +1,67 @@ += ERC 721 + +[.readme-notice] +NOTE: This document is better viewed at https://docs.openzeppelin.com/contracts/api/token/erc721 + +This set of interfaces, contracts, and utilities are all related to the https://eips.ethereum.org/EIPS/eip-721[ERC721 Non-Fungible Token Standard]. + +TIP: For a walk through on how to create an ERC721 token read our xref:ROOT:erc721.adoc[ERC721 guide]. + +The EIP specifies four interfaces: + +* {IERC721}: Core functionality required in all compliant implementation. +* {IERC721Metadata}: Optional extension that adds name, symbol, and token URI, almost always included. +* {IERC721Enumerable}: Optional extension that allows enumerating the tokens on chain, often not included since it requires large gas overhead. +* {IERC721Receiver}: An interface that must be implemented by contracts if they want to accept tokens through `safeTransferFrom`. + +OpenZeppelin Contracts provides implementations of all four interfaces: + +* {ERC721}: The core and metadata extensions, with a base URI mechanism. +* {ERC721Enumerable}: The enumerable extension. +* {ERC721Holder}: A bare bones implementation of the receiver interface. + +Additionally there are a few of other extensions: + +* {ERC721Consecutive}: An implementation of https://eips.ethereum.org/EIPS/eip-2309[ERC2309] for minting batchs of tokens during construction, in accordance with ERC721. +* {ERC721URIStorage}: A more flexible but more expensive way of storing metadata. +* {ERC721Votes}: Support for voting and vote delegation. +* {ERC721Royalty}: A way to signal royalty information following ERC2981. +* {ERC721Pausable}: A primitive to pause contract operation. +* {ERC721Burnable}: A way for token holders to burn their own tokens. +* {ERC721Wrapper}: Wrapper to create an ERC721 backed by another ERC721, with deposit and withdraw methods. Useful in conjunction with {ERC721Votes}. + +NOTE: This core set of contracts is designed to be unopinionated, allowing developers to access the internal functions in ERC721 (such as <>) and expose them as external functions in the way they prefer. + +== Core + +{{IERC721}} + +{{IERC721Metadata}} + +{{IERC721Enumerable}} + +{{ERC721}} + +{{ERC721Enumerable}} + +{{IERC721Receiver}} + +== Extensions + +{{ERC721Pausable}} + +{{ERC721Burnable}} + +{{ERC721Consecutive}} + +{{ERC721URIStorage}} + +{{ERC721Votes}} + +{{ERC721Royalty}} + +{{ERC721Wrapper}} + +== Utilities + +{{ERC721Holder}} diff --git a/CVLByExample/UniswapHooks/lib/uniswap-hooks/lib/v4-core/lib/openzeppelin-contracts/contracts/token/ERC721/extensions/ERC721Burnable.sol b/CVLByExample/UniswapHooks/lib/uniswap-hooks/lib/v4-core/lib/openzeppelin-contracts/contracts/token/ERC721/extensions/ERC721Burnable.sol new file mode 100644 index 00000000..2a150afb --- /dev/null +++ b/CVLByExample/UniswapHooks/lib/uniswap-hooks/lib/v4-core/lib/openzeppelin-contracts/contracts/token/ERC721/extensions/ERC721Burnable.sol @@ -0,0 +1,26 @@ +// SPDX-License-Identifier: MIT +// OpenZeppelin Contracts (last updated v5.0.0) (token/ERC721/extensions/ERC721Burnable.sol) + +pragma solidity ^0.8.20; + +import {ERC721} from "../ERC721.sol"; +import {Context} from "../../../utils/Context.sol"; + +/** + * @title ERC721 Burnable Token + * @dev ERC721 Token that can be burned (destroyed). + */ +abstract contract ERC721Burnable is Context, ERC721 { + /** + * @dev Burns `tokenId`. See {ERC721-_burn}. + * + * Requirements: + * + * - The caller must own `tokenId` or be an approved operator. + */ + function burn(uint256 tokenId) public virtual { + // Setting an "auth" arguments enables the `_isAuthorized` check which verifies that the token exists + // (from != 0). Therefore, it is not needed to verify that the return value is not 0 here. + _update(address(0), tokenId, _msgSender()); + } +} diff --git a/CVLByExample/UniswapHooks/lib/uniswap-hooks/lib/v4-core/lib/openzeppelin-contracts/contracts/token/ERC721/extensions/ERC721Consecutive.sol b/CVLByExample/UniswapHooks/lib/uniswap-hooks/lib/v4-core/lib/openzeppelin-contracts/contracts/token/ERC721/extensions/ERC721Consecutive.sol new file mode 100644 index 00000000..0d6cbc7e --- /dev/null +++ b/CVLByExample/UniswapHooks/lib/uniswap-hooks/lib/v4-core/lib/openzeppelin-contracts/contracts/token/ERC721/extensions/ERC721Consecutive.sol @@ -0,0 +1,176 @@ +// SPDX-License-Identifier: MIT +// OpenZeppelin Contracts (last updated v5.0.0) (token/ERC721/extensions/ERC721Consecutive.sol) + +pragma solidity ^0.8.20; + +import {ERC721} from "../ERC721.sol"; +import {IERC2309} from "../../../interfaces/IERC2309.sol"; +import {BitMaps} from "../../../utils/structs/BitMaps.sol"; +import {Checkpoints} from "../../../utils/structs/Checkpoints.sol"; + +/** + * @dev Implementation of the ERC2309 "Consecutive Transfer Extension" as defined in + * https://eips.ethereum.org/EIPS/eip-2309[EIP-2309]. + * + * This extension allows the minting of large batches of tokens, during contract construction only. For upgradeable + * contracts this implies that batch minting is only available during proxy deployment, and not in subsequent upgrades. + * These batches are limited to 5000 tokens at a time by default to accommodate off-chain indexers. + * + * Using this extension removes the ability to mint single tokens during contract construction. This ability is + * regained after construction. During construction, only batch minting is allowed. + * + * IMPORTANT: This extension does not call the {_update} function for tokens minted in batch. Any logic added to this + * function through overrides will not be triggered when token are minted in batch. You may want to also override + * {_increaseBalance} or {_mintConsecutive} to account for these mints. + * + * IMPORTANT: When overriding {_mintConsecutive}, be careful about call ordering. {ownerOf} may return invalid + * values during the {_mintConsecutive} execution if the super call is not called first. To be safe, execute the + * super call before your custom logic. + */ +abstract contract ERC721Consecutive is IERC2309, ERC721 { + using BitMaps for BitMaps.BitMap; + using Checkpoints for Checkpoints.Trace160; + + Checkpoints.Trace160 private _sequentialOwnership; + BitMaps.BitMap private _sequentialBurn; + + /** + * @dev Batch mint is restricted to the constructor. + * Any batch mint not emitting the {IERC721-Transfer} event outside of the constructor + * is non-ERC721 compliant. + */ + error ERC721ForbiddenBatchMint(); + + /** + * @dev Exceeds the max amount of mints per batch. + */ + error ERC721ExceededMaxBatchMint(uint256 batchSize, uint256 maxBatch); + + /** + * @dev Individual minting is not allowed. + */ + error ERC721ForbiddenMint(); + + /** + * @dev Batch burn is not supported. + */ + error ERC721ForbiddenBatchBurn(); + + /** + * @dev Maximum size of a batch of consecutive tokens. This is designed to limit stress on off-chain indexing + * services that have to record one entry per token, and have protections against "unreasonably large" batches of + * tokens. + * + * NOTE: Overriding the default value of 5000 will not cause on-chain issues, but may result in the asset not being + * correctly supported by off-chain indexing services (including marketplaces). + */ + function _maxBatchSize() internal view virtual returns (uint96) { + return 5000; + } + + /** + * @dev See {ERC721-_ownerOf}. Override that checks the sequential ownership structure for tokens that have + * been minted as part of a batch, and not yet transferred. + */ + function _ownerOf(uint256 tokenId) internal view virtual override returns (address) { + address owner = super._ownerOf(tokenId); + + // If token is owned by the core, or beyond consecutive range, return base value + if (owner != address(0) || tokenId > type(uint96).max || tokenId < _firstConsecutiveId()) { + return owner; + } + + // Otherwise, check the token was not burned, and fetch ownership from the anchors + // Note: no need for safe cast, we know that tokenId <= type(uint96).max + return _sequentialBurn.get(tokenId) ? address(0) : address(_sequentialOwnership.lowerLookup(uint96(tokenId))); + } + + /** + * @dev Mint a batch of tokens of length `batchSize` for `to`. Returns the token id of the first token minted in the + * batch; if `batchSize` is 0, returns the number of consecutive ids minted so far. + * + * Requirements: + * + * - `batchSize` must not be greater than {_maxBatchSize}. + * - The function is called in the constructor of the contract (directly or indirectly). + * + * CAUTION: Does not emit a `Transfer` event. This is ERC721 compliant as long as it is done inside of the + * constructor, which is enforced by this function. + * + * CAUTION: Does not invoke `onERC721Received` on the receiver. + * + * Emits a {IERC2309-ConsecutiveTransfer} event. + */ + function _mintConsecutive(address to, uint96 batchSize) internal virtual returns (uint96) { + uint96 next = _nextConsecutiveId(); + + // minting a batch of size 0 is a no-op + if (batchSize > 0) { + if (address(this).code.length > 0) { + revert ERC721ForbiddenBatchMint(); + } + if (to == address(0)) { + revert ERC721InvalidReceiver(address(0)); + } + + uint256 maxBatchSize = _maxBatchSize(); + if (batchSize > maxBatchSize) { + revert ERC721ExceededMaxBatchMint(batchSize, maxBatchSize); + } + + // push an ownership checkpoint & emit event + uint96 last = next + batchSize - 1; + _sequentialOwnership.push(last, uint160(to)); + + // The invariant required by this function is preserved because the new sequentialOwnership checkpoint + // is attributing ownership of `batchSize` new tokens to account `to`. + _increaseBalance(to, batchSize); + + emit ConsecutiveTransfer(next, last, address(0), to); + } + + return next; + } + + /** + * @dev See {ERC721-_update}. Override version that restricts normal minting to after construction. + * + * WARNING: Using {ERC721Consecutive} prevents minting during construction in favor of {_mintConsecutive}. + * After construction, {_mintConsecutive} is no longer available and minting through {_update} becomes available. + */ + function _update(address to, uint256 tokenId, address auth) internal virtual override returns (address) { + address previousOwner = super._update(to, tokenId, auth); + + // only mint after construction + if (previousOwner == address(0) && address(this).code.length == 0) { + revert ERC721ForbiddenMint(); + } + + // record burn + if ( + to == address(0) && // if we burn + tokenId < _nextConsecutiveId() && // and the tokenId was minted in a batch + !_sequentialBurn.get(tokenId) // and the token was never marked as burnt + ) { + _sequentialBurn.set(tokenId); + } + + return previousOwner; + } + + /** + * @dev Used to offset the first token id in {_nextConsecutiveId} + */ + function _firstConsecutiveId() internal view virtual returns (uint96) { + return 0; + } + + /** + * @dev Returns the next tokenId to mint using {_mintConsecutive}. It will return {_firstConsecutiveId} + * if no consecutive tokenId has been minted before. + */ + function _nextConsecutiveId() private view returns (uint96) { + (bool exists, uint96 latestId, ) = _sequentialOwnership.latestCheckpoint(); + return exists ? latestId + 1 : _firstConsecutiveId(); + } +} diff --git a/CVLByExample/UniswapHooks/lib/uniswap-hooks/lib/v4-core/lib/openzeppelin-contracts/contracts/token/ERC721/extensions/ERC721Enumerable.sol b/CVLByExample/UniswapHooks/lib/uniswap-hooks/lib/v4-core/lib/openzeppelin-contracts/contracts/token/ERC721/extensions/ERC721Enumerable.sol new file mode 100644 index 00000000..cbf3e03f --- /dev/null +++ b/CVLByExample/UniswapHooks/lib/uniswap-hooks/lib/v4-core/lib/openzeppelin-contracts/contracts/token/ERC721/extensions/ERC721Enumerable.sol @@ -0,0 +1,172 @@ +// SPDX-License-Identifier: MIT +// OpenZeppelin Contracts (last updated v5.0.0) (token/ERC721/extensions/ERC721Enumerable.sol) + +pragma solidity ^0.8.20; + +import {ERC721} from "../ERC721.sol"; +import {IERC721Enumerable} from "./IERC721Enumerable.sol"; +import {IERC165} from "../../../utils/introspection/ERC165.sol"; + +/** + * @dev This implements an optional extension of {ERC721} defined in the EIP that adds enumerability + * of all the token ids in the contract as well as all token ids owned by each account. + * + * CAUTION: `ERC721` extensions that implement custom `balanceOf` logic, such as `ERC721Consecutive`, + * interfere with enumerability and should not be used together with `ERC721Enumerable`. + */ +abstract contract ERC721Enumerable is ERC721, IERC721Enumerable { + mapping(address owner => mapping(uint256 index => uint256)) private _ownedTokens; + mapping(uint256 tokenId => uint256) private _ownedTokensIndex; + + uint256[] private _allTokens; + mapping(uint256 tokenId => uint256) private _allTokensIndex; + + /** + * @dev An `owner`'s token query was out of bounds for `index`. + * + * NOTE: The owner being `address(0)` indicates a global out of bounds index. + */ + error ERC721OutOfBoundsIndex(address owner, uint256 index); + + /** + * @dev Batch mint is not allowed. + */ + error ERC721EnumerableForbiddenBatchMint(); + + /** + * @dev See {IERC165-supportsInterface}. + */ + function supportsInterface(bytes4 interfaceId) public view virtual override(IERC165, ERC721) returns (bool) { + return interfaceId == type(IERC721Enumerable).interfaceId || super.supportsInterface(interfaceId); + } + + /** + * @dev See {IERC721Enumerable-tokenOfOwnerByIndex}. + */ + function tokenOfOwnerByIndex(address owner, uint256 index) public view virtual returns (uint256) { + if (index >= balanceOf(owner)) { + revert ERC721OutOfBoundsIndex(owner, index); + } + return _ownedTokens[owner][index]; + } + + /** + * @dev See {IERC721Enumerable-totalSupply}. + */ + function totalSupply() public view virtual returns (uint256) { + return _allTokens.length; + } + + /** + * @dev See {IERC721Enumerable-tokenByIndex}. + */ + function tokenByIndex(uint256 index) public view virtual returns (uint256) { + if (index >= totalSupply()) { + revert ERC721OutOfBoundsIndex(address(0), index); + } + return _allTokens[index]; + } + + /** + * @dev See {ERC721-_update}. + */ + function _update(address to, uint256 tokenId, address auth) internal virtual override returns (address) { + address previousOwner = super._update(to, tokenId, auth); + + if (previousOwner == address(0)) { + _addTokenToAllTokensEnumeration(tokenId); + } else if (previousOwner != to) { + _removeTokenFromOwnerEnumeration(previousOwner, tokenId); + } + if (to == address(0)) { + _removeTokenFromAllTokensEnumeration(tokenId); + } else if (previousOwner != to) { + _addTokenToOwnerEnumeration(to, tokenId); + } + + return previousOwner; + } + + /** + * @dev Private function to add a token to this extension's ownership-tracking data structures. + * @param to address representing the new owner of the given token ID + * @param tokenId uint256 ID of the token to be added to the tokens list of the given address + */ + function _addTokenToOwnerEnumeration(address to, uint256 tokenId) private { + uint256 length = balanceOf(to) - 1; + _ownedTokens[to][length] = tokenId; + _ownedTokensIndex[tokenId] = length; + } + + /** + * @dev Private function to add a token to this extension's token tracking data structures. + * @param tokenId uint256 ID of the token to be added to the tokens list + */ + function _addTokenToAllTokensEnumeration(uint256 tokenId) private { + _allTokensIndex[tokenId] = _allTokens.length; + _allTokens.push(tokenId); + } + + /** + * @dev Private function to remove a token from this extension's ownership-tracking data structures. Note that + * while the token is not assigned a new owner, the `_ownedTokensIndex` mapping is _not_ updated: this allows for + * gas optimizations e.g. when performing a transfer operation (avoiding double writes). + * This has O(1) time complexity, but alters the order of the _ownedTokens array. + * @param from address representing the previous owner of the given token ID + * @param tokenId uint256 ID of the token to be removed from the tokens list of the given address + */ + function _removeTokenFromOwnerEnumeration(address from, uint256 tokenId) private { + // To prevent a gap in from's tokens array, we store the last token in the index of the token to delete, and + // then delete the last slot (swap and pop). + + uint256 lastTokenIndex = balanceOf(from); + uint256 tokenIndex = _ownedTokensIndex[tokenId]; + + // When the token to delete is the last token, the swap operation is unnecessary + if (tokenIndex != lastTokenIndex) { + uint256 lastTokenId = _ownedTokens[from][lastTokenIndex]; + + _ownedTokens[from][tokenIndex] = lastTokenId; // Move the last token to the slot of the to-delete token + _ownedTokensIndex[lastTokenId] = tokenIndex; // Update the moved token's index + } + + // This also deletes the contents at the last position of the array + delete _ownedTokensIndex[tokenId]; + delete _ownedTokens[from][lastTokenIndex]; + } + + /** + * @dev Private function to remove a token from this extension's token tracking data structures. + * This has O(1) time complexity, but alters the order of the _allTokens array. + * @param tokenId uint256 ID of the token to be removed from the tokens list + */ + function _removeTokenFromAllTokensEnumeration(uint256 tokenId) private { + // To prevent a gap in the tokens array, we store the last token in the index of the token to delete, and + // then delete the last slot (swap and pop). + + uint256 lastTokenIndex = _allTokens.length - 1; + uint256 tokenIndex = _allTokensIndex[tokenId]; + + // When the token to delete is the last token, the swap operation is unnecessary. However, since this occurs so + // rarely (when the last minted token is burnt) that we still do the swap here to avoid the gas cost of adding + // an 'if' statement (like in _removeTokenFromOwnerEnumeration) + uint256 lastTokenId = _allTokens[lastTokenIndex]; + + _allTokens[tokenIndex] = lastTokenId; // Move the last token to the slot of the to-delete token + _allTokensIndex[lastTokenId] = tokenIndex; // Update the moved token's index + + // This also deletes the contents at the last position of the array + delete _allTokensIndex[tokenId]; + _allTokens.pop(); + } + + /** + * See {ERC721-_increaseBalance}. We need that to account tokens that were minted in batch + */ + function _increaseBalance(address account, uint128 amount) internal virtual override { + if (amount > 0) { + revert ERC721EnumerableForbiddenBatchMint(); + } + super._increaseBalance(account, amount); + } +} diff --git a/CVLByExample/UniswapHooks/lib/uniswap-hooks/lib/v4-core/lib/openzeppelin-contracts/contracts/token/ERC721/extensions/ERC721Pausable.sol b/CVLByExample/UniswapHooks/lib/uniswap-hooks/lib/v4-core/lib/openzeppelin-contracts/contracts/token/ERC721/extensions/ERC721Pausable.sol new file mode 100644 index 00000000..0b34fd9c --- /dev/null +++ b/CVLByExample/UniswapHooks/lib/uniswap-hooks/lib/v4-core/lib/openzeppelin-contracts/contracts/token/ERC721/extensions/ERC721Pausable.sol @@ -0,0 +1,37 @@ +// SPDX-License-Identifier: MIT +// OpenZeppelin Contracts (last updated v5.0.0) (token/ERC721/extensions/ERC721Pausable.sol) + +pragma solidity ^0.8.20; + +import {ERC721} from "../ERC721.sol"; +import {Pausable} from "../../../utils/Pausable.sol"; + +/** + * @dev ERC721 token with pausable token transfers, minting and burning. + * + * Useful for scenarios such as preventing trades until the end of an evaluation + * period, or having an emergency switch for freezing all token transfers in the + * event of a large bug. + * + * IMPORTANT: This contract does not include public pause and unpause functions. In + * addition to inheriting this contract, you must define both functions, invoking the + * {Pausable-_pause} and {Pausable-_unpause} internal functions, with appropriate + * access control, e.g. using {AccessControl} or {Ownable}. Not doing so will + * make the contract pause mechanism of the contract unreachable, and thus unusable. + */ +abstract contract ERC721Pausable is ERC721, Pausable { + /** + * @dev See {ERC721-_update}. + * + * Requirements: + * + * - the contract must not be paused. + */ + function _update( + address to, + uint256 tokenId, + address auth + ) internal virtual override whenNotPaused returns (address) { + return super._update(to, tokenId, auth); + } +} diff --git a/CVLByExample/UniswapHooks/lib/uniswap-hooks/lib/v4-core/lib/openzeppelin-contracts/contracts/token/ERC721/extensions/ERC721Royalty.sol b/CVLByExample/UniswapHooks/lib/uniswap-hooks/lib/v4-core/lib/openzeppelin-contracts/contracts/token/ERC721/extensions/ERC721Royalty.sol new file mode 100644 index 00000000..be98ec7c --- /dev/null +++ b/CVLByExample/UniswapHooks/lib/uniswap-hooks/lib/v4-core/lib/openzeppelin-contracts/contracts/token/ERC721/extensions/ERC721Royalty.sol @@ -0,0 +1,27 @@ +// SPDX-License-Identifier: MIT +// OpenZeppelin Contracts (last updated v5.0.0) (token/ERC721/extensions/ERC721Royalty.sol) + +pragma solidity ^0.8.20; + +import {ERC721} from "../ERC721.sol"; +import {ERC2981} from "../../common/ERC2981.sol"; + +/** + * @dev Extension of ERC721 with the ERC2981 NFT Royalty Standard, a standardized way to retrieve royalty payment + * information. + * + * Royalty information can be specified globally for all token ids via {ERC2981-_setDefaultRoyalty}, and/or individually + * for specific token ids via {ERC2981-_setTokenRoyalty}. The latter takes precedence over the first. + * + * IMPORTANT: ERC-2981 only specifies a way to signal royalty information and does not enforce its payment. See + * https://eips.ethereum.org/EIPS/eip-2981#optional-royalty-payments[Rationale] in the EIP. Marketplaces are expected to + * voluntarily pay royalties together with sales, but note that this standard is not yet widely supported. + */ +abstract contract ERC721Royalty is ERC2981, ERC721 { + /** + * @dev See {IERC165-supportsInterface}. + */ + function supportsInterface(bytes4 interfaceId) public view virtual override(ERC721, ERC2981) returns (bool) { + return super.supportsInterface(interfaceId); + } +} diff --git a/CVLByExample/UniswapHooks/lib/uniswap-hooks/lib/v4-core/lib/openzeppelin-contracts/contracts/token/ERC721/extensions/ERC721URIStorage.sol b/CVLByExample/UniswapHooks/lib/uniswap-hooks/lib/v4-core/lib/openzeppelin-contracts/contracts/token/ERC721/extensions/ERC721URIStorage.sol new file mode 100644 index 00000000..2584cb58 --- /dev/null +++ b/CVLByExample/UniswapHooks/lib/uniswap-hooks/lib/v4-core/lib/openzeppelin-contracts/contracts/token/ERC721/extensions/ERC721URIStorage.sol @@ -0,0 +1,61 @@ +// SPDX-License-Identifier: MIT +// OpenZeppelin Contracts (last updated v5.0.0) (token/ERC721/extensions/ERC721URIStorage.sol) + +pragma solidity ^0.8.20; + +import {ERC721} from "../ERC721.sol"; +import {Strings} from "../../../utils/Strings.sol"; +import {IERC4906} from "../../../interfaces/IERC4906.sol"; +import {IERC165} from "../../../interfaces/IERC165.sol"; + +/** + * @dev ERC721 token with storage based token URI management. + */ +abstract contract ERC721URIStorage is IERC4906, ERC721 { + using Strings for uint256; + + // Interface ID as defined in ERC-4906. This does not correspond to a traditional interface ID as ERC-4906 only + // defines events and does not include any external function. + bytes4 private constant ERC4906_INTERFACE_ID = bytes4(0x49064906); + + // Optional mapping for token URIs + mapping(uint256 tokenId => string) private _tokenURIs; + + /** + * @dev See {IERC165-supportsInterface} + */ + function supportsInterface(bytes4 interfaceId) public view virtual override(ERC721, IERC165) returns (bool) { + return interfaceId == ERC4906_INTERFACE_ID || super.supportsInterface(interfaceId); + } + + /** + * @dev See {IERC721Metadata-tokenURI}. + */ + function tokenURI(uint256 tokenId) public view virtual override returns (string memory) { + _requireOwned(tokenId); + + string memory _tokenURI = _tokenURIs[tokenId]; + string memory base = _baseURI(); + + // If there is no base URI, return the token URI. + if (bytes(base).length == 0) { + return _tokenURI; + } + // If both are set, concatenate the baseURI and tokenURI (via string.concat). + if (bytes(_tokenURI).length > 0) { + return string.concat(base, _tokenURI); + } + + return super.tokenURI(tokenId); + } + + /** + * @dev Sets `_tokenURI` as the tokenURI of `tokenId`. + * + * Emits {MetadataUpdate}. + */ + function _setTokenURI(uint256 tokenId, string memory _tokenURI) internal virtual { + _tokenURIs[tokenId] = _tokenURI; + emit MetadataUpdate(tokenId); + } +} diff --git a/CVLByExample/UniswapHooks/lib/uniswap-hooks/lib/v4-core/lib/openzeppelin-contracts/contracts/token/ERC721/extensions/ERC721Votes.sol b/CVLByExample/UniswapHooks/lib/uniswap-hooks/lib/v4-core/lib/openzeppelin-contracts/contracts/token/ERC721/extensions/ERC721Votes.sol new file mode 100644 index 00000000..56287151 --- /dev/null +++ b/CVLByExample/UniswapHooks/lib/uniswap-hooks/lib/v4-core/lib/openzeppelin-contracts/contracts/token/ERC721/extensions/ERC721Votes.sol @@ -0,0 +1,47 @@ +// SPDX-License-Identifier: MIT +// OpenZeppelin Contracts (last updated v5.0.0) (token/ERC721/extensions/ERC721Votes.sol) + +pragma solidity ^0.8.20; + +import {ERC721} from "../ERC721.sol"; +import {Votes} from "../../../governance/utils/Votes.sol"; + +/** + * @dev Extension of ERC721 to support voting and delegation as implemented by {Votes}, where each individual NFT counts + * as 1 vote unit. + * + * Tokens do not count as votes until they are delegated, because votes must be tracked which incurs an additional cost + * on every transfer. Token holders can either delegate to a trusted representative who will decide how to make use of + * the votes in governance decisions, or they can delegate to themselves to be their own representative. + */ +abstract contract ERC721Votes is ERC721, Votes { + /** + * @dev See {ERC721-_update}. Adjusts votes when tokens are transferred. + * + * Emits a {IVotes-DelegateVotesChanged} event. + */ + function _update(address to, uint256 tokenId, address auth) internal virtual override returns (address) { + address previousOwner = super._update(to, tokenId, auth); + + _transferVotingUnits(previousOwner, to, 1); + + return previousOwner; + } + + /** + * @dev Returns the balance of `account`. + * + * WARNING: Overriding this function will likely result in incorrect vote tracking. + */ + function _getVotingUnits(address account) internal view virtual override returns (uint256) { + return balanceOf(account); + } + + /** + * @dev See {ERC721-_increaseBalance}. We need that to account tokens that were minted in batch. + */ + function _increaseBalance(address account, uint128 amount) internal virtual override { + super._increaseBalance(account, amount); + _transferVotingUnits(address(0), account, amount); + } +} diff --git a/CVLByExample/UniswapHooks/lib/uniswap-hooks/lib/v4-core/lib/openzeppelin-contracts/contracts/token/ERC721/extensions/ERC721Wrapper.sol b/CVLByExample/UniswapHooks/lib/uniswap-hooks/lib/v4-core/lib/openzeppelin-contracts/contracts/token/ERC721/extensions/ERC721Wrapper.sol new file mode 100644 index 00000000..e091bdd9 --- /dev/null +++ b/CVLByExample/UniswapHooks/lib/uniswap-hooks/lib/v4-core/lib/openzeppelin-contracts/contracts/token/ERC721/extensions/ERC721Wrapper.sol @@ -0,0 +1,102 @@ +// SPDX-License-Identifier: MIT +// OpenZeppelin Contracts (last updated v5.0.0) (token/ERC721/extensions/ERC721Wrapper.sol) + +pragma solidity ^0.8.20; + +import {IERC721, ERC721} from "../ERC721.sol"; +import {IERC721Receiver} from "../IERC721Receiver.sol"; + +/** + * @dev Extension of the ERC721 token contract to support token wrapping. + * + * Users can deposit and withdraw an "underlying token" and receive a "wrapped token" with a matching tokenId. This is + * useful in conjunction with other modules. For example, combining this wrapping mechanism with {ERC721Votes} will allow + * the wrapping of an existing "basic" ERC721 into a governance token. + */ +abstract contract ERC721Wrapper is ERC721, IERC721Receiver { + IERC721 private immutable _underlying; + + /** + * @dev The received ERC721 token couldn't be wrapped. + */ + error ERC721UnsupportedToken(address token); + + constructor(IERC721 underlyingToken) { + _underlying = underlyingToken; + } + + /** + * @dev Allow a user to deposit underlying tokens and mint the corresponding tokenIds. + */ + function depositFor(address account, uint256[] memory tokenIds) public virtual returns (bool) { + uint256 length = tokenIds.length; + for (uint256 i = 0; i < length; ++i) { + uint256 tokenId = tokenIds[i]; + + // This is an "unsafe" transfer that doesn't call any hook on the receiver. With underlying() being trusted + // (by design of this contract) and no other contracts expected to be called from there, we are safe. + // slither-disable-next-line reentrancy-no-eth + underlying().transferFrom(_msgSender(), address(this), tokenId); + _safeMint(account, tokenId); + } + + return true; + } + + /** + * @dev Allow a user to burn wrapped tokens and withdraw the corresponding tokenIds of the underlying tokens. + */ + function withdrawTo(address account, uint256[] memory tokenIds) public virtual returns (bool) { + uint256 length = tokenIds.length; + for (uint256 i = 0; i < length; ++i) { + uint256 tokenId = tokenIds[i]; + // Setting an "auth" arguments enables the `_isAuthorized` check which verifies that the token exists + // (from != 0). Therefore, it is not needed to verify that the return value is not 0 here. + _update(address(0), tokenId, _msgSender()); + // Checks were already performed at this point, and there's no way to retake ownership or approval from + // the wrapped tokenId after this point, so it's safe to remove the reentrancy check for the next line. + // slither-disable-next-line reentrancy-no-eth + underlying().safeTransferFrom(address(this), account, tokenId); + } + + return true; + } + + /** + * @dev Overrides {IERC721Receiver-onERC721Received} to allow minting on direct ERC721 transfers to + * this contract. + * + * In case there's data attached, it validates that the operator is this contract, so only trusted data + * is accepted from {depositFor}. + * + * WARNING: Doesn't work with unsafe transfers (eg. {IERC721-transferFrom}). Use {ERC721Wrapper-_recover} + * for recovering in that scenario. + */ + function onERC721Received(address, address from, uint256 tokenId, bytes memory) public virtual returns (bytes4) { + if (address(underlying()) != _msgSender()) { + revert ERC721UnsupportedToken(_msgSender()); + } + _safeMint(from, tokenId); + return IERC721Receiver.onERC721Received.selector; + } + + /** + * @dev Mint a wrapped token to cover any underlyingToken that would have been transferred by mistake. Internal + * function that can be exposed with access control if desired. + */ + function _recover(address account, uint256 tokenId) internal virtual returns (uint256) { + address owner = underlying().ownerOf(tokenId); + if (owner != address(this)) { + revert ERC721IncorrectOwner(address(this), tokenId, owner); + } + _safeMint(account, tokenId); + return tokenId; + } + + /** + * @dev Returns the underlying token. + */ + function underlying() public view virtual returns (IERC721) { + return _underlying; + } +} diff --git a/CVLByExample/UniswapHooks/lib/uniswap-hooks/lib/v4-core/lib/openzeppelin-contracts/contracts/token/ERC721/extensions/IERC721Enumerable.sol b/CVLByExample/UniswapHooks/lib/uniswap-hooks/lib/v4-core/lib/openzeppelin-contracts/contracts/token/ERC721/extensions/IERC721Enumerable.sol new file mode 100644 index 00000000..7a09cc6a --- /dev/null +++ b/CVLByExample/UniswapHooks/lib/uniswap-hooks/lib/v4-core/lib/openzeppelin-contracts/contracts/token/ERC721/extensions/IERC721Enumerable.sol @@ -0,0 +1,29 @@ +// SPDX-License-Identifier: MIT +// OpenZeppelin Contracts (last updated v5.0.0) (token/ERC721/extensions/IERC721Enumerable.sol) + +pragma solidity ^0.8.20; + +import {IERC721} from "../IERC721.sol"; + +/** + * @title ERC-721 Non-Fungible Token Standard, optional enumeration extension + * @dev See https://eips.ethereum.org/EIPS/eip-721 + */ +interface IERC721Enumerable is IERC721 { + /** + * @dev Returns the total amount of tokens stored by the contract. + */ + function totalSupply() external view returns (uint256); + + /** + * @dev Returns a token ID owned by `owner` at a given `index` of its token list. + * Use along with {balanceOf} to enumerate all of ``owner``'s tokens. + */ + function tokenOfOwnerByIndex(address owner, uint256 index) external view returns (uint256); + + /** + * @dev Returns a token ID at a given `index` of all the tokens stored by the contract. + * Use along with {totalSupply} to enumerate all tokens. + */ + function tokenByIndex(uint256 index) external view returns (uint256); +} diff --git a/CVLByExample/UniswapHooks/lib/uniswap-hooks/lib/v4-core/lib/openzeppelin-contracts/contracts/token/ERC721/extensions/IERC721Metadata.sol b/CVLByExample/UniswapHooks/lib/uniswap-hooks/lib/v4-core/lib/openzeppelin-contracts/contracts/token/ERC721/extensions/IERC721Metadata.sol new file mode 100644 index 00000000..e9e00fab --- /dev/null +++ b/CVLByExample/UniswapHooks/lib/uniswap-hooks/lib/v4-core/lib/openzeppelin-contracts/contracts/token/ERC721/extensions/IERC721Metadata.sol @@ -0,0 +1,27 @@ +// SPDX-License-Identifier: MIT +// OpenZeppelin Contracts (last updated v5.0.0) (token/ERC721/extensions/IERC721Metadata.sol) + +pragma solidity ^0.8.20; + +import {IERC721} from "../IERC721.sol"; + +/** + * @title ERC-721 Non-Fungible Token Standard, optional metadata extension + * @dev See https://eips.ethereum.org/EIPS/eip-721 + */ +interface IERC721Metadata is IERC721 { + /** + * @dev Returns the token collection name. + */ + function name() external view returns (string memory); + + /** + * @dev Returns the token collection symbol. + */ + function symbol() external view returns (string memory); + + /** + * @dev Returns the Uniform Resource Identifier (URI) for `tokenId` token. + */ + function tokenURI(uint256 tokenId) external view returns (string memory); +} diff --git a/CVLByExample/UniswapHooks/lib/uniswap-hooks/lib/v4-core/lib/openzeppelin-contracts/contracts/token/ERC721/utils/ERC721Holder.sol b/CVLByExample/UniswapHooks/lib/uniswap-hooks/lib/v4-core/lib/openzeppelin-contracts/contracts/token/ERC721/utils/ERC721Holder.sol new file mode 100644 index 00000000..6bb23ace --- /dev/null +++ b/CVLByExample/UniswapHooks/lib/uniswap-hooks/lib/v4-core/lib/openzeppelin-contracts/contracts/token/ERC721/utils/ERC721Holder.sol @@ -0,0 +1,24 @@ +// SPDX-License-Identifier: MIT +// OpenZeppelin Contracts (last updated v5.0.0) (token/ERC721/utils/ERC721Holder.sol) + +pragma solidity ^0.8.20; + +import {IERC721Receiver} from "../IERC721Receiver.sol"; + +/** + * @dev Implementation of the {IERC721Receiver} interface. + * + * Accepts all token transfers. + * Make sure the contract is able to use its token with {IERC721-safeTransferFrom}, {IERC721-approve} or + * {IERC721-setApprovalForAll}. + */ +abstract contract ERC721Holder is IERC721Receiver { + /** + * @dev See {IERC721Receiver-onERC721Received}. + * + * Always returns `IERC721Receiver.onERC721Received.selector`. + */ + function onERC721Received(address, address, uint256, bytes memory) public virtual returns (bytes4) { + return this.onERC721Received.selector; + } +} diff --git a/CVLByExample/UniswapHooks/lib/uniswap-hooks/lib/v4-core/lib/openzeppelin-contracts/contracts/token/common/ERC2981.sol b/CVLByExample/UniswapHooks/lib/uniswap-hooks/lib/v4-core/lib/openzeppelin-contracts/contracts/token/common/ERC2981.sol new file mode 100644 index 00000000..fce02514 --- /dev/null +++ b/CVLByExample/UniswapHooks/lib/uniswap-hooks/lib/v4-core/lib/openzeppelin-contracts/contracts/token/common/ERC2981.sol @@ -0,0 +1,137 @@ +// SPDX-License-Identifier: MIT +// OpenZeppelin Contracts (last updated v5.0.0) (token/common/ERC2981.sol) + +pragma solidity ^0.8.20; + +import {IERC2981} from "../../interfaces/IERC2981.sol"; +import {IERC165, ERC165} from "../../utils/introspection/ERC165.sol"; + +/** + * @dev Implementation of the NFT Royalty Standard, a standardized way to retrieve royalty payment information. + * + * Royalty information can be specified globally for all token ids via {_setDefaultRoyalty}, and/or individually for + * specific token ids via {_setTokenRoyalty}. The latter takes precedence over the first. + * + * Royalty is specified as a fraction of sale price. {_feeDenominator} is overridable but defaults to 10000, meaning the + * fee is specified in basis points by default. + * + * IMPORTANT: ERC-2981 only specifies a way to signal royalty information and does not enforce its payment. See + * https://eips.ethereum.org/EIPS/eip-2981#optional-royalty-payments[Rationale] in the EIP. Marketplaces are expected to + * voluntarily pay royalties together with sales, but note that this standard is not yet widely supported. + */ +abstract contract ERC2981 is IERC2981, ERC165 { + struct RoyaltyInfo { + address receiver; + uint96 royaltyFraction; + } + + RoyaltyInfo private _defaultRoyaltyInfo; + mapping(uint256 tokenId => RoyaltyInfo) private _tokenRoyaltyInfo; + + /** + * @dev The default royalty set is invalid (eg. (numerator / denominator) >= 1). + */ + error ERC2981InvalidDefaultRoyalty(uint256 numerator, uint256 denominator); + + /** + * @dev The default royalty receiver is invalid. + */ + error ERC2981InvalidDefaultRoyaltyReceiver(address receiver); + + /** + * @dev The royalty set for an specific `tokenId` is invalid (eg. (numerator / denominator) >= 1). + */ + error ERC2981InvalidTokenRoyalty(uint256 tokenId, uint256 numerator, uint256 denominator); + + /** + * @dev The royalty receiver for `tokenId` is invalid. + */ + error ERC2981InvalidTokenRoyaltyReceiver(uint256 tokenId, address receiver); + + /** + * @dev See {IERC165-supportsInterface}. + */ + function supportsInterface(bytes4 interfaceId) public view virtual override(IERC165, ERC165) returns (bool) { + return interfaceId == type(IERC2981).interfaceId || super.supportsInterface(interfaceId); + } + + /** + * @inheritdoc IERC2981 + */ + function royaltyInfo(uint256 tokenId, uint256 salePrice) public view virtual returns (address, uint256) { + RoyaltyInfo memory royalty = _tokenRoyaltyInfo[tokenId]; + + if (royalty.receiver == address(0)) { + royalty = _defaultRoyaltyInfo; + } + + uint256 royaltyAmount = (salePrice * royalty.royaltyFraction) / _feeDenominator(); + + return (royalty.receiver, royaltyAmount); + } + + /** + * @dev The denominator with which to interpret the fee set in {_setTokenRoyalty} and {_setDefaultRoyalty} as a + * fraction of the sale price. Defaults to 10000 so fees are expressed in basis points, but may be customized by an + * override. + */ + function _feeDenominator() internal pure virtual returns (uint96) { + return 10000; + } + + /** + * @dev Sets the royalty information that all ids in this contract will default to. + * + * Requirements: + * + * - `receiver` cannot be the zero address. + * - `feeNumerator` cannot be greater than the fee denominator. + */ + function _setDefaultRoyalty(address receiver, uint96 feeNumerator) internal virtual { + uint256 denominator = _feeDenominator(); + if (feeNumerator > denominator) { + // Royalty fee will exceed the sale price + revert ERC2981InvalidDefaultRoyalty(feeNumerator, denominator); + } + if (receiver == address(0)) { + revert ERC2981InvalidDefaultRoyaltyReceiver(address(0)); + } + + _defaultRoyaltyInfo = RoyaltyInfo(receiver, feeNumerator); + } + + /** + * @dev Removes default royalty information. + */ + function _deleteDefaultRoyalty() internal virtual { + delete _defaultRoyaltyInfo; + } + + /** + * @dev Sets the royalty information for a specific token id, overriding the global default. + * + * Requirements: + * + * - `receiver` cannot be the zero address. + * - `feeNumerator` cannot be greater than the fee denominator. + */ + function _setTokenRoyalty(uint256 tokenId, address receiver, uint96 feeNumerator) internal virtual { + uint256 denominator = _feeDenominator(); + if (feeNumerator > denominator) { + // Royalty fee will exceed the sale price + revert ERC2981InvalidTokenRoyalty(tokenId, feeNumerator, denominator); + } + if (receiver == address(0)) { + revert ERC2981InvalidTokenRoyaltyReceiver(tokenId, address(0)); + } + + _tokenRoyaltyInfo[tokenId] = RoyaltyInfo(receiver, feeNumerator); + } + + /** + * @dev Resets royalty information for the token id back to the global default. + */ + function _resetTokenRoyalty(uint256 tokenId) internal virtual { + delete _tokenRoyaltyInfo[tokenId]; + } +} diff --git a/CVLByExample/UniswapHooks/lib/uniswap-hooks/lib/v4-core/lib/openzeppelin-contracts/contracts/token/common/README.adoc b/CVLByExample/UniswapHooks/lib/uniswap-hooks/lib/v4-core/lib/openzeppelin-contracts/contracts/token/common/README.adoc new file mode 100644 index 00000000..af616746 --- /dev/null +++ b/CVLByExample/UniswapHooks/lib/uniswap-hooks/lib/v4-core/lib/openzeppelin-contracts/contracts/token/common/README.adoc @@ -0,0 +1,10 @@ += Common (Tokens) + +Functionality that is common to multiple token standards. + +* {ERC2981}: NFT Royalties compatible with both ERC721 and ERC1155. +** For ERC721 consider {ERC721Royalty} which clears the royalty information from storage on burn. + +== Contracts + +{{ERC2981}} diff --git a/CVLByExample/UniswapHooks/lib/uniswap-hooks/lib/v4-core/lib/openzeppelin-contracts/contracts/utils/Address.sol b/CVLByExample/UniswapHooks/lib/uniswap-hooks/lib/v4-core/lib/openzeppelin-contracts/contracts/utils/Address.sol new file mode 100644 index 00000000..b7e30595 --- /dev/null +++ b/CVLByExample/UniswapHooks/lib/uniswap-hooks/lib/v4-core/lib/openzeppelin-contracts/contracts/utils/Address.sol @@ -0,0 +1,159 @@ +// SPDX-License-Identifier: MIT +// OpenZeppelin Contracts (last updated v5.0.0) (utils/Address.sol) + +pragma solidity ^0.8.20; + +/** + * @dev Collection of functions related to the address type + */ +library Address { + /** + * @dev The ETH balance of the account is not enough to perform the operation. + */ + error AddressInsufficientBalance(address account); + + /** + * @dev There's no code at `target` (it is not a contract). + */ + error AddressEmptyCode(address target); + + /** + * @dev A call to an address target failed. The target may have reverted. + */ + error FailedInnerCall(); + + /** + * @dev Replacement for Solidity's `transfer`: sends `amount` wei to + * `recipient`, forwarding all available gas and reverting on errors. + * + * https://eips.ethereum.org/EIPS/eip-1884[EIP1884] increases the gas cost + * of certain opcodes, possibly making contracts go over the 2300 gas limit + * imposed by `transfer`, making them unable to receive funds via + * `transfer`. {sendValue} removes this limitation. + * + * https://consensys.net/diligence/blog/2019/09/stop-using-soliditys-transfer-now/[Learn more]. + * + * IMPORTANT: because control is transferred to `recipient`, care must be + * taken to not create reentrancy vulnerabilities. Consider using + * {ReentrancyGuard} or the + * https://solidity.readthedocs.io/en/v0.8.20/security-considerations.html#use-the-checks-effects-interactions-pattern[checks-effects-interactions pattern]. + */ + function sendValue(address payable recipient, uint256 amount) internal { + if (address(this).balance < amount) { + revert AddressInsufficientBalance(address(this)); + } + + (bool success, ) = recipient.call{value: amount}(""); + if (!success) { + revert FailedInnerCall(); + } + } + + /** + * @dev Performs a Solidity function call using a low level `call`. A + * plain `call` is an unsafe replacement for a function call: use this + * function instead. + * + * If `target` reverts with a revert reason or custom error, it is bubbled + * up by this function (like regular Solidity function calls). However, if + * the call reverted with no returned reason, this function reverts with a + * {FailedInnerCall} error. + * + * Returns the raw returned data. To convert to the expected return value, + * use https://solidity.readthedocs.io/en/latest/units-and-global-variables.html?highlight=abi.decode#abi-encoding-and-decoding-functions[`abi.decode`]. + * + * Requirements: + * + * - `target` must be a contract. + * - calling `target` with `data` must not revert. + */ + function functionCall(address target, bytes memory data) internal returns (bytes memory) { + return functionCallWithValue(target, data, 0); + } + + /** + * @dev Same as {xref-Address-functionCall-address-bytes-}[`functionCall`], + * but also transferring `value` wei to `target`. + * + * Requirements: + * + * - the calling contract must have an ETH balance of at least `value`. + * - the called Solidity function must be `payable`. + */ + function functionCallWithValue(address target, bytes memory data, uint256 value) internal returns (bytes memory) { + if (address(this).balance < value) { + revert AddressInsufficientBalance(address(this)); + } + (bool success, bytes memory returndata) = target.call{value: value}(data); + return verifyCallResultFromTarget(target, success, returndata); + } + + /** + * @dev Same as {xref-Address-functionCall-address-bytes-}[`functionCall`], + * but performing a static call. + */ + function functionStaticCall(address target, bytes memory data) internal view returns (bytes memory) { + (bool success, bytes memory returndata) = target.staticcall(data); + return verifyCallResultFromTarget(target, success, returndata); + } + + /** + * @dev Same as {xref-Address-functionCall-address-bytes-}[`functionCall`], + * but performing a delegate call. + */ + function functionDelegateCall(address target, bytes memory data) internal returns (bytes memory) { + (bool success, bytes memory returndata) = target.delegatecall(data); + return verifyCallResultFromTarget(target, success, returndata); + } + + /** + * @dev Tool to verify that a low level call to smart-contract was successful, and reverts if the target + * was not a contract or bubbling up the revert reason (falling back to {FailedInnerCall}) in case of an + * unsuccessful call. + */ + function verifyCallResultFromTarget( + address target, + bool success, + bytes memory returndata + ) internal view returns (bytes memory) { + if (!success) { + _revert(returndata); + } else { + // only check if target is a contract if the call was successful and the return data is empty + // otherwise we already know that it was a contract + if (returndata.length == 0 && target.code.length == 0) { + revert AddressEmptyCode(target); + } + return returndata; + } + } + + /** + * @dev Tool to verify that a low level call was successful, and reverts if it wasn't, either by bubbling the + * revert reason or with a default {FailedInnerCall} error. + */ + function verifyCallResult(bool success, bytes memory returndata) internal pure returns (bytes memory) { + if (!success) { + _revert(returndata); + } else { + return returndata; + } + } + + /** + * @dev Reverts with returndata if present. Otherwise reverts with {FailedInnerCall}. + */ + function _revert(bytes memory returndata) private pure { + // Look for revert reason and bubble it up if present + if (returndata.length > 0) { + // The easiest way to bubble the revert reason is using memory via assembly + /// @solidity memory-safe-assembly + assembly { + let returndata_size := mload(returndata) + revert(add(32, returndata), returndata_size) + } + } else { + revert FailedInnerCall(); + } + } +} diff --git a/CVLByExample/UniswapHooks/lib/uniswap-hooks/lib/v4-core/lib/openzeppelin-contracts/contracts/utils/Arrays.sol b/CVLByExample/UniswapHooks/lib/uniswap-hooks/lib/v4-core/lib/openzeppelin-contracts/contracts/utils/Arrays.sol new file mode 100644 index 00000000..aaab3ce5 --- /dev/null +++ b/CVLByExample/UniswapHooks/lib/uniswap-hooks/lib/v4-core/lib/openzeppelin-contracts/contracts/utils/Arrays.sol @@ -0,0 +1,127 @@ +// SPDX-License-Identifier: MIT +// OpenZeppelin Contracts (last updated v5.0.0) (utils/Arrays.sol) + +pragma solidity ^0.8.20; + +import {StorageSlot} from "./StorageSlot.sol"; +import {Math} from "./math/Math.sol"; + +/** + * @dev Collection of functions related to array types. + */ +library Arrays { + using StorageSlot for bytes32; + + /** + * @dev Searches a sorted `array` and returns the first index that contains + * a value greater or equal to `element`. If no such index exists (i.e. all + * values in the array are strictly less than `element`), the array length is + * returned. Time complexity O(log n). + * + * `array` is expected to be sorted in ascending order, and to contain no + * repeated elements. + */ + function findUpperBound(uint256[] storage array, uint256 element) internal view returns (uint256) { + uint256 low = 0; + uint256 high = array.length; + + if (high == 0) { + return 0; + } + + while (low < high) { + uint256 mid = Math.average(low, high); + + // Note that mid will always be strictly less than high (i.e. it will be a valid array index) + // because Math.average rounds towards zero (it does integer division with truncation). + if (unsafeAccess(array, mid).value > element) { + high = mid; + } else { + low = mid + 1; + } + } + + // At this point `low` is the exclusive upper bound. We will return the inclusive upper bound. + if (low > 0 && unsafeAccess(array, low - 1).value == element) { + return low - 1; + } else { + return low; + } + } + + /** + * @dev Access an array in an "unsafe" way. Skips solidity "index-out-of-range" check. + * + * WARNING: Only use if you are certain `pos` is lower than the array length. + */ + function unsafeAccess(address[] storage arr, uint256 pos) internal pure returns (StorageSlot.AddressSlot storage) { + bytes32 slot; + // We use assembly to calculate the storage slot of the element at index `pos` of the dynamic array `arr` + // following https://docs.soliditylang.org/en/v0.8.20/internals/layout_in_storage.html#mappings-and-dynamic-arrays. + + /// @solidity memory-safe-assembly + assembly { + mstore(0, arr.slot) + slot := add(keccak256(0, 0x20), pos) + } + return slot.getAddressSlot(); + } + + /** + * @dev Access an array in an "unsafe" way. Skips solidity "index-out-of-range" check. + * + * WARNING: Only use if you are certain `pos` is lower than the array length. + */ + function unsafeAccess(bytes32[] storage arr, uint256 pos) internal pure returns (StorageSlot.Bytes32Slot storage) { + bytes32 slot; + // We use assembly to calculate the storage slot of the element at index `pos` of the dynamic array `arr` + // following https://docs.soliditylang.org/en/v0.8.20/internals/layout_in_storage.html#mappings-and-dynamic-arrays. + + /// @solidity memory-safe-assembly + assembly { + mstore(0, arr.slot) + slot := add(keccak256(0, 0x20), pos) + } + return slot.getBytes32Slot(); + } + + /** + * @dev Access an array in an "unsafe" way. Skips solidity "index-out-of-range" check. + * + * WARNING: Only use if you are certain `pos` is lower than the array length. + */ + function unsafeAccess(uint256[] storage arr, uint256 pos) internal pure returns (StorageSlot.Uint256Slot storage) { + bytes32 slot; + // We use assembly to calculate the storage slot of the element at index `pos` of the dynamic array `arr` + // following https://docs.soliditylang.org/en/v0.8.20/internals/layout_in_storage.html#mappings-and-dynamic-arrays. + + /// @solidity memory-safe-assembly + assembly { + mstore(0, arr.slot) + slot := add(keccak256(0, 0x20), pos) + } + return slot.getUint256Slot(); + } + + /** + * @dev Access an array in an "unsafe" way. Skips solidity "index-out-of-range" check. + * + * WARNING: Only use if you are certain `pos` is lower than the array length. + */ + function unsafeMemoryAccess(uint256[] memory arr, uint256 pos) internal pure returns (uint256 res) { + assembly { + res := mload(add(add(arr, 0x20), mul(pos, 0x20))) + } + } + + /** + * @dev Access an array in an "unsafe" way. Skips solidity "index-out-of-range" check. + * + * WARNING: Only use if you are certain `pos` is lower than the array length. + */ + function unsafeMemoryAccess(address[] memory arr, uint256 pos) internal pure returns (address res) { + assembly { + res := mload(add(add(arr, 0x20), mul(pos, 0x20))) + } + } +} diff --git a/CVLByExample/UniswapHooks/lib/uniswap-hooks/lib/v4-core/lib/openzeppelin-contracts/contracts/utils/Base64.sol b/CVLByExample/UniswapHooks/lib/uniswap-hooks/lib/v4-core/lib/openzeppelin-contracts/contracts/utils/Base64.sol new file mode 100644 index 00000000..0124cdde --- /dev/null +++ b/CVLByExample/UniswapHooks/lib/uniswap-hooks/lib/v4-core/lib/openzeppelin-contracts/contracts/utils/Base64.sol @@ -0,0 +1,99 @@ +// SPDX-License-Identifier: MIT +// OpenZeppelin Contracts (last updated v5.0.2) (utils/Base64.sol) + +pragma solidity ^0.8.20; + +/** + * @dev Provides a set of functions to operate with Base64 strings. + */ +library Base64 { + /** + * @dev Base64 Encoding/Decoding Table + */ + string internal constant _TABLE = "ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz0123456789+/"; + + /** + * @dev Converts a `bytes` to its Bytes64 `string` representation. + */ + function encode(bytes memory data) internal pure returns (string memory) { + /** + * Inspired by Brecht Devos (Brechtpd) implementation - MIT licence + * https://github.com/Brechtpd/base64/blob/e78d9fd951e7b0977ddca77d92dc85183770daf4/base64.sol + */ + if (data.length == 0) return ""; + + // Loads the table into memory + string memory table = _TABLE; + + // Encoding takes 3 bytes chunks of binary data from `bytes` data parameter + // and split into 4 numbers of 6 bits. + // The final Base64 length should be `bytes` data length multiplied by 4/3 rounded up + // - `data.length + 2` -> Round up + // - `/ 3` -> Number of 3-bytes chunks + // - `4 *` -> 4 characters for each chunk + string memory result = new string(4 * ((data.length + 2) / 3)); + + /// @solidity memory-safe-assembly + assembly { + // Prepare the lookup table (skip the first "length" byte) + let tablePtr := add(table, 1) + + // Prepare result pointer, jump over length + let resultPtr := add(result, 0x20) + let dataPtr := data + let endPtr := add(data, mload(data)) + + // In some cases, the last iteration will read bytes after the end of the data. We cache the value, and + // set it to zero to make sure no dirty bytes are read in that section. + let afterPtr := add(endPtr, 0x20) + let afterCache := mload(afterPtr) + mstore(afterPtr, 0x00) + + // Run over the input, 3 bytes at a time + for { + + } lt(dataPtr, endPtr) { + + } { + // Advance 3 bytes + dataPtr := add(dataPtr, 3) + let input := mload(dataPtr) + + // To write each character, shift the 3 byte (24 bits) chunk + // 4 times in blocks of 6 bits for each character (18, 12, 6, 0) + // and apply logical AND with 0x3F to bitmask the least significant 6 bits. + // Use this as an index into the lookup table, mload an entire word + // so the desired character is in the least significant byte, and + // mstore8 this least significant byte into the result and continue. + + mstore8(resultPtr, mload(add(tablePtr, and(shr(18, input), 0x3F)))) + resultPtr := add(resultPtr, 1) // Advance + + mstore8(resultPtr, mload(add(tablePtr, and(shr(12, input), 0x3F)))) + resultPtr := add(resultPtr, 1) // Advance + + mstore8(resultPtr, mload(add(tablePtr, and(shr(6, input), 0x3F)))) + resultPtr := add(resultPtr, 1) // Advance + + mstore8(resultPtr, mload(add(tablePtr, and(input, 0x3F)))) + resultPtr := add(resultPtr, 1) // Advance + } + + // Reset the value that was cached + mstore(afterPtr, afterCache) + + // When data `bytes` is not exactly 3 bytes long + // it is padded with `=` characters at the end + switch mod(mload(data), 3) + case 1 { + mstore8(sub(resultPtr, 1), 0x3d) + mstore8(sub(resultPtr, 2), 0x3d) + } + case 2 { + mstore8(sub(resultPtr, 1), 0x3d) + } + } + + return result; + } +} diff --git a/CVLByExample/UniswapHooks/lib/uniswap-hooks/lib/v4-core/lib/openzeppelin-contracts/contracts/utils/Context.sol b/CVLByExample/UniswapHooks/lib/uniswap-hooks/lib/v4-core/lib/openzeppelin-contracts/contracts/utils/Context.sol new file mode 100644 index 00000000..4e535fe0 --- /dev/null +++ b/CVLByExample/UniswapHooks/lib/uniswap-hooks/lib/v4-core/lib/openzeppelin-contracts/contracts/utils/Context.sol @@ -0,0 +1,28 @@ +// SPDX-License-Identifier: MIT +// OpenZeppelin Contracts (last updated v5.0.1) (utils/Context.sol) + +pragma solidity ^0.8.20; + +/** + * @dev Provides information about the current execution context, including the + * sender of the transaction and its data. While these are generally available + * via msg.sender and msg.data, they should not be accessed in such a direct + * manner, since when dealing with meta-transactions the account sending and + * paying for execution may not be the actual sender (as far as an application + * is concerned). + * + * This contract is only required for intermediate, library-like contracts. + */ +abstract contract Context { + function _msgSender() internal view virtual returns (address) { + return msg.sender; + } + + function _msgData() internal view virtual returns (bytes calldata) { + return msg.data; + } + + function _contextSuffixLength() internal view virtual returns (uint256) { + return 0; + } +} diff --git a/CVLByExample/UniswapHooks/lib/uniswap-hooks/lib/v4-core/lib/openzeppelin-contracts/contracts/utils/Create2.sol b/CVLByExample/UniswapHooks/lib/uniswap-hooks/lib/v4-core/lib/openzeppelin-contracts/contracts/utils/Create2.sol new file mode 100644 index 00000000..ad1cd5f4 --- /dev/null +++ b/CVLByExample/UniswapHooks/lib/uniswap-hooks/lib/v4-core/lib/openzeppelin-contracts/contracts/utils/Create2.sol @@ -0,0 +1,96 @@ +// SPDX-License-Identifier: MIT +// OpenZeppelin Contracts (last updated v5.0.0) (utils/Create2.sol) + +pragma solidity ^0.8.20; + +/** + * @dev Helper to make usage of the `CREATE2` EVM opcode easier and safer. + * `CREATE2` can be used to compute in advance the address where a smart + * contract will be deployed, which allows for interesting new mechanisms known + * as 'counterfactual interactions'. + * + * See the https://eips.ethereum.org/EIPS/eip-1014#motivation[EIP] for more + * information. + */ +library Create2 { + /** + * @dev Not enough balance for performing a CREATE2 deploy. + */ + error Create2InsufficientBalance(uint256 balance, uint256 needed); + + /** + * @dev There's no code to deploy. + */ + error Create2EmptyBytecode(); + + /** + * @dev The deployment failed. + */ + error Create2FailedDeployment(); + + /** + * @dev Deploys a contract using `CREATE2`. The address where the contract + * will be deployed can be known in advance via {computeAddress}. + * + * The bytecode for a contract can be obtained from Solidity with + * `type(contractName).creationCode`. + * + * Requirements: + * + * - `bytecode` must not be empty. + * - `salt` must have not been used for `bytecode` already. + * - the factory must have a balance of at least `amount`. + * - if `amount` is non-zero, `bytecode` must have a `payable` constructor. + */ + function deploy(uint256 amount, bytes32 salt, bytes memory bytecode) internal returns (address addr) { + if (address(this).balance < amount) { + revert Create2InsufficientBalance(address(this).balance, amount); + } + if (bytecode.length == 0) { + revert Create2EmptyBytecode(); + } + /// @solidity memory-safe-assembly + assembly { + addr := create2(amount, add(bytecode, 0x20), mload(bytecode), salt) + } + if (addr == address(0)) { + revert Create2FailedDeployment(); + } + } + + /** + * @dev Returns the address where a contract will be stored if deployed via {deploy}. Any change in the + * `bytecodeHash` or `salt` will result in a new destination address. + */ + function computeAddress(bytes32 salt, bytes32 bytecodeHash) internal view returns (address) { + return computeAddress(salt, bytecodeHash, address(this)); + } + + /** + * @dev Returns the address where a contract will be stored if deployed via {deploy} from a contract located at + * `deployer`. If `deployer` is this contract's address, returns the same value as {computeAddress}. + */ + function computeAddress(bytes32 salt, bytes32 bytecodeHash, address deployer) internal pure returns (address addr) { + /// @solidity memory-safe-assembly + assembly { + let ptr := mload(0x40) // Get free memory pointer + + // | | ↓ ptr ... ↓ ptr + 0x0B (start) ... ↓ ptr + 0x20 ... ↓ ptr + 0x40 ... | + // |-------------------|---------------------------------------------------------------------------| + // | bytecodeHash | CCCCCCCCCCCCC...CC | + // | salt | BBBBBBBBBBBBB...BB | + // | deployer | 000000...0000AAAAAAAAAAAAAAAAAAA...AA | + // | 0xFF | FF | + // |-------------------|---------------------------------------------------------------------------| + // | memory | 000000...00FFAAAAAAAAAAAAAAAAAAA...AABBBBBBBBBBBBB...BBCCCCCCCCCCCCC...CC | + // | keccak(start, 85) | ↑↑↑↑↑↑↑↑↑↑↑↑↑↑↑↑↑↑↑↑↑↑↑↑↑↑↑↑↑↑↑↑↑↑↑↑↑↑↑↑↑↑↑↑↑↑↑↑↑↑↑↑↑↑↑↑↑↑↑↑↑↑ | + + mstore(add(ptr, 0x40), bytecodeHash) + mstore(add(ptr, 0x20), salt) + mstore(ptr, deployer) // Right-aligned with 12 preceding garbage bytes + let start := add(ptr, 0x0b) // The hashed data starts at the final garbage byte which we will set to 0xff + mstore8(start, 0xff) + addr := keccak256(start, 85) + } + } +} diff --git a/CVLByExample/UniswapHooks/lib/uniswap-hooks/lib/v4-core/lib/openzeppelin-contracts/contracts/utils/Multicall.sol b/CVLByExample/UniswapHooks/lib/uniswap-hooks/lib/v4-core/lib/openzeppelin-contracts/contracts/utils/Multicall.sol new file mode 100644 index 00000000..0dd5b4ad --- /dev/null +++ b/CVLByExample/UniswapHooks/lib/uniswap-hooks/lib/v4-core/lib/openzeppelin-contracts/contracts/utils/Multicall.sol @@ -0,0 +1,37 @@ +// SPDX-License-Identifier: MIT +// OpenZeppelin Contracts (last updated v5.0.1) (utils/Multicall.sol) + +pragma solidity ^0.8.20; + +import {Address} from "./Address.sol"; +import {Context} from "./Context.sol"; + +/** + * @dev Provides a function to batch together multiple calls in a single external call. + * + * Consider any assumption about calldata validation performed by the sender may be violated if it's not especially + * careful about sending transactions invoking {multicall}. For example, a relay address that filters function + * selectors won't filter calls nested within a {multicall} operation. + * + * NOTE: Since 5.0.1 and 4.9.4, this contract identifies non-canonical contexts (i.e. `msg.sender` is not {_msgSender}). + * If a non-canonical context is identified, the following self `delegatecall` appends the last bytes of `msg.data` + * to the subcall. This makes it safe to use with {ERC2771Context}. Contexts that don't affect the resolution of + * {_msgSender} are not propagated to subcalls. + */ +abstract contract Multicall is Context { + /** + * @dev Receives and executes a batch of function calls on this contract. + * @custom:oz-upgrades-unsafe-allow-reachable delegatecall + */ + function multicall(bytes[] calldata data) external virtual returns (bytes[] memory results) { + bytes memory context = msg.sender == _msgSender() + ? new bytes(0) + : msg.data[msg.data.length - _contextSuffixLength():]; + + results = new bytes[](data.length); + for (uint256 i = 0; i < data.length; i++) { + results[i] = Address.functionDelegateCall(address(this), bytes.concat(data[i], context)); + } + return results; + } +} diff --git a/CVLByExample/UniswapHooks/lib/uniswap-hooks/lib/v4-core/lib/openzeppelin-contracts/contracts/utils/Nonces.sol b/CVLByExample/UniswapHooks/lib/uniswap-hooks/lib/v4-core/lib/openzeppelin-contracts/contracts/utils/Nonces.sol new file mode 100644 index 00000000..37451ff9 --- /dev/null +++ b/CVLByExample/UniswapHooks/lib/uniswap-hooks/lib/v4-core/lib/openzeppelin-contracts/contracts/utils/Nonces.sol @@ -0,0 +1,46 @@ +// SPDX-License-Identifier: MIT +// OpenZeppelin Contracts (last updated v5.0.0) (utils/Nonces.sol) +pragma solidity ^0.8.20; + +/** + * @dev Provides tracking nonces for addresses. Nonces will only increment. + */ +abstract contract Nonces { + /** + * @dev The nonce used for an `account` is not the expected current nonce. + */ + error InvalidAccountNonce(address account, uint256 currentNonce); + + mapping(address account => uint256) private _nonces; + + /** + * @dev Returns the next unused nonce for an address. + */ + function nonces(address owner) public view virtual returns (uint256) { + return _nonces[owner]; + } + + /** + * @dev Consumes a nonce. + * + * Returns the current value and increments nonce. + */ + function _useNonce(address owner) internal virtual returns (uint256) { + // For each account, the nonce has an initial value of 0, can only be incremented by one, and cannot be + // decremented or reset. This guarantees that the nonce never overflows. + unchecked { + // It is important to do x++ and not ++x here. + return _nonces[owner]++; + } + } + + /** + * @dev Same as {_useNonce} but checking that `nonce` is the next valid for `owner`. + */ + function _useCheckedNonce(address owner, uint256 nonce) internal virtual { + uint256 current = _useNonce(owner); + if (nonce != current) { + revert InvalidAccountNonce(owner, current); + } + } +} diff --git a/CVLByExample/UniswapHooks/lib/uniswap-hooks/lib/v4-core/lib/openzeppelin-contracts/contracts/utils/Pausable.sol b/CVLByExample/UniswapHooks/lib/uniswap-hooks/lib/v4-core/lib/openzeppelin-contracts/contracts/utils/Pausable.sol new file mode 100644 index 00000000..312f1cb9 --- /dev/null +++ b/CVLByExample/UniswapHooks/lib/uniswap-hooks/lib/v4-core/lib/openzeppelin-contracts/contracts/utils/Pausable.sol @@ -0,0 +1,119 @@ +// SPDX-License-Identifier: MIT +// OpenZeppelin Contracts (last updated v5.0.0) (utils/Pausable.sol) + +pragma solidity ^0.8.20; + +import {Context} from "../utils/Context.sol"; + +/** + * @dev Contract module which allows children to implement an emergency stop + * mechanism that can be triggered by an authorized account. + * + * This module is used through inheritance. It will make available the + * modifiers `whenNotPaused` and `whenPaused`, which can be applied to + * the functions of your contract. Note that they will not be pausable by + * simply including this module, only once the modifiers are put in place. + */ +abstract contract Pausable is Context { + bool private _paused; + + /** + * @dev Emitted when the pause is triggered by `account`. + */ + event Paused(address account); + + /** + * @dev Emitted when the pause is lifted by `account`. + */ + event Unpaused(address account); + + /** + * @dev The operation failed because the contract is paused. + */ + error EnforcedPause(); + + /** + * @dev The operation failed because the contract is not paused. + */ + error ExpectedPause(); + + /** + * @dev Initializes the contract in unpaused state. + */ + constructor() { + _paused = false; + } + + /** + * @dev Modifier to make a function callable only when the contract is not paused. + * + * Requirements: + * + * - The contract must not be paused. + */ + modifier whenNotPaused() { + _requireNotPaused(); + _; + } + + /** + * @dev Modifier to make a function callable only when the contract is paused. + * + * Requirements: + * + * - The contract must be paused. + */ + modifier whenPaused() { + _requirePaused(); + _; + } + + /** + * @dev Returns true if the contract is paused, and false otherwise. + */ + function paused() public view virtual returns (bool) { + return _paused; + } + + /** + * @dev Throws if the contract is paused. + */ + function _requireNotPaused() internal view virtual { + if (paused()) { + revert EnforcedPause(); + } + } + + /** + * @dev Throws if the contract is not paused. + */ + function _requirePaused() internal view virtual { + if (!paused()) { + revert ExpectedPause(); + } + } + + /** + * @dev Triggers stopped state. + * + * Requirements: + * + * - The contract must not be paused. + */ + function _pause() internal virtual whenNotPaused { + _paused = true; + emit Paused(_msgSender()); + } + + /** + * @dev Returns to normal state. + * + * Requirements: + * + * - The contract must be paused. + */ + function _unpause() internal virtual whenPaused { + _paused = false; + emit Unpaused(_msgSender()); + } +} diff --git a/CVLByExample/UniswapHooks/lib/uniswap-hooks/lib/v4-core/lib/openzeppelin-contracts/contracts/utils/README.adoc b/CVLByExample/UniswapHooks/lib/uniswap-hooks/lib/v4-core/lib/openzeppelin-contracts/contracts/utils/README.adoc new file mode 100644 index 00000000..089f2ef8 --- /dev/null +++ b/CVLByExample/UniswapHooks/lib/uniswap-hooks/lib/v4-core/lib/openzeppelin-contracts/contracts/utils/README.adoc @@ -0,0 +1,108 @@ += Utilities + +[.readme-notice] +NOTE: This document is better viewed at https://docs.openzeppelin.com/contracts/api/utils + +Miscellaneous contracts and libraries containing utility functions you can use to improve security, work with new data types, or safely use low-level primitives. + + * {Math}, {SignedMath}: Implementation of various arithmetic functions. + * {SafeCast}: Checked downcasting functions to avoid silent truncation. + * {ECDSA}, {MessageHashUtils}: Libraries for interacting with ECDSA signatures. + * {SignatureChecker}: A library helper to support regular ECDSA from EOAs as well as ERC-1271 signatures for smart contracts. + * {MerkleProof}: Functions for verifying https://en.wikipedia.org/wiki/Merkle_tree[Merkle Tree] proofs. + * {EIP712}: Contract with functions to allow processing signed typed structure data according to https://eips.ethereum.org/EIPS/eip-712[EIP-712]. + * {ReentrancyGuard}: A modifier that can prevent reentrancy during certain functions. + * {Pausable}: A common emergency response mechanism that can pause functionality while a remediation is pending. + * {Nonces}: Utility for tracking and verifying address nonces that only increment. + * {ERC165, ERC165Checker}: Utilities for inspecting interfaces supported by contracts. + * {BitMaps}: A simple library to manage boolean value mapped to a numerical index in an efficient way. + * {EnumerableMap}: A type like Solidity's https://solidity.readthedocs.io/en/latest/types.html#mapping-types[`mapping`], but with key-value _enumeration_: this will let you know how many entries a mapping has, and iterate over them (which is not possible with `mapping`). + * {EnumerableSet}: Like {EnumerableMap}, but for https://en.wikipedia.org/wiki/Set_(abstract_data_type)[sets]. Can be used to store privileged accounts, issued IDs, etc. + * {DoubleEndedQueue}: An implementation of a https://en.wikipedia.org/wiki/Double-ended_queue[double ended queue] whose values can be removed added or remove from both sides. Useful for FIFO and LIFO structures. + * {Checkpoints}: A data structure to store values mapped to an strictly increasing key. Can be used for storing and accessing values over time. + * {Create2}: Wrapper around the https://blog.openzeppelin.com/getting-the-most-out-of-create2/[`CREATE2` EVM opcode] for safe use without having to deal with low-level assembly. + * {Address}: Collection of functions for overloading Solidity's https://docs.soliditylang.org/en/latest/types.html#address[`address`] type. + * {Arrays}: Collection of functions that operate on https://docs.soliditylang.org/en/latest/types.html#arrays[`arrays`]. + * {Base64}: On-chain base64 and base64URL encoding according to https://datatracker.ietf.org/doc/html/rfc4648[RFC-4648]. + * {Strings}: Common operations for strings formatting. + * {ShortString}: Library to encode (and decode) short strings into (or from) a single bytes32 slot for optimizing costs. Short strings are limited to 31 characters. + * {StorageSlot}: Methods for accessing specific storage slots formatted as common primitive types. + * {Multicall}: Abstract contract with an utility to allow batching together multiple calls in a single transaction. Useful for allowing EOAs to perform multiple operations at once. + * {Context}: An utility for abstracting the sender and calldata in the current execution context. + +[NOTE] +==== +Because Solidity does not support generic types, {EnumerableMap} and {EnumerableSet} are specialized to a limited number of key-value types. +==== + +== Math + +{{Math}} + +{{SignedMath}} + +{{SafeCast}} + +== Cryptography + +{{ECDSA}} + +{{MessageHashUtils}} + +{{SignatureChecker}} + +{{MerkleProof}} + +{{EIP712}} + +== Security + +{{ReentrancyGuard}} + +{{Pausable}} + +{{Nonces}} + +== Introspection + +This set of interfaces and contracts deal with https://en.wikipedia.org/wiki/Type_introspection[type introspection] of contracts, that is, examining which functions can be called on them. This is usually referred to as a contract's _interface_. + +Ethereum contracts have no native concept of an interface, so applications must usually simply trust they are not making an incorrect call. For trusted setups this is a non-issue, but often unknown and untrusted third-party addresses need to be interacted with. There may even not be any direct calls to them! (e.g. `ERC20` tokens may be sent to a contract that lacks a way to transfer them out of it, locking them forever). In these cases, a contract _declaring_ its interface can be very helpful in preventing errors. + +{{IERC165}} + +{{ERC165}} + +{{ERC165Checker}} + +== Data Structures + +{{BitMaps}} + +{{EnumerableMap}} + +{{EnumerableSet}} + +{{DoubleEndedQueue}} + +{{Checkpoints}} + +== Libraries + +{{Create2}} + +{{Address}} + +{{Arrays}} + +{{Base64}} + +{{Strings}} + +{{ShortStrings}} + +{{StorageSlot}} + +{{Multicall}} + +{{Context}} diff --git a/CVLByExample/UniswapHooks/lib/uniswap-hooks/lib/v4-core/lib/openzeppelin-contracts/contracts/utils/ReentrancyGuard.sol b/CVLByExample/UniswapHooks/lib/uniswap-hooks/lib/v4-core/lib/openzeppelin-contracts/contracts/utils/ReentrancyGuard.sol new file mode 100644 index 00000000..291d92fd --- /dev/null +++ b/CVLByExample/UniswapHooks/lib/uniswap-hooks/lib/v4-core/lib/openzeppelin-contracts/contracts/utils/ReentrancyGuard.sol @@ -0,0 +1,84 @@ +// SPDX-License-Identifier: MIT +// OpenZeppelin Contracts (last updated v5.0.0) (utils/ReentrancyGuard.sol) + +pragma solidity ^0.8.20; + +/** + * @dev Contract module that helps prevent reentrant calls to a function. + * + * Inheriting from `ReentrancyGuard` will make the {nonReentrant} modifier + * available, which can be applied to functions to make sure there are no nested + * (reentrant) calls to them. + * + * Note that because there is a single `nonReentrant` guard, functions marked as + * `nonReentrant` may not call one another. This can be worked around by making + * those functions `private`, and then adding `external` `nonReentrant` entry + * points to them. + * + * TIP: If you would like to learn more about reentrancy and alternative ways + * to protect against it, check out our blog post + * https://blog.openzeppelin.com/reentrancy-after-istanbul/[Reentrancy After Istanbul]. + */ +abstract contract ReentrancyGuard { + // Booleans are more expensive than uint256 or any type that takes up a full + // word because each write operation emits an extra SLOAD to first read the + // slot's contents, replace the bits taken up by the boolean, and then write + // back. This is the compiler's defense against contract upgrades and + // pointer aliasing, and it cannot be disabled. + + // The values being non-zero value makes deployment a bit more expensive, + // but in exchange the refund on every call to nonReentrant will be lower in + // amount. Since refunds are capped to a percentage of the total + // transaction's gas, it is best to keep them low in cases like this one, to + // increase the likelihood of the full refund coming into effect. + uint256 private constant NOT_ENTERED = 1; + uint256 private constant ENTERED = 2; + + uint256 private _status; + + /** + * @dev Unauthorized reentrant call. + */ + error ReentrancyGuardReentrantCall(); + + constructor() { + _status = NOT_ENTERED; + } + + /** + * @dev Prevents a contract from calling itself, directly or indirectly. + * Calling a `nonReentrant` function from another `nonReentrant` + * function is not supported. It is possible to prevent this from happening + * by making the `nonReentrant` function external, and making it call a + * `private` function that does the actual work. + */ + modifier nonReentrant() { + _nonReentrantBefore(); + _; + _nonReentrantAfter(); + } + + function _nonReentrantBefore() private { + // On the first call to nonReentrant, _status will be NOT_ENTERED + if (_status == ENTERED) { + revert ReentrancyGuardReentrantCall(); + } + + // Any calls to nonReentrant after this point will fail + _status = ENTERED; + } + + function _nonReentrantAfter() private { + // By storing the original value once again, a refund is triggered (see + // https://eips.ethereum.org/EIPS/eip-2200) + _status = NOT_ENTERED; + } + + /** + * @dev Returns true if the reentrancy guard is currently set to "entered", which indicates there is a + * `nonReentrant` function in the call stack. + */ + function _reentrancyGuardEntered() internal view returns (bool) { + return _status == ENTERED; + } +} diff --git a/CVLByExample/UniswapHooks/lib/uniswap-hooks/lib/v4-core/lib/openzeppelin-contracts/contracts/utils/ShortStrings.sol b/CVLByExample/UniswapHooks/lib/uniswap-hooks/lib/v4-core/lib/openzeppelin-contracts/contracts/utils/ShortStrings.sol new file mode 100644 index 00000000..fdfe774d --- /dev/null +++ b/CVLByExample/UniswapHooks/lib/uniswap-hooks/lib/v4-core/lib/openzeppelin-contracts/contracts/utils/ShortStrings.sol @@ -0,0 +1,123 @@ +// SPDX-License-Identifier: MIT +// OpenZeppelin Contracts (last updated v5.0.0) (utils/ShortStrings.sol) + +pragma solidity ^0.8.20; + +import {StorageSlot} from "./StorageSlot.sol"; + +// | string | 0xAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA | +// | length | 0x BB | +type ShortString is bytes32; + +/** + * @dev This library provides functions to convert short memory strings + * into a `ShortString` type that can be used as an immutable variable. + * + * Strings of arbitrary length can be optimized using this library if + * they are short enough (up to 31 bytes) by packing them with their + * length (1 byte) in a single EVM word (32 bytes). Additionally, a + * fallback mechanism can be used for every other case. + * + * Usage example: + * + * ```solidity + * contract Named { + * using ShortStrings for *; + * + * ShortString private immutable _name; + * string private _nameFallback; + * + * constructor(string memory contractName) { + * _name = contractName.toShortStringWithFallback(_nameFallback); + * } + * + * function name() external view returns (string memory) { + * return _name.toStringWithFallback(_nameFallback); + * } + * } + * ``` + */ +library ShortStrings { + // Used as an identifier for strings longer than 31 bytes. + bytes32 private constant FALLBACK_SENTINEL = 0x00000000000000000000000000000000000000000000000000000000000000FF; + + error StringTooLong(string str); + error InvalidShortString(); + + /** + * @dev Encode a string of at most 31 chars into a `ShortString`. + * + * This will trigger a `StringTooLong` error is the input string is too long. + */ + function toShortString(string memory str) internal pure returns (ShortString) { + bytes memory bstr = bytes(str); + if (bstr.length > 31) { + revert StringTooLong(str); + } + return ShortString.wrap(bytes32(uint256(bytes32(bstr)) | bstr.length)); + } + + /** + * @dev Decode a `ShortString` back to a "normal" string. + */ + function toString(ShortString sstr) internal pure returns (string memory) { + uint256 len = byteLength(sstr); + // using `new string(len)` would work locally but is not memory safe. + string memory str = new string(32); + /// @solidity memory-safe-assembly + assembly { + mstore(str, len) + mstore(add(str, 0x20), sstr) + } + return str; + } + + /** + * @dev Return the length of a `ShortString`. + */ + function byteLength(ShortString sstr) internal pure returns (uint256) { + uint256 result = uint256(ShortString.unwrap(sstr)) & 0xFF; + if (result > 31) { + revert InvalidShortString(); + } + return result; + } + + /** + * @dev Encode a string into a `ShortString`, or write it to storage if it is too long. + */ + function toShortStringWithFallback(string memory value, string storage store) internal returns (ShortString) { + if (bytes(value).length < 32) { + return toShortString(value); + } else { + StorageSlot.getStringSlot(store).value = value; + return ShortString.wrap(FALLBACK_SENTINEL); + } + } + + /** + * @dev Decode a string that was encoded to `ShortString` or written to storage using {setWithFallback}. + */ + function toStringWithFallback(ShortString value, string storage store) internal pure returns (string memory) { + if (ShortString.unwrap(value) != FALLBACK_SENTINEL) { + return toString(value); + } else { + return store; + } + } + + /** + * @dev Return the length of a string that was encoded to `ShortString` or written to storage using + * {setWithFallback}. + * + * WARNING: This will return the "byte length" of the string. This may not reflect the actual length in terms of + * actual characters as the UTF-8 encoding of a single character can span over multiple bytes. + */ + function byteLengthWithFallback(ShortString value, string storage store) internal view returns (uint256) { + if (ShortString.unwrap(value) != FALLBACK_SENTINEL) { + return byteLength(value); + } else { + return bytes(store).length; + } + } +} diff --git a/CVLByExample/UniswapHooks/lib/uniswap-hooks/lib/v4-core/lib/openzeppelin-contracts/contracts/utils/StorageSlot.sol b/CVLByExample/UniswapHooks/lib/uniswap-hooks/lib/v4-core/lib/openzeppelin-contracts/contracts/utils/StorageSlot.sol new file mode 100644 index 00000000..08418327 --- /dev/null +++ b/CVLByExample/UniswapHooks/lib/uniswap-hooks/lib/v4-core/lib/openzeppelin-contracts/contracts/utils/StorageSlot.sol @@ -0,0 +1,135 @@ +// SPDX-License-Identifier: MIT +// OpenZeppelin Contracts (last updated v5.0.0) (utils/StorageSlot.sol) +// This file was procedurally generated from scripts/generate/templates/StorageSlot.js. + +pragma solidity ^0.8.20; + +/** + * @dev Library for reading and writing primitive types to specific storage slots. + * + * Storage slots are often used to avoid storage conflict when dealing with upgradeable contracts. + * This library helps with reading and writing to such slots without the need for inline assembly. + * + * The functions in this library return Slot structs that contain a `value` member that can be used to read or write. + * + * Example usage to set ERC1967 implementation slot: + * ```solidity + * contract ERC1967 { + * bytes32 internal constant _IMPLEMENTATION_SLOT = 0x360894a13ba1a3210667c828492db98dca3e2076cc3735a920a3ca505d382bbc; + * + * function _getImplementation() internal view returns (address) { + * return StorageSlot.getAddressSlot(_IMPLEMENTATION_SLOT).value; + * } + * + * function _setImplementation(address newImplementation) internal { + * require(newImplementation.code.length > 0); + * StorageSlot.getAddressSlot(_IMPLEMENTATION_SLOT).value = newImplementation; + * } + * } + * ``` + */ +library StorageSlot { + struct AddressSlot { + address value; + } + + struct BooleanSlot { + bool value; + } + + struct Bytes32Slot { + bytes32 value; + } + + struct Uint256Slot { + uint256 value; + } + + struct StringSlot { + string value; + } + + struct BytesSlot { + bytes value; + } + + /** + * @dev Returns an `AddressSlot` with member `value` located at `slot`. + */ + function getAddressSlot(bytes32 slot) internal pure returns (AddressSlot storage r) { + /// @solidity memory-safe-assembly + assembly { + r.slot := slot + } + } + + /** + * @dev Returns an `BooleanSlot` with member `value` located at `slot`. + */ + function getBooleanSlot(bytes32 slot) internal pure returns (BooleanSlot storage r) { + /// @solidity memory-safe-assembly + assembly { + r.slot := slot + } + } + + /** + * @dev Returns an `Bytes32Slot` with member `value` located at `slot`. + */ + function getBytes32Slot(bytes32 slot) internal pure returns (Bytes32Slot storage r) { + /// @solidity memory-safe-assembly + assembly { + r.slot := slot + } + } + + /** + * @dev Returns an `Uint256Slot` with member `value` located at `slot`. + */ + function getUint256Slot(bytes32 slot) internal pure returns (Uint256Slot storage r) { + /// @solidity memory-safe-assembly + assembly { + r.slot := slot + } + } + + /** + * @dev Returns an `StringSlot` with member `value` located at `slot`. + */ + function getStringSlot(bytes32 slot) internal pure returns (StringSlot storage r) { + /// @solidity memory-safe-assembly + assembly { + r.slot := slot + } + } + + /** + * @dev Returns an `StringSlot` representation of the string storage pointer `store`. + */ + function getStringSlot(string storage store) internal pure returns (StringSlot storage r) { + /// @solidity memory-safe-assembly + assembly { + r.slot := store.slot + } + } + + /** + * @dev Returns an `BytesSlot` with member `value` located at `slot`. + */ + function getBytesSlot(bytes32 slot) internal pure returns (BytesSlot storage r) { + /// @solidity memory-safe-assembly + assembly { + r.slot := slot + } + } + + /** + * @dev Returns an `BytesSlot` representation of the bytes storage pointer `store`. + */ + function getBytesSlot(bytes storage store) internal pure returns (BytesSlot storage r) { + /// @solidity memory-safe-assembly + assembly { + r.slot := store.slot + } + } +} diff --git a/CVLByExample/UniswapHooks/lib/uniswap-hooks/lib/v4-core/lib/openzeppelin-contracts/contracts/utils/Strings.sol b/CVLByExample/UniswapHooks/lib/uniswap-hooks/lib/v4-core/lib/openzeppelin-contracts/contracts/utils/Strings.sol new file mode 100644 index 00000000..b2c0a40f --- /dev/null +++ b/CVLByExample/UniswapHooks/lib/uniswap-hooks/lib/v4-core/lib/openzeppelin-contracts/contracts/utils/Strings.sol @@ -0,0 +1,94 @@ +// SPDX-License-Identifier: MIT +// OpenZeppelin Contracts (last updated v5.0.0) (utils/Strings.sol) + +pragma solidity ^0.8.20; + +import {Math} from "./math/Math.sol"; +import {SignedMath} from "./math/SignedMath.sol"; + +/** + * @dev String operations. + */ +library Strings { + bytes16 private constant HEX_DIGITS = "0123456789abcdef"; + uint8 private constant ADDRESS_LENGTH = 20; + + /** + * @dev The `value` string doesn't fit in the specified `length`. + */ + error StringsInsufficientHexLength(uint256 value, uint256 length); + + /** + * @dev Converts a `uint256` to its ASCII `string` decimal representation. + */ + function toString(uint256 value) internal pure returns (string memory) { + unchecked { + uint256 length = Math.log10(value) + 1; + string memory buffer = new string(length); + uint256 ptr; + /// @solidity memory-safe-assembly + assembly { + ptr := add(buffer, add(32, length)) + } + while (true) { + ptr--; + /// @solidity memory-safe-assembly + assembly { + mstore8(ptr, byte(mod(value, 10), HEX_DIGITS)) + } + value /= 10; + if (value == 0) break; + } + return buffer; + } + } + + /** + * @dev Converts a `int256` to its ASCII `string` decimal representation. + */ + function toStringSigned(int256 value) internal pure returns (string memory) { + return string.concat(value < 0 ? "-" : "", toString(SignedMath.abs(value))); + } + + /** + * @dev Converts a `uint256` to its ASCII `string` hexadecimal representation. + */ + function toHexString(uint256 value) internal pure returns (string memory) { + unchecked { + return toHexString(value, Math.log256(value) + 1); + } + } + + /** + * @dev Converts a `uint256` to its ASCII `string` hexadecimal representation with fixed length. + */ + function toHexString(uint256 value, uint256 length) internal pure returns (string memory) { + uint256 localValue = value; + bytes memory buffer = new bytes(2 * length + 2); + buffer[0] = "0"; + buffer[1] = "x"; + for (uint256 i = 2 * length + 1; i > 1; --i) { + buffer[i] = HEX_DIGITS[localValue & 0xf]; + localValue >>= 4; + } + if (localValue != 0) { + revert StringsInsufficientHexLength(value, length); + } + return string(buffer); + } + + /** + * @dev Converts an `address` with fixed length of 20 bytes to its not checksummed ASCII `string` hexadecimal + * representation. + */ + function toHexString(address addr) internal pure returns (string memory) { + return toHexString(uint256(uint160(addr)), ADDRESS_LENGTH); + } + + /** + * @dev Returns true if the two strings are equal. + */ + function equal(string memory a, string memory b) internal pure returns (bool) { + return bytes(a).length == bytes(b).length && keccak256(bytes(a)) == keccak256(bytes(b)); + } +} diff --git a/CVLByExample/UniswapHooks/lib/uniswap-hooks/lib/v4-core/lib/openzeppelin-contracts/contracts/utils/cryptography/ECDSA.sol b/CVLByExample/UniswapHooks/lib/uniswap-hooks/lib/v4-core/lib/openzeppelin-contracts/contracts/utils/cryptography/ECDSA.sol new file mode 100644 index 00000000..04b3e5e0 --- /dev/null +++ b/CVLByExample/UniswapHooks/lib/uniswap-hooks/lib/v4-core/lib/openzeppelin-contracts/contracts/utils/cryptography/ECDSA.sol @@ -0,0 +1,174 @@ +// SPDX-License-Identifier: MIT +// OpenZeppelin Contracts (last updated v5.0.0) (utils/cryptography/ECDSA.sol) + +pragma solidity ^0.8.20; + +/** + * @dev Elliptic Curve Digital Signature Algorithm (ECDSA) operations. + * + * These functions can be used to verify that a message was signed by the holder + * of the private keys of a given address. + */ +library ECDSA { + enum RecoverError { + NoError, + InvalidSignature, + InvalidSignatureLength, + InvalidSignatureS + } + + /** + * @dev The signature derives the `address(0)`. + */ + error ECDSAInvalidSignature(); + + /** + * @dev The signature has an invalid length. + */ + error ECDSAInvalidSignatureLength(uint256 length); + + /** + * @dev The signature has an S value that is in the upper half order. + */ + error ECDSAInvalidSignatureS(bytes32 s); + + /** + * @dev Returns the address that signed a hashed message (`hash`) with `signature` or an error. This will not + * return address(0) without also returning an error description. Errors are documented using an enum (error type) + * and a bytes32 providing additional information about the error. + * + * If no error is returned, then the address can be used for verification purposes. + * + * The `ecrecover` EVM precompile allows for malleable (non-unique) signatures: + * this function rejects them by requiring the `s` value to be in the lower + * half order, and the `v` value to be either 27 or 28. + * + * IMPORTANT: `hash` _must_ be the result of a hash operation for the + * verification to be secure: it is possible to craft signatures that + * recover to arbitrary addresses for non-hashed data. A safe way to ensure + * this is by receiving a hash of the original message (which may otherwise + * be too long), and then calling {MessageHashUtils-toEthSignedMessageHash} on it. + * + * Documentation for signature generation: + * - with https://web3js.readthedocs.io/en/v1.3.4/web3-eth-accounts.html#sign[Web3.js] + * - with https://docs.ethers.io/v5/api/signer/#Signer-signMessage[ethers] + */ + function tryRecover(bytes32 hash, bytes memory signature) internal pure returns (address, RecoverError, bytes32) { + if (signature.length == 65) { + bytes32 r; + bytes32 s; + uint8 v; + // ecrecover takes the signature parameters, and the only way to get them + // currently is to use assembly. + /// @solidity memory-safe-assembly + assembly { + r := mload(add(signature, 0x20)) + s := mload(add(signature, 0x40)) + v := byte(0, mload(add(signature, 0x60))) + } + return tryRecover(hash, v, r, s); + } else { + return (address(0), RecoverError.InvalidSignatureLength, bytes32(signature.length)); + } + } + + /** + * @dev Returns the address that signed a hashed message (`hash`) with + * `signature`. This address can then be used for verification purposes. + * + * The `ecrecover` EVM precompile allows for malleable (non-unique) signatures: + * this function rejects them by requiring the `s` value to be in the lower + * half order, and the `v` value to be either 27 or 28. + * + * IMPORTANT: `hash` _must_ be the result of a hash operation for the + * verification to be secure: it is possible to craft signatures that + * recover to arbitrary addresses for non-hashed data. A safe way to ensure + * this is by receiving a hash of the original message (which may otherwise + * be too long), and then calling {MessageHashUtils-toEthSignedMessageHash} on it. + */ + function recover(bytes32 hash, bytes memory signature) internal pure returns (address) { + (address recovered, RecoverError error, bytes32 errorArg) = tryRecover(hash, signature); + _throwError(error, errorArg); + return recovered; + } + + /** + * @dev Overload of {ECDSA-tryRecover} that receives the `r` and `vs` short-signature fields separately. + * + * See https://eips.ethereum.org/EIPS/eip-2098[EIP-2098 short signatures] + */ + function tryRecover(bytes32 hash, bytes32 r, bytes32 vs) internal pure returns (address, RecoverError, bytes32) { + unchecked { + bytes32 s = vs & bytes32(0x7fffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff); + // We do not check for an overflow here since the shift operation results in 0 or 1. + uint8 v = uint8((uint256(vs) >> 255) + 27); + return tryRecover(hash, v, r, s); + } + } + + /** + * @dev Overload of {ECDSA-recover} that receives the `r and `vs` short-signature fields separately. + */ + function recover(bytes32 hash, bytes32 r, bytes32 vs) internal pure returns (address) { + (address recovered, RecoverError error, bytes32 errorArg) = tryRecover(hash, r, vs); + _throwError(error, errorArg); + return recovered; + } + + /** + * @dev Overload of {ECDSA-tryRecover} that receives the `v`, + * `r` and `s` signature fields separately. + */ + function tryRecover( + bytes32 hash, + uint8 v, + bytes32 r, + bytes32 s + ) internal pure returns (address, RecoverError, bytes32) { + // EIP-2 still allows signature malleability for ecrecover(). Remove this possibility and make the signature + // unique. Appendix F in the Ethereum Yellow paper (https://ethereum.github.io/yellowpaper/paper.pdf), defines + // the valid range for s in (301): 0 < s < secp256k1n ÷ 2 + 1, and for v in (302): v ∈ {27, 28}. Most + // signatures from current libraries generate a unique signature with an s-value in the lower half order. + // + // If your library generates malleable signatures, such as s-values in the upper range, calculate a new s-value + // with 0xFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFEBAAEDCE6AF48A03BBFD25E8CD0364141 - s1 and flip v from 27 to 28 or + // vice versa. If your library also generates signatures with 0/1 for v instead 27/28, add 27 to v to accept + // these malleable signatures as well. + if (uint256(s) > 0x7FFFFFFFFFFFFFFFFFFFFFFFFFFFFFFF5D576E7357A4501DDFE92F46681B20A0) { + return (address(0), RecoverError.InvalidSignatureS, s); + } + + // If the signature is valid (and not malleable), return the signer address + address signer = ecrecover(hash, v, r, s); + if (signer == address(0)) { + return (address(0), RecoverError.InvalidSignature, bytes32(0)); + } + + return (signer, RecoverError.NoError, bytes32(0)); + } + + /** + * @dev Overload of {ECDSA-recover} that receives the `v`, + * `r` and `s` signature fields separately. + */ + function recover(bytes32 hash, uint8 v, bytes32 r, bytes32 s) internal pure returns (address) { + (address recovered, RecoverError error, bytes32 errorArg) = tryRecover(hash, v, r, s); + _throwError(error, errorArg); + return recovered; + } + + /** + * @dev Optionally reverts with the corresponding custom error according to the `error` argument provided. + */ + function _throwError(RecoverError error, bytes32 errorArg) private pure { + if (error == RecoverError.NoError) { + return; // no error: do nothing + } else if (error == RecoverError.InvalidSignature) { + revert ECDSAInvalidSignature(); + } else if (error == RecoverError.InvalidSignatureLength) { + revert ECDSAInvalidSignatureLength(uint256(errorArg)); + } else if (error == RecoverError.InvalidSignatureS) { + revert ECDSAInvalidSignatureS(errorArg); + } + } +} diff --git a/CVLByExample/UniswapHooks/lib/uniswap-hooks/lib/v4-core/lib/openzeppelin-contracts/contracts/utils/cryptography/EIP712.sol b/CVLByExample/UniswapHooks/lib/uniswap-hooks/lib/v4-core/lib/openzeppelin-contracts/contracts/utils/cryptography/EIP712.sol new file mode 100644 index 00000000..8e548cdd --- /dev/null +++ b/CVLByExample/UniswapHooks/lib/uniswap-hooks/lib/v4-core/lib/openzeppelin-contracts/contracts/utils/cryptography/EIP712.sol @@ -0,0 +1,160 @@ +// SPDX-License-Identifier: MIT +// OpenZeppelin Contracts (last updated v5.0.0) (utils/cryptography/EIP712.sol) + +pragma solidity ^0.8.20; + +import {MessageHashUtils} from "./MessageHashUtils.sol"; +import {ShortStrings, ShortString} from "../ShortStrings.sol"; +import {IERC5267} from "../../interfaces/IERC5267.sol"; + +/** + * @dev https://eips.ethereum.org/EIPS/eip-712[EIP 712] is a standard for hashing and signing of typed structured data. + * + * The encoding scheme specified in the EIP requires a domain separator and a hash of the typed structured data, whose + * encoding is very generic and therefore its implementation in Solidity is not feasible, thus this contract + * does not implement the encoding itself. Protocols need to implement the type-specific encoding they need in order to + * produce the hash of their typed data using a combination of `abi.encode` and `keccak256`. + * + * This contract implements the EIP 712 domain separator ({_domainSeparatorV4}) that is used as part of the encoding + * scheme, and the final step of the encoding to obtain the message digest that is then signed via ECDSA + * ({_hashTypedDataV4}). + * + * The implementation of the domain separator was designed to be as efficient as possible while still properly updating + * the chain id to protect against replay attacks on an eventual fork of the chain. + * + * NOTE: This contract implements the version of the encoding known as "v4", as implemented by the JSON RPC method + * https://docs.metamask.io/guide/signing-data.html[`eth_signTypedDataV4` in MetaMask]. + * + * NOTE: In the upgradeable version of this contract, the cached values will correspond to the address, and the domain + * separator of the implementation contract. This will cause the {_domainSeparatorV4} function to always rebuild the + * separator from the immutable values, which is cheaper than accessing a cached version in cold storage. + * + * @custom:oz-upgrades-unsafe-allow state-variable-immutable + */ +abstract contract EIP712 is IERC5267 { + using ShortStrings for *; + + bytes32 private constant TYPE_HASH = + keccak256("EIP712Domain(string name,string version,uint256 chainId,address verifyingContract)"); + + // Cache the domain separator as an immutable value, but also store the chain id that it corresponds to, in order to + // invalidate the cached domain separator if the chain id changes. + bytes32 private immutable _cachedDomainSeparator; + uint256 private immutable _cachedChainId; + address private immutable _cachedThis; + + bytes32 private immutable _hashedName; + bytes32 private immutable _hashedVersion; + + ShortString private immutable _name; + ShortString private immutable _version; + string private _nameFallback; + string private _versionFallback; + + /** + * @dev Initializes the domain separator and parameter caches. + * + * The meaning of `name` and `version` is specified in + * https://eips.ethereum.org/EIPS/eip-712#definition-of-domainseparator[EIP 712]: + * + * - `name`: the user readable name of the signing domain, i.e. the name of the DApp or the protocol. + * - `version`: the current major version of the signing domain. + * + * NOTE: These parameters cannot be changed except through a xref:learn::upgrading-smart-contracts.adoc[smart + * contract upgrade]. + */ + constructor(string memory name, string memory version) { + _name = name.toShortStringWithFallback(_nameFallback); + _version = version.toShortStringWithFallback(_versionFallback); + _hashedName = keccak256(bytes(name)); + _hashedVersion = keccak256(bytes(version)); + + _cachedChainId = block.chainid; + _cachedDomainSeparator = _buildDomainSeparator(); + _cachedThis = address(this); + } + + /** + * @dev Returns the domain separator for the current chain. + */ + function _domainSeparatorV4() internal view returns (bytes32) { + if (address(this) == _cachedThis && block.chainid == _cachedChainId) { + return _cachedDomainSeparator; + } else { + return _buildDomainSeparator(); + } + } + + function _buildDomainSeparator() private view returns (bytes32) { + return keccak256(abi.encode(TYPE_HASH, _hashedName, _hashedVersion, block.chainid, address(this))); + } + + /** + * @dev Given an already https://eips.ethereum.org/EIPS/eip-712#definition-of-hashstruct[hashed struct], this + * function returns the hash of the fully encoded EIP712 message for this domain. + * + * This hash can be used together with {ECDSA-recover} to obtain the signer of a message. For example: + * + * ```solidity + * bytes32 digest = _hashTypedDataV4(keccak256(abi.encode( + * keccak256("Mail(address to,string contents)"), + * mailTo, + * keccak256(bytes(mailContents)) + * ))); + * address signer = ECDSA.recover(digest, signature); + * ``` + */ + function _hashTypedDataV4(bytes32 structHash) internal view virtual returns (bytes32) { + return MessageHashUtils.toTypedDataHash(_domainSeparatorV4(), structHash); + } + + /** + * @dev See {IERC-5267}. + */ + function eip712Domain() + public + view + virtual + returns ( + bytes1 fields, + string memory name, + string memory version, + uint256 chainId, + address verifyingContract, + bytes32 salt, + uint256[] memory extensions + ) + { + return ( + hex"0f", // 01111 + _EIP712Name(), + _EIP712Version(), + block.chainid, + address(this), + bytes32(0), + new uint256[](0) + ); + } + + /** + * @dev The name parameter for the EIP712 domain. + * + * NOTE: By default this function reads _name which is an immutable value. + * It only reads from storage if necessary (in case the value is too large to fit in a ShortString). + */ + // solhint-disable-next-line func-name-mixedcase + function _EIP712Name() internal view returns (string memory) { + return _name.toStringWithFallback(_nameFallback); + } + + /** + * @dev The version parameter for the EIP712 domain. + * + * NOTE: By default this function reads _version which is an immutable value. + * It only reads from storage if necessary (in case the value is too large to fit in a ShortString). + */ + // solhint-disable-next-line func-name-mixedcase + function _EIP712Version() internal view returns (string memory) { + return _version.toStringWithFallback(_versionFallback); + } +} diff --git a/CVLByExample/UniswapHooks/lib/uniswap-hooks/lib/v4-core/lib/openzeppelin-contracts/contracts/utils/cryptography/MerkleProof.sol b/CVLByExample/UniswapHooks/lib/uniswap-hooks/lib/v4-core/lib/openzeppelin-contracts/contracts/utils/cryptography/MerkleProof.sol new file mode 100644 index 00000000..525f5da3 --- /dev/null +++ b/CVLByExample/UniswapHooks/lib/uniswap-hooks/lib/v4-core/lib/openzeppelin-contracts/contracts/utils/cryptography/MerkleProof.sol @@ -0,0 +1,232 @@ +// SPDX-License-Identifier: MIT +// OpenZeppelin Contracts (last updated v5.0.0) (utils/cryptography/MerkleProof.sol) + +pragma solidity ^0.8.20; + +/** + * @dev These functions deal with verification of Merkle Tree proofs. + * + * The tree and the proofs can be generated using our + * https://github.com/OpenZeppelin/merkle-tree[JavaScript library]. + * You will find a quickstart guide in the readme. + * + * WARNING: You should avoid using leaf values that are 64 bytes long prior to + * hashing, or use a hash function other than keccak256 for hashing leaves. + * This is because the concatenation of a sorted pair of internal nodes in + * the Merkle tree could be reinterpreted as a leaf value. + * OpenZeppelin's JavaScript library generates Merkle trees that are safe + * against this attack out of the box. + */ +library MerkleProof { + /** + *@dev The multiproof provided is not valid. + */ + error MerkleProofInvalidMultiproof(); + + /** + * @dev Returns true if a `leaf` can be proved to be a part of a Merkle tree + * defined by `root`. For this, a `proof` must be provided, containing + * sibling hashes on the branch from the leaf to the root of the tree. Each + * pair of leaves and each pair of pre-images are assumed to be sorted. + */ + function verify(bytes32[] memory proof, bytes32 root, bytes32 leaf) internal pure returns (bool) { + return processProof(proof, leaf) == root; + } + + /** + * @dev Calldata version of {verify} + */ + function verifyCalldata(bytes32[] calldata proof, bytes32 root, bytes32 leaf) internal pure returns (bool) { + return processProofCalldata(proof, leaf) == root; + } + + /** + * @dev Returns the rebuilt hash obtained by traversing a Merkle tree up + * from `leaf` using `proof`. A `proof` is valid if and only if the rebuilt + * hash matches the root of the tree. When processing the proof, the pairs + * of leafs & pre-images are assumed to be sorted. + */ + function processProof(bytes32[] memory proof, bytes32 leaf) internal pure returns (bytes32) { + bytes32 computedHash = leaf; + for (uint256 i = 0; i < proof.length; i++) { + computedHash = _hashPair(computedHash, proof[i]); + } + return computedHash; + } + + /** + * @dev Calldata version of {processProof} + */ + function processProofCalldata(bytes32[] calldata proof, bytes32 leaf) internal pure returns (bytes32) { + bytes32 computedHash = leaf; + for (uint256 i = 0; i < proof.length; i++) { + computedHash = _hashPair(computedHash, proof[i]); + } + return computedHash; + } + + /** + * @dev Returns true if the `leaves` can be simultaneously proven to be a part of a Merkle tree defined by + * `root`, according to `proof` and `proofFlags` as described in {processMultiProof}. + * + * CAUTION: Not all Merkle trees admit multiproofs. See {processMultiProof} for details. + */ + function multiProofVerify( + bytes32[] memory proof, + bool[] memory proofFlags, + bytes32 root, + bytes32[] memory leaves + ) internal pure returns (bool) { + return processMultiProof(proof, proofFlags, leaves) == root; + } + + /** + * @dev Calldata version of {multiProofVerify} + * + * CAUTION: Not all Merkle trees admit multiproofs. See {processMultiProof} for details. + */ + function multiProofVerifyCalldata( + bytes32[] calldata proof, + bool[] calldata proofFlags, + bytes32 root, + bytes32[] memory leaves + ) internal pure returns (bool) { + return processMultiProofCalldata(proof, proofFlags, leaves) == root; + } + + /** + * @dev Returns the root of a tree reconstructed from `leaves` and sibling nodes in `proof`. The reconstruction + * proceeds by incrementally reconstructing all inner nodes by combining a leaf/inner node with either another + * leaf/inner node or a proof sibling node, depending on whether each `proofFlags` item is true or false + * respectively. + * + * CAUTION: Not all Merkle trees admit multiproofs. To use multiproofs, it is sufficient to ensure that: 1) the tree + * is complete (but not necessarily perfect), 2) the leaves to be proven are in the opposite order they are in the + * tree (i.e., as seen from right to left starting at the deepest layer and continuing at the next layer). + */ + function processMultiProof( + bytes32[] memory proof, + bool[] memory proofFlags, + bytes32[] memory leaves + ) internal pure returns (bytes32 merkleRoot) { + // This function rebuilds the root hash by traversing the tree up from the leaves. The root is rebuilt by + // consuming and producing values on a queue. The queue starts with the `leaves` array, then goes onto the + // `hashes` array. At the end of the process, the last hash in the `hashes` array should contain the root of + // the Merkle tree. + uint256 leavesLen = leaves.length; + uint256 proofLen = proof.length; + uint256 totalHashes = proofFlags.length; + + // Check proof validity. + if (leavesLen + proofLen != totalHashes + 1) { + revert MerkleProofInvalidMultiproof(); + } + + // The xxxPos values are "pointers" to the next value to consume in each array. All accesses are done using + // `xxx[xxxPos++]`, which return the current value and increment the pointer, thus mimicking a queue's "pop". + bytes32[] memory hashes = new bytes32[](totalHashes); + uint256 leafPos = 0; + uint256 hashPos = 0; + uint256 proofPos = 0; + // At each step, we compute the next hash using two values: + // - a value from the "main queue". If not all leaves have been consumed, we get the next leaf, otherwise we + // get the next hash. + // - depending on the flag, either another value from the "main queue" (merging branches) or an element from the + // `proof` array. + for (uint256 i = 0; i < totalHashes; i++) { + bytes32 a = leafPos < leavesLen ? leaves[leafPos++] : hashes[hashPos++]; + bytes32 b = proofFlags[i] + ? (leafPos < leavesLen ? leaves[leafPos++] : hashes[hashPos++]) + : proof[proofPos++]; + hashes[i] = _hashPair(a, b); + } + + if (totalHashes > 0) { + if (proofPos != proofLen) { + revert MerkleProofInvalidMultiproof(); + } + unchecked { + return hashes[totalHashes - 1]; + } + } else if (leavesLen > 0) { + return leaves[0]; + } else { + return proof[0]; + } + } + + /** + * @dev Calldata version of {processMultiProof}. + * + * CAUTION: Not all Merkle trees admit multiproofs. See {processMultiProof} for details. + */ + function processMultiProofCalldata( + bytes32[] calldata proof, + bool[] calldata proofFlags, + bytes32[] memory leaves + ) internal pure returns (bytes32 merkleRoot) { + // This function rebuilds the root hash by traversing the tree up from the leaves. The root is rebuilt by + // consuming and producing values on a queue. The queue starts with the `leaves` array, then goes onto the + // `hashes` array. At the end of the process, the last hash in the `hashes` array should contain the root of + // the Merkle tree. + uint256 leavesLen = leaves.length; + uint256 proofLen = proof.length; + uint256 totalHashes = proofFlags.length; + + // Check proof validity. + if (leavesLen + proofLen != totalHashes + 1) { + revert MerkleProofInvalidMultiproof(); + } + + // The xxxPos values are "pointers" to the next value to consume in each array. All accesses are done using + // `xxx[xxxPos++]`, which return the current value and increment the pointer, thus mimicking a queue's "pop". + bytes32[] memory hashes = new bytes32[](totalHashes); + uint256 leafPos = 0; + uint256 hashPos = 0; + uint256 proofPos = 0; + // At each step, we compute the next hash using two values: + // - a value from the "main queue". If not all leaves have been consumed, we get the next leaf, otherwise we + // get the next hash. + // - depending on the flag, either another value from the "main queue" (merging branches) or an element from the + // `proof` array. + for (uint256 i = 0; i < totalHashes; i++) { + bytes32 a = leafPos < leavesLen ? leaves[leafPos++] : hashes[hashPos++]; + bytes32 b = proofFlags[i] + ? (leafPos < leavesLen ? leaves[leafPos++] : hashes[hashPos++]) + : proof[proofPos++]; + hashes[i] = _hashPair(a, b); + } + + if (totalHashes > 0) { + if (proofPos != proofLen) { + revert MerkleProofInvalidMultiproof(); + } + unchecked { + return hashes[totalHashes - 1]; + } + } else if (leavesLen > 0) { + return leaves[0]; + } else { + return proof[0]; + } + } + + /** + * @dev Sorts the pair (a, b) and hashes the result. + */ + function _hashPair(bytes32 a, bytes32 b) private pure returns (bytes32) { + return a < b ? _efficientHash(a, b) : _efficientHash(b, a); + } + + /** + * @dev Implementation of keccak256(abi.encode(a, b)) that doesn't allocate or expand memory. + */ + function _efficientHash(bytes32 a, bytes32 b) private pure returns (bytes32 value) { + /// @solidity memory-safe-assembly + assembly { + mstore(0x00, a) + mstore(0x20, b) + value := keccak256(0x00, 0x40) + } + } +} diff --git a/CVLByExample/UniswapHooks/lib/uniswap-hooks/lib/v4-core/lib/openzeppelin-contracts/contracts/utils/cryptography/MessageHashUtils.sol b/CVLByExample/UniswapHooks/lib/uniswap-hooks/lib/v4-core/lib/openzeppelin-contracts/contracts/utils/cryptography/MessageHashUtils.sol new file mode 100644 index 00000000..8836693e --- /dev/null +++ b/CVLByExample/UniswapHooks/lib/uniswap-hooks/lib/v4-core/lib/openzeppelin-contracts/contracts/utils/cryptography/MessageHashUtils.sol @@ -0,0 +1,86 @@ +// SPDX-License-Identifier: MIT +// OpenZeppelin Contracts (last updated v5.0.0) (utils/cryptography/MessageHashUtils.sol) + +pragma solidity ^0.8.20; + +import {Strings} from "../Strings.sol"; + +/** + * @dev Signature message hash utilities for producing digests to be consumed by {ECDSA} recovery or signing. + * + * The library provides methods for generating a hash of a message that conforms to the + * https://eips.ethereum.org/EIPS/eip-191[EIP 191] and https://eips.ethereum.org/EIPS/eip-712[EIP 712] + * specifications. + */ +library MessageHashUtils { + /** + * @dev Returns the keccak256 digest of an EIP-191 signed data with version + * `0x45` (`personal_sign` messages). + * + * The digest is calculated by prefixing a bytes32 `messageHash` with + * `"\x19Ethereum Signed Message:\n32"` and hashing the result. It corresponds with the + * hash signed when using the https://eth.wiki/json-rpc/API#eth_sign[`eth_sign`] JSON-RPC method. + * + * NOTE: The `messageHash` parameter is intended to be the result of hashing a raw message with + * keccak256, although any bytes32 value can be safely used because the final digest will + * be re-hashed. + * + * See {ECDSA-recover}. + */ + function toEthSignedMessageHash(bytes32 messageHash) internal pure returns (bytes32 digest) { + /// @solidity memory-safe-assembly + assembly { + mstore(0x00, "\x19Ethereum Signed Message:\n32") // 32 is the bytes-length of messageHash + mstore(0x1c, messageHash) // 0x1c (28) is the length of the prefix + digest := keccak256(0x00, 0x3c) // 0x3c is the length of the prefix (0x1c) + messageHash (0x20) + } + } + + /** + * @dev Returns the keccak256 digest of an EIP-191 signed data with version + * `0x45` (`personal_sign` messages). + * + * The digest is calculated by prefixing an arbitrary `message` with + * `"\x19Ethereum Signed Message:\n" + len(message)` and hashing the result. It corresponds with the + * hash signed when using the https://eth.wiki/json-rpc/API#eth_sign[`eth_sign`] JSON-RPC method. + * + * See {ECDSA-recover}. + */ + function toEthSignedMessageHash(bytes memory message) internal pure returns (bytes32) { + return + keccak256(bytes.concat("\x19Ethereum Signed Message:\n", bytes(Strings.toString(message.length)), message)); + } + + /** + * @dev Returns the keccak256 digest of an EIP-191 signed data with version + * `0x00` (data with intended validator). + * + * The digest is calculated by prefixing an arbitrary `data` with `"\x19\x00"` and the intended + * `validator` address. Then hashing the result. + * + * See {ECDSA-recover}. + */ + function toDataWithIntendedValidatorHash(address validator, bytes memory data) internal pure returns (bytes32) { + return keccak256(abi.encodePacked(hex"19_00", validator, data)); + } + + /** + * @dev Returns the keccak256 digest of an EIP-712 typed data (EIP-191 version `0x01`). + * + * The digest is calculated from a `domainSeparator` and a `structHash`, by prefixing them with + * `\x19\x01` and hashing the result. It corresponds to the hash signed by the + * https://eips.ethereum.org/EIPS/eip-712[`eth_signTypedData`] JSON-RPC method as part of EIP-712. + * + * See {ECDSA-recover}. + */ + function toTypedDataHash(bytes32 domainSeparator, bytes32 structHash) internal pure returns (bytes32 digest) { + /// @solidity memory-safe-assembly + assembly { + let ptr := mload(0x40) + mstore(ptr, hex"19_01") + mstore(add(ptr, 0x02), domainSeparator) + mstore(add(ptr, 0x22), structHash) + digest := keccak256(ptr, 0x42) + } + } +} diff --git a/CVLByExample/UniswapHooks/lib/uniswap-hooks/lib/v4-core/lib/openzeppelin-contracts/contracts/utils/cryptography/SignatureChecker.sol b/CVLByExample/UniswapHooks/lib/uniswap-hooks/lib/v4-core/lib/openzeppelin-contracts/contracts/utils/cryptography/SignatureChecker.sol new file mode 100644 index 00000000..59a2c6d9 --- /dev/null +++ b/CVLByExample/UniswapHooks/lib/uniswap-hooks/lib/v4-core/lib/openzeppelin-contracts/contracts/utils/cryptography/SignatureChecker.sol @@ -0,0 +1,48 @@ +// SPDX-License-Identifier: MIT +// OpenZeppelin Contracts (last updated v5.0.0) (utils/cryptography/SignatureChecker.sol) + +pragma solidity ^0.8.20; + +import {ECDSA} from "./ECDSA.sol"; +import {IERC1271} from "../../interfaces/IERC1271.sol"; + +/** + * @dev Signature verification helper that can be used instead of `ECDSA.recover` to seamlessly support both ECDSA + * signatures from externally owned accounts (EOAs) as well as ERC1271 signatures from smart contract wallets like + * Argent and Safe Wallet (previously Gnosis Safe). + */ +library SignatureChecker { + /** + * @dev Checks if a signature is valid for a given signer and data hash. If the signer is a smart contract, the + * signature is validated against that smart contract using ERC1271, otherwise it's validated using `ECDSA.recover`. + * + * NOTE: Unlike ECDSA signatures, contract signatures are revocable, and the outcome of this function can thus + * change through time. It could return true at block N and false at block N+1 (or the opposite). + */ + function isValidSignatureNow(address signer, bytes32 hash, bytes memory signature) internal view returns (bool) { + (address recovered, ECDSA.RecoverError error, ) = ECDSA.tryRecover(hash, signature); + return + (error == ECDSA.RecoverError.NoError && recovered == signer) || + isValidERC1271SignatureNow(signer, hash, signature); + } + + /** + * @dev Checks if a signature is valid for a given signer and data hash. The signature is validated + * against the signer smart contract using ERC1271. + * + * NOTE: Unlike ECDSA signatures, contract signatures are revocable, and the outcome of this function can thus + * change through time. It could return true at block N and false at block N+1 (or the opposite). + */ + function isValidERC1271SignatureNow( + address signer, + bytes32 hash, + bytes memory signature + ) internal view returns (bool) { + (bool success, bytes memory result) = signer.staticcall( + abi.encodeCall(IERC1271.isValidSignature, (hash, signature)) + ); + return (success && + result.length >= 32 && + abi.decode(result, (bytes32)) == bytes32(IERC1271.isValidSignature.selector)); + } +} diff --git a/CVLByExample/UniswapHooks/lib/uniswap-hooks/lib/v4-core/lib/openzeppelin-contracts/contracts/utils/introspection/ERC165.sol b/CVLByExample/UniswapHooks/lib/uniswap-hooks/lib/v4-core/lib/openzeppelin-contracts/contracts/utils/introspection/ERC165.sol new file mode 100644 index 00000000..1e77b60d --- /dev/null +++ b/CVLByExample/UniswapHooks/lib/uniswap-hooks/lib/v4-core/lib/openzeppelin-contracts/contracts/utils/introspection/ERC165.sol @@ -0,0 +1,27 @@ +// SPDX-License-Identifier: MIT +// OpenZeppelin Contracts (last updated v5.0.0) (utils/introspection/ERC165.sol) + +pragma solidity ^0.8.20; + +import {IERC165} from "./IERC165.sol"; + +/** + * @dev Implementation of the {IERC165} interface. + * + * Contracts that want to implement ERC165 should inherit from this contract and override {supportsInterface} to check + * for the additional interface id that will be supported. For example: + * + * ```solidity + * function supportsInterface(bytes4 interfaceId) public view virtual override returns (bool) { + * return interfaceId == type(MyInterface).interfaceId || super.supportsInterface(interfaceId); + * } + * ``` + */ +abstract contract ERC165 is IERC165 { + /** + * @dev See {IERC165-supportsInterface}. + */ + function supportsInterface(bytes4 interfaceId) public view virtual returns (bool) { + return interfaceId == type(IERC165).interfaceId; + } +} diff --git a/CVLByExample/UniswapHooks/lib/uniswap-hooks/lib/v4-core/lib/openzeppelin-contracts/contracts/utils/introspection/ERC165Checker.sol b/CVLByExample/UniswapHooks/lib/uniswap-hooks/lib/v4-core/lib/openzeppelin-contracts/contracts/utils/introspection/ERC165Checker.sol new file mode 100644 index 00000000..7b522414 --- /dev/null +++ b/CVLByExample/UniswapHooks/lib/uniswap-hooks/lib/v4-core/lib/openzeppelin-contracts/contracts/utils/introspection/ERC165Checker.sol @@ -0,0 +1,124 @@ +// SPDX-License-Identifier: MIT +// OpenZeppelin Contracts (last updated v5.0.0) (utils/introspection/ERC165Checker.sol) + +pragma solidity ^0.8.20; + +import {IERC165} from "./IERC165.sol"; + +/** + * @dev Library used to query support of an interface declared via {IERC165}. + * + * Note that these functions return the actual result of the query: they do not + * `revert` if an interface is not supported. It is up to the caller to decide + * what to do in these cases. + */ +library ERC165Checker { + // As per the EIP-165 spec, no interface should ever match 0xffffffff + bytes4 private constant INTERFACE_ID_INVALID = 0xffffffff; + + /** + * @dev Returns true if `account` supports the {IERC165} interface. + */ + function supportsERC165(address account) internal view returns (bool) { + // Any contract that implements ERC165 must explicitly indicate support of + // InterfaceId_ERC165 and explicitly indicate non-support of InterfaceId_Invalid + return + supportsERC165InterfaceUnchecked(account, type(IERC165).interfaceId) && + !supportsERC165InterfaceUnchecked(account, INTERFACE_ID_INVALID); + } + + /** + * @dev Returns true if `account` supports the interface defined by + * `interfaceId`. Support for {IERC165} itself is queried automatically. + * + * See {IERC165-supportsInterface}. + */ + function supportsInterface(address account, bytes4 interfaceId) internal view returns (bool) { + // query support of both ERC165 as per the spec and support of _interfaceId + return supportsERC165(account) && supportsERC165InterfaceUnchecked(account, interfaceId); + } + + /** + * @dev Returns a boolean array where each value corresponds to the + * interfaces passed in and whether they're supported or not. This allows + * you to batch check interfaces for a contract where your expectation + * is that some interfaces may not be supported. + * + * See {IERC165-supportsInterface}. + */ + function getSupportedInterfaces( + address account, + bytes4[] memory interfaceIds + ) internal view returns (bool[] memory) { + // an array of booleans corresponding to interfaceIds and whether they're supported or not + bool[] memory interfaceIdsSupported = new bool[](interfaceIds.length); + + // query support of ERC165 itself + if (supportsERC165(account)) { + // query support of each interface in interfaceIds + for (uint256 i = 0; i < interfaceIds.length; i++) { + interfaceIdsSupported[i] = supportsERC165InterfaceUnchecked(account, interfaceIds[i]); + } + } + + return interfaceIdsSupported; + } + + /** + * @dev Returns true if `account` supports all the interfaces defined in + * `interfaceIds`. Support for {IERC165} itself is queried automatically. + * + * Batch-querying can lead to gas savings by skipping repeated checks for + * {IERC165} support. + * + * See {IERC165-supportsInterface}. + */ + function supportsAllInterfaces(address account, bytes4[] memory interfaceIds) internal view returns (bool) { + // query support of ERC165 itself + if (!supportsERC165(account)) { + return false; + } + + // query support of each interface in interfaceIds + for (uint256 i = 0; i < interfaceIds.length; i++) { + if (!supportsERC165InterfaceUnchecked(account, interfaceIds[i])) { + return false; + } + } + + // all interfaces supported + return true; + } + + /** + * @notice Query if a contract implements an interface, does not check ERC165 support + * @param account The address of the contract to query for support of an interface + * @param interfaceId The interface identifier, as specified in ERC-165 + * @return true if the contract at account indicates support of the interface with + * identifier interfaceId, false otherwise + * @dev Assumes that account contains a contract that supports ERC165, otherwise + * the behavior of this method is undefined. This precondition can be checked + * with {supportsERC165}. + * + * Some precompiled contracts will falsely indicate support for a given interface, so caution + * should be exercised when using this function. + * + * Interface identification is specified in ERC-165. + */ + function supportsERC165InterfaceUnchecked(address account, bytes4 interfaceId) internal view returns (bool) { + // prepare call + bytes memory encodedParams = abi.encodeCall(IERC165.supportsInterface, (interfaceId)); + + // perform static call + bool success; + uint256 returnSize; + uint256 returnValue; + assembly { + success := staticcall(30000, account, add(encodedParams, 0x20), mload(encodedParams), 0x00, 0x20) + returnSize := returndatasize() + returnValue := mload(0x00) + } + + return success && returnSize >= 0x20 && returnValue > 0; + } +} diff --git a/CVLByExample/UniswapHooks/lib/uniswap-hooks/lib/v4-core/lib/openzeppelin-contracts/contracts/utils/introspection/IERC165.sol b/CVLByExample/UniswapHooks/lib/uniswap-hooks/lib/v4-core/lib/openzeppelin-contracts/contracts/utils/introspection/IERC165.sol new file mode 100644 index 00000000..c09f31fe --- /dev/null +++ b/CVLByExample/UniswapHooks/lib/uniswap-hooks/lib/v4-core/lib/openzeppelin-contracts/contracts/utils/introspection/IERC165.sol @@ -0,0 +1,25 @@ +// SPDX-License-Identifier: MIT +// OpenZeppelin Contracts (last updated v5.0.0) (utils/introspection/IERC165.sol) + +pragma solidity ^0.8.20; + +/** + * @dev Interface of the ERC165 standard, as defined in the + * https://eips.ethereum.org/EIPS/eip-165[EIP]. + * + * Implementers can declare support of contract interfaces, which can then be + * queried by others ({ERC165Checker}). + * + * For an implementation, see {ERC165}. + */ +interface IERC165 { + /** + * @dev Returns true if this contract implements the interface defined by + * `interfaceId`. See the corresponding + * https://eips.ethereum.org/EIPS/eip-165#how-interfaces-are-identified[EIP section] + * to learn more about how these ids are created. + * + * This function call must use less than 30 000 gas. + */ + function supportsInterface(bytes4 interfaceId) external view returns (bool); +} diff --git a/CVLByExample/UniswapHooks/lib/uniswap-hooks/lib/v4-core/lib/openzeppelin-contracts/contracts/utils/math/Math.sol b/CVLByExample/UniswapHooks/lib/uniswap-hooks/lib/v4-core/lib/openzeppelin-contracts/contracts/utils/math/Math.sol new file mode 100644 index 00000000..96815245 --- /dev/null +++ b/CVLByExample/UniswapHooks/lib/uniswap-hooks/lib/v4-core/lib/openzeppelin-contracts/contracts/utils/math/Math.sol @@ -0,0 +1,415 @@ +// SPDX-License-Identifier: MIT +// OpenZeppelin Contracts (last updated v5.0.0) (utils/math/Math.sol) + +pragma solidity ^0.8.20; + +/** + * @dev Standard math utilities missing in the Solidity language. + */ +library Math { + /** + * @dev Muldiv operation overflow. + */ + error MathOverflowedMulDiv(); + + enum Rounding { + Floor, // Toward negative infinity + Ceil, // Toward positive infinity + Trunc, // Toward zero + Expand // Away from zero + } + + /** + * @dev Returns the addition of two unsigned integers, with an overflow flag. + */ + function tryAdd(uint256 a, uint256 b) internal pure returns (bool, uint256) { + unchecked { + uint256 c = a + b; + if (c < a) return (false, 0); + return (true, c); + } + } + + /** + * @dev Returns the subtraction of two unsigned integers, with an overflow flag. + */ + function trySub(uint256 a, uint256 b) internal pure returns (bool, uint256) { + unchecked { + if (b > a) return (false, 0); + return (true, a - b); + } + } + + /** + * @dev Returns the multiplication of two unsigned integers, with an overflow flag. + */ + function tryMul(uint256 a, uint256 b) internal pure returns (bool, uint256) { + unchecked { + // Gas optimization: this is cheaper than requiring 'a' not being zero, but the + // benefit is lost if 'b' is also tested. + // See: https://github.com/OpenZeppelin/openzeppelin-contracts/pull/522 + if (a == 0) return (true, 0); + uint256 c = a * b; + if (c / a != b) return (false, 0); + return (true, c); + } + } + + /** + * @dev Returns the division of two unsigned integers, with a division by zero flag. + */ + function tryDiv(uint256 a, uint256 b) internal pure returns (bool, uint256) { + unchecked { + if (b == 0) return (false, 0); + return (true, a / b); + } + } + + /** + * @dev Returns the remainder of dividing two unsigned integers, with a division by zero flag. + */ + function tryMod(uint256 a, uint256 b) internal pure returns (bool, uint256) { + unchecked { + if (b == 0) return (false, 0); + return (true, a % b); + } + } + + /** + * @dev Returns the largest of two numbers. + */ + function max(uint256 a, uint256 b) internal pure returns (uint256) { + return a > b ? a : b; + } + + /** + * @dev Returns the smallest of two numbers. + */ + function min(uint256 a, uint256 b) internal pure returns (uint256) { + return a < b ? a : b; + } + + /** + * @dev Returns the average of two numbers. The result is rounded towards + * zero. + */ + function average(uint256 a, uint256 b) internal pure returns (uint256) { + // (a + b) / 2 can overflow. + return (a & b) + (a ^ b) / 2; + } + + /** + * @dev Returns the ceiling of the division of two numbers. + * + * This differs from standard division with `/` in that it rounds towards infinity instead + * of rounding towards zero. + */ + function ceilDiv(uint256 a, uint256 b) internal pure returns (uint256) { + if (b == 0) { + // Guarantee the same behavior as in a regular Solidity division. + return a / b; + } + + // (a + b - 1) / b can overflow on addition, so we distribute. + return a == 0 ? 0 : (a - 1) / b + 1; + } + + /** + * @notice Calculates floor(x * y / denominator) with full precision. Throws if result overflows a uint256 or + * denominator == 0. + * @dev Original credit to Remco Bloemen under MIT license (https://xn--2-umb.com/21/muldiv) with further edits by + * Uniswap Labs also under MIT license. + */ + function mulDiv(uint256 x, uint256 y, uint256 denominator) internal pure returns (uint256 result) { + unchecked { + // 512-bit multiply [prod1 prod0] = x * y. Compute the product mod 2^256 and mod 2^256 - 1, then use + // use the Chinese Remainder Theorem to reconstruct the 512 bit result. The result is stored in two 256 + // variables such that product = prod1 * 2^256 + prod0. + uint256 prod0 = x * y; // Least significant 256 bits of the product + uint256 prod1; // Most significant 256 bits of the product + assembly { + let mm := mulmod(x, y, not(0)) + prod1 := sub(sub(mm, prod0), lt(mm, prod0)) + } + + // Handle non-overflow cases, 256 by 256 division. + if (prod1 == 0) { + // Solidity will revert if denominator == 0, unlike the div opcode on its own. + // The surrounding unchecked block does not change this fact. + // See https://docs.soliditylang.org/en/latest/control-structures.html#checked-or-unchecked-arithmetic. + return prod0 / denominator; + } + + // Make sure the result is less than 2^256. Also prevents denominator == 0. + if (denominator <= prod1) { + revert MathOverflowedMulDiv(); + } + + /////////////////////////////////////////////// + // 512 by 256 division. + /////////////////////////////////////////////// + + // Make division exact by subtracting the remainder from [prod1 prod0]. + uint256 remainder; + assembly { + // Compute remainder using mulmod. + remainder := mulmod(x, y, denominator) + + // Subtract 256 bit number from 512 bit number. + prod1 := sub(prod1, gt(remainder, prod0)) + prod0 := sub(prod0, remainder) + } + + // Factor powers of two out of denominator and compute largest power of two divisor of denominator. + // Always >= 1. See https://cs.stackexchange.com/q/138556/92363. + + uint256 twos = denominator & (0 - denominator); + assembly { + // Divide denominator by twos. + denominator := div(denominator, twos) + + // Divide [prod1 prod0] by twos. + prod0 := div(prod0, twos) + + // Flip twos such that it is 2^256 / twos. If twos is zero, then it becomes one. + twos := add(div(sub(0, twos), twos), 1) + } + + // Shift in bits from prod1 into prod0. + prod0 |= prod1 * twos; + + // Invert denominator mod 2^256. Now that denominator is an odd number, it has an inverse modulo 2^256 such + // that denominator * inv = 1 mod 2^256. Compute the inverse by starting with a seed that is correct for + // four bits. That is, denominator * inv = 1 mod 2^4. + uint256 inverse = (3 * denominator) ^ 2; + + // Use the Newton-Raphson iteration to improve the precision. Thanks to Hensel's lifting lemma, this also + // works in modular arithmetic, doubling the correct bits in each step. + inverse *= 2 - denominator * inverse; // inverse mod 2^8 + inverse *= 2 - denominator * inverse; // inverse mod 2^16 + inverse *= 2 - denominator * inverse; // inverse mod 2^32 + inverse *= 2 - denominator * inverse; // inverse mod 2^64 + inverse *= 2 - denominator * inverse; // inverse mod 2^128 + inverse *= 2 - denominator * inverse; // inverse mod 2^256 + + // Because the division is now exact we can divide by multiplying with the modular inverse of denominator. + // This will give us the correct result modulo 2^256. Since the preconditions guarantee that the outcome is + // less than 2^256, this is the final result. We don't need to compute the high bits of the result and prod1 + // is no longer required. + result = prod0 * inverse; + return result; + } + } + + /** + * @notice Calculates x * y / denominator with full precision, following the selected rounding direction. + */ + function mulDiv(uint256 x, uint256 y, uint256 denominator, Rounding rounding) internal pure returns (uint256) { + uint256 result = mulDiv(x, y, denominator); + if (unsignedRoundsUp(rounding) && mulmod(x, y, denominator) > 0) { + result += 1; + } + return result; + } + + /** + * @dev Returns the square root of a number. If the number is not a perfect square, the value is rounded + * towards zero. + * + * Inspired by Henry S. Warren, Jr.'s "Hacker's Delight" (Chapter 11). + */ + function sqrt(uint256 a) internal pure returns (uint256) { + if (a == 0) { + return 0; + } + + // For our first guess, we get the biggest power of 2 which is smaller than the square root of the target. + // + // We know that the "msb" (most significant bit) of our target number `a` is a power of 2 such that we have + // `msb(a) <= a < 2*msb(a)`. This value can be written `msb(a)=2**k` with `k=log2(a)`. + // + // This can be rewritten `2**log2(a) <= a < 2**(log2(a) + 1)` + // → `sqrt(2**k) <= sqrt(a) < sqrt(2**(k+1))` + // → `2**(k/2) <= sqrt(a) < 2**((k+1)/2) <= 2**(k/2 + 1)` + // + // Consequently, `2**(log2(a) / 2)` is a good first approximation of `sqrt(a)` with at least 1 correct bit. + uint256 result = 1 << (log2(a) >> 1); + + // At this point `result` is an estimation with one bit of precision. We know the true value is a uint128, + // since it is the square root of a uint256. Newton's method converges quadratically (precision doubles at + // every iteration). We thus need at most 7 iteration to turn our partial result with one bit of precision + // into the expected uint128 result. + unchecked { + result = (result + a / result) >> 1; + result = (result + a / result) >> 1; + result = (result + a / result) >> 1; + result = (result + a / result) >> 1; + result = (result + a / result) >> 1; + result = (result + a / result) >> 1; + result = (result + a / result) >> 1; + return min(result, a / result); + } + } + + /** + * @notice Calculates sqrt(a), following the selected rounding direction. + */ + function sqrt(uint256 a, Rounding rounding) internal pure returns (uint256) { + unchecked { + uint256 result = sqrt(a); + return result + (unsignedRoundsUp(rounding) && result * result < a ? 1 : 0); + } + } + + /** + * @dev Return the log in base 2 of a positive value rounded towards zero. + * Returns 0 if given 0. + */ + function log2(uint256 value) internal pure returns (uint256) { + uint256 result = 0; + unchecked { + if (value >> 128 > 0) { + value >>= 128; + result += 128; + } + if (value >> 64 > 0) { + value >>= 64; + result += 64; + } + if (value >> 32 > 0) { + value >>= 32; + result += 32; + } + if (value >> 16 > 0) { + value >>= 16; + result += 16; + } + if (value >> 8 > 0) { + value >>= 8; + result += 8; + } + if (value >> 4 > 0) { + value >>= 4; + result += 4; + } + if (value >> 2 > 0) { + value >>= 2; + result += 2; + } + if (value >> 1 > 0) { + result += 1; + } + } + return result; + } + + /** + * @dev Return the log in base 2, following the selected rounding direction, of a positive value. + * Returns 0 if given 0. + */ + function log2(uint256 value, Rounding rounding) internal pure returns (uint256) { + unchecked { + uint256 result = log2(value); + return result + (unsignedRoundsUp(rounding) && 1 << result < value ? 1 : 0); + } + } + + /** + * @dev Return the log in base 10 of a positive value rounded towards zero. + * Returns 0 if given 0. + */ + function log10(uint256 value) internal pure returns (uint256) { + uint256 result = 0; + unchecked { + if (value >= 10 ** 64) { + value /= 10 ** 64; + result += 64; + } + if (value >= 10 ** 32) { + value /= 10 ** 32; + result += 32; + } + if (value >= 10 ** 16) { + value /= 10 ** 16; + result += 16; + } + if (value >= 10 ** 8) { + value /= 10 ** 8; + result += 8; + } + if (value >= 10 ** 4) { + value /= 10 ** 4; + result += 4; + } + if (value >= 10 ** 2) { + value /= 10 ** 2; + result += 2; + } + if (value >= 10 ** 1) { + result += 1; + } + } + return result; + } + + /** + * @dev Return the log in base 10, following the selected rounding direction, of a positive value. + * Returns 0 if given 0. + */ + function log10(uint256 value, Rounding rounding) internal pure returns (uint256) { + unchecked { + uint256 result = log10(value); + return result + (unsignedRoundsUp(rounding) && 10 ** result < value ? 1 : 0); + } + } + + /** + * @dev Return the log in base 256 of a positive value rounded towards zero. + * Returns 0 if given 0. + * + * Adding one to the result gives the number of pairs of hex symbols needed to represent `value` as a hex string. + */ + function log256(uint256 value) internal pure returns (uint256) { + uint256 result = 0; + unchecked { + if (value >> 128 > 0) { + value >>= 128; + result += 16; + } + if (value >> 64 > 0) { + value >>= 64; + result += 8; + } + if (value >> 32 > 0) { + value >>= 32; + result += 4; + } + if (value >> 16 > 0) { + value >>= 16; + result += 2; + } + if (value >> 8 > 0) { + result += 1; + } + } + return result; + } + + /** + * @dev Return the log in base 256, following the selected rounding direction, of a positive value. + * Returns 0 if given 0. + */ + function log256(uint256 value, Rounding rounding) internal pure returns (uint256) { + unchecked { + uint256 result = log256(value); + return result + (unsignedRoundsUp(rounding) && 1 << (result << 3) < value ? 1 : 0); + } + } + + /** + * @dev Returns whether a provided rounding mode is considered rounding up for unsigned integers. + */ + function unsignedRoundsUp(Rounding rounding) internal pure returns (bool) { + return uint8(rounding) % 2 == 1; + } +} diff --git a/CVLByExample/UniswapHooks/lib/uniswap-hooks/lib/v4-core/lib/openzeppelin-contracts/contracts/utils/math/SafeCast.sol b/CVLByExample/UniswapHooks/lib/uniswap-hooks/lib/v4-core/lib/openzeppelin-contracts/contracts/utils/math/SafeCast.sol new file mode 100644 index 00000000..0ed458b4 --- /dev/null +++ b/CVLByExample/UniswapHooks/lib/uniswap-hooks/lib/v4-core/lib/openzeppelin-contracts/contracts/utils/math/SafeCast.sol @@ -0,0 +1,1153 @@ +// SPDX-License-Identifier: MIT +// OpenZeppelin Contracts (last updated v5.0.0) (utils/math/SafeCast.sol) +// This file was procedurally generated from scripts/generate/templates/SafeCast.js. + +pragma solidity ^0.8.20; + +/** + * @dev Wrappers over Solidity's uintXX/intXX casting operators with added overflow + * checks. + * + * Downcasting from uint256/int256 in Solidity does not revert on overflow. This can + * easily result in undesired exploitation or bugs, since developers usually + * assume that overflows raise errors. `SafeCast` restores this intuition by + * reverting the transaction when such an operation overflows. + * + * Using this library instead of the unchecked operations eliminates an entire + * class of bugs, so it's recommended to use it always. + */ +library SafeCast { + /** + * @dev Value doesn't fit in an uint of `bits` size. + */ + error SafeCastOverflowedUintDowncast(uint8 bits, uint256 value); + + /** + * @dev An int value doesn't fit in an uint of `bits` size. + */ + error SafeCastOverflowedIntToUint(int256 value); + + /** + * @dev Value doesn't fit in an int of `bits` size. + */ + error SafeCastOverflowedIntDowncast(uint8 bits, int256 value); + + /** + * @dev An uint value doesn't fit in an int of `bits` size. + */ + error SafeCastOverflowedUintToInt(uint256 value); + + /** + * @dev Returns the downcasted uint248 from uint256, reverting on + * overflow (when the input is greater than largest uint248). + * + * Counterpart to Solidity's `uint248` operator. + * + * Requirements: + * + * - input must fit into 248 bits + */ + function toUint248(uint256 value) internal pure returns (uint248) { + if (value > type(uint248).max) { + revert SafeCastOverflowedUintDowncast(248, value); + } + return uint248(value); + } + + /** + * @dev Returns the downcasted uint240 from uint256, reverting on + * overflow (when the input is greater than largest uint240). + * + * Counterpart to Solidity's `uint240` operator. + * + * Requirements: + * + * - input must fit into 240 bits + */ + function toUint240(uint256 value) internal pure returns (uint240) { + if (value > type(uint240).max) { + revert SafeCastOverflowedUintDowncast(240, value); + } + return uint240(value); + } + + /** + * @dev Returns the downcasted uint232 from uint256, reverting on + * overflow (when the input is greater than largest uint232). + * + * Counterpart to Solidity's `uint232` operator. + * + * Requirements: + * + * - input must fit into 232 bits + */ + function toUint232(uint256 value) internal pure returns (uint232) { + if (value > type(uint232).max) { + revert SafeCastOverflowedUintDowncast(232, value); + } + return uint232(value); + } + + /** + * @dev Returns the downcasted uint224 from uint256, reverting on + * overflow (when the input is greater than largest uint224). + * + * Counterpart to Solidity's `uint224` operator. + * + * Requirements: + * + * - input must fit into 224 bits + */ + function toUint224(uint256 value) internal pure returns (uint224) { + if (value > type(uint224).max) { + revert SafeCastOverflowedUintDowncast(224, value); + } + return uint224(value); + } + + /** + * @dev Returns the downcasted uint216 from uint256, reverting on + * overflow (when the input is greater than largest uint216). + * + * Counterpart to Solidity's `uint216` operator. + * + * Requirements: + * + * - input must fit into 216 bits + */ + function toUint216(uint256 value) internal pure returns (uint216) { + if (value > type(uint216).max) { + revert SafeCastOverflowedUintDowncast(216, value); + } + return uint216(value); + } + + /** + * @dev Returns the downcasted uint208 from uint256, reverting on + * overflow (when the input is greater than largest uint208). + * + * Counterpart to Solidity's `uint208` operator. + * + * Requirements: + * + * - input must fit into 208 bits + */ + function toUint208(uint256 value) internal pure returns (uint208) { + if (value > type(uint208).max) { + revert SafeCastOverflowedUintDowncast(208, value); + } + return uint208(value); + } + + /** + * @dev Returns the downcasted uint200 from uint256, reverting on + * overflow (when the input is greater than largest uint200). + * + * Counterpart to Solidity's `uint200` operator. + * + * Requirements: + * + * - input must fit into 200 bits + */ + function toUint200(uint256 value) internal pure returns (uint200) { + if (value > type(uint200).max) { + revert SafeCastOverflowedUintDowncast(200, value); + } + return uint200(value); + } + + /** + * @dev Returns the downcasted uint192 from uint256, reverting on + * overflow (when the input is greater than largest uint192). + * + * Counterpart to Solidity's `uint192` operator. + * + * Requirements: + * + * - input must fit into 192 bits + */ + function toUint192(uint256 value) internal pure returns (uint192) { + if (value > type(uint192).max) { + revert SafeCastOverflowedUintDowncast(192, value); + } + return uint192(value); + } + + /** + * @dev Returns the downcasted uint184 from uint256, reverting on + * overflow (when the input is greater than largest uint184). + * + * Counterpart to Solidity's `uint184` operator. + * + * Requirements: + * + * - input must fit into 184 bits + */ + function toUint184(uint256 value) internal pure returns (uint184) { + if (value > type(uint184).max) { + revert SafeCastOverflowedUintDowncast(184, value); + } + return uint184(value); + } + + /** + * @dev Returns the downcasted uint176 from uint256, reverting on + * overflow (when the input is greater than largest uint176). + * + * Counterpart to Solidity's `uint176` operator. + * + * Requirements: + * + * - input must fit into 176 bits + */ + function toUint176(uint256 value) internal pure returns (uint176) { + if (value > type(uint176).max) { + revert SafeCastOverflowedUintDowncast(176, value); + } + return uint176(value); + } + + /** + * @dev Returns the downcasted uint168 from uint256, reverting on + * overflow (when the input is greater than largest uint168). + * + * Counterpart to Solidity's `uint168` operator. + * + * Requirements: + * + * - input must fit into 168 bits + */ + function toUint168(uint256 value) internal pure returns (uint168) { + if (value > type(uint168).max) { + revert SafeCastOverflowedUintDowncast(168, value); + } + return uint168(value); + } + + /** + * @dev Returns the downcasted uint160 from uint256, reverting on + * overflow (when the input is greater than largest uint160). + * + * Counterpart to Solidity's `uint160` operator. + * + * Requirements: + * + * - input must fit into 160 bits + */ + function toUint160(uint256 value) internal pure returns (uint160) { + if (value > type(uint160).max) { + revert SafeCastOverflowedUintDowncast(160, value); + } + return uint160(value); + } + + /** + * @dev Returns the downcasted uint152 from uint256, reverting on + * overflow (when the input is greater than largest uint152). + * + * Counterpart to Solidity's `uint152` operator. + * + * Requirements: + * + * - input must fit into 152 bits + */ + function toUint152(uint256 value) internal pure returns (uint152) { + if (value > type(uint152).max) { + revert SafeCastOverflowedUintDowncast(152, value); + } + return uint152(value); + } + + /** + * @dev Returns the downcasted uint144 from uint256, reverting on + * overflow (when the input is greater than largest uint144). + * + * Counterpart to Solidity's `uint144` operator. + * + * Requirements: + * + * - input must fit into 144 bits + */ + function toUint144(uint256 value) internal pure returns (uint144) { + if (value > type(uint144).max) { + revert SafeCastOverflowedUintDowncast(144, value); + } + return uint144(value); + } + + /** + * @dev Returns the downcasted uint136 from uint256, reverting on + * overflow (when the input is greater than largest uint136). + * + * Counterpart to Solidity's `uint136` operator. + * + * Requirements: + * + * - input must fit into 136 bits + */ + function toUint136(uint256 value) internal pure returns (uint136) { + if (value > type(uint136).max) { + revert SafeCastOverflowedUintDowncast(136, value); + } + return uint136(value); + } + + /** + * @dev Returns the downcasted uint128 from uint256, reverting on + * overflow (when the input is greater than largest uint128). + * + * Counterpart to Solidity's `uint128` operator. + * + * Requirements: + * + * - input must fit into 128 bits + */ + function toUint128(uint256 value) internal pure returns (uint128) { + if (value > type(uint128).max) { + revert SafeCastOverflowedUintDowncast(128, value); + } + return uint128(value); + } + + /** + * @dev Returns the downcasted uint120 from uint256, reverting on + * overflow (when the input is greater than largest uint120). + * + * Counterpart to Solidity's `uint120` operator. + * + * Requirements: + * + * - input must fit into 120 bits + */ + function toUint120(uint256 value) internal pure returns (uint120) { + if (value > type(uint120).max) { + revert SafeCastOverflowedUintDowncast(120, value); + } + return uint120(value); + } + + /** + * @dev Returns the downcasted uint112 from uint256, reverting on + * overflow (when the input is greater than largest uint112). + * + * Counterpart to Solidity's `uint112` operator. + * + * Requirements: + * + * - input must fit into 112 bits + */ + function toUint112(uint256 value) internal pure returns (uint112) { + if (value > type(uint112).max) { + revert SafeCastOverflowedUintDowncast(112, value); + } + return uint112(value); + } + + /** + * @dev Returns the downcasted uint104 from uint256, reverting on + * overflow (when the input is greater than largest uint104). + * + * Counterpart to Solidity's `uint104` operator. + * + * Requirements: + * + * - input must fit into 104 bits + */ + function toUint104(uint256 value) internal pure returns (uint104) { + if (value > type(uint104).max) { + revert SafeCastOverflowedUintDowncast(104, value); + } + return uint104(value); + } + + /** + * @dev Returns the downcasted uint96 from uint256, reverting on + * overflow (when the input is greater than largest uint96). + * + * Counterpart to Solidity's `uint96` operator. + * + * Requirements: + * + * - input must fit into 96 bits + */ + function toUint96(uint256 value) internal pure returns (uint96) { + if (value > type(uint96).max) { + revert SafeCastOverflowedUintDowncast(96, value); + } + return uint96(value); + } + + /** + * @dev Returns the downcasted uint88 from uint256, reverting on + * overflow (when the input is greater than largest uint88). + * + * Counterpart to Solidity's `uint88` operator. + * + * Requirements: + * + * - input must fit into 88 bits + */ + function toUint88(uint256 value) internal pure returns (uint88) { + if (value > type(uint88).max) { + revert SafeCastOverflowedUintDowncast(88, value); + } + return uint88(value); + } + + /** + * @dev Returns the downcasted uint80 from uint256, reverting on + * overflow (when the input is greater than largest uint80). + * + * Counterpart to Solidity's `uint80` operator. + * + * Requirements: + * + * - input must fit into 80 bits + */ + function toUint80(uint256 value) internal pure returns (uint80) { + if (value > type(uint80).max) { + revert SafeCastOverflowedUintDowncast(80, value); + } + return uint80(value); + } + + /** + * @dev Returns the downcasted uint72 from uint256, reverting on + * overflow (when the input is greater than largest uint72). + * + * Counterpart to Solidity's `uint72` operator. + * + * Requirements: + * + * - input must fit into 72 bits + */ + function toUint72(uint256 value) internal pure returns (uint72) { + if (value > type(uint72).max) { + revert SafeCastOverflowedUintDowncast(72, value); + } + return uint72(value); + } + + /** + * @dev Returns the downcasted uint64 from uint256, reverting on + * overflow (when the input is greater than largest uint64). + * + * Counterpart to Solidity's `uint64` operator. + * + * Requirements: + * + * - input must fit into 64 bits + */ + function toUint64(uint256 value) internal pure returns (uint64) { + if (value > type(uint64).max) { + revert SafeCastOverflowedUintDowncast(64, value); + } + return uint64(value); + } + + /** + * @dev Returns the downcasted uint56 from uint256, reverting on + * overflow (when the input is greater than largest uint56). + * + * Counterpart to Solidity's `uint56` operator. + * + * Requirements: + * + * - input must fit into 56 bits + */ + function toUint56(uint256 value) internal pure returns (uint56) { + if (value > type(uint56).max) { + revert SafeCastOverflowedUintDowncast(56, value); + } + return uint56(value); + } + + /** + * @dev Returns the downcasted uint48 from uint256, reverting on + * overflow (when the input is greater than largest uint48). + * + * Counterpart to Solidity's `uint48` operator. + * + * Requirements: + * + * - input must fit into 48 bits + */ + function toUint48(uint256 value) internal pure returns (uint48) { + if (value > type(uint48).max) { + revert SafeCastOverflowedUintDowncast(48, value); + } + return uint48(value); + } + + /** + * @dev Returns the downcasted uint40 from uint256, reverting on + * overflow (when the input is greater than largest uint40). + * + * Counterpart to Solidity's `uint40` operator. + * + * Requirements: + * + * - input must fit into 40 bits + */ + function toUint40(uint256 value) internal pure returns (uint40) { + if (value > type(uint40).max) { + revert SafeCastOverflowedUintDowncast(40, value); + } + return uint40(value); + } + + /** + * @dev Returns the downcasted uint32 from uint256, reverting on + * overflow (when the input is greater than largest uint32). + * + * Counterpart to Solidity's `uint32` operator. + * + * Requirements: + * + * - input must fit into 32 bits + */ + function toUint32(uint256 value) internal pure returns (uint32) { + if (value > type(uint32).max) { + revert SafeCastOverflowedUintDowncast(32, value); + } + return uint32(value); + } + + /** + * @dev Returns the downcasted uint24 from uint256, reverting on + * overflow (when the input is greater than largest uint24). + * + * Counterpart to Solidity's `uint24` operator. + * + * Requirements: + * + * - input must fit into 24 bits + */ + function toUint24(uint256 value) internal pure returns (uint24) { + if (value > type(uint24).max) { + revert SafeCastOverflowedUintDowncast(24, value); + } + return uint24(value); + } + + /** + * @dev Returns the downcasted uint16 from uint256, reverting on + * overflow (when the input is greater than largest uint16). + * + * Counterpart to Solidity's `uint16` operator. + * + * Requirements: + * + * - input must fit into 16 bits + */ + function toUint16(uint256 value) internal pure returns (uint16) { + if (value > type(uint16).max) { + revert SafeCastOverflowedUintDowncast(16, value); + } + return uint16(value); + } + + /** + * @dev Returns the downcasted uint8 from uint256, reverting on + * overflow (when the input is greater than largest uint8). + * + * Counterpart to Solidity's `uint8` operator. + * + * Requirements: + * + * - input must fit into 8 bits + */ + function toUint8(uint256 value) internal pure returns (uint8) { + if (value > type(uint8).max) { + revert SafeCastOverflowedUintDowncast(8, value); + } + return uint8(value); + } + + /** + * @dev Converts a signed int256 into an unsigned uint256. + * + * Requirements: + * + * - input must be greater than or equal to 0. + */ + function toUint256(int256 value) internal pure returns (uint256) { + if (value < 0) { + revert SafeCastOverflowedIntToUint(value); + } + return uint256(value); + } + + /** + * @dev Returns the downcasted int248 from int256, reverting on + * overflow (when the input is less than smallest int248 or + * greater than largest int248). + * + * Counterpart to Solidity's `int248` operator. + * + * Requirements: + * + * - input must fit into 248 bits + */ + function toInt248(int256 value) internal pure returns (int248 downcasted) { + downcasted = int248(value); + if (downcasted != value) { + revert SafeCastOverflowedIntDowncast(248, value); + } + } + + /** + * @dev Returns the downcasted int240 from int256, reverting on + * overflow (when the input is less than smallest int240 or + * greater than largest int240). + * + * Counterpart to Solidity's `int240` operator. + * + * Requirements: + * + * - input must fit into 240 bits + */ + function toInt240(int256 value) internal pure returns (int240 downcasted) { + downcasted = int240(value); + if (downcasted != value) { + revert SafeCastOverflowedIntDowncast(240, value); + } + } + + /** + * @dev Returns the downcasted int232 from int256, reverting on + * overflow (when the input is less than smallest int232 or + * greater than largest int232). + * + * Counterpart to Solidity's `int232` operator. + * + * Requirements: + * + * - input must fit into 232 bits + */ + function toInt232(int256 value) internal pure returns (int232 downcasted) { + downcasted = int232(value); + if (downcasted != value) { + revert SafeCastOverflowedIntDowncast(232, value); + } + } + + /** + * @dev Returns the downcasted int224 from int256, reverting on + * overflow (when the input is less than smallest int224 or + * greater than largest int224). + * + * Counterpart to Solidity's `int224` operator. + * + * Requirements: + * + * - input must fit into 224 bits + */ + function toInt224(int256 value) internal pure returns (int224 downcasted) { + downcasted = int224(value); + if (downcasted != value) { + revert SafeCastOverflowedIntDowncast(224, value); + } + } + + /** + * @dev Returns the downcasted int216 from int256, reverting on + * overflow (when the input is less than smallest int216 or + * greater than largest int216). + * + * Counterpart to Solidity's `int216` operator. + * + * Requirements: + * + * - input must fit into 216 bits + */ + function toInt216(int256 value) internal pure returns (int216 downcasted) { + downcasted = int216(value); + if (downcasted != value) { + revert SafeCastOverflowedIntDowncast(216, value); + } + } + + /** + * @dev Returns the downcasted int208 from int256, reverting on + * overflow (when the input is less than smallest int208 or + * greater than largest int208). + * + * Counterpart to Solidity's `int208` operator. + * + * Requirements: + * + * - input must fit into 208 bits + */ + function toInt208(int256 value) internal pure returns (int208 downcasted) { + downcasted = int208(value); + if (downcasted != value) { + revert SafeCastOverflowedIntDowncast(208, value); + } + } + + /** + * @dev Returns the downcasted int200 from int256, reverting on + * overflow (when the input is less than smallest int200 or + * greater than largest int200). + * + * Counterpart to Solidity's `int200` operator. + * + * Requirements: + * + * - input must fit into 200 bits + */ + function toInt200(int256 value) internal pure returns (int200 downcasted) { + downcasted = int200(value); + if (downcasted != value) { + revert SafeCastOverflowedIntDowncast(200, value); + } + } + + /** + * @dev Returns the downcasted int192 from int256, reverting on + * overflow (when the input is less than smallest int192 or + * greater than largest int192). + * + * Counterpart to Solidity's `int192` operator. + * + * Requirements: + * + * - input must fit into 192 bits + */ + function toInt192(int256 value) internal pure returns (int192 downcasted) { + downcasted = int192(value); + if (downcasted != value) { + revert SafeCastOverflowedIntDowncast(192, value); + } + } + + /** + * @dev Returns the downcasted int184 from int256, reverting on + * overflow (when the input is less than smallest int184 or + * greater than largest int184). + * + * Counterpart to Solidity's `int184` operator. + * + * Requirements: + * + * - input must fit into 184 bits + */ + function toInt184(int256 value) internal pure returns (int184 downcasted) { + downcasted = int184(value); + if (downcasted != value) { + revert SafeCastOverflowedIntDowncast(184, value); + } + } + + /** + * @dev Returns the downcasted int176 from int256, reverting on + * overflow (when the input is less than smallest int176 or + * greater than largest int176). + * + * Counterpart to Solidity's `int176` operator. + * + * Requirements: + * + * - input must fit into 176 bits + */ + function toInt176(int256 value) internal pure returns (int176 downcasted) { + downcasted = int176(value); + if (downcasted != value) { + revert SafeCastOverflowedIntDowncast(176, value); + } + } + + /** + * @dev Returns the downcasted int168 from int256, reverting on + * overflow (when the input is less than smallest int168 or + * greater than largest int168). + * + * Counterpart to Solidity's `int168` operator. + * + * Requirements: + * + * - input must fit into 168 bits + */ + function toInt168(int256 value) internal pure returns (int168 downcasted) { + downcasted = int168(value); + if (downcasted != value) { + revert SafeCastOverflowedIntDowncast(168, value); + } + } + + /** + * @dev Returns the downcasted int160 from int256, reverting on + * overflow (when the input is less than smallest int160 or + * greater than largest int160). + * + * Counterpart to Solidity's `int160` operator. + * + * Requirements: + * + * - input must fit into 160 bits + */ + function toInt160(int256 value) internal pure returns (int160 downcasted) { + downcasted = int160(value); + if (downcasted != value) { + revert SafeCastOverflowedIntDowncast(160, value); + } + } + + /** + * @dev Returns the downcasted int152 from int256, reverting on + * overflow (when the input is less than smallest int152 or + * greater than largest int152). + * + * Counterpart to Solidity's `int152` operator. + * + * Requirements: + * + * - input must fit into 152 bits + */ + function toInt152(int256 value) internal pure returns (int152 downcasted) { + downcasted = int152(value); + if (downcasted != value) { + revert SafeCastOverflowedIntDowncast(152, value); + } + } + + /** + * @dev Returns the downcasted int144 from int256, reverting on + * overflow (when the input is less than smallest int144 or + * greater than largest int144). + * + * Counterpart to Solidity's `int144` operator. + * + * Requirements: + * + * - input must fit into 144 bits + */ + function toInt144(int256 value) internal pure returns (int144 downcasted) { + downcasted = int144(value); + if (downcasted != value) { + revert SafeCastOverflowedIntDowncast(144, value); + } + } + + /** + * @dev Returns the downcasted int136 from int256, reverting on + * overflow (when the input is less than smallest int136 or + * greater than largest int136). + * + * Counterpart to Solidity's `int136` operator. + * + * Requirements: + * + * - input must fit into 136 bits + */ + function toInt136(int256 value) internal pure returns (int136 downcasted) { + downcasted = int136(value); + if (downcasted != value) { + revert SafeCastOverflowedIntDowncast(136, value); + } + } + + /** + * @dev Returns the downcasted int128 from int256, reverting on + * overflow (when the input is less than smallest int128 or + * greater than largest int128). + * + * Counterpart to Solidity's `int128` operator. + * + * Requirements: + * + * - input must fit into 128 bits + */ + function toInt128(int256 value) internal pure returns (int128 downcasted) { + downcasted = int128(value); + if (downcasted != value) { + revert SafeCastOverflowedIntDowncast(128, value); + } + } + + /** + * @dev Returns the downcasted int120 from int256, reverting on + * overflow (when the input is less than smallest int120 or + * greater than largest int120). + * + * Counterpart to Solidity's `int120` operator. + * + * Requirements: + * + * - input must fit into 120 bits + */ + function toInt120(int256 value) internal pure returns (int120 downcasted) { + downcasted = int120(value); + if (downcasted != value) { + revert SafeCastOverflowedIntDowncast(120, value); + } + } + + /** + * @dev Returns the downcasted int112 from int256, reverting on + * overflow (when the input is less than smallest int112 or + * greater than largest int112). + * + * Counterpart to Solidity's `int112` operator. + * + * Requirements: + * + * - input must fit into 112 bits + */ + function toInt112(int256 value) internal pure returns (int112 downcasted) { + downcasted = int112(value); + if (downcasted != value) { + revert SafeCastOverflowedIntDowncast(112, value); + } + } + + /** + * @dev Returns the downcasted int104 from int256, reverting on + * overflow (when the input is less than smallest int104 or + * greater than largest int104). + * + * Counterpart to Solidity's `int104` operator. + * + * Requirements: + * + * - input must fit into 104 bits + */ + function toInt104(int256 value) internal pure returns (int104 downcasted) { + downcasted = int104(value); + if (downcasted != value) { + revert SafeCastOverflowedIntDowncast(104, value); + } + } + + /** + * @dev Returns the downcasted int96 from int256, reverting on + * overflow (when the input is less than smallest int96 or + * greater than largest int96). + * + * Counterpart to Solidity's `int96` operator. + * + * Requirements: + * + * - input must fit into 96 bits + */ + function toInt96(int256 value) internal pure returns (int96 downcasted) { + downcasted = int96(value); + if (downcasted != value) { + revert SafeCastOverflowedIntDowncast(96, value); + } + } + + /** + * @dev Returns the downcasted int88 from int256, reverting on + * overflow (when the input is less than smallest int88 or + * greater than largest int88). + * + * Counterpart to Solidity's `int88` operator. + * + * Requirements: + * + * - input must fit into 88 bits + */ + function toInt88(int256 value) internal pure returns (int88 downcasted) { + downcasted = int88(value); + if (downcasted != value) { + revert SafeCastOverflowedIntDowncast(88, value); + } + } + + /** + * @dev Returns the downcasted int80 from int256, reverting on + * overflow (when the input is less than smallest int80 or + * greater than largest int80). + * + * Counterpart to Solidity's `int80` operator. + * + * Requirements: + * + * - input must fit into 80 bits + */ + function toInt80(int256 value) internal pure returns (int80 downcasted) { + downcasted = int80(value); + if (downcasted != value) { + revert SafeCastOverflowedIntDowncast(80, value); + } + } + + /** + * @dev Returns the downcasted int72 from int256, reverting on + * overflow (when the input is less than smallest int72 or + * greater than largest int72). + * + * Counterpart to Solidity's `int72` operator. + * + * Requirements: + * + * - input must fit into 72 bits + */ + function toInt72(int256 value) internal pure returns (int72 downcasted) { + downcasted = int72(value); + if (downcasted != value) { + revert SafeCastOverflowedIntDowncast(72, value); + } + } + + /** + * @dev Returns the downcasted int64 from int256, reverting on + * overflow (when the input is less than smallest int64 or + * greater than largest int64). + * + * Counterpart to Solidity's `int64` operator. + * + * Requirements: + * + * - input must fit into 64 bits + */ + function toInt64(int256 value) internal pure returns (int64 downcasted) { + downcasted = int64(value); + if (downcasted != value) { + revert SafeCastOverflowedIntDowncast(64, value); + } + } + + /** + * @dev Returns the downcasted int56 from int256, reverting on + * overflow (when the input is less than smallest int56 or + * greater than largest int56). + * + * Counterpart to Solidity's `int56` operator. + * + * Requirements: + * + * - input must fit into 56 bits + */ + function toInt56(int256 value) internal pure returns (int56 downcasted) { + downcasted = int56(value); + if (downcasted != value) { + revert SafeCastOverflowedIntDowncast(56, value); + } + } + + /** + * @dev Returns the downcasted int48 from int256, reverting on + * overflow (when the input is less than smallest int48 or + * greater than largest int48). + * + * Counterpart to Solidity's `int48` operator. + * + * Requirements: + * + * - input must fit into 48 bits + */ + function toInt48(int256 value) internal pure returns (int48 downcasted) { + downcasted = int48(value); + if (downcasted != value) { + revert SafeCastOverflowedIntDowncast(48, value); + } + } + + /** + * @dev Returns the downcasted int40 from int256, reverting on + * overflow (when the input is less than smallest int40 or + * greater than largest int40). + * + * Counterpart to Solidity's `int40` operator. + * + * Requirements: + * + * - input must fit into 40 bits + */ + function toInt40(int256 value) internal pure returns (int40 downcasted) { + downcasted = int40(value); + if (downcasted != value) { + revert SafeCastOverflowedIntDowncast(40, value); + } + } + + /** + * @dev Returns the downcasted int32 from int256, reverting on + * overflow (when the input is less than smallest int32 or + * greater than largest int32). + * + * Counterpart to Solidity's `int32` operator. + * + * Requirements: + * + * - input must fit into 32 bits + */ + function toInt32(int256 value) internal pure returns (int32 downcasted) { + downcasted = int32(value); + if (downcasted != value) { + revert SafeCastOverflowedIntDowncast(32, value); + } + } + + /** + * @dev Returns the downcasted int24 from int256, reverting on + * overflow (when the input is less than smallest int24 or + * greater than largest int24). + * + * Counterpart to Solidity's `int24` operator. + * + * Requirements: + * + * - input must fit into 24 bits + */ + function toInt24(int256 value) internal pure returns (int24 downcasted) { + downcasted = int24(value); + if (downcasted != value) { + revert SafeCastOverflowedIntDowncast(24, value); + } + } + + /** + * @dev Returns the downcasted int16 from int256, reverting on + * overflow (when the input is less than smallest int16 or + * greater than largest int16). + * + * Counterpart to Solidity's `int16` operator. + * + * Requirements: + * + * - input must fit into 16 bits + */ + function toInt16(int256 value) internal pure returns (int16 downcasted) { + downcasted = int16(value); + if (downcasted != value) { + revert SafeCastOverflowedIntDowncast(16, value); + } + } + + /** + * @dev Returns the downcasted int8 from int256, reverting on + * overflow (when the input is less than smallest int8 or + * greater than largest int8). + * + * Counterpart to Solidity's `int8` operator. + * + * Requirements: + * + * - input must fit into 8 bits + */ + function toInt8(int256 value) internal pure returns (int8 downcasted) { + downcasted = int8(value); + if (downcasted != value) { + revert SafeCastOverflowedIntDowncast(8, value); + } + } + + /** + * @dev Converts an unsigned uint256 into a signed int256. + * + * Requirements: + * + * - input must be less than or equal to maxInt256. + */ + function toInt256(uint256 value) internal pure returns (int256) { + // Note: Unsafe cast below is okay because `type(int256).max` is guaranteed to be positive + if (value > uint256(type(int256).max)) { + revert SafeCastOverflowedUintToInt(value); + } + return int256(value); + } +} diff --git a/CVLByExample/UniswapHooks/lib/uniswap-hooks/lib/v4-core/lib/openzeppelin-contracts/contracts/utils/math/SignedMath.sol b/CVLByExample/UniswapHooks/lib/uniswap-hooks/lib/v4-core/lib/openzeppelin-contracts/contracts/utils/math/SignedMath.sol new file mode 100644 index 00000000..66a61516 --- /dev/null +++ b/CVLByExample/UniswapHooks/lib/uniswap-hooks/lib/v4-core/lib/openzeppelin-contracts/contracts/utils/math/SignedMath.sol @@ -0,0 +1,43 @@ +// SPDX-License-Identifier: MIT +// OpenZeppelin Contracts (last updated v5.0.0) (utils/math/SignedMath.sol) + +pragma solidity ^0.8.20; + +/** + * @dev Standard signed math utilities missing in the Solidity language. + */ +library SignedMath { + /** + * @dev Returns the largest of two signed numbers. + */ + function max(int256 a, int256 b) internal pure returns (int256) { + return a > b ? a : b; + } + + /** + * @dev Returns the smallest of two signed numbers. + */ + function min(int256 a, int256 b) internal pure returns (int256) { + return a < b ? a : b; + } + + /** + * @dev Returns the average of two signed numbers without overflow. + * The result is rounded towards zero. + */ + function average(int256 a, int256 b) internal pure returns (int256) { + // Formula from the book "Hacker's Delight" + int256 x = (a & b) + ((a ^ b) >> 1); + return x + (int256(uint256(x) >> 255) & (a ^ b)); + } + + /** + * @dev Returns the absolute unsigned value of a signed value. + */ + function abs(int256 n) internal pure returns (uint256) { + unchecked { + // must be unchecked in order to support `n = type(int256).min` + return uint256(n >= 0 ? n : -n); + } + } +} diff --git a/CVLByExample/UniswapHooks/lib/uniswap-hooks/lib/v4-core/lib/openzeppelin-contracts/contracts/utils/structs/BitMaps.sol b/CVLByExample/UniswapHooks/lib/uniswap-hooks/lib/v4-core/lib/openzeppelin-contracts/contracts/utils/structs/BitMaps.sol new file mode 100644 index 00000000..40cceb90 --- /dev/null +++ b/CVLByExample/UniswapHooks/lib/uniswap-hooks/lib/v4-core/lib/openzeppelin-contracts/contracts/utils/structs/BitMaps.sol @@ -0,0 +1,60 @@ +// SPDX-License-Identifier: MIT +// OpenZeppelin Contracts (last updated v5.0.0) (utils/structs/BitMaps.sol) +pragma solidity ^0.8.20; + +/** + * @dev Library for managing uint256 to bool mapping in a compact and efficient way, provided the keys are sequential. + * Largely inspired by Uniswap's https://github.com/Uniswap/merkle-distributor/blob/master/contracts/MerkleDistributor.sol[merkle-distributor]. + * + * BitMaps pack 256 booleans across each bit of a single 256-bit slot of `uint256` type. + * Hence booleans corresponding to 256 _sequential_ indices would only consume a single slot, + * unlike the regular `bool` which would consume an entire slot for a single value. + * + * This results in gas savings in two ways: + * + * - Setting a zero value to non-zero only once every 256 times + * - Accessing the same warm slot for every 256 _sequential_ indices + */ +library BitMaps { + struct BitMap { + mapping(uint256 bucket => uint256) _data; + } + + /** + * @dev Returns whether the bit at `index` is set. + */ + function get(BitMap storage bitmap, uint256 index) internal view returns (bool) { + uint256 bucket = index >> 8; + uint256 mask = 1 << (index & 0xff); + return bitmap._data[bucket] & mask != 0; + } + + /** + * @dev Sets the bit at `index` to the boolean `value`. + */ + function setTo(BitMap storage bitmap, uint256 index, bool value) internal { + if (value) { + set(bitmap, index); + } else { + unset(bitmap, index); + } + } + + /** + * @dev Sets the bit at `index`. + */ + function set(BitMap storage bitmap, uint256 index) internal { + uint256 bucket = index >> 8; + uint256 mask = 1 << (index & 0xff); + bitmap._data[bucket] |= mask; + } + + /** + * @dev Unsets the bit at `index`. + */ + function unset(BitMap storage bitmap, uint256 index) internal { + uint256 bucket = index >> 8; + uint256 mask = 1 << (index & 0xff); + bitmap._data[bucket] &= ~mask; + } +} diff --git a/CVLByExample/UniswapHooks/lib/uniswap-hooks/lib/v4-core/lib/openzeppelin-contracts/contracts/utils/structs/Checkpoints.sol b/CVLByExample/UniswapHooks/lib/uniswap-hooks/lib/v4-core/lib/openzeppelin-contracts/contracts/utils/structs/Checkpoints.sol new file mode 100644 index 00000000..6561b0d6 --- /dev/null +++ b/CVLByExample/UniswapHooks/lib/uniswap-hooks/lib/v4-core/lib/openzeppelin-contracts/contracts/utils/structs/Checkpoints.sol @@ -0,0 +1,603 @@ +// SPDX-License-Identifier: MIT +// OpenZeppelin Contracts (last updated v5.0.0) (utils/structs/Checkpoints.sol) +// This file was procedurally generated from scripts/generate/templates/Checkpoints.js. + +pragma solidity ^0.8.20; + +import {Math} from "../math/Math.sol"; + +/** + * @dev This library defines the `Trace*` struct, for checkpointing values as they change at different points in + * time, and later looking up past values by block number. See {Votes} as an example. + * + * To create a history of checkpoints define a variable type `Checkpoints.Trace*` in your contract, and store a new + * checkpoint for the current transaction block using the {push} function. + */ +library Checkpoints { + /** + * @dev A value was attempted to be inserted on a past checkpoint. + */ + error CheckpointUnorderedInsertion(); + + struct Trace224 { + Checkpoint224[] _checkpoints; + } + + struct Checkpoint224 { + uint32 _key; + uint224 _value; + } + + /** + * @dev Pushes a (`key`, `value`) pair into a Trace224 so that it is stored as the checkpoint. + * + * Returns previous value and new value. + * + * IMPORTANT: Never accept `key` as a user input, since an arbitrary `type(uint32).max` key set will disable the + * library. + */ + function push(Trace224 storage self, uint32 key, uint224 value) internal returns (uint224, uint224) { + return _insert(self._checkpoints, key, value); + } + + /** + * @dev Returns the value in the first (oldest) checkpoint with key greater or equal than the search key, or zero if + * there is none. + */ + function lowerLookup(Trace224 storage self, uint32 key) internal view returns (uint224) { + uint256 len = self._checkpoints.length; + uint256 pos = _lowerBinaryLookup(self._checkpoints, key, 0, len); + return pos == len ? 0 : _unsafeAccess(self._checkpoints, pos)._value; + } + + /** + * @dev Returns the value in the last (most recent) checkpoint with key lower or equal than the search key, or zero + * if there is none. + */ + function upperLookup(Trace224 storage self, uint32 key) internal view returns (uint224) { + uint256 len = self._checkpoints.length; + uint256 pos = _upperBinaryLookup(self._checkpoints, key, 0, len); + return pos == 0 ? 0 : _unsafeAccess(self._checkpoints, pos - 1)._value; + } + + /** + * @dev Returns the value in the last (most recent) checkpoint with key lower or equal than the search key, or zero + * if there is none. + * + * NOTE: This is a variant of {upperLookup} that is optimised to find "recent" checkpoint (checkpoints with high + * keys). + */ + function upperLookupRecent(Trace224 storage self, uint32 key) internal view returns (uint224) { + uint256 len = self._checkpoints.length; + + uint256 low = 0; + uint256 high = len; + + if (len > 5) { + uint256 mid = len - Math.sqrt(len); + if (key < _unsafeAccess(self._checkpoints, mid)._key) { + high = mid; + } else { + low = mid + 1; + } + } + + uint256 pos = _upperBinaryLookup(self._checkpoints, key, low, high); + + return pos == 0 ? 0 : _unsafeAccess(self._checkpoints, pos - 1)._value; + } + + /** + * @dev Returns the value in the most recent checkpoint, or zero if there are no checkpoints. + */ + function latest(Trace224 storage self) internal view returns (uint224) { + uint256 pos = self._checkpoints.length; + return pos == 0 ? 0 : _unsafeAccess(self._checkpoints, pos - 1)._value; + } + + /** + * @dev Returns whether there is a checkpoint in the structure (i.e. it is not empty), and if so the key and value + * in the most recent checkpoint. + */ + function latestCheckpoint(Trace224 storage self) internal view returns (bool exists, uint32 _key, uint224 _value) { + uint256 pos = self._checkpoints.length; + if (pos == 0) { + return (false, 0, 0); + } else { + Checkpoint224 memory ckpt = _unsafeAccess(self._checkpoints, pos - 1); + return (true, ckpt._key, ckpt._value); + } + } + + /** + * @dev Returns the number of checkpoint. + */ + function length(Trace224 storage self) internal view returns (uint256) { + return self._checkpoints.length; + } + + /** + * @dev Returns checkpoint at given position. + */ + function at(Trace224 storage self, uint32 pos) internal view returns (Checkpoint224 memory) { + return self._checkpoints[pos]; + } + + /** + * @dev Pushes a (`key`, `value`) pair into an ordered list of checkpoints, either by inserting a new checkpoint, + * or by updating the last one. + */ + function _insert(Checkpoint224[] storage self, uint32 key, uint224 value) private returns (uint224, uint224) { + uint256 pos = self.length; + + if (pos > 0) { + // Copying to memory is important here. + Checkpoint224 memory last = _unsafeAccess(self, pos - 1); + + // Checkpoint keys must be non-decreasing. + if (last._key > key) { + revert CheckpointUnorderedInsertion(); + } + + // Update or push new checkpoint + if (last._key == key) { + _unsafeAccess(self, pos - 1)._value = value; + } else { + self.push(Checkpoint224({_key: key, _value: value})); + } + return (last._value, value); + } else { + self.push(Checkpoint224({_key: key, _value: value})); + return (0, value); + } + } + + /** + * @dev Return the index of the last (most recent) checkpoint with key lower or equal than the search key, or `high` + * if there is none. `low` and `high` define a section where to do the search, with inclusive `low` and exclusive + * `high`. + * + * WARNING: `high` should not be greater than the array's length. + */ + function _upperBinaryLookup( + Checkpoint224[] storage self, + uint32 key, + uint256 low, + uint256 high + ) private view returns (uint256) { + while (low < high) { + uint256 mid = Math.average(low, high); + if (_unsafeAccess(self, mid)._key > key) { + high = mid; + } else { + low = mid + 1; + } + } + return high; + } + + /** + * @dev Return the index of the first (oldest) checkpoint with key is greater or equal than the search key, or + * `high` if there is none. `low` and `high` define a section where to do the search, with inclusive `low` and + * exclusive `high`. + * + * WARNING: `high` should not be greater than the array's length. + */ + function _lowerBinaryLookup( + Checkpoint224[] storage self, + uint32 key, + uint256 low, + uint256 high + ) private view returns (uint256) { + while (low < high) { + uint256 mid = Math.average(low, high); + if (_unsafeAccess(self, mid)._key < key) { + low = mid + 1; + } else { + high = mid; + } + } + return high; + } + + /** + * @dev Access an element of the array without performing bounds check. The position is assumed to be within bounds. + */ + function _unsafeAccess( + Checkpoint224[] storage self, + uint256 pos + ) private pure returns (Checkpoint224 storage result) { + assembly { + mstore(0, self.slot) + result.slot := add(keccak256(0, 0x20), pos) + } + } + + struct Trace208 { + Checkpoint208[] _checkpoints; + } + + struct Checkpoint208 { + uint48 _key; + uint208 _value; + } + + /** + * @dev Pushes a (`key`, `value`) pair into a Trace208 so that it is stored as the checkpoint. + * + * Returns previous value and new value. + * + * IMPORTANT: Never accept `key` as a user input, since an arbitrary `type(uint48).max` key set will disable the + * library. + */ + function push(Trace208 storage self, uint48 key, uint208 value) internal returns (uint208, uint208) { + return _insert(self._checkpoints, key, value); + } + + /** + * @dev Returns the value in the first (oldest) checkpoint with key greater or equal than the search key, or zero if + * there is none. + */ + function lowerLookup(Trace208 storage self, uint48 key) internal view returns (uint208) { + uint256 len = self._checkpoints.length; + uint256 pos = _lowerBinaryLookup(self._checkpoints, key, 0, len); + return pos == len ? 0 : _unsafeAccess(self._checkpoints, pos)._value; + } + + /** + * @dev Returns the value in the last (most recent) checkpoint with key lower or equal than the search key, or zero + * if there is none. + */ + function upperLookup(Trace208 storage self, uint48 key) internal view returns (uint208) { + uint256 len = self._checkpoints.length; + uint256 pos = _upperBinaryLookup(self._checkpoints, key, 0, len); + return pos == 0 ? 0 : _unsafeAccess(self._checkpoints, pos - 1)._value; + } + + /** + * @dev Returns the value in the last (most recent) checkpoint with key lower or equal than the search key, or zero + * if there is none. + * + * NOTE: This is a variant of {upperLookup} that is optimised to find "recent" checkpoint (checkpoints with high + * keys). + */ + function upperLookupRecent(Trace208 storage self, uint48 key) internal view returns (uint208) { + uint256 len = self._checkpoints.length; + + uint256 low = 0; + uint256 high = len; + + if (len > 5) { + uint256 mid = len - Math.sqrt(len); + if (key < _unsafeAccess(self._checkpoints, mid)._key) { + high = mid; + } else { + low = mid + 1; + } + } + + uint256 pos = _upperBinaryLookup(self._checkpoints, key, low, high); + + return pos == 0 ? 0 : _unsafeAccess(self._checkpoints, pos - 1)._value; + } + + /** + * @dev Returns the value in the most recent checkpoint, or zero if there are no checkpoints. + */ + function latest(Trace208 storage self) internal view returns (uint208) { + uint256 pos = self._checkpoints.length; + return pos == 0 ? 0 : _unsafeAccess(self._checkpoints, pos - 1)._value; + } + + /** + * @dev Returns whether there is a checkpoint in the structure (i.e. it is not empty), and if so the key and value + * in the most recent checkpoint. + */ + function latestCheckpoint(Trace208 storage self) internal view returns (bool exists, uint48 _key, uint208 _value) { + uint256 pos = self._checkpoints.length; + if (pos == 0) { + return (false, 0, 0); + } else { + Checkpoint208 memory ckpt = _unsafeAccess(self._checkpoints, pos - 1); + return (true, ckpt._key, ckpt._value); + } + } + + /** + * @dev Returns the number of checkpoint. + */ + function length(Trace208 storage self) internal view returns (uint256) { + return self._checkpoints.length; + } + + /** + * @dev Returns checkpoint at given position. + */ + function at(Trace208 storage self, uint32 pos) internal view returns (Checkpoint208 memory) { + return self._checkpoints[pos]; + } + + /** + * @dev Pushes a (`key`, `value`) pair into an ordered list of checkpoints, either by inserting a new checkpoint, + * or by updating the last one. + */ + function _insert(Checkpoint208[] storage self, uint48 key, uint208 value) private returns (uint208, uint208) { + uint256 pos = self.length; + + if (pos > 0) { + // Copying to memory is important here. + Checkpoint208 memory last = _unsafeAccess(self, pos - 1); + + // Checkpoint keys must be non-decreasing. + if (last._key > key) { + revert CheckpointUnorderedInsertion(); + } + + // Update or push new checkpoint + if (last._key == key) { + _unsafeAccess(self, pos - 1)._value = value; + } else { + self.push(Checkpoint208({_key: key, _value: value})); + } + return (last._value, value); + } else { + self.push(Checkpoint208({_key: key, _value: value})); + return (0, value); + } + } + + /** + * @dev Return the index of the last (most recent) checkpoint with key lower or equal than the search key, or `high` + * if there is none. `low` and `high` define a section where to do the search, with inclusive `low` and exclusive + * `high`. + * + * WARNING: `high` should not be greater than the array's length. + */ + function _upperBinaryLookup( + Checkpoint208[] storage self, + uint48 key, + uint256 low, + uint256 high + ) private view returns (uint256) { + while (low < high) { + uint256 mid = Math.average(low, high); + if (_unsafeAccess(self, mid)._key > key) { + high = mid; + } else { + low = mid + 1; + } + } + return high; + } + + /** + * @dev Return the index of the first (oldest) checkpoint with key is greater or equal than the search key, or + * `high` if there is none. `low` and `high` define a section where to do the search, with inclusive `low` and + * exclusive `high`. + * + * WARNING: `high` should not be greater than the array's length. + */ + function _lowerBinaryLookup( + Checkpoint208[] storage self, + uint48 key, + uint256 low, + uint256 high + ) private view returns (uint256) { + while (low < high) { + uint256 mid = Math.average(low, high); + if (_unsafeAccess(self, mid)._key < key) { + low = mid + 1; + } else { + high = mid; + } + } + return high; + } + + /** + * @dev Access an element of the array without performing bounds check. The position is assumed to be within bounds. + */ + function _unsafeAccess( + Checkpoint208[] storage self, + uint256 pos + ) private pure returns (Checkpoint208 storage result) { + assembly { + mstore(0, self.slot) + result.slot := add(keccak256(0, 0x20), pos) + } + } + + struct Trace160 { + Checkpoint160[] _checkpoints; + } + + struct Checkpoint160 { + uint96 _key; + uint160 _value; + } + + /** + * @dev Pushes a (`key`, `value`) pair into a Trace160 so that it is stored as the checkpoint. + * + * Returns previous value and new value. + * + * IMPORTANT: Never accept `key` as a user input, since an arbitrary `type(uint96).max` key set will disable the + * library. + */ + function push(Trace160 storage self, uint96 key, uint160 value) internal returns (uint160, uint160) { + return _insert(self._checkpoints, key, value); + } + + /** + * @dev Returns the value in the first (oldest) checkpoint with key greater or equal than the search key, or zero if + * there is none. + */ + function lowerLookup(Trace160 storage self, uint96 key) internal view returns (uint160) { + uint256 len = self._checkpoints.length; + uint256 pos = _lowerBinaryLookup(self._checkpoints, key, 0, len); + return pos == len ? 0 : _unsafeAccess(self._checkpoints, pos)._value; + } + + /** + * @dev Returns the value in the last (most recent) checkpoint with key lower or equal than the search key, or zero + * if there is none. + */ + function upperLookup(Trace160 storage self, uint96 key) internal view returns (uint160) { + uint256 len = self._checkpoints.length; + uint256 pos = _upperBinaryLookup(self._checkpoints, key, 0, len); + return pos == 0 ? 0 : _unsafeAccess(self._checkpoints, pos - 1)._value; + } + + /** + * @dev Returns the value in the last (most recent) checkpoint with key lower or equal than the search key, or zero + * if there is none. + * + * NOTE: This is a variant of {upperLookup} that is optimised to find "recent" checkpoint (checkpoints with high + * keys). + */ + function upperLookupRecent(Trace160 storage self, uint96 key) internal view returns (uint160) { + uint256 len = self._checkpoints.length; + + uint256 low = 0; + uint256 high = len; + + if (len > 5) { + uint256 mid = len - Math.sqrt(len); + if (key < _unsafeAccess(self._checkpoints, mid)._key) { + high = mid; + } else { + low = mid + 1; + } + } + + uint256 pos = _upperBinaryLookup(self._checkpoints, key, low, high); + + return pos == 0 ? 0 : _unsafeAccess(self._checkpoints, pos - 1)._value; + } + + /** + * @dev Returns the value in the most recent checkpoint, or zero if there are no checkpoints. + */ + function latest(Trace160 storage self) internal view returns (uint160) { + uint256 pos = self._checkpoints.length; + return pos == 0 ? 0 : _unsafeAccess(self._checkpoints, pos - 1)._value; + } + + /** + * @dev Returns whether there is a checkpoint in the structure (i.e. it is not empty), and if so the key and value + * in the most recent checkpoint. + */ + function latestCheckpoint(Trace160 storage self) internal view returns (bool exists, uint96 _key, uint160 _value) { + uint256 pos = self._checkpoints.length; + if (pos == 0) { + return (false, 0, 0); + } else { + Checkpoint160 memory ckpt = _unsafeAccess(self._checkpoints, pos - 1); + return (true, ckpt._key, ckpt._value); + } + } + + /** + * @dev Returns the number of checkpoint. + */ + function length(Trace160 storage self) internal view returns (uint256) { + return self._checkpoints.length; + } + + /** + * @dev Returns checkpoint at given position. + */ + function at(Trace160 storage self, uint32 pos) internal view returns (Checkpoint160 memory) { + return self._checkpoints[pos]; + } + + /** + * @dev Pushes a (`key`, `value`) pair into an ordered list of checkpoints, either by inserting a new checkpoint, + * or by updating the last one. + */ + function _insert(Checkpoint160[] storage self, uint96 key, uint160 value) private returns (uint160, uint160) { + uint256 pos = self.length; + + if (pos > 0) { + // Copying to memory is important here. + Checkpoint160 memory last = _unsafeAccess(self, pos - 1); + + // Checkpoint keys must be non-decreasing. + if (last._key > key) { + revert CheckpointUnorderedInsertion(); + } + + // Update or push new checkpoint + if (last._key == key) { + _unsafeAccess(self, pos - 1)._value = value; + } else { + self.push(Checkpoint160({_key: key, _value: value})); + } + return (last._value, value); + } else { + self.push(Checkpoint160({_key: key, _value: value})); + return (0, value); + } + } + + /** + * @dev Return the index of the last (most recent) checkpoint with key lower or equal than the search key, or `high` + * if there is none. `low` and `high` define a section where to do the search, with inclusive `low` and exclusive + * `high`. + * + * WARNING: `high` should not be greater than the array's length. + */ + function _upperBinaryLookup( + Checkpoint160[] storage self, + uint96 key, + uint256 low, + uint256 high + ) private view returns (uint256) { + while (low < high) { + uint256 mid = Math.average(low, high); + if (_unsafeAccess(self, mid)._key > key) { + high = mid; + } else { + low = mid + 1; + } + } + return high; + } + + /** + * @dev Return the index of the first (oldest) checkpoint with key is greater or equal than the search key, or + * `high` if there is none. `low` and `high` define a section where to do the search, with inclusive `low` and + * exclusive `high`. + * + * WARNING: `high` should not be greater than the array's length. + */ + function _lowerBinaryLookup( + Checkpoint160[] storage self, + uint96 key, + uint256 low, + uint256 high + ) private view returns (uint256) { + while (low < high) { + uint256 mid = Math.average(low, high); + if (_unsafeAccess(self, mid)._key < key) { + low = mid + 1; + } else { + high = mid; + } + } + return high; + } + + /** + * @dev Access an element of the array without performing bounds check. The position is assumed to be within bounds. + */ + function _unsafeAccess( + Checkpoint160[] storage self, + uint256 pos + ) private pure returns (Checkpoint160 storage result) { + assembly { + mstore(0, self.slot) + result.slot := add(keccak256(0, 0x20), pos) + } + } +} diff --git a/CVLByExample/UniswapHooks/lib/uniswap-hooks/lib/v4-core/lib/openzeppelin-contracts/contracts/utils/structs/DoubleEndedQueue.sol b/CVLByExample/UniswapHooks/lib/uniswap-hooks/lib/v4-core/lib/openzeppelin-contracts/contracts/utils/structs/DoubleEndedQueue.sol new file mode 100644 index 00000000..218a2fbd --- /dev/null +++ b/CVLByExample/UniswapHooks/lib/uniswap-hooks/lib/v4-core/lib/openzeppelin-contracts/contracts/utils/structs/DoubleEndedQueue.sol @@ -0,0 +1,169 @@ +// SPDX-License-Identifier: MIT +// OpenZeppelin Contracts (last updated v5.0.0) (utils/structs/DoubleEndedQueue.sol) +pragma solidity ^0.8.20; + +/** + * @dev A sequence of items with the ability to efficiently push and pop items (i.e. insert and remove) on both ends of + * the sequence (called front and back). Among other access patterns, it can be used to implement efficient LIFO and + * FIFO queues. Storage use is optimized, and all operations are O(1) constant time. This includes {clear}, given that + * the existing queue contents are left in storage. + * + * The struct is called `Bytes32Deque`. Other types can be cast to and from `bytes32`. This data structure can only be + * used in storage, and not in memory. + * ```solidity + * DoubleEndedQueue.Bytes32Deque queue; + * ``` + */ +library DoubleEndedQueue { + /** + * @dev An operation (e.g. {front}) couldn't be completed due to the queue being empty. + */ + error QueueEmpty(); + + /** + * @dev A push operation couldn't be completed due to the queue being full. + */ + error QueueFull(); + + /** + * @dev An operation (e.g. {at}) couldn't be completed due to an index being out of bounds. + */ + error QueueOutOfBounds(); + + /** + * @dev Indices are 128 bits so begin and end are packed in a single storage slot for efficient access. + * + * Struct members have an underscore prefix indicating that they are "private" and should not be read or written to + * directly. Use the functions provided below instead. Modifying the struct manually may violate assumptions and + * lead to unexpected behavior. + * + * The first item is at data[begin] and the last item is at data[end - 1]. This range can wrap around. + */ + struct Bytes32Deque { + uint128 _begin; + uint128 _end; + mapping(uint128 index => bytes32) _data; + } + + /** + * @dev Inserts an item at the end of the queue. + * + * Reverts with {QueueFull} if the queue is full. + */ + function pushBack(Bytes32Deque storage deque, bytes32 value) internal { + unchecked { + uint128 backIndex = deque._end; + if (backIndex + 1 == deque._begin) revert QueueFull(); + deque._data[backIndex] = value; + deque._end = backIndex + 1; + } + } + + /** + * @dev Removes the item at the end of the queue and returns it. + * + * Reverts with {QueueEmpty} if the queue is empty. + */ + function popBack(Bytes32Deque storage deque) internal returns (bytes32 value) { + unchecked { + uint128 backIndex = deque._end; + if (backIndex == deque._begin) revert QueueEmpty(); + --backIndex; + value = deque._data[backIndex]; + delete deque._data[backIndex]; + deque._end = backIndex; + } + } + + /** + * @dev Inserts an item at the beginning of the queue. + * + * Reverts with {QueueFull} if the queue is full. + */ + function pushFront(Bytes32Deque storage deque, bytes32 value) internal { + unchecked { + uint128 frontIndex = deque._begin - 1; + if (frontIndex == deque._end) revert QueueFull(); + deque._data[frontIndex] = value; + deque._begin = frontIndex; + } + } + + /** + * @dev Removes the item at the beginning of the queue and returns it. + * + * Reverts with `QueueEmpty` if the queue is empty. + */ + function popFront(Bytes32Deque storage deque) internal returns (bytes32 value) { + unchecked { + uint128 frontIndex = deque._begin; + if (frontIndex == deque._end) revert QueueEmpty(); + value = deque._data[frontIndex]; + delete deque._data[frontIndex]; + deque._begin = frontIndex + 1; + } + } + + /** + * @dev Returns the item at the beginning of the queue. + * + * Reverts with `QueueEmpty` if the queue is empty. + */ + function front(Bytes32Deque storage deque) internal view returns (bytes32 value) { + if (empty(deque)) revert QueueEmpty(); + return deque._data[deque._begin]; + } + + /** + * @dev Returns the item at the end of the queue. + * + * Reverts with `QueueEmpty` if the queue is empty. + */ + function back(Bytes32Deque storage deque) internal view returns (bytes32 value) { + if (empty(deque)) revert QueueEmpty(); + unchecked { + return deque._data[deque._end - 1]; + } + } + + /** + * @dev Return the item at a position in the queue given by `index`, with the first item at 0 and last item at + * `length(deque) - 1`. + * + * Reverts with `QueueOutOfBounds` if the index is out of bounds. + */ + function at(Bytes32Deque storage deque, uint256 index) internal view returns (bytes32 value) { + if (index >= length(deque)) revert QueueOutOfBounds(); + // By construction, length is a uint128, so the check above ensures that index can be safely downcast to uint128 + unchecked { + return deque._data[deque._begin + uint128(index)]; + } + } + + /** + * @dev Resets the queue back to being empty. + * + * NOTE: The current items are left behind in storage. This does not affect the functioning of the queue, but misses + * out on potential gas refunds. + */ + function clear(Bytes32Deque storage deque) internal { + deque._begin = 0; + deque._end = 0; + } + + /** + * @dev Returns the number of items in the queue. + */ + function length(Bytes32Deque storage deque) internal view returns (uint256) { + unchecked { + return uint256(deque._end - deque._begin); + } + } + + /** + * @dev Returns true if the queue is empty. + */ + function empty(Bytes32Deque storage deque) internal view returns (bool) { + return deque._end == deque._begin; + } +} diff --git a/CVLByExample/UniswapHooks/lib/uniswap-hooks/lib/v4-core/lib/openzeppelin-contracts/contracts/utils/structs/EnumerableMap.sol b/CVLByExample/UniswapHooks/lib/uniswap-hooks/lib/v4-core/lib/openzeppelin-contracts/contracts/utils/structs/EnumerableMap.sol new file mode 100644 index 00000000..929ae7c5 --- /dev/null +++ b/CVLByExample/UniswapHooks/lib/uniswap-hooks/lib/v4-core/lib/openzeppelin-contracts/contracts/utils/structs/EnumerableMap.sol @@ -0,0 +1,533 @@ +// SPDX-License-Identifier: MIT +// OpenZeppelin Contracts (last updated v5.0.0) (utils/structs/EnumerableMap.sol) +// This file was procedurally generated from scripts/generate/templates/EnumerableMap.js. + +pragma solidity ^0.8.20; + +import {EnumerableSet} from "./EnumerableSet.sol"; + +/** + * @dev Library for managing an enumerable variant of Solidity's + * https://solidity.readthedocs.io/en/latest/types.html#mapping-types[`mapping`] + * type. + * + * Maps have the following properties: + * + * - Entries are added, removed, and checked for existence in constant time + * (O(1)). + * - Entries are enumerated in O(n). No guarantees are made on the ordering. + * + * ```solidity + * contract Example { + * // Add the library methods + * using EnumerableMap for EnumerableMap.UintToAddressMap; + * + * // Declare a set state variable + * EnumerableMap.UintToAddressMap private myMap; + * } + * ``` + * + * The following map types are supported: + * + * - `uint256 -> address` (`UintToAddressMap`) since v3.0.0 + * - `address -> uint256` (`AddressToUintMap`) since v4.6.0 + * - `bytes32 -> bytes32` (`Bytes32ToBytes32Map`) since v4.6.0 + * - `uint256 -> uint256` (`UintToUintMap`) since v4.7.0 + * - `bytes32 -> uint256` (`Bytes32ToUintMap`) since v4.7.0 + * + * [WARNING] + * ==== + * Trying to delete such a structure from storage will likely result in data corruption, rendering the structure + * unusable. + * See https://github.com/ethereum/solidity/pull/11843[ethereum/solidity#11843] for more info. + * + * In order to clean an EnumerableMap, you can either remove all elements one by one or create a fresh instance using an + * array of EnumerableMap. + * ==== + */ +library EnumerableMap { + using EnumerableSet for EnumerableSet.Bytes32Set; + + // To implement this library for multiple types with as little code repetition as possible, we write it in + // terms of a generic Map type with bytes32 keys and values. The Map implementation uses private functions, + // and user-facing implementations such as `UintToAddressMap` are just wrappers around the underlying Map. + // This means that we can only create new EnumerableMaps for types that fit in bytes32. + + /** + * @dev Query for a nonexistent map key. + */ + error EnumerableMapNonexistentKey(bytes32 key); + + struct Bytes32ToBytes32Map { + // Storage of keys + EnumerableSet.Bytes32Set _keys; + mapping(bytes32 key => bytes32) _values; + } + + /** + * @dev Adds a key-value pair to a map, or updates the value for an existing + * key. O(1). + * + * Returns true if the key was added to the map, that is if it was not + * already present. + */ + function set(Bytes32ToBytes32Map storage map, bytes32 key, bytes32 value) internal returns (bool) { + map._values[key] = value; + return map._keys.add(key); + } + + /** + * @dev Removes a key-value pair from a map. O(1). + * + * Returns true if the key was removed from the map, that is if it was present. + */ + function remove(Bytes32ToBytes32Map storage map, bytes32 key) internal returns (bool) { + delete map._values[key]; + return map._keys.remove(key); + } + + /** + * @dev Returns true if the key is in the map. O(1). + */ + function contains(Bytes32ToBytes32Map storage map, bytes32 key) internal view returns (bool) { + return map._keys.contains(key); + } + + /** + * @dev Returns the number of key-value pairs in the map. O(1). + */ + function length(Bytes32ToBytes32Map storage map) internal view returns (uint256) { + return map._keys.length(); + } + + /** + * @dev Returns the key-value pair stored at position `index` in the map. O(1). + * + * Note that there are no guarantees on the ordering of entries inside the + * array, and it may change when more entries are added or removed. + * + * Requirements: + * + * - `index` must be strictly less than {length}. + */ + function at(Bytes32ToBytes32Map storage map, uint256 index) internal view returns (bytes32, bytes32) { + bytes32 key = map._keys.at(index); + return (key, map._values[key]); + } + + /** + * @dev Tries to returns the value associated with `key`. O(1). + * Does not revert if `key` is not in the map. + */ + function tryGet(Bytes32ToBytes32Map storage map, bytes32 key) internal view returns (bool, bytes32) { + bytes32 value = map._values[key]; + if (value == bytes32(0)) { + return (contains(map, key), bytes32(0)); + } else { + return (true, value); + } + } + + /** + * @dev Returns the value associated with `key`. O(1). + * + * Requirements: + * + * - `key` must be in the map. + */ + function get(Bytes32ToBytes32Map storage map, bytes32 key) internal view returns (bytes32) { + bytes32 value = map._values[key]; + if (value == 0 && !contains(map, key)) { + revert EnumerableMapNonexistentKey(key); + } + return value; + } + + /** + * @dev Return the an array containing all the keys + * + * WARNING: This operation will copy the entire storage to memory, which can be quite expensive. This is designed + * to mostly be used by view accessors that are queried without any gas fees. Developers should keep in mind that + * this function has an unbounded cost, and using it as part of a state-changing function may render the function + * uncallable if the map grows to a point where copying to memory consumes too much gas to fit in a block. + */ + function keys(Bytes32ToBytes32Map storage map) internal view returns (bytes32[] memory) { + return map._keys.values(); + } + + // UintToUintMap + + struct UintToUintMap { + Bytes32ToBytes32Map _inner; + } + + /** + * @dev Adds a key-value pair to a map, or updates the value for an existing + * key. O(1). + * + * Returns true if the key was added to the map, that is if it was not + * already present. + */ + function set(UintToUintMap storage map, uint256 key, uint256 value) internal returns (bool) { + return set(map._inner, bytes32(key), bytes32(value)); + } + + /** + * @dev Removes a value from a map. O(1). + * + * Returns true if the key was removed from the map, that is if it was present. + */ + function remove(UintToUintMap storage map, uint256 key) internal returns (bool) { + return remove(map._inner, bytes32(key)); + } + + /** + * @dev Returns true if the key is in the map. O(1). + */ + function contains(UintToUintMap storage map, uint256 key) internal view returns (bool) { + return contains(map._inner, bytes32(key)); + } + + /** + * @dev Returns the number of elements in the map. O(1). + */ + function length(UintToUintMap storage map) internal view returns (uint256) { + return length(map._inner); + } + + /** + * @dev Returns the element stored at position `index` in the map. O(1). + * Note that there are no guarantees on the ordering of values inside the + * array, and it may change when more values are added or removed. + * + * Requirements: + * + * - `index` must be strictly less than {length}. + */ + function at(UintToUintMap storage map, uint256 index) internal view returns (uint256, uint256) { + (bytes32 key, bytes32 value) = at(map._inner, index); + return (uint256(key), uint256(value)); + } + + /** + * @dev Tries to returns the value associated with `key`. O(1). + * Does not revert if `key` is not in the map. + */ + function tryGet(UintToUintMap storage map, uint256 key) internal view returns (bool, uint256) { + (bool success, bytes32 value) = tryGet(map._inner, bytes32(key)); + return (success, uint256(value)); + } + + /** + * @dev Returns the value associated with `key`. O(1). + * + * Requirements: + * + * - `key` must be in the map. + */ + function get(UintToUintMap storage map, uint256 key) internal view returns (uint256) { + return uint256(get(map._inner, bytes32(key))); + } + + /** + * @dev Return the an array containing all the keys + * + * WARNING: This operation will copy the entire storage to memory, which can be quite expensive. This is designed + * to mostly be used by view accessors that are queried without any gas fees. Developers should keep in mind that + * this function has an unbounded cost, and using it as part of a state-changing function may render the function + * uncallable if the map grows to a point where copying to memory consumes too much gas to fit in a block. + */ + function keys(UintToUintMap storage map) internal view returns (uint256[] memory) { + bytes32[] memory store = keys(map._inner); + uint256[] memory result; + + /// @solidity memory-safe-assembly + assembly { + result := store + } + + return result; + } + + // UintToAddressMap + + struct UintToAddressMap { + Bytes32ToBytes32Map _inner; + } + + /** + * @dev Adds a key-value pair to a map, or updates the value for an existing + * key. O(1). + * + * Returns true if the key was added to the map, that is if it was not + * already present. + */ + function set(UintToAddressMap storage map, uint256 key, address value) internal returns (bool) { + return set(map._inner, bytes32(key), bytes32(uint256(uint160(value)))); + } + + /** + * @dev Removes a value from a map. O(1). + * + * Returns true if the key was removed from the map, that is if it was present. + */ + function remove(UintToAddressMap storage map, uint256 key) internal returns (bool) { + return remove(map._inner, bytes32(key)); + } + + /** + * @dev Returns true if the key is in the map. O(1). + */ + function contains(UintToAddressMap storage map, uint256 key) internal view returns (bool) { + return contains(map._inner, bytes32(key)); + } + + /** + * @dev Returns the number of elements in the map. O(1). + */ + function length(UintToAddressMap storage map) internal view returns (uint256) { + return length(map._inner); + } + + /** + * @dev Returns the element stored at position `index` in the map. O(1). + * Note that there are no guarantees on the ordering of values inside the + * array, and it may change when more values are added or removed. + * + * Requirements: + * + * - `index` must be strictly less than {length}. + */ + function at(UintToAddressMap storage map, uint256 index) internal view returns (uint256, address) { + (bytes32 key, bytes32 value) = at(map._inner, index); + return (uint256(key), address(uint160(uint256(value)))); + } + + /** + * @dev Tries to returns the value associated with `key`. O(1). + * Does not revert if `key` is not in the map. + */ + function tryGet(UintToAddressMap storage map, uint256 key) internal view returns (bool, address) { + (bool success, bytes32 value) = tryGet(map._inner, bytes32(key)); + return (success, address(uint160(uint256(value)))); + } + + /** + * @dev Returns the value associated with `key`. O(1). + * + * Requirements: + * + * - `key` must be in the map. + */ + function get(UintToAddressMap storage map, uint256 key) internal view returns (address) { + return address(uint160(uint256(get(map._inner, bytes32(key))))); + } + + /** + * @dev Return the an array containing all the keys + * + * WARNING: This operation will copy the entire storage to memory, which can be quite expensive. This is designed + * to mostly be used by view accessors that are queried without any gas fees. Developers should keep in mind that + * this function has an unbounded cost, and using it as part of a state-changing function may render the function + * uncallable if the map grows to a point where copying to memory consumes too much gas to fit in a block. + */ + function keys(UintToAddressMap storage map) internal view returns (uint256[] memory) { + bytes32[] memory store = keys(map._inner); + uint256[] memory result; + + /// @solidity memory-safe-assembly + assembly { + result := store + } + + return result; + } + + // AddressToUintMap + + struct AddressToUintMap { + Bytes32ToBytes32Map _inner; + } + + /** + * @dev Adds a key-value pair to a map, or updates the value for an existing + * key. O(1). + * + * Returns true if the key was added to the map, that is if it was not + * already present. + */ + function set(AddressToUintMap storage map, address key, uint256 value) internal returns (bool) { + return set(map._inner, bytes32(uint256(uint160(key))), bytes32(value)); + } + + /** + * @dev Removes a value from a map. O(1). + * + * Returns true if the key was removed from the map, that is if it was present. + */ + function remove(AddressToUintMap storage map, address key) internal returns (bool) { + return remove(map._inner, bytes32(uint256(uint160(key)))); + } + + /** + * @dev Returns true if the key is in the map. O(1). + */ + function contains(AddressToUintMap storage map, address key) internal view returns (bool) { + return contains(map._inner, bytes32(uint256(uint160(key)))); + } + + /** + * @dev Returns the number of elements in the map. O(1). + */ + function length(AddressToUintMap storage map) internal view returns (uint256) { + return length(map._inner); + } + + /** + * @dev Returns the element stored at position `index` in the map. O(1). + * Note that there are no guarantees on the ordering of values inside the + * array, and it may change when more values are added or removed. + * + * Requirements: + * + * - `index` must be strictly less than {length}. + */ + function at(AddressToUintMap storage map, uint256 index) internal view returns (address, uint256) { + (bytes32 key, bytes32 value) = at(map._inner, index); + return (address(uint160(uint256(key))), uint256(value)); + } + + /** + * @dev Tries to returns the value associated with `key`. O(1). + * Does not revert if `key` is not in the map. + */ + function tryGet(AddressToUintMap storage map, address key) internal view returns (bool, uint256) { + (bool success, bytes32 value) = tryGet(map._inner, bytes32(uint256(uint160(key)))); + return (success, uint256(value)); + } + + /** + * @dev Returns the value associated with `key`. O(1). + * + * Requirements: + * + * - `key` must be in the map. + */ + function get(AddressToUintMap storage map, address key) internal view returns (uint256) { + return uint256(get(map._inner, bytes32(uint256(uint160(key))))); + } + + /** + * @dev Return the an array containing all the keys + * + * WARNING: This operation will copy the entire storage to memory, which can be quite expensive. This is designed + * to mostly be used by view accessors that are queried without any gas fees. Developers should keep in mind that + * this function has an unbounded cost, and using it as part of a state-changing function may render the function + * uncallable if the map grows to a point where copying to memory consumes too much gas to fit in a block. + */ + function keys(AddressToUintMap storage map) internal view returns (address[] memory) { + bytes32[] memory store = keys(map._inner); + address[] memory result; + + /// @solidity memory-safe-assembly + assembly { + result := store + } + + return result; + } + + // Bytes32ToUintMap + + struct Bytes32ToUintMap { + Bytes32ToBytes32Map _inner; + } + + /** + * @dev Adds a key-value pair to a map, or updates the value for an existing + * key. O(1). + * + * Returns true if the key was added to the map, that is if it was not + * already present. + */ + function set(Bytes32ToUintMap storage map, bytes32 key, uint256 value) internal returns (bool) { + return set(map._inner, key, bytes32(value)); + } + + /** + * @dev Removes a value from a map. O(1). + * + * Returns true if the key was removed from the map, that is if it was present. + */ + function remove(Bytes32ToUintMap storage map, bytes32 key) internal returns (bool) { + return remove(map._inner, key); + } + + /** + * @dev Returns true if the key is in the map. O(1). + */ + function contains(Bytes32ToUintMap storage map, bytes32 key) internal view returns (bool) { + return contains(map._inner, key); + } + + /** + * @dev Returns the number of elements in the map. O(1). + */ + function length(Bytes32ToUintMap storage map) internal view returns (uint256) { + return length(map._inner); + } + + /** + * @dev Returns the element stored at position `index` in the map. O(1). + * Note that there are no guarantees on the ordering of values inside the + * array, and it may change when more values are added or removed. + * + * Requirements: + * + * - `index` must be strictly less than {length}. + */ + function at(Bytes32ToUintMap storage map, uint256 index) internal view returns (bytes32, uint256) { + (bytes32 key, bytes32 value) = at(map._inner, index); + return (key, uint256(value)); + } + + /** + * @dev Tries to returns the value associated with `key`. O(1). + * Does not revert if `key` is not in the map. + */ + function tryGet(Bytes32ToUintMap storage map, bytes32 key) internal view returns (bool, uint256) { + (bool success, bytes32 value) = tryGet(map._inner, key); + return (success, uint256(value)); + } + + /** + * @dev Returns the value associated with `key`. O(1). + * + * Requirements: + * + * - `key` must be in the map. + */ + function get(Bytes32ToUintMap storage map, bytes32 key) internal view returns (uint256) { + return uint256(get(map._inner, key)); + } + + /** + * @dev Return the an array containing all the keys + * + * WARNING: This operation will copy the entire storage to memory, which can be quite expensive. This is designed + * to mostly be used by view accessors that are queried without any gas fees. Developers should keep in mind that + * this function has an unbounded cost, and using it as part of a state-changing function may render the function + * uncallable if the map grows to a point where copying to memory consumes too much gas to fit in a block. + */ + function keys(Bytes32ToUintMap storage map) internal view returns (bytes32[] memory) { + bytes32[] memory store = keys(map._inner); + bytes32[] memory result; + + /// @solidity memory-safe-assembly + assembly { + result := store + } + + return result; + } +} diff --git a/CVLByExample/UniswapHooks/lib/uniswap-hooks/lib/v4-core/lib/openzeppelin-contracts/contracts/utils/structs/EnumerableSet.sol b/CVLByExample/UniswapHooks/lib/uniswap-hooks/lib/v4-core/lib/openzeppelin-contracts/contracts/utils/structs/EnumerableSet.sol new file mode 100644 index 00000000..4c7fc5e1 --- /dev/null +++ b/CVLByExample/UniswapHooks/lib/uniswap-hooks/lib/v4-core/lib/openzeppelin-contracts/contracts/utils/structs/EnumerableSet.sol @@ -0,0 +1,378 @@ +// SPDX-License-Identifier: MIT +// OpenZeppelin Contracts (last updated v5.0.0) (utils/structs/EnumerableSet.sol) +// This file was procedurally generated from scripts/generate/templates/EnumerableSet.js. + +pragma solidity ^0.8.20; + +/** + * @dev Library for managing + * https://en.wikipedia.org/wiki/Set_(abstract_data_type)[sets] of primitive + * types. + * + * Sets have the following properties: + * + * - Elements are added, removed, and checked for existence in constant time + * (O(1)). + * - Elements are enumerated in O(n). No guarantees are made on the ordering. + * + * ```solidity + * contract Example { + * // Add the library methods + * using EnumerableSet for EnumerableSet.AddressSet; + * + * // Declare a set state variable + * EnumerableSet.AddressSet private mySet; + * } + * ``` + * + * As of v3.3.0, sets of type `bytes32` (`Bytes32Set`), `address` (`AddressSet`) + * and `uint256` (`UintSet`) are supported. + * + * [WARNING] + * ==== + * Trying to delete such a structure from storage will likely result in data corruption, rendering the structure + * unusable. + * See https://github.com/ethereum/solidity/pull/11843[ethereum/solidity#11843] for more info. + * + * In order to clean an EnumerableSet, you can either remove all elements one by one or create a fresh instance using an + * array of EnumerableSet. + * ==== + */ +library EnumerableSet { + // To implement this library for multiple types with as little code + // repetition as possible, we write it in terms of a generic Set type with + // bytes32 values. + // The Set implementation uses private functions, and user-facing + // implementations (such as AddressSet) are just wrappers around the + // underlying Set. + // This means that we can only create new EnumerableSets for types that fit + // in bytes32. + + struct Set { + // Storage of set values + bytes32[] _values; + // Position is the index of the value in the `values` array plus 1. + // Position 0 is used to mean a value is not in the set. + mapping(bytes32 value => uint256) _positions; + } + + /** + * @dev Add a value to a set. O(1). + * + * Returns true if the value was added to the set, that is if it was not + * already present. + */ + function _add(Set storage set, bytes32 value) private returns (bool) { + if (!_contains(set, value)) { + set._values.push(value); + // The value is stored at length-1, but we add 1 to all indexes + // and use 0 as a sentinel value + set._positions[value] = set._values.length; + return true; + } else { + return false; + } + } + + /** + * @dev Removes a value from a set. O(1). + * + * Returns true if the value was removed from the set, that is if it was + * present. + */ + function _remove(Set storage set, bytes32 value) private returns (bool) { + // We cache the value's position to prevent multiple reads from the same storage slot + uint256 position = set._positions[value]; + + if (position != 0) { + // Equivalent to contains(set, value) + // To delete an element from the _values array in O(1), we swap the element to delete with the last one in + // the array, and then remove the last element (sometimes called as 'swap and pop'). + // This modifies the order of the array, as noted in {at}. + + uint256 valueIndex = position - 1; + uint256 lastIndex = set._values.length - 1; + + if (valueIndex != lastIndex) { + bytes32 lastValue = set._values[lastIndex]; + + // Move the lastValue to the index where the value to delete is + set._values[valueIndex] = lastValue; + // Update the tracked position of the lastValue (that was just moved) + set._positions[lastValue] = position; + } + + // Delete the slot where the moved value was stored + set._values.pop(); + + // Delete the tracked position for the deleted slot + delete set._positions[value]; + + return true; + } else { + return false; + } + } + + /** + * @dev Returns true if the value is in the set. O(1). + */ + function _contains(Set storage set, bytes32 value) private view returns (bool) { + return set._positions[value] != 0; + } + + /** + * @dev Returns the number of values on the set. O(1). + */ + function _length(Set storage set) private view returns (uint256) { + return set._values.length; + } + + /** + * @dev Returns the value stored at position `index` in the set. O(1). + * + * Note that there are no guarantees on the ordering of values inside the + * array, and it may change when more values are added or removed. + * + * Requirements: + * + * - `index` must be strictly less than {length}. + */ + function _at(Set storage set, uint256 index) private view returns (bytes32) { + return set._values[index]; + } + + /** + * @dev Return the entire set in an array + * + * WARNING: This operation will copy the entire storage to memory, which can be quite expensive. This is designed + * to mostly be used by view accessors that are queried without any gas fees. Developers should keep in mind that + * this function has an unbounded cost, and using it as part of a state-changing function may render the function + * uncallable if the set grows to a point where copying to memory consumes too much gas to fit in a block. + */ + function _values(Set storage set) private view returns (bytes32[] memory) { + return set._values; + } + + // Bytes32Set + + struct Bytes32Set { + Set _inner; + } + + /** + * @dev Add a value to a set. O(1). + * + * Returns true if the value was added to the set, that is if it was not + * already present. + */ + function add(Bytes32Set storage set, bytes32 value) internal returns (bool) { + return _add(set._inner, value); + } + + /** + * @dev Removes a value from a set. O(1). + * + * Returns true if the value was removed from the set, that is if it was + * present. + */ + function remove(Bytes32Set storage set, bytes32 value) internal returns (bool) { + return _remove(set._inner, value); + } + + /** + * @dev Returns true if the value is in the set. O(1). + */ + function contains(Bytes32Set storage set, bytes32 value) internal view returns (bool) { + return _contains(set._inner, value); + } + + /** + * @dev Returns the number of values in the set. O(1). + */ + function length(Bytes32Set storage set) internal view returns (uint256) { + return _length(set._inner); + } + + /** + * @dev Returns the value stored at position `index` in the set. O(1). + * + * Note that there are no guarantees on the ordering of values inside the + * array, and it may change when more values are added or removed. + * + * Requirements: + * + * - `index` must be strictly less than {length}. + */ + function at(Bytes32Set storage set, uint256 index) internal view returns (bytes32) { + return _at(set._inner, index); + } + + /** + * @dev Return the entire set in an array + * + * WARNING: This operation will copy the entire storage to memory, which can be quite expensive. This is designed + * to mostly be used by view accessors that are queried without any gas fees. Developers should keep in mind that + * this function has an unbounded cost, and using it as part of a state-changing function may render the function + * uncallable if the set grows to a point where copying to memory consumes too much gas to fit in a block. + */ + function values(Bytes32Set storage set) internal view returns (bytes32[] memory) { + bytes32[] memory store = _values(set._inner); + bytes32[] memory result; + + /// @solidity memory-safe-assembly + assembly { + result := store + } + + return result; + } + + // AddressSet + + struct AddressSet { + Set _inner; + } + + /** + * @dev Add a value to a set. O(1). + * + * Returns true if the value was added to the set, that is if it was not + * already present. + */ + function add(AddressSet storage set, address value) internal returns (bool) { + return _add(set._inner, bytes32(uint256(uint160(value)))); + } + + /** + * @dev Removes a value from a set. O(1). + * + * Returns true if the value was removed from the set, that is if it was + * present. + */ + function remove(AddressSet storage set, address value) internal returns (bool) { + return _remove(set._inner, bytes32(uint256(uint160(value)))); + } + + /** + * @dev Returns true if the value is in the set. O(1). + */ + function contains(AddressSet storage set, address value) internal view returns (bool) { + return _contains(set._inner, bytes32(uint256(uint160(value)))); + } + + /** + * @dev Returns the number of values in the set. O(1). + */ + function length(AddressSet storage set) internal view returns (uint256) { + return _length(set._inner); + } + + /** + * @dev Returns the value stored at position `index` in the set. O(1). + * + * Note that there are no guarantees on the ordering of values inside the + * array, and it may change when more values are added or removed. + * + * Requirements: + * + * - `index` must be strictly less than {length}. + */ + function at(AddressSet storage set, uint256 index) internal view returns (address) { + return address(uint160(uint256(_at(set._inner, index)))); + } + + /** + * @dev Return the entire set in an array + * + * WARNING: This operation will copy the entire storage to memory, which can be quite expensive. This is designed + * to mostly be used by view accessors that are queried without any gas fees. Developers should keep in mind that + * this function has an unbounded cost, and using it as part of a state-changing function may render the function + * uncallable if the set grows to a point where copying to memory consumes too much gas to fit in a block. + */ + function values(AddressSet storage set) internal view returns (address[] memory) { + bytes32[] memory store = _values(set._inner); + address[] memory result; + + /// @solidity memory-safe-assembly + assembly { + result := store + } + + return result; + } + + // UintSet + + struct UintSet { + Set _inner; + } + + /** + * @dev Add a value to a set. O(1). + * + * Returns true if the value was added to the set, that is if it was not + * already present. + */ + function add(UintSet storage set, uint256 value) internal returns (bool) { + return _add(set._inner, bytes32(value)); + } + + /** + * @dev Removes a value from a set. O(1). + * + * Returns true if the value was removed from the set, that is if it was + * present. + */ + function remove(UintSet storage set, uint256 value) internal returns (bool) { + return _remove(set._inner, bytes32(value)); + } + + /** + * @dev Returns true if the value is in the set. O(1). + */ + function contains(UintSet storage set, uint256 value) internal view returns (bool) { + return _contains(set._inner, bytes32(value)); + } + + /** + * @dev Returns the number of values in the set. O(1). + */ + function length(UintSet storage set) internal view returns (uint256) { + return _length(set._inner); + } + + /** + * @dev Returns the value stored at position `index` in the set. O(1). + * + * Note that there are no guarantees on the ordering of values inside the + * array, and it may change when more values are added or removed. + * + * Requirements: + * + * - `index` must be strictly less than {length}. + */ + function at(UintSet storage set, uint256 index) internal view returns (uint256) { + return uint256(_at(set._inner, index)); + } + + /** + * @dev Return the entire set in an array + * + * WARNING: This operation will copy the entire storage to memory, which can be quite expensive. This is designed + * to mostly be used by view accessors that are queried without any gas fees. Developers should keep in mind that + * this function has an unbounded cost, and using it as part of a state-changing function may render the function + * uncallable if the set grows to a point where copying to memory consumes too much gas to fit in a block. + */ + function values(UintSet storage set) internal view returns (uint256[] memory) { + bytes32[] memory store = _values(set._inner); + uint256[] memory result; + + /// @solidity memory-safe-assembly + assembly { + result := store + } + + return result; + } +} diff --git a/CVLByExample/UniswapHooks/lib/uniswap-hooks/lib/v4-core/lib/openzeppelin-contracts/contracts/utils/types/Time.sol b/CVLByExample/UniswapHooks/lib/uniswap-hooks/lib/v4-core/lib/openzeppelin-contracts/contracts/utils/types/Time.sol new file mode 100644 index 00000000..9faef31f --- /dev/null +++ b/CVLByExample/UniswapHooks/lib/uniswap-hooks/lib/v4-core/lib/openzeppelin-contracts/contracts/utils/types/Time.sol @@ -0,0 +1,130 @@ +// SPDX-License-Identifier: MIT +// OpenZeppelin Contracts (last updated v5.0.0) (utils/types/Time.sol) + +pragma solidity ^0.8.20; + +import {Math} from "../math/Math.sol"; +import {SafeCast} from "../math/SafeCast.sol"; + +/** + * @dev This library provides helpers for manipulating time-related objects. + * + * It uses the following types: + * - `uint48` for timepoints + * - `uint32` for durations + * + * While the library doesn't provide specific types for timepoints and duration, it does provide: + * - a `Delay` type to represent duration that can be programmed to change value automatically at a given point + * - additional helper functions + */ +library Time { + using Time for *; + + /** + * @dev Get the block timestamp as a Timepoint. + */ + function timestamp() internal view returns (uint48) { + return SafeCast.toUint48(block.timestamp); + } + + /** + * @dev Get the block number as a Timepoint. + */ + function blockNumber() internal view returns (uint48) { + return SafeCast.toUint48(block.number); + } + + // ==================================================== Delay ===================================================== + /** + * @dev A `Delay` is a uint32 duration that can be programmed to change value automatically at a given point in the + * future. The "effect" timepoint describes when the transitions happens from the "old" value to the "new" value. + * This allows updating the delay applied to some operation while keeping some guarantees. + * + * In particular, the {update} function guarantees that if the delay is reduced, the old delay still applies for + * some time. For example if the delay is currently 7 days to do an upgrade, the admin should not be able to set + * the delay to 0 and upgrade immediately. If the admin wants to reduce the delay, the old delay (7 days) should + * still apply for some time. + * + * + * The `Delay` type is 112 bits long, and packs the following: + * + * ``` + * | [uint48]: effect date (timepoint) + * | | [uint32]: value before (duration) + * ↓ ↓ ↓ [uint32]: value after (duration) + * 0xAAAAAAAAAAAABBBBBBBBCCCCCCCC + * ``` + * + * NOTE: The {get} and {withUpdate} functions operate using timestamps. Block number based delays are not currently + * supported. + */ + type Delay is uint112; + + /** + * @dev Wrap a duration into a Delay to add the one-step "update in the future" feature + */ + function toDelay(uint32 duration) internal pure returns (Delay) { + return Delay.wrap(duration); + } + + /** + * @dev Get the value at a given timepoint plus the pending value and effect timepoint if there is a scheduled + * change after this timepoint. If the effect timepoint is 0, then the pending value should not be considered. + */ + function _getFullAt(Delay self, uint48 timepoint) private pure returns (uint32, uint32, uint48) { + (uint32 valueBefore, uint32 valueAfter, uint48 effect) = self.unpack(); + return effect <= timepoint ? (valueAfter, 0, 0) : (valueBefore, valueAfter, effect); + } + + /** + * @dev Get the current value plus the pending value and effect timepoint if there is a scheduled change. If the + * effect timepoint is 0, then the pending value should not be considered. + */ + function getFull(Delay self) internal view returns (uint32, uint32, uint48) { + return _getFullAt(self, timestamp()); + } + + /** + * @dev Get the current value. + */ + function get(Delay self) internal view returns (uint32) { + (uint32 delay, , ) = self.getFull(); + return delay; + } + + /** + * @dev Update a Delay object so that it takes a new duration after a timepoint that is automatically computed to + * enforce the old delay at the moment of the update. Returns the updated Delay object and the timestamp when the + * new delay becomes effective. + */ + function withUpdate( + Delay self, + uint32 newValue, + uint32 minSetback + ) internal view returns (Delay updatedDelay, uint48 effect) { + uint32 value = self.get(); + uint32 setback = uint32(Math.max(minSetback, value > newValue ? value - newValue : 0)); + effect = timestamp() + setback; + return (pack(value, newValue, effect), effect); + } + + /** + * @dev Split a delay into its components: valueBefore, valueAfter and effect (transition timepoint). + */ + function unpack(Delay self) internal pure returns (uint32 valueBefore, uint32 valueAfter, uint48 effect) { + uint112 raw = Delay.unwrap(self); + + valueAfter = uint32(raw); + valueBefore = uint32(raw >> 32); + effect = uint48(raw >> 64); + + return (valueBefore, valueAfter, effect); + } + + /** + * @dev pack the components into a Delay object. + */ + function pack(uint32 valueBefore, uint32 valueAfter, uint48 effect) internal pure returns (Delay) { + return Delay.wrap((uint112(effect) << 64) | (uint112(valueBefore) << 32) | uint112(valueAfter)); + } +} diff --git a/CVLByExample/UniswapHooks/lib/uniswap-hooks/lib/v4-core/lib/openzeppelin-contracts/contracts/vendor/compound/ICompoundTimelock.sol b/CVLByExample/UniswapHooks/lib/uniswap-hooks/lib/v4-core/lib/openzeppelin-contracts/contracts/vendor/compound/ICompoundTimelock.sol new file mode 100644 index 00000000..320eea1c --- /dev/null +++ b/CVLByExample/UniswapHooks/lib/uniswap-hooks/lib/v4-core/lib/openzeppelin-contracts/contracts/vendor/compound/ICompoundTimelock.sol @@ -0,0 +1,86 @@ +// SPDX-License-Identifier: MIT +// OpenZeppelin Contracts (last updated v5.0.0) (vendor/compound/ICompoundTimelock.sol) + +pragma solidity ^0.8.20; + +/** + * https://github.com/compound-finance/compound-protocol/blob/master/contracts/Timelock.sol[Compound timelock] interface + */ +interface ICompoundTimelock { + event NewAdmin(address indexed newAdmin); + event NewPendingAdmin(address indexed newPendingAdmin); + event NewDelay(uint256 indexed newDelay); + event CancelTransaction( + bytes32 indexed txHash, + address indexed target, + uint256 value, + string signature, + bytes data, + uint256 eta + ); + event ExecuteTransaction( + bytes32 indexed txHash, + address indexed target, + uint256 value, + string signature, + bytes data, + uint256 eta + ); + event QueueTransaction( + bytes32 indexed txHash, + address indexed target, + uint256 value, + string signature, + bytes data, + uint256 eta + ); + + receive() external payable; + + // solhint-disable-next-line func-name-mixedcase + function GRACE_PERIOD() external view returns (uint256); + + // solhint-disable-next-line func-name-mixedcase + function MINIMUM_DELAY() external view returns (uint256); + + // solhint-disable-next-line func-name-mixedcase + function MAXIMUM_DELAY() external view returns (uint256); + + function admin() external view returns (address); + + function pendingAdmin() external view returns (address); + + function delay() external view returns (uint256); + + function queuedTransactions(bytes32) external view returns (bool); + + function setDelay(uint256) external; + + function acceptAdmin() external; + + function setPendingAdmin(address) external; + + function queueTransaction( + address target, + uint256 value, + string memory signature, + bytes memory data, + uint256 eta + ) external returns (bytes32); + + function cancelTransaction( + address target, + uint256 value, + string memory signature, + bytes memory data, + uint256 eta + ) external; + + function executeTransaction( + address target, + uint256 value, + string memory signature, + bytes memory data, + uint256 eta + ) external payable returns (bytes memory); +} diff --git a/CVLByExample/UniswapHooks/lib/uniswap-hooks/lib/v4-core/lib/openzeppelin-contracts/contracts/vendor/compound/LICENSE b/CVLByExample/UniswapHooks/lib/uniswap-hooks/lib/v4-core/lib/openzeppelin-contracts/contracts/vendor/compound/LICENSE new file mode 100644 index 00000000..7da23247 --- /dev/null +++ b/CVLByExample/UniswapHooks/lib/uniswap-hooks/lib/v4-core/lib/openzeppelin-contracts/contracts/vendor/compound/LICENSE @@ -0,0 +1,11 @@ +Copyright 2020 Compound Labs, Inc. + +Redistribution and use in source and binary forms, with or without modification, are permitted provided that the following conditions are met: + +1. Redistributions of source code must retain the above copyright notice, this list of conditions and the following disclaimer. + +2. Redistributions in binary form must reproduce the above copyright notice, this list of conditions and the following disclaimer in the documentation and/or other materials provided with the distribution. + +3. Neither the name of the copyright holder nor the names of its contributors may be used to endorse or promote products derived from this software without specific prior written permission. + +THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. \ No newline at end of file From 953b9e03e3de4478c34a7ddf8f45a418e78e6709 Mon Sep 17 00:00:00 2001 From: Jochen Hoenicke Date: Thu, 21 Aug 2025 14:32:01 +0200 Subject: [PATCH 5/5] Added READMEs --- CVLByExample/UniswapHooks/README.md | 16 +++++++++++++++ CVLByExample/UniswapHooks/certora/README.md | 22 +++++++++++++++++++++ 2 files changed, 38 insertions(+) create mode 100644 CVLByExample/UniswapHooks/certora/README.md diff --git a/CVLByExample/UniswapHooks/README.md b/CVLByExample/UniswapHooks/README.md index 1877544d..9e2584f2 100644 --- a/CVLByExample/UniswapHooks/README.md +++ b/CVLByExample/UniswapHooks/README.md @@ -1,5 +1,21 @@ +# Uniswap v4 Points Hook Example. + +This is based on the Uniswapv4 Hook Template. + +### Points Hook + +The PointsHook example was taken from + +https://docs.uniswap.org/contracts/v4/guides/hooks/your-first-hook + +It's a simple hook that mints token for the pool if the user sells ether +for the token in the pool, or if he adds ether as liquidity to the pool. + +The original ReadMe for the Uniswap v4 Hook Template follows below + # Uniswap v4 Hook Template + **A template for writing Uniswap v4 Hooks 🦄** ### Get Started diff --git a/CVLByExample/UniswapHooks/certora/README.md b/CVLByExample/UniswapHooks/certora/README.md new file mode 100644 index 00000000..e4f8ba51 --- /dev/null +++ b/CVLByExample/UniswapHooks/certora/README.md @@ -0,0 +1,22 @@ +# PointsHook rules. + +We have two specific rules for the PointsHook that check that the right +amount of tokens are minted: + +```sh +certoraRun certora/conf/PointsHook.conf +``` + +The generic rules to check that the hook permissions are corresponding +to the implemented hook and to check that the hook prevents calls from +other contracts than the PoolManager are here: + +```sh +certoraRun certora/conf/HookRules.conf +``` + +A very simple rule for the Uniswap v4 PoolManager is here: + +```sh +certoraRun certora/conf/UniswapTake.conf +```