Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Hook Middleware #127

Closed
wants to merge 25 commits into from
Closed
Show file tree
Hide file tree
Changes from 17 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
17 changes: 17 additions & 0 deletions contracts/interfaces/IMiddlewareFactory.sol
Original file line number Diff line number Diff line change
@@ -0,0 +1,17 @@
// SPDX-License-Identifier: UNLICENSED
pragma solidity ^0.8.19;

interface IMiddlewareFactory {
event MiddlewareCreated(address implementation, address middleware);

/// @notice Returns the implementation address for a given middleware
/// @param middleware The middleware address
/// @return implementation The implementation address
function getImplementation(address middleware) external view returns (address implementation);

/// @notice Creates a middleware for the given implementation
/// @param implementation The implementation address
/// @param salt The salt to use to deploy the middleware
/// @return middleware The address of the newly created middleware
function createMiddleware(address implementation, bytes32 salt) external returns (address middleware);
}
31 changes: 31 additions & 0 deletions contracts/middleware/BaseMiddleware.sol
Original file line number Diff line number Diff line change
@@ -0,0 +1,31 @@
// SPDX-License-Identifier: UNLICENSED
pragma solidity ^0.8.24;

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 {Proxy} from "@openzeppelin/contracts/proxy/Proxy.sol";
import {console} from "../../lib/forge-std/src/console.sol";

contract BaseMiddleware is Proxy {
/// @notice The address of the pool manager
IPoolManager public immutable poolManager;
address public immutable implementation;

constructor(IPoolManager _poolManager, address _impl) {
poolManager = _poolManager;
implementation = _impl;
}

function _implementation() internal view override returns (address) {
console.logAddress(implementation);
return implementation;
}

receive() external payable {
// ??
}
}
65 changes: 65 additions & 0 deletions contracts/middleware/BaseMiddlewareDefault.txt
Original file line number Diff line number Diff line change
@@ -0,0 +1,65 @@
// SPDX-License-Identifier: UNLICENSED
pragma solidity ^0.8.24;

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 {Proxy} from "@openzeppelin/contracts/proxy/Proxy.sol";
import {console} from "../../../lib/forge-std/src/console.sol";

contract Middleware is IHooks{
IPoolManager public immutable poolManager;
Hooks.Permissions private permissions;
IHooks public immutable implementation;

constructor(IPoolManager _poolManager, address _implementation, uint160 _flags) {
poolManager = _poolManager;
permissions = IHooks(_implementation).getHookPermissions();
implementation = IHooks(_implementation);
_flags = _flags;
}

function getHookPermissions() public view returns (Hooks.Permissions memory) {
return permissions;
}

modifier nonReentrantBefore() private {
if (_status == ENTERED) {
revert ActionBetweenHook();
}
_status = ENTERED;
_;
}

modifier nonReentrantAfter() private {
_;
_status = NOT_ENTERED;
}

function beforeSwap(address sender, PoolKey calldata key, IPoolManager.SwapParams calldata params, bytes calldata hookData)
nonReentrantBefore
external
returns (bytes4, BeforeSwapDelta, uint24)
{
try implementation.beforeSwap(sender, key, params, hookData) returns (
bytes4 selector,
BeforeSwapDelta memory beforeSwapDelta,
uint24 lpFeeOverride
) {

return (selector, beforeSwapDelta, lpFeeOverride);
} catch {
return (defaultSelector, defaultBeforeSwapDelta, defaultLpFeeOverride);
}
}

function afterSwap(...) nonReentrantAfter { try catch... }
function beforeAddLiquidity(...) nonReentrantBefore { try catch... }
function afterAddLiquidity(...) nonReentrantAfter { try catch... }
function beforeRemoveLiquidity(...) nonReentrantBefore { try catch... }
function afterRemoveLiquidity(...) nonReentrantAfter { try catch... }
// who cares about donate lol
}
94 changes: 94 additions & 0 deletions contracts/middleware/MiddlewareProtect.sol
Original file line number Diff line number Diff line change
@@ -0,0 +1,94 @@
// SPDX-License-Identifier: MIT
pragma solidity ^0.8.24;

import {IPoolManager} from "@uniswap/v4-core/src/interfaces/IPoolManager.sol";
import {PoolKey} from "@uniswap/v4-core/src/types/PoolKey.sol";
import {BaseMiddleware} from "./BaseMiddleware.sol";
import {BalanceDelta, BalanceDeltaLibrary} from "@uniswap/v4-core/src/types/BalanceDelta.sol";
import {BeforeSwapDelta} from "@uniswap/v4-core/src/types/BeforeSwapDelta.sol";
import {console} from "../../lib/forge-std/src/console.sol";
import {BaseHook} from "./../BaseHook.sol";

