Skip to content

Commit

Permalink
add base
Browse files Browse the repository at this point in the history
  • Loading branch information
Jun1on committed Jul 10, 2024
1 parent 8611713 commit e14a747
Show file tree
Hide file tree
Showing 7 changed files with 433 additions and 0 deletions.
9 changes: 9 additions & 0 deletions contracts/interfaces/IBaseHook.sol
Original file line number Diff line number Diff line change
@@ -0,0 +1,9 @@
// SPDX-License-Identifier: UNLICENSED
pragma solidity ^0.8.24;

import {Hooks} from "@uniswap/v4-core/src/libraries/Hooks.sol";
import {IHooks} from "@uniswap/v4-core/src/interfaces/IHooks.sol";

interface IBaseHook is IHooks {
function getHookPermissions() external pure returns (Hooks.Permissions memory);
}
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);
}
30 changes: 30 additions & 0 deletions contracts/middleware/BaseMiddleware.sol
Original file line number Diff line number Diff line change
@@ -0,0 +1,30 @@
// 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";

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) {
return implementation;
}

// yo i wanna delete this function but how do i remove this warning
receive() external payable {
_delegate(_implementation());
}
}
34 changes: 34 additions & 0 deletions contracts/middleware/BaseMiddlewareFactory.sol
Original file line number Diff line number Diff line change
@@ -0,0 +1,34 @@
// SPDX-License-Identifier: UNLICENSED
pragma solidity ^0.8.19;

import {IMiddlewareFactory} from "../interfaces/IMiddlewareFactory.sol";
import {BaseMiddleware} from "./BaseMiddleware.sol";
import {IPoolManager} from "@uniswap/v4-core/src/interfaces/IPoolManager.sol";
import {IHooks} from "@uniswap/v4-core/src/interfaces/IHooks.sol";
import {Hooks} from "@uniswap/v4-core/src/libraries/Hooks.sol";
import {IBaseHook} from "../interfaces/IBaseHook.sol";

contract BaseMiddlewareFactory 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 = _deployMiddleware(implementation, salt);
Hooks.validateHookPermissions(IHooks(middleware), IBaseHook(implementation).getHookPermissions());
_implementations[middleware] = implementation;
emit MiddlewareCreated(implementation, middleware);
}

function _deployMiddleware(address implementation, bytes32 salt) internal virtual returns (address middleware) {
return address(new BaseMiddleware{salt: salt}(poolManager, implementation));
}
}
124 changes: 124 additions & 0 deletions test/BaseMiddlewareFactory.t.sol
Original file line number Diff line number Diff line change
@@ -0,0 +1,124 @@
// SPDX-License-Identifier: UNLICENSED
pragma solidity ^0.8.19;

import {Test} from "forge-std/Test.sol";
import {Hooks} from "@uniswap/v4-core/src/libraries/Hooks.sol";
import {Deployers} from "@uniswap/v4-core/test/utils/Deployers.sol";
import {TestERC20} from "@uniswap/v4-core/src/test/TestERC20.sol";
import {CurrencyLibrary, Currency} from "@uniswap/v4-core/src/types/Currency.sol";
import {PoolId, PoolIdLibrary} from "@uniswap/v4-core/src/types/PoolId.sol";
import {PoolKey} from "@uniswap/v4-core/src/types/PoolKey.sol";
import {HookEnabledSwapRouter} from "./utils/HookEnabledSwapRouter.sol";
import {BalanceDelta} from "@uniswap/v4-core/src/types/BalanceDelta.sol";
import {IHooks} from "@uniswap/v4-core/src/interfaces/IHooks.sol";
import {console} from "../../../lib/forge-std/src/console.sol";
import {BaseMiddleware} from "../contracts/middleware/BaseMiddleware.sol";
import {BaseMiddlewareFactory} from "./../contracts/middleware/BaseMiddlewareFactory.sol";
import {HookMiner} from "./utils/HookMiner.sol";
import {Counter} from "./middleware/Counter.sol";
import {SafeCallback} from "./../contracts/base/SafeCallback.sol";

