Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
2 changes: 1 addition & 1 deletion .gitignore
Original file line number Diff line number Diff line change
Expand Up @@ -32,4 +32,4 @@ package-lock.json
*.tgz

# VSCode
.vscode/
.vscode/
2 changes: 1 addition & 1 deletion docs/contracts/permit2/reference/signature-transfer.md
Original file line number Diff line number Diff line change
Expand Up @@ -181,7 +181,7 @@ struct ExampleTrade {
}
```

Following EIP-721, the typehash for the data would be defined by:
Following EIP-712, the typehash for the data would be defined by:

```solidity
bytes32 _EXAMPLE_TRADE_TYPEHASH = keccak256('ExampleTrade(address exampleTokenAddress,uint256 exampleMinimumAmountOut)');
Expand Down
91 changes: 90 additions & 1 deletion docs/contracts/v4/guides/06-unlock-callback.mdx
Original file line number Diff line number Diff line change
Expand Up @@ -96,4 +96,93 @@ _settling_ afterwards
* _burn_ - removes ERC6909 claims, creating a positive delta for tokens to
be transferred back to the owner or used in settling negative balances
* _clear_ - used to zero out positive token deltas, helpful to forfeit
insignificant token amounts in order to avoid paying further transfer costs
insignificant token amounts in order to avoid paying further transfer costs

### Handling Deltas for Liquidity Modifications

#### When it happens
- **Building custom routers** that pre-calculate token amounts.
- **Estimating values** for user interfaces or simulations.

#### Why It Happens
- **Pre-calculated amounts** (e.g., from `LiquidityAmounts.getAmountsForLiquidity()`) use static math.
- **Actual deltas** (from `modifyLiquidity()`) reflect real-time pool state, including:
- Tick crossings during execution.
- Rounding in fixed-point arithmetic (`Q128.128`).

#### Why LiquidityAmounts ≠ Liquidity Delta

The discrepancy occurs because:

- **Price Movement:** The pool's price changes between pre-calculation and execution
- **Tick Crossings:** Transactions may cross ticks, changing liquidity math
- **Rounding:** Static calculations use idealized math while execution uses Q128.128 fixed-point

#### 📊 Price Movement Example

When ETH/USDC price changes during transaction execution:

```solidity
// Static math calculation
LiquidityAmounts.getAmountsForLiquidity(
sqrtRatioX96: 3000, // Fixed price
...
);

// Interacts with the pool and uses actual execution (reflects real-time price)
poolManager.modifyLiquidity(
sqrtRatioX96: 3001, // Updated price
...
);
```

getAmountsForLiquidity() assumes static 3000 price
modifyLiquidity() reflects actual 3001 price

#### Key Impact
| Scenario | Risk |
|----------|------|
| **Underestimating deltas** | Transactions revert with `CurrencyNotSettled`. |
| **Overestimating deltas** | Users overpay and lose funds to residual dust. |
| **No slippage check** | Significant financial losses. |

#### Best Practices for Custom Routers

1. **Never settle without validating against slippage**

Supposing slippage tolerance is 50 (basis point)

```solidity
require(
actualAmount0 >= expectedAmount0 * (10_000 - slippageTolerance) / 10_000,
"Slippage too high (token0)"
);
require(
actualAmount1 >= expectedAmount1 * (10_000 - slippageTolerance) / 10_000,
"Slippage too high (token1)"
);
```

2. **Use Deltas for Settlement**
Always derive final amounts from `modifyLiquidity()` deltas:
```solidity
CallbackData memory _data = abi.decode(data, (CallbackData));
(BalanceDelta delta, ) = poolManager.modifyLiquidity(
_data.key,
_data.params,
hex""
);

_data.key.currency0.settle(poolManager, _data.key.hookAddress, delta.amount0() < 0
? uint256(uint128(-delta.amount0()))
: uint256(uint128(delta.amount0())), false);
_data.key.currency1.settle(poolManager, _data.key.hookAddress, delta.amount1() < 0
? uint256(uint128(-delta.amount1()))
: uint256(uint128(delta.amount1())), false);
```

> ⚠️ **Custom Router Pitfall**
> When pre-calculating liquidity changes, always account for rounding differences.
> **Never** assume `getAmountsForLiquidity() == modifyLiquidity()` deltas.
> Enforce slippage post-execution.