Skip to content

Commit

Permalink
merge main
Browse files Browse the repository at this point in the history
  • Loading branch information
hensha256 committed Jul 14, 2024
2 parents 98f98d8 + 87292df commit 3d5b0ac
Show file tree
Hide file tree
Showing 20 changed files with 589 additions and 321 deletions.
2 changes: 1 addition & 1 deletion .forge-snapshots/addLiquidity CA fee.snap
Original file line number Diff line number Diff line change
@@ -1 +1 @@
319084
170951
2 changes: 1 addition & 1 deletion .forge-snapshots/poolManager bytecode size.snap
Original file line number Diff line number Diff line change
@@ -1 +1 @@
24013
24029
2 changes: 1 addition & 1 deletion .forge-snapshots/removeLiquidity CA fee.snap
Original file line number Diff line number Diff line change
@@ -1 +1 @@
175444
141244
2 changes: 1 addition & 1 deletion .forge-snapshots/swap CA fee on unspecified.snap
Original file line number Diff line number Diff line change
@@ -1 +1 @@
172690
155602
Original file line number Diff line number Diff line change
@@ -1 +1 @@
208163
208214
5 changes: 5 additions & 0 deletions foundry.toml
Original file line number Diff line number Diff line change
Expand Up @@ -16,4 +16,9 @@ runs = 10000
[profile.ci.fuzz]
runs = 100000

[profile.debug]
via_ir = false
optimizer_runs = 200
fuzz.runs = 100

# See more config options https://github.com/foundry-rs/foundry/tree/master/config
6 changes: 4 additions & 2 deletions src/PoolManager.sol
Original file line number Diff line number Diff line change
Expand Up @@ -104,7 +104,7 @@ contract PoolManager is IPoolManager, ProtocolFees, NoDelegateCall, ERC6909Claim
}

/// @inheritdoc IPoolManager
function unlock(bytes calldata data) external noDelegateCall returns (bytes memory result) {
function unlock(bytes calldata data) external override returns (bytes memory result) {
if (Lock.isUnlocked()) AlreadyUnlocked.selector.revertWith();

Lock.unlock();
Expand Down Expand Up @@ -149,7 +149,7 @@ contract PoolManager is IPoolManager, ProtocolFees, NoDelegateCall, ERC6909Claim
PoolKey memory key,
IPoolManager.ModifyLiquidityParams memory params,
bytes calldata hookData
) external onlyWhenUnlocked returns (BalanceDelta callerDelta, BalanceDelta feesAccrued) {
) external onlyWhenUnlocked noDelegateCall returns (BalanceDelta callerDelta, BalanceDelta feesAccrued) {
PoolId id = key.toId();
Pool.State storage pool = _getPool(id);
pool.checkPoolInitialized();
Expand Down Expand Up @@ -186,6 +186,7 @@ contract PoolManager is IPoolManager, ProtocolFees, NoDelegateCall, ERC6909Claim
function swap(PoolKey memory key, IPoolManager.SwapParams memory params, bytes calldata hookData)
external
onlyWhenUnlocked
noDelegateCall
returns (BalanceDelta swapDelta)
{
if (params.amountSpecified == 0) SwapAmountCannotBeZero.selector.revertWith();
Expand Down Expand Up @@ -246,6 +247,7 @@ contract PoolManager is IPoolManager, ProtocolFees, NoDelegateCall, ERC6909Claim
function donate(PoolKey memory key, uint256 amount0, uint256 amount1, bytes calldata hookData)
external
onlyWhenUnlocked
noDelegateCall
returns (BalanceDelta delta)
{
Pool.State storage pool = _getPool(key.toId());
Expand Down
2 changes: 1 addition & 1 deletion src/interfaces/IHooks.sol
Original file line number Diff line number Diff line change
Expand Up @@ -101,7 +101,7 @@ interface IHooks {
/// @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 leading bit is set to 1 (24th bit, 0x800000), 3) the value is less than or equal to the maximum fee (1 million)
/// @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,
Expand Down
2 changes: 1 addition & 1 deletion src/libraries/LPFeeLibrary.sol
Original file line number Diff line number Diff line change
Expand Up @@ -55,7 +55,7 @@ library LPFeeLibrary {
return self;
}

/// @notice returns true if the fee has the override flag set (top bit of the uint24)
/// @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) {
Expand Down
12 changes: 5 additions & 7 deletions src/libraries/TickBitmap.sol
Original file line number Diff line number Diff line change
Expand Up @@ -43,13 +43,11 @@ library TickBitmap {
/// @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;
*/
// 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") {
// ensure that the tick is spaced
if smod(tick, tickSpacing) {
Expand Down
204 changes: 204 additions & 0 deletions src/test/ProxyPoolManager.sol
Original file line number Diff line number Diff line change
@@ -0,0 +1,204 @@
// 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, 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, PoolIdLibrary} from "../types/PoolId.sol";
import {BalanceDelta, BalanceDeltaLibrary, toBalanceDelta} 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";

/// @notice A proxy pool manager that delegates calls to the real/delegate pool manager
contract ProxyPoolManager is IPoolManager, ProtocolFees, NoDelegateCall, ERC6909Claims, Extsload, Exttload {
using PoolIdLibrary for PoolKey;
using SafeCast for *;
using Pool for *;
using Hooks for IHooks;
using Position for mapping(bytes32 => Position.Info);
using CurrencyDelta for Currency;
using LPFeeLibrary for uint24;
using CurrencyReserves for Currency;
using CustomRevert for bytes4;

/// @inheritdoc IPoolManager
int24 public constant MAX_TICK_SPACING = TickMath.MAX_TICK_SPACING;

/// @inheritdoc IPoolManager
int24 public constant MIN_TICK_SPACING = TickMath.MIN_TICK_SPACING;

mapping(PoolId id => Pool.State) internal _pools;

address internal immutable _delegateManager;

constructor(address delegateManager, uint256 controllerGasLimit) ProtocolFees(controllerGasLimit) {
_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, bytes calldata hookData)
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();
if (key.tickSpacing < MIN_TICK_SPACING) TickSpacingTooSmall.selector.revertWith();
if (key.currency0 >= key.currency1) CurrenciesOutOfOrderOrEqual.selector.revertWith();
if (!key.hooks.isValidHookAddress(key.fee)) Hooks.HookAddressNotValid.selector.revertWith(address(key.hooks));

uint24 lpFee = key.fee.getInitialLPFee();

key.hooks.beforeInitialize(key, sqrtPriceX96, hookData);

PoolId id = key.toId();
(, uint24 protocolFee) = _fetchProtocolFee(key);

tick = _pools[id].initialize(sqrtPriceX96, protocolFee, lpFee);

key.hooks.afterInitialize(key, sqrtPriceX96, tick, hookData);

// 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);
}

/// @inheritdoc IPoolManager
function modifyLiquidity(
PoolKey memory key,
IPoolManager.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, IPoolManager.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 {
CurrencyReserves.requireNotSynced();
if (currency.isNative()) return;
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 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];
}
}
Loading

0 comments on commit 3d5b0ac

Please sign in to comment.