contract BaseMiddlewareFactoryTest is Test, Deployers {
HookEnabledSwapRouter router;
TestERC20 token0;
TestERC20 token1;

BaseMiddlewareFactory factory;
Counter counter;

address middleware;

function setUp() public {
deployFreshManagerAndRouters();
(currency0, currency1) = deployMintAndApprove2Currencies();

router = new HookEnabledSwapRouter(manager);
token0 = TestERC20(Currency.unwrap(currency0));
token1 = TestERC20(Currency.unwrap(currency1));

factory = new BaseMiddlewareFactory(manager);
counter = new Counter(manager);

token0.approve(address(router), type(uint256).max);
token1.approve(address(router), type(uint256).max);

uint160 flags = uint160(
Hooks.BEFORE_INITIALIZE_FLAG | Hooks.AFTER_INITIALIZE_FLAG | Hooks.BEFORE_SWAP_FLAG | Hooks.AFTER_SWAP_FLAG
| Hooks.BEFORE_ADD_LIQUIDITY_FLAG | Hooks.AFTER_ADD_LIQUIDITY_FLAG | Hooks.BEFORE_REMOVE_LIQUIDITY_FLAG
| Hooks.AFTER_REMOVE_LIQUIDITY_FLAG | Hooks.BEFORE_DONATE_FLAG | Hooks.AFTER_DONATE_FLAG
);

(address hookAddress, bytes32 salt) = HookMiner.find(
address(factory), flags, type(BaseMiddleware).creationCode, abi.encode(address(manager), address(counter))
);
middleware = factory.createMiddleware(address(counter), salt);
assertEq(hookAddress, middleware);
}

function testRevertOnSameDeployment() public {
uint160 flags = uint160(
Hooks.BEFORE_INITIALIZE_FLAG | Hooks.AFTER_INITIALIZE_FLAG | Hooks.BEFORE_SWAP_FLAG | Hooks.AFTER_SWAP_FLAG
| Hooks.BEFORE_ADD_LIQUIDITY_FLAG | Hooks.AFTER_ADD_LIQUIDITY_FLAG | Hooks.BEFORE_REMOVE_LIQUIDITY_FLAG
| Hooks.AFTER_REMOVE_LIQUIDITY_FLAG | Hooks.BEFORE_DONATE_FLAG | Hooks.AFTER_DONATE_FLAG
);
(address hookAddress, bytes32 salt) = HookMiner.find(
address(factory), flags, type(BaseMiddleware).creationCode, abi.encode(address(manager), address(counter))
);
factory.createMiddleware(address(counter), salt);
// second deployment should revert
vm.expectRevert(bytes(""));
factory.createMiddleware(address(counter), salt);
}

function testRevertOnIncorrectFlags() public {
Counter counter2 = new Counter(manager);
uint160 flags = uint160(Hooks.BEFORE_INITIALIZE_FLAG);

(address hookAddress, bytes32 salt) = HookMiner.find(
address(factory), flags, type(BaseMiddleware).creationCode, abi.encode(address(manager), address(counter2))
);
address implementation = address(counter2);
vm.expectRevert(abi.encodePacked(bytes16(Hooks.HookAddressNotValid.selector), hookAddress));
factory.createMiddleware(implementation, salt);
}

function testRevertOnIncorrectFlagsMined() public {
Counter counter2 = new Counter(manager);
address implementation = address(counter2);
vm.expectRevert(); // HookAddressNotValid
factory.createMiddleware(implementation, bytes32("who needs to mine a salt?"));
}

function testRevertOnIncorrectCaller() public {
vm.expectRevert(SafeCallback.NotManager.selector);
counter.afterDonate(address(this), key, 0, 0, ZERO_BYTES);
}

function testCounters() public {
(PoolKey memory key, PoolId id) =
initPoolAndAddLiquidity(currency0, currency1, IHooks(middleware), 3000, SQRT_PRICE_1_1, ZERO_BYTES);

Counter counterProxy = Counter(middleware);
assertEq(counterProxy.beforeInitializeCount(id), 1);
assertEq(counterProxy.afterInitializeCount(id), 1);
assertEq(counterProxy.beforeSwapCount(id), 0);
assertEq(counterProxy.afterSwapCount(id), 0);
assertEq(counterProxy.beforeAddLiquidityCount(id), 1);
assertEq(counterProxy.afterAddLiquidityCount(id), 1);
assertEq(counterProxy.beforeRemoveLiquidityCount(id), 0);
assertEq(counterProxy.afterRemoveLiquidityCount(id), 0);
assertEq(counterProxy.beforeDonateCount(id), 0);
assertEq(counterProxy.afterDonateCount(id), 0);

assertEq(counterProxy.lastHookData(), ZERO_BYTES);
swap(key, true, 1, bytes("hi"));
assertEq(counterProxy.lastHookData(), bytes("hi"));
assertEq(counterProxy.beforeSwapCount(id), 1);
assertEq(counterProxy.afterSwapCount(id), 1);

// counter does not store data itself
assertEq(counter.lastHookData(), bytes(""));
assertEq(counter.beforeSwapCount(id), 0);
assertEq(counter.afterSwapCount(id), 0);
}
}
167 changes: 167 additions & 0 deletions test/middleware/Counter.sol
Original file line number Diff line number Diff line change
@@ -0,0 +1,167 @@
// SPDX-License-Identifier: MIT
pragma solidity ^0.8.24;

import {BaseHook} from "./../../contracts/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 {BeforeSwapDelta, BeforeSwapDeltaLibrary} from "@uniswap/v4-core/src/types/BeforeSwapDelta.sol";
import {BalanceDelta, BalanceDeltaLibrary} from "@uniswap/v4-core/src/types/BalanceDelta.sol";
import {PoolId, PoolIdLibrary} from "@uniswap/v4-core/src/types/PoolId.sol";

contract Counter is BaseHook {
using PoolIdLibrary for PoolKey;

mapping(PoolId => uint256) public beforeInitializeCount;
mapping(PoolId => uint256) public afterInitializeCount;

mapping(PoolId => uint256) public beforeSwapCount;
mapping(PoolId => uint256) public afterSwapCount;

mapping(PoolId => uint256) public beforeAddLiquidityCount;
mapping(PoolId => uint256) public afterAddLiquidityCount;
mapping(PoolId => uint256) public beforeRemoveLiquidityCount;
mapping(PoolId => uint256) public afterRemoveLiquidityCount;

mapping(PoolId => uint256) public beforeDonateCount;
mapping(PoolId => uint256) public afterDonateCount;

bytes public lastHookData;

constructor(IPoolManager _manager) BaseHook(_manager) {}

// middleware implementations do not need to be mined
function validateHookAddress(BaseHook _this) internal pure override {}

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

function beforeInitialize(address, PoolKey calldata key, uint160, bytes calldata hookData)
external
override
onlyByManager
returns (bytes4)
{
beforeInitializeCount[key.toId()]++;
lastHookData = hookData;
return BaseHook.beforeInitialize.selector;
}

function afterInitialize(address, PoolKey calldata key, uint160, int24, bytes calldata hookData)
external
override
onlyByManager
returns (bytes4)
{
afterInitializeCount[key.toId()]++;
lastHookData = hookData;
return BaseHook.afterInitialize.selector;
}

function beforeAddLiquidity(
address,
PoolKey calldata key,
IPoolManager.ModifyLiquidityParams calldata,
bytes calldata hookData
) external override onlyByManager returns (bytes4) {
beforeAddLiquidityCount[key.toId()]++;
lastHookData = hookData;
return BaseHook.beforeAddLiquidity.selector;
}

function afterAddLiquidity(
address,
PoolKey calldata key,
IPoolManager.ModifyLiquidityParams calldata,
BalanceDelta,
bytes calldata hookData
) external override onlyByManager returns (bytes4, BalanceDelta) {
afterAddLiquidityCount[key.toId()]++;
lastHookData = hookData;
return (BaseHook.afterAddLiquidity.selector, BalanceDeltaLibrary.ZERO_DELTA);
}

function beforeRemoveLiquidity(
address,
PoolKey calldata key,
IPoolManager.ModifyLiquidityParams calldata,
bytes calldata hookData
) external override onlyByManager returns (bytes4) {
beforeRemoveLiquidityCount[key.toId()]++;
lastHookData = hookData;
return BaseHook.beforeRemoveLiquidity.selector;
}

function afterRemoveLiquidity(
address,
PoolKey calldata key,
IPoolManager.ModifyLiquidityParams calldata,
BalanceDelta,
bytes calldata hookData
) external override onlyByManager returns (bytes4, BalanceDelta) {
afterRemoveLiquidityCount[key.toId()]++;
lastHookData = hookData;
return (BaseHook.afterRemoveLiquidity.selector, BalanceDeltaLibrary.ZERO_DELTA);
}

function beforeSwap(address, PoolKey calldata key, IPoolManager.SwapParams calldata, bytes calldata hookData)
external
override
onlyByManager
returns (bytes4, BeforeSwapDelta, uint24)
{
beforeSwapCount[key.toId()]++;
lastHookData = hookData;
return (BaseHook.beforeSwap.selector, BeforeSwapDeltaLibrary.ZERO_DELTA, 0);
}

function afterSwap(
address,
PoolKey calldata key,
IPoolManager.SwapParams calldata,
BalanceDelta,
bytes calldata hookData
) external override onlyByManager returns (bytes4, int128) {
afterSwapCount[key.toId()]++;
lastHookData = hookData;
return (BaseHook.afterSwap.selector, 0);
}

function beforeDonate(address, PoolKey calldata key, uint256, uint256, bytes calldata hookData)
external
override
onlyByManager
returns (bytes4)
{
beforeDonateCount[key.toId()]++;
lastHookData = hookData;
return BaseHook.beforeDonate.selector;
}

function afterDonate(address, PoolKey calldata key, uint256, uint256, bytes calldata hookData)
external
override
onlyByManager
returns (bytes4)
{
afterDonateCount[key.toId()]++;
lastHookData = hookData;
return BaseHook.afterDonate.selector;
}
}
Loading

0 comments on commit e14a747

Please sign in to comment.