contract MiddlewareProtect is BaseMiddleware {
bool private swapBlocked;
bool private removeBlocked;

uint256 public constant gasLimit = 100000;

error ActionBetweenHook();

constructor(IPoolManager _poolManager, address _impl) BaseMiddleware(_poolManager, _impl) {}

modifier swapNotBlocked() {
if (swapBlocked) {
revert ActionBetweenHook();
}
_;
}

modifier removeNotBlocked() {
if (removeBlocked) {
revert ActionBetweenHook();
}
_;
}

// block swaps and removes
function beforeSwap(address, PoolKey calldata, IPoolManager.SwapParams calldata, bytes calldata)
external
swapNotBlocked
returns (bytes4, BeforeSwapDelta, uint24)
{
swapBlocked = true;
removeBlocked = true;
console.log("beforeSwap middleware");
(bool success, bytes memory returnData) = implementation.delegatecall{gas: gasLimit}(msg.data);
require(success);
swapBlocked = false;
removeBlocked = false;
return abi.decode(returnData, (bytes4, BeforeSwapDelta, uint24));
}

// afterSwap - no protections

// block swaps
function beforeAddLiquidity(address, PoolKey calldata, IPoolManager.ModifyLiquidityParams calldata, bytes calldata)
external
returns (bytes4)
{
swapBlocked = true;
console.log("beforeAddLiquidity middleware");
(bool success, bytes memory returnData) = implementation.delegatecall{gas: gasLimit}(msg.data);
require(success);
swapBlocked = false;
return abi.decode(returnData, (bytes4));
}

// afterAddLiquidity - no protections

// block swaps and reverts
function beforeRemoveLiquidity(
address,
PoolKey calldata,
IPoolManager.ModifyLiquidityParams calldata,
bytes calldata
) external removeNotBlocked returns (bytes4) {
swapBlocked = true;
console.log("beforeRemoveLiquidity middleware");
implementation.delegatecall{gas: gasLimit}(msg.data);
swapBlocked = false;
return BaseHook.beforeRemoveLiquidity.selector;
}

// block reverts
function afterRemoveLiquidity(
address,
PoolKey calldata,
IPoolManager.ModifyLiquidityParams calldata,
bytes calldata
) external returns (bytes4, BalanceDelta) {
console.log("afterRemoveLiquidity middleware");
implementation.delegatecall{gas: gasLimit}(msg.data);
return (BaseHook.afterRemoveLiquidity.selector, BalanceDeltaLibrary.ZERO_DELTA);
}
}
44 changes: 44 additions & 0 deletions contracts/middleware/MiddlewareRemove.sol
Original file line number Diff line number Diff line change
@@ -0,0 +1,44 @@
// SPDX-License-Identifier: UNLICENSED
pragma solidity ^0.8.24;

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 {Proxy} from "@openzeppelin/contracts/proxy/Proxy.sol";
import {BaseMiddleware} from "./BaseMiddleware.sol";
import {BaseHook} from "../BaseHook.sol";
import {console} from "../../lib/forge-std/src/console.sol";
import {BalanceDeltaLibrary} from "@uniswap/v4-core/src/types/BalanceDelta.sol";

contract MiddlewareRemove is BaseMiddleware {
bytes internal constant ZERO_BYTES = bytes("");
uint256 public constant gasLimit = 1000000;

constructor(IPoolManager _poolManager, address _impl) BaseMiddleware(_poolManager, _impl) {}

function beforeRemoveLiquidity(
address,
PoolKey calldata,
IPoolManager.ModifyLiquidityParams calldata,
bytes calldata
) external returns (bytes4) {
console.log("beforeRemoveLiquidity middleware");
(bool success, bytes memory returnData) = implementation.delegatecall{gas: gasLimit}(msg.data);
console.log(success);
return BaseHook.beforeRemoveLiquidity.selector;
}

function afterRemoveLiquidity(
address,
PoolKey calldata,
IPoolManager.ModifyLiquidityParams calldata,
BalanceDelta,
bytes calldata
) external returns (bytes4, BalanceDelta) {
console.log("afterRemoveLiquidity middleware");
(bool success, bytes memory returnData) = implementation.delegatecall{gas: gasLimit}(msg.data);
console.log(success);
// hook cannot return delta
return (BaseHook.afterRemoveLiquidity.selector, BalanceDeltaLibrary.ZERO_DELTA);
}
}
26 changes: 26 additions & 0 deletions contracts/middleware/MiddlewareRemoveFactory.sol
Original file line number Diff line number Diff line change
@@ -0,0 +1,26 @@
// SPDX-License-Identifier: UNLICENSED
pragma solidity ^0.8.19;

import {IMiddlewareFactory} from "../interfaces/IMiddlewareFactory.sol";
import {MiddlewareRemove} from "./MiddlewareRemove.sol";
import {IPoolManager} from "@uniswap/v4-core/src/interfaces/IPoolManager.sol";

contract MiddlewareRemoveFactory is IMiddlewareFactory {
mapping(address => address) private _implementations;

IPoolManager public immutable poolManager;

constructor(IPoolManager _poolManager) {
poolManager = _poolManager;
}

function getImplementation(address middleware) external view override returns (address implementation) {
return _implementations[middleware];
}

function createMiddleware(address implementation, bytes32 salt) external override returns (address middleware) {
middleware = address(new MiddlewareRemove{salt: salt}(poolManager, implementation));
_implementations[middleware] = implementation;
emit MiddlewareCreated(implementation, middleware);
}
}
96 changes: 96 additions & 0 deletions contracts/middleware/test/FeeTakingLite.sol
Original file line number Diff line number Diff line change
@@ -0,0 +1,96 @@
// SPDX-License-Identifier: MIT
pragma solidity ^0.8.24;

import {BaseHook} from "../../BaseHook.sol";
import {Hooks} from "@uniswap/v4-core/src/libraries/Hooks.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 {BeforeSwapDelta, BeforeSwapDeltaLibrary} from "@uniswap/v4-core/src/types/BeforeSwapDelta.sol";
import {Currency, CurrencyLibrary} from "@uniswap/v4-core/src/types/Currency.sol";
import {SafeCast} from "@uniswap/v4-core/src/libraries/SafeCast.sol";
import {Owned} from "solmate/auth/Owned.sol";
import {IUnlockCallback} from "@uniswap/v4-core/src/interfaces/callback/IUnlockCallback.sol";
import {console} from "../../../lib/forge-std/src/console.sol";

contract FeeTakingLite is IUnlockCallback {
using SafeCast for uint256;

bytes internal constant ZERO_BYTES = bytes("");
uint128 private constant TOTAL_BIPS = 10000;
uint128 private constant MAX_BIPS = 100;
uint128 public constant swapFeeBips = 25;
IPoolManager public immutable poolManager;

struct CallbackData {
address to;
Currency[] currencies;
}

constructor(IPoolManager _poolManager) {
poolManager = _poolManager;
}

function getHookPermissions() public pure returns (Hooks.Permissions memory) {
return Hooks.Permissions({
beforeInitialize: false,
afterInitialize: false,
beforeAddLiquidity: false,
afterAddLiquidity: false,
beforeRemoveLiquidity: false,
afterRemoveLiquidity: false,
beforeSwap: false,
afterSwap: true,
beforeDonate: false,
afterDonate: false,
beforeSwapReturnDelta: false,
afterSwapReturnDelta: true,
afterAddLiquidityReturnDelta: false,
afterRemoveLiquidityReturnDelta: false
});
}

function afterSwap(
address,
PoolKey calldata key,
IPoolManager.SwapParams calldata params,
BalanceDelta delta,
bytes calldata
) external returns (bytes4, int128) {
// fee will be in the unspecified token of the swap
bool currency0Specified = (params.amountSpecified < 0 == params.zeroForOne);
(Currency feeCurrency, int128 swapAmount) =
(currency0Specified) ? (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) * swapFeeBips) / TOTAL_BIPS;
// mint ERC6909 instead of take to avoid edge case where PM doesn't have enough balance
poolManager.mint(address(this), CurrencyLibrary.toId(feeCurrency), feeAmount);

return (BaseHook.afterSwap.selector, feeAmount.toInt128());
}

function setSwapFeeBips(uint128 _swapFeeBips) external pure {
require(_swapFeeBips <= MAX_BIPS);
//swapFeeBips = _swapFeeBips;
}

function withdraw(address to, Currency[] calldata currencies) external {
poolManager.unlock(abi.encode(CallbackData(to, currencies)));
}

function unlockCallback(bytes calldata rawData) external override returns (bytes memory) {
CallbackData memory data = abi.decode(rawData, (CallbackData));
uint256 length = data.currencies.length;
for (uint256 i = 0; i < length;) {
uint256 amount = poolManager.balanceOf(address(this), CurrencyLibrary.toId(data.currencies[i]));
poolManager.burn(address(this), CurrencyLibrary.toId(data.currencies[i]), amount);
poolManager.take(data.currencies[i], data.to, amount);
unchecked {
i++;
}
}
return ZERO_BYTES;
}
}
Loading
Loading