diff --git a/.DS_Store b/.DS_Store new file mode 100644 index 000000000..df4cb444a Binary files /dev/null and b/.DS_Store differ diff --git a/README.md b/README.md index a8ba09a43..1ca5a0907 100644 --- a/README.md +++ b/README.md @@ -1,39 +1,110 @@ # Uniswap v4 Periphery -Uniswap v4 is a new automated market maker protocol that provides extensibility and customizability to pools. `v4-periphery` hosts the logic that builds on top of the core pool logic like hook contracts, position managers, and even possibly libraries needed for integrations. The `v4-periphery` contracts in this repository are still in development and further periphery contracts have not yet been built. +A production-focused set of contracts and utilities that build on Uniswap v4 core. This repository provides routers with batched actions and per-hop slippage checks, position management via an ERC-721 position manager (PosM) supporting mint/increase/decrease/burn flows with settlement/take helpers, quoting/lens contracts for off-chain estimation via revert-encoding, base building blocks for hooks (e.g., BaseHook, SafeCallback, DeltaResolver), and libraries used by integrators. -## Contributing - -If you’re interested in contributing please see the [contribution guidelines](https://github.com/Uniswap/v4-periphery/blob/main/CONTRIBUTING.md)! +The periphery is under active development; APIs may evolve as v4 matures. Review audits in `audits/` and run the comprehensive Foundry test suite before integrating. When integrating, ensure remappings point to `@uniswap/v4-core` and `v4-periphery`, target Solidity 0.8.24+ on the Cancun EVM, and prefer via-IR with the optimizer enabled. -## Local Deployment and Usage +## Contents +- `src/V4Router.sol`: internal swap routing with batched actions, per-hop slippage checks, settlement/take helpers +- `src/PositionManager.sol`: ERC-721 position NFT minting/management (PosM), liquidity modify flows, notifier integration +- `src/lens/V4Quoter.sol`: off-chain quoting via revert-encoding (not gas-efficient; do not call on-chain) +- `src/lens/StateView.sol`: read-only views into pool and position state +- `src/base/*`: shared infrastructure (`BaseActionsRouter`, `SafeCallback`, `DeltaResolver`, `ReentrancyLock`, `NativeWrapper`, `Notifier`) +- `src/libraries/*`: calldata decoding, slippage checks, path utilities, amounts math, etc. +- `src/hooks/*`: example token wrapper hooks (e.g. WETH, wstETH) +- `script/*`: Foundry deploy/util scripts +- `test/*`: extensive Foundry tests and gas benchmarks -To utilize the contracts and deploy to a local testnet, you can install the code in your repo with forge: +## Requirements +- Foundry (`forge`, `cast`) +- Uniswap v4 core as a dependency (via `remappings.txt`/`foundry.toml`) +- Solidity 0.8.24+ (some contracts use transient storage opcodes; tested on Cancun EVM) -```solidity +## Install & Build +```bash forge install https://github.com/Uniswap/v4-periphery +forge build ``` +If using as a submodule, add remappings for `@uniswap/v4-core` and `v4-periphery` and run `forge build`. -If you are building hooks, it may be useful to inherit from the `BaseHook` contract: +Example remappings (adjust to your repo): +```ini +@uniswap/v4-core/=lib/v4-core/ +v4-periphery/=lib/v4-periphery/ +forge-std/=lib/forge-std/src/ +solmate/=lib/solmate/src/ +``` +Foundry config hints (`foundry.toml`): +- `solc_version = "0.8.26"` (compatible with `0.8.24+`) +- `evm_version = "cancun"` +- `via_ir = true` +- enable optimizer with appropriate runs per file -```solidity +## Quickstart +- Position management: call `modifyLiquidities` with a batched action payload to mint/increase/decrease/burn positions while atomically settling/taking credits. +- Swaps: invoke router actions (`SWAP_EXACT_IN`, `SWAP_EXACT_OUT`, single/multi-hop) with optional per-hop slippage controls, then settle/take. +- Quoting: use `V4Quoter` off-chain to estimate amounts and gas before executing on-chain. +Example: Hook skeleton +```solidity import {BaseHook} from 'v4-periphery/src/utils/BaseHook.sol'; +import {IHooks} from '@uniswap/v4-core/src/interfaces/IHooks.sol'; +import {IPoolManager} from '@uniswap/v4-core/src/interfaces/IPoolManager.sol'; contract CoolHook is BaseHook { - // Override the hook callbacks you want on your hook - function beforeAddLiquidity( - address, - IPoolManager.PoolKey calldata key, - IPoolManager.ModifyLiquidityParams calldata params - ) external override onlyByManager returns (bytes4) { - // hook logic - return BaseHook.beforeAddLiquidity.selector; + constructor(IPoolManager manager) BaseHook(manager) {} + function getHookPermissions() public pure override returns (IHooks.Permissions memory) { + // enable only the callbacks you need + return IHooks.Permissions({ + beforeInitialize: true, + beforeAddLiquidity: true, + beforeSwap: true, + beforeSwapReturnDelta: false, + afterSwap: false, + afterInitialize: false, + beforeRemoveLiquidity: false, + afterAddLiquidity: false, + afterRemoveLiquidity: false, + beforeDonate: false, + afterDonate: false, + afterSwapReturnDelta: false, + afterAddLiquidityReturnDelta: false, + afterRemoveLiquidityReturnDelta: false + }); } } +``` +## Scripts +```bash +forge script script/DeployV4Quoter.s.sol --rpc-url $RPC --private-key $PK --broadcast +forge script script/DeployPositionManager.s.sol --rpc-url $RPC --broadcast +forge script script/DeployV4Router.s.sol --rpc-url $RPC --broadcast ``` +Use environment variables or `.env`; do not commit secrets. -## License +## Testing +```bash +forge test -vvv +``` +- Gas snapshots in `snapshots/` +- Scenario tests cover swaps, liquidity flows, notifier behavior, and edge cases +- Fuzzing defaults can be tuned via `foundry.toml` (e.g., `fuzz_runs`) + +## Design Notes +- Actions are executed inside `PoolManager.unlock` via `SafeCallback.unlockCallback`, ensuring only the v4 `PoolManager` drives execution. +- Deltas: `_take` and `_settle` in `DeltaResolver` manage positive/negative credits; special constants like `OPEN_DELTA` and `CONTRACT_BALANCE` enable ergonomic flows. +- Slippage: `SlippageCheck` validates principal deltas on liquidity operations; router enforces min-out/max-in across hops and per-hop slippage checks. +- Reentrancy: transient lock via `ReentrancyLock`; `msgSender()` returns the locker for correct attribution. + +## Audits & Security +- Audit drafts and reports are available in `audits/` +- Callback protection: only `PoolManager` may call `unlockCallback` +- ETH handling: `NativeWrapper` only accepts ETH from trusted sources (e.g., `WETH9` or `PoolManager`) +- Follow best practices for key management and deployment; validate inputs and slippage parameters -The license for Uniswap V4 Periphery is the GNU General Public License (GPL 2.0), see [LICENSE](https://github.com/Uniswap/v4-periphery/blob/main/LICENSE). +## Contributing +Please read the [CONTRIBUTING.md](./CONTRIBUTING.md). Run lint/tests before submitting PRs. + +## License +MIT. See [LICENSE](./LICENSE). diff --git a/script/01_PoolManager.s.sol b/script/01_PoolManager.s.sol index 3d9d570d8..02712ab27 100644 --- a/script/01_PoolManager.s.sol +++ b/script/01_PoolManager.s.sol @@ -1,15 +1,29 @@ // SPDX-License-Identifier: MIT pragma solidity ^0.8.20; +/** + * Script: DeployPoolManager + * Purpose: Deploy Uniswap v4 PoolManager and log its address + * Usage: + * forge script script/01_PoolManager.s.sol:DeployPoolManager --rpc-url $RPC --private-key $PK --broadcast + * Notes: + * PoolManager constructor takes an owner/controller; this script passes its own address. + */ + import "forge-std/Script.sol"; import {PoolManager} from "@uniswap/v4-core/src/PoolManager.sol"; import {IPoolManager} from "@uniswap/v4-core/src/interfaces/IPoolManager.sol"; import "forge-std/console2.sol"; +/// @title DeployPoolManager Script +/// @notice Deploys PoolManager and prints its address contract DeployPoolManager is Script { + /// @notice Optional pre-run setup function setUp() public {} + /// @notice Deploy PoolManager + /// @return manager The deployed PoolManager instance function run() public returns (IPoolManager manager) { vm.startBroadcast(); diff --git a/script/02_PoolModifyLiquidityTest.s.sol b/script/02_PoolModifyLiquidityTest.s.sol index a2c4975ed..a9cb76f4b 100644 --- a/script/02_PoolModifyLiquidityTest.s.sol +++ b/script/02_PoolModifyLiquidityTest.s.sol @@ -1,15 +1,30 @@ // SPDX-License-Identifier: MIT pragma solidity ^0.8.22; +/** + * Script: DeployPoolModifyLiquidityTest + * Purpose: Deploy core PoolModifyLiquidityTest helper against an existing PoolManager + * Usage: + * forge script script/02_PoolModifyLiquidityTest.s.sol:DeployPoolModifyLiquidityTest --rpc-url $RPC --private-key $PK --broadcast --sig "run(address)" + * Notes: + * Useful for exercising liquidity flows in local/testing environments. + */ + import {Script} from "forge-std/Script.sol"; import {IPoolManager} from "@uniswap/v4-core/src/interfaces/IPoolManager.sol"; import {PoolModifyLiquidityTest} from "@uniswap/v4-core/src/test/PoolModifyLiquidityTest.sol"; import "forge-std/console2.sol"; +/// @title DeployPoolModifyLiquidityTest Script +/// @notice Deploys PoolModifyLiquidityTest bound to a PoolManager contract DeployPoolModifyLiquidityTest is Script { + /// @notice Optional pre-run setup function setUp() public {} + /// @notice Deploy the PoolModifyLiquidityTest helper + /// @param poolManager Address of the PoolManager to bind + /// @return testModifyRouter The deployed helper instance function run(address poolManager) public returns (PoolModifyLiquidityTest testModifyRouter) { vm.broadcast(); testModifyRouter = new PoolModifyLiquidityTest(IPoolManager(poolManager)); diff --git a/script/03_PoolSwapTest.s.sol b/script/03_PoolSwapTest.s.sol index acd650d25..ab9c6c71d 100644 --- a/script/03_PoolSwapTest.s.sol +++ b/script/03_PoolSwapTest.s.sol @@ -1,15 +1,30 @@ // SPDX-License-Identifier: MIT pragma solidity ^0.8.22; +/** + * Script: DeployPoolSwapTest + * Purpose: Deploy core PoolSwapTest helper against an existing PoolManager + * Usage: + * forge script script/03_PoolSwapTest.s.sol:DeployPoolSwapTest --rpc-url $RPC --private-key $PK --broadcast --sig "run(address)" + * Notes: + * Helps validate swap behavior in controlled environments. + */ + import {Script} from "forge-std/Script.sol"; import {IPoolManager} from "@uniswap/v4-core/src/interfaces/IPoolManager.sol"; import {PoolSwapTest} from "@uniswap/v4-core/src/test/PoolSwapTest.sol"; import "forge-std/console2.sol"; +/// @title DeployPoolSwapTest Script +/// @notice Deploys PoolSwapTest bound to a PoolManager contract DeployPoolSwapTest is Script { + /// @notice Optional pre-run setup function setUp() public {} + /// @notice Deploy the PoolSwapTest helper + /// @param poolManager Address of the PoolManager to bind + /// @return testSwapRouter The deployed helper instance function run(address poolManager) public returns (PoolSwapTest testSwapRouter) { vm.broadcast(); testSwapRouter = new PoolSwapTest(IPoolManager(poolManager)); diff --git a/script/DeployHook.s.sol b/script/DeployHook.s.sol index 43f1d7493..a002fe255 100644 --- a/script/DeployHook.s.sol +++ b/script/DeployHook.s.sol @@ -1,23 +1,37 @@ // SPDX-License-Identifier: MIT pragma solidity ^0.8.19; +/** + * Script: DeployHookScript + * Purpose: Mine a CREATE2 salt for a hook address that encodes required flags, then deploy the hook + * Usage: + * forge script script/DeployHook.s.sol:DeployHookScript --rpc-url $RPC --private-key $PK --broadcast + * Customize: + * Replace MockCounterHook import with your hook and set POOLMANAGER accordingly. + * Notes: + * Hook addresses must encode flags in their address per v4; HookMiner finds a matching salt. + */ + import "forge-std/Script.sol"; import {Hooks} from "@uniswap/v4-core/src/libraries/Hooks.sol"; import {IPoolManager} from "@uniswap/v4-core/src/interfaces/IPoolManager.sol"; import {HookMiner} from "../src/utils/HookMiner.sol"; -/// @dev Replace import with your own hook +// Replace import with your own hook import {MockCounterHook} from "../test/mocks/MockCounterHook.sol"; -/// @notice Mines the address and deploys the Counter.sol Hook contract +/// @title DeployHookScript +/// @notice Mines salt and deploys a hook with the desired flags via CREATE2 contract DeployHookScript is Script { address constant CREATE2_DEPLOYER = address(0x4e59b44847b379578588920cA78FbF26c0B4956C); /// @dev Replace with the desired PoolManager on its corresponding chain IPoolManager constant POOLMANAGER = IPoolManager(address(0xE03A1074c86CFeDd5C142C4F04F1a1536e203543)); + /// @notice Optional pre-run setup function setUp() public {} + /// @notice Mine salt for desired flags and deploy the hook via CREATE2 function run() public { // hook contracts must have specific flags encoded in the address uint160 flags = uint160( diff --git a/script/DeployPosm.s.sol b/script/DeployPosm.s.sol index 1e969df2e..a7fd71491 100644 --- a/script/DeployPosm.s.sol +++ b/script/DeployPosm.s.sol @@ -1,6 +1,20 @@ // SPDX-License-Identifier: MIT pragma solidity ^0.8.20; +/** + * Script: DeployPosmTest + * Purpose: Deploy PositionDescriptor and PositionManager (PosM) configured for a chain + * Usage: + * forge script script/DeployPosm.s.sol:DeployPosmTest --rpc-url $RPC --private-key $PK --broadcast \ + * --sig "run(address,address,uint256,address,bytes32)" + * Params: + * poolManager: v4 PoolManager address + * permit2: Permit2 contract address for token transfers + * unsubscribeGasLimit: gas allocated for unsubscribe notifications + * wrappedNative: WETH9 (or chain’s wrapped native) address + * nativeCurrencyLabelBytes: 32-byte label used by descriptor (e.g., symbol) + */ + import "forge-std/console2.sol"; import "forge-std/Script.sol"; @@ -9,9 +23,20 @@ import {IAllowanceTransfer} from "permit2/src/interfaces/IAllowanceTransfer.sol" import {Deploy, IPositionDescriptor, IPositionManager} from "../test/shared/Deploy.sol"; import {IWETH9} from "../src/interfaces/external/IWETH9.sol"; +/// @title DeployPosmTest Script +/// @notice Deploys PositionDescriptor and PositionManager for a given chain contract DeployPosmTest is Script { + /// @notice Optional pre-run setup function setUp() public {} + /// @notice Deploy PositionDescriptor and PositionManager + /// @param poolManager PoolManager address + /// @param permit2 Permit2 address + /// @param unsubscribeGasLimit Gas limit for unsubscribe notifications + /// @param wrappedNative Wrapped native (e.g., WETH9) address + /// @param nativeCurrencyLabelBytes 32-byte label for native currency + /// @return positionDescriptor The deployed position descriptor + /// @return posm The deployed position manager function run( address poolManager, address permit2, diff --git a/script/DeployStateView.s.sol b/script/DeployStateView.s.sol index 230bd819a..d531a5f55 100644 --- a/script/DeployStateView.s.sol +++ b/script/DeployStateView.s.sol @@ -1,14 +1,27 @@ // SPDX-License-Identifier: MIT pragma solidity ^0.8.20; +/** + * Script: DeployStateView + * Purpose: Deploy read-only StateView lens bound to a PoolManager + * Usage: + * forge script script/DeployStateView.s.sol:DeployStateView --rpc-url $RPC --private-key $PK --broadcast --sig "run(address)" + */ + import "forge-std/console2.sol"; import "forge-std/Script.sol"; import {Deploy, IStateView} from "../test/shared/Deploy.sol"; +/// @title DeployStateView Script +/// @notice Deploys StateView bound to a PoolManager contract DeployStateView is Script { + /// @notice Optional pre-run setup function setUp() public {} + /// @notice Deploy the StateView lens + /// @param poolManager PoolManager address + /// @return state The deployed StateView instance function run(address poolManager) public returns (IStateView state) { vm.startBroadcast(); diff --git a/script/DeployV4Quoter.s.sol b/script/DeployV4Quoter.s.sol index 72361df65..9477ab723 100644 --- a/script/DeployV4Quoter.s.sol +++ b/script/DeployV4Quoter.s.sol @@ -1,18 +1,35 @@ // SPDX-License-Identifier: MIT pragma solidity ^0.8.20; +/** + * Script: DeployV4Quoter + * Purpose: Deploy V4Quoter bound to a PoolManager for off-chain quoting + * Usage: + * forge script script/DeployV4Quoter.s.sol:DeployV4Quoter --rpc-url $RPC --private-key $PK --broadcast + * --sig "run(address)" + * Notes: + * V4Quoter performs revert-encoded simulations and is intended for off-chain use. + */ + import "forge-std/console2.sol"; import "forge-std/Script.sol"; import {Deploy, IV4Quoter} from "../test/shared/Deploy.sol"; +/// @title DeployV4Quoter Script +/// @notice Deploys V4Quoter bound to a given PoolManager for off-chain quoting +/// @dev Uses Foundry broadcast; V4Quoter is intended for off-chain simulation contract DeployV4Quoter is Script { + /// @notice Optional pre-run setup for the script function setUp() public {} + /// @notice Deploy the V4Quoter contract + /// @param poolManager The Uniswap v4 PoolManager address to bind the quoter to + /// @return state The deployed IV4Quoter instance function run(address poolManager) public returns (IV4Quoter state) { vm.startBroadcast(); - // forge script --broadcast --sig 'run(address)' --rpc-url --private-key --verify script/DeployV4Quoter.s.sol:DeployV4Quoter + // Broadcast, deploy V4Quoter bound to poolManager, and log addresses state = Deploy.v4Quoter(poolManager, hex"00"); console2.log("V4Quoter", address(state)); console2.log("PoolManager", address(state.poolManager